mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-29 18:52:22 +01:00
create stripe subscription with all data
This commit is contained in:
parent
ed5b90cbdc
commit
b5504d2342
@ -40,7 +40,7 @@ const PaymentScheduleSummary: React.FC<PaymentScheduleSummaryProps> = ({ schedul
|
||||
* Test if all payment deadlines have the same amount
|
||||
*/
|
||||
const hasEqualDeadlines = (): boolean => {
|
||||
const prices = schedule.items.map(i => i.price);
|
||||
const prices = schedule.items.map(i => i.amount);
|
||||
return prices.every(p => p === prices[0]);
|
||||
}
|
||||
/**
|
||||
@ -57,7 +57,7 @@ const PaymentScheduleSummary: React.FC<PaymentScheduleSummaryProps> = ({ schedul
|
||||
{hasEqualDeadlines() && <ul>
|
||||
<li>
|
||||
<span className="schedule-item-info">
|
||||
{t('app.shared.cart.NUMBER_monthly_payment_of_AMOUNT', { NUMBER: schedule.items.length, AMOUNT: formatPrice(schedule.items[0].price) })}
|
||||
{t('app.shared.cart.NUMBER_monthly_payment_of_AMOUNT', { NUMBER: schedule.items.length, AMOUNT: formatPrice(schedule.items[0].amount) })}
|
||||
</span>
|
||||
<span className="schedule-item-date">{t('app.shared.cart.first_debit')}</span>
|
||||
</li>
|
||||
@ -65,12 +65,12 @@ const PaymentScheduleSummary: React.FC<PaymentScheduleSummaryProps> = ({ schedul
|
||||
{!hasEqualDeadlines() && <ul>
|
||||
<li>
|
||||
<span className="schedule-item-info">{t('app.shared.cart.monthly_payment_NUMBER', { NUMBER: 1 })}</span>
|
||||
<span className="schedule-item-price">{formatPrice(schedule.items[0].price)}</span>
|
||||
<span className="schedule-item-price">{formatPrice(schedule.items[0].amount)}</span>
|
||||
<span className="schedule-item-date">{t('app.shared.cart.debit')}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="schedule-item-info">
|
||||
{t('app.shared.cart.NUMBER_monthly_payment_of_AMOUNT', { NUMBER: schedule.items.length - 1, AMOUNT: formatPrice(schedule.items[1].price) })}
|
||||
{t('app.shared.cart.NUMBER_monthly_payment_of_AMOUNT', { NUMBER: schedule.items.length - 1, AMOUNT: formatPrice(schedule.items[1].amount) })}
|
||||
</span>
|
||||
</li>
|
||||
</ul>}
|
||||
@ -81,7 +81,7 @@ const PaymentScheduleSummary: React.FC<PaymentScheduleSummaryProps> = ({ schedul
|
||||
<li key={String(item.due_date)}>
|
||||
<span className="schedule-item-date">{formatDate(item.due_date)}</span>
|
||||
<span> </span>
|
||||
<span className="schedule-item-price">{formatPrice(item.price)}</span>
|
||||
<span className="schedule-item-price">{formatPrice(item.amount)}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
@ -94,6 +94,7 @@ const PaymentScheduleSummaryWrapper: React.FC<PaymentScheduleSummaryProps> = ({
|
||||
return (
|
||||
<Loader>
|
||||
<PaymentScheduleSummary schedule={schedule} $filter={$filter} />
|
||||
<div>lorem ipsum</div>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,12 @@
|
||||
export interface PaymentScheduleItem {
|
||||
id: number,
|
||||
price: number,
|
||||
amount: number,
|
||||
due_date: Date
|
||||
details: {
|
||||
recurring: number,
|
||||
adjustment: number,
|
||||
other_items: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface PaymentSchedule {
|
||||
|
@ -3,6 +3,7 @@
|
||||
# Coupon is a textual code associated with a discount rate or an amount of discount
|
||||
class Coupon < ApplicationRecord
|
||||
has_many :invoices
|
||||
has_many :payment_schedule
|
||||
|
||||
after_commit :create_stripe_coupon, on: [:create]
|
||||
after_commit :delete_stripe_coupon, on: [:destroy]
|
||||
|
@ -22,6 +22,7 @@ class Invoice < ApplicationRecord
|
||||
belongs_to :offer_day, foreign_type: 'OfferDay', foreign_key: 'invoiced_id'
|
||||
|
||||
has_one :avoir, class_name: 'Invoice', foreign_key: :invoice_id, dependent: :destroy
|
||||
has_one :payment_schedule_item
|
||||
belongs_to :operator_profile, foreign_key: :operator_profile_id, class_name: 'InvoicingProfile'
|
||||
|
||||
before_create :add_environment
|
||||
|
@ -10,6 +10,7 @@ class InvoicingProfile < ApplicationRecord
|
||||
has_one :organization, dependent: :destroy
|
||||
accepts_nested_attributes_for :organization, allow_destroy: false
|
||||
has_many :invoices, dependent: :destroy
|
||||
has_many :payment_schedules, dependent: :destroy
|
||||
|
||||
has_one :wallet, dependent: :destroy
|
||||
has_many :wallet_transactions, dependent: :destroy
|
||||
@ -17,6 +18,7 @@ class InvoicingProfile < ApplicationRecord
|
||||
has_many :history_values, dependent: :nullify
|
||||
|
||||
has_many :operated_invoices, foreign_key: :operator_profile_id, class_name: 'Invoice', dependent: :nullify
|
||||
has_many :operated_payment_schedules, foreign_key: :operator_profile_id, class_name: 'PaymentSchedule', dependent: :nullify
|
||||
|
||||
def full_name
|
||||
# if first_name or last_name is nil, the empty string will be used as a temporary replacement
|
||||
|
@ -8,4 +8,16 @@ class PaymentSchedule < ApplicationRecord
|
||||
belongs_to :coupon
|
||||
belongs_to :invoicing_profile
|
||||
belongs_to :operator_profile, foreign_key: :operator_profile_id, class_name: 'InvoicingProfile'
|
||||
|
||||
belongs_to :subscription, foreign_type: 'Subscription', foreign_key: 'scheduled_id'
|
||||
belongs_to :reservation, foreign_type: 'Reservation', foreign_key: 'scheduled_id'
|
||||
|
||||
has_many :payment_schedule_items
|
||||
|
||||
##
|
||||
# This is useful to check the first item because its amount may be different from the others
|
||||
##
|
||||
def ordered_items
|
||||
payment_schedule_items.order(due_date: :asc)
|
||||
end
|
||||
end
|
||||
|
@ -3,4 +3,5 @@
|
||||
# Represents a due date and the associated amount for a PaymentSchedule
|
||||
class PaymentScheduleItem < ApplicationRecord
|
||||
belongs_to :payment_schedule
|
||||
belongs_to :invoice
|
||||
end
|
||||
|
@ -144,10 +144,9 @@ class Price < ApplicationRecord
|
||||
cp = cs.validate(options[:coupon_code], user.id)
|
||||
total_amount = cs.apply(total_amount, cp)
|
||||
|
||||
# == generate PaymentSchedule ()if applicable) ===
|
||||
# == generate PaymentSchedule (if applicable) ===
|
||||
schedule = if options[:payment_schedule] && plan.monthly_payment
|
||||
pss = PaymentScheduleService.new
|
||||
pss.compute(plan, _amount_no_coupon, cp)
|
||||
PaymentScheduleService.new.compute(plan, _amount_no_coupon, cp)
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
@ -18,6 +18,7 @@ class Reservation < ApplicationRecord
|
||||
accepts_nested_attributes_for :tickets, allow_destroy: false
|
||||
|
||||
has_one :invoice, -> { where(type: nil) }, as: :invoiced, dependent: :destroy
|
||||
has_one :payment_schedule, as: :scheduled, dependent: :destroy
|
||||
|
||||
validates_presence_of :reservable_id, :reservable_type
|
||||
validate :machine_not_already_reserved, if: -> { reservable.is_a?(Machine) }
|
||||
|
@ -7,6 +7,7 @@ class Subscription < ApplicationRecord
|
||||
belongs_to :plan
|
||||
belongs_to :statistic_profile
|
||||
|
||||
has_one :payment_schedule, as: :scheduled, dependent: :destroy
|
||||
has_many :invoices, as: :invoiced, dependent: :destroy
|
||||
has_many :offer_days, dependent: :destroy
|
||||
|
||||
@ -53,7 +54,7 @@ class Subscription < ApplicationRecord
|
||||
method = operator&.admin? || (operator&.manager? && operator != user) ? nil : 'stripe'
|
||||
coupon = Coupon.find_by(code: coupon_code) unless coupon_code.nil?
|
||||
|
||||
schedule = PaymentScheduleService.new.create(self, plan.amount, coupon, operator, method)
|
||||
schedule = PaymentScheduleService.new.create(self, plan.amount, coupon: coupon, operator: operator, payment_method: method)
|
||||
|
||||
end
|
||||
|
||||
|
@ -9,6 +9,7 @@ class WalletTransaction < ApplicationRecord
|
||||
belongs_to :reservation
|
||||
belongs_to :transactable, polymorphic: true
|
||||
has_one :invoice
|
||||
has_one :payment_schedule
|
||||
|
||||
validates_inclusion_of :transaction_type, in: %w[credit debit]
|
||||
validates :invoicing_profile, :wallet, presence: true
|
||||
|
@ -28,7 +28,10 @@ class PaymentScheduleService
|
||||
items = []
|
||||
(0..deadlines - 1).each do |i|
|
||||
date = DateTime.current + i.months
|
||||
details = { recurring: per_month }
|
||||
amount = if i.zero?
|
||||
details[:adjustment] = adjustment
|
||||
details[:other_items] = other_items
|
||||
per_month + adjustment + other_items
|
||||
else
|
||||
per_month
|
||||
@ -36,13 +39,14 @@ class PaymentScheduleService
|
||||
items.push PaymentScheduleItem.new(
|
||||
amount: amount,
|
||||
due_date: date,
|
||||
payment_schedule: ps
|
||||
payment_schedule: ps,
|
||||
details: details
|
||||
)
|
||||
end
|
||||
{ payment_schedule: ps, items: items }
|
||||
end
|
||||
|
||||
def create(subscription, total, coupon, operator, payment_method)
|
||||
def create(subscription, total, coupon: nil, operator: nil, payment_method: nil, reservation: nil)
|
||||
schedule = compute(subscription.plan, total, coupon)
|
||||
ps = schedule[:payment_schedule]
|
||||
items = schedule[:items]
|
||||
@ -50,11 +54,13 @@ class PaymentScheduleService
|
||||
ps.scheduled = subscription
|
||||
ps.payment_method = payment_method
|
||||
ps.operator_profile_id = operator
|
||||
ps.save!
|
||||
# TODO, fields: reference, wallet_amount, wallet_transaction_id, footprint, environment, invoicing_profile
|
||||
items.each do |item|
|
||||
item.payment_schedule = ps
|
||||
item.save!
|
||||
end
|
||||
|
||||
StripeWorker.perform_async(:create_stripe_subscription, ps.id)
|
||||
StripeWorker.perform_async(:create_stripe_subscription, ps.id, reservation&.reservable&.stp_product_id)
|
||||
end
|
||||
end
|
||||
|
@ -13,7 +13,7 @@ end
|
||||
if @amount[:schedule]
|
||||
json.schedule do
|
||||
json.items @amount[:schedule][:items] do |item|
|
||||
json.price item.amount / 100.00
|
||||
json.amount item.amount / 100.00
|
||||
json.due_date item.due_date
|
||||
end
|
||||
end
|
||||
|
@ -68,30 +68,54 @@ class StripeWorker
|
||||
end
|
||||
end
|
||||
|
||||
def create_stripe_subscription(payment_schedule_id, first_invoice_items)
|
||||
def create_stripe_subscription(payment_schedule_id, reservable_stp_id)
|
||||
payment_schedule = PaymentSchedule.find(payment_schedule_id)
|
||||
|
||||
first_item = payment_schedule.ordered_items.first
|
||||
second_item = payment_schedule.ordered_items[1]
|
||||
|
||||
items = []
|
||||
first_invoice_items.each do |fii|
|
||||
# TODO, fill this prices with real data
|
||||
price = Stripe::Price.create({
|
||||
unit_amount: 2000,
|
||||
currency: 'eur',
|
||||
recurring: { interval: 'month' },
|
||||
product_data: {
|
||||
name: 'lorem ipsum'
|
||||
}
|
||||
},
|
||||
{ api_key: Setting.get('stripe_secret_key') })
|
||||
items.push(price: price[:id])
|
||||
if first_item.amount != second_item.amount
|
||||
if first_item.details[:adjustment]
|
||||
# adjustment: when dividing the price of the plan / months, sometimes it forces us to round the amount per month.
|
||||
# The difference is invoiced here
|
||||
p1 = Stripe::Price.create({
|
||||
unit_amount: first_item.details[:adjustment],
|
||||
currency: Setting.get('stripe_currency'),
|
||||
product: payment_schedule.scheduled.plan.stp_product_id,
|
||||
nickname: "Price adjustment payment schedule #{payment_schedule_id}"
|
||||
}, { api_key: Setting.get('stripe_secret_key') })
|
||||
items.push(price: p1[:id])
|
||||
end
|
||||
if first_item.details[:other_items]
|
||||
# when taking a subscription at the same time of a reservation (space, machine or training), the amount of the
|
||||
# reservation is invoiced here.
|
||||
p2 = Stripe::Price.create({
|
||||
unit_amount: first_item.details[:other_items],
|
||||
currency: Setting.get('stripe_currency'),
|
||||
product: reservable_stp_id,
|
||||
nickname: "Reservations for payment schedule #{payment_schedule_id}"
|
||||
}, { api_key: Setting.get('stripe_secret_key') })
|
||||
items.push(price: p2[:id])
|
||||
end
|
||||
end
|
||||
|
||||
# subscription (recurring price)
|
||||
price = Stripe::Price.create({
|
||||
unit_amount: first_item.details[:recurring],
|
||||
currency: Setting.get('stripe_currency'),
|
||||
recurring: { interval: 'month', interval_count: 1 },
|
||||
product: payment_schedule.scheduled.plan.stp_product_id
|
||||
},
|
||||
{ api_key: Setting.get('stripe_secret_key') })
|
||||
|
||||
stp_subscription = Stripe::Subscription.create({
|
||||
customer: payment_schedule.invoicing_profile.user.stp_customer_id,
|
||||
cancel_at: payment_schedule.scheduled.expiration_date,
|
||||
promotion_code: payment_schedule.coupon&.code,
|
||||
add_invoice_items: items,
|
||||
items: [
|
||||
{ price: payment_schedule.scheduled.plan.stp_price_id }
|
||||
{ price: price[:id] }
|
||||
]
|
||||
}, { api_key: Setting.get('stripe_secret_key') })
|
||||
payment_schedule.update_attributes(stp_subscription_id: stp_subscription.id)
|
||||
|
@ -6,6 +6,7 @@ class CreatePaymentScheduleItems < ActiveRecord::Migration[5.2]
|
||||
create_table :payment_schedule_items do |t|
|
||||
t.integer :amount
|
||||
t.datetime :due_date
|
||||
t.jsonb :details, default: '{}'
|
||||
t.belongs_to :payment_schedule, foreign_key: true
|
||||
t.belongs_to :invoice, foreign_key: true
|
||||
|
||||
|
@ -1470,6 +1470,7 @@ CREATE TABLE public.payment_schedule_items (
|
||||
id bigint NOT NULL,
|
||||
amount integer,
|
||||
due_date timestamp without time zone,
|
||||
details jsonb DEFAULT '"{}"'::jsonb,
|
||||
payment_schedule_id bigint,
|
||||
invoice_id bigint,
|
||||
created_at timestamp without time zone NOT NULL,
|
||||
|
@ -77,5 +77,6 @@ This is currently not supported, because of some PostgreSQL specific instruction
|
||||
- `app/models/project.rb` is using the `pg_search` gem.
|
||||
- `db/migrate/20200622135401_add_pg_search_dmetaphone_support_functions.rb` is using [fuzzystrmatch](http://www.postgresql.org/docs/current/static/fuzzystrmatch.html) module and defines a PL/pgSQL function (`pg_search_dmetaphone()`);
|
||||
- `db/migrate/20200623134900_add_search_vector_to_project.rb` is using [tsvector](https://www.postgresql.org/docs/10/datatype-textsearch.html), a PostgreSQL datatype and [GIN (Generalized Inverted Index)](https://www.postgresql.org/docs/9.1/textsearch-indexes.html) a PostgreSQL index type;
|
||||
- `db/migrate/20200623141305_update_search_vector_of_projects.rb` is defines a PL/pgSQL function (`fill_search_vector_for_project()`) and create an SQL trigger for this function;
|
||||
- `db/migrate/20200623141305_update_search_vector_of_projects.rb` defines a PL/pgSQL function (`fill_search_vector_for_project()`) and create an SQL trigger for this function;
|
||||
- `db/migrate/20200629123011_update_pg_trgm.rb` is using [ALTER EXTENSION](https://www.postgresql.org/docs/10/sql-alterextension.html);
|
||||
- `db/migrate/20201027101809_create_payment_schedule_items.rb` is using [jsonb](https://www.postgresql.org/docs/9.4/static/datatype-json.html);
|
||||
|
Loading…
x
Reference in New Issue
Block a user