mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-20 14:54:15 +01:00
handle percent coupons w/ stripe subscriptions
This commit is contained in:
parent
ffc3051444
commit
b8319a5124
@ -4,6 +4,7 @@
|
|||||||
- Refactored theme builder to use scss files
|
- Refactored theme builder to use scss files
|
||||||
- Updated stripe gem to 5.21.0
|
- Updated stripe gem to 5.21.0
|
||||||
- Architecture documentation
|
- Architecture documentation
|
||||||
|
- Improved coupon creation/deletion workflow
|
||||||
- Fix a bug: unable to access embedded plan views
|
- Fix a bug: unable to access embedded plan views
|
||||||
- Fix a bug: warning message overflow in credit wallet modal
|
- Fix a bug: warning message overflow in credit wallet modal
|
||||||
- Fix a bug: when using a cash coupon, the amount shown in the statistics is invalid
|
- Fix a bug: when using a cash coupon, the amount shown in the statistics is invalid
|
||||||
|
@ -5,7 +5,7 @@ class Coupon < ApplicationRecord
|
|||||||
has_many :invoices
|
has_many :invoices
|
||||||
has_many :payment_schedule
|
has_many :payment_schedule
|
||||||
|
|
||||||
after_commit :create_stripe_coupon, on: [:create]
|
after_save :create_stripe_coupon, on: [:create]
|
||||||
after_commit :delete_stripe_coupon, on: [:destroy]
|
after_commit :delete_stripe_coupon, on: [:destroy]
|
||||||
|
|
||||||
validates :name, presence: true
|
validates :name, presence: true
|
||||||
@ -94,7 +94,7 @@ class Coupon < ApplicationRecord
|
|||||||
private
|
private
|
||||||
|
|
||||||
def create_stripe_coupon
|
def create_stripe_coupon
|
||||||
StripeWorker.perform_async(:create_stripe_coupon, id)
|
StripeService.create_stripe_coupon(id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_stripe_coupon
|
def delete_stripe_coupon
|
||||||
|
@ -52,7 +52,7 @@ class PaymentSchedule < PaymentDocument
|
|||||||
def post_save(setup_intent_id)
|
def post_save(setup_intent_id)
|
||||||
return unless payment_method == 'stripe'
|
return unless payment_method == 'stripe'
|
||||||
|
|
||||||
StripeService.create_stripe_subscription(id, setup_intent_id)
|
StripeService.create_stripe_subscription(self, setup_intent_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -45,9 +45,7 @@ class Reservation < ApplicationRecord
|
|||||||
def generate_subscription
|
def generate_subscription
|
||||||
return unless plan_id
|
return unless plan_id
|
||||||
|
|
||||||
self.subscription = Subscription.find_or_initialize_by(statistic_profile_id: statistic_profile_id)
|
self.subscription = Subscription.new(plan_id: plan_id, statistic_profile_id: statistic_profile_id, expiration_date: nil)
|
||||||
subscription.attributes = { plan_id: plan_id, statistic_profile_id: statistic_profile_id, expiration_date: nil }
|
|
||||||
|
|
||||||
subscription.init_save
|
subscription.init_save
|
||||||
subscription
|
subscription
|
||||||
end
|
end
|
||||||
|
@ -20,8 +20,8 @@ class PaymentScheduleService
|
|||||||
ps = PaymentSchedule.new(scheduled: plan, total: price + other_items, coupon: coupon)
|
ps = PaymentSchedule.new(scheduled: plan, total: price + other_items, coupon: coupon)
|
||||||
deadlines = plan.duration / 1.month
|
deadlines = plan.duration / 1.month
|
||||||
per_month = (price / deadlines).truncate
|
per_month = (price / deadlines).truncate
|
||||||
adjustment = if per_month * deadlines != price
|
adjustment = if per_month * deadlines + other_items.truncate != ps.total
|
||||||
price - (per_month * deadlines)
|
ps.total - (per_month * deadlines + other_items.truncate)
|
||||||
else
|
else
|
||||||
0
|
0
|
||||||
end
|
end
|
||||||
@ -30,9 +30,9 @@ class PaymentScheduleService
|
|||||||
date = DateTime.current + i.months
|
date = DateTime.current + i.months
|
||||||
details = { recurring: per_month }
|
details = { recurring: per_month }
|
||||||
amount = if i.zero?
|
amount = if i.zero?
|
||||||
details[:adjustment] = adjustment
|
details[:adjustment] = adjustment.truncate
|
||||||
details[:other_items] = other_items
|
details[:other_items] = other_items.truncate
|
||||||
per_month + adjustment + other_items
|
per_month + adjustment.truncate + other_items.truncate
|
||||||
else
|
else
|
||||||
per_month
|
per_month
|
||||||
end
|
end
|
||||||
@ -48,7 +48,7 @@ class PaymentScheduleService
|
|||||||
|
|
||||||
def create(subscription, total, coupon: nil, operator: nil, payment_method: nil, reservation: nil, user: nil, setup_intent_id: nil)
|
def create(subscription, total, coupon: nil, operator: nil, payment_method: nil, reservation: nil, user: nil, setup_intent_id: nil)
|
||||||
subscription = reservation.generate_subscription if !subscription && reservation&.plan_id
|
subscription = reservation.generate_subscription if !subscription && reservation&.plan_id
|
||||||
raise InvalidSubscriptionError unless subscription
|
raise InvalidSubscriptionError unless subscription&.persisted?
|
||||||
|
|
||||||
schedule = compute(subscription.plan, total, coupon)
|
schedule = compute(subscription.plan, total, coupon)
|
||||||
ps = schedule[:payment_schedule]
|
ps = schedule[:payment_schedule]
|
||||||
|
@ -25,16 +25,16 @@ class Reservations::Reserve
|
|||||||
operator_profile_id: operator_profile_id,
|
operator_profile_id: operator_profile_id,
|
||||||
user: user,
|
user: user,
|
||||||
payment_method: payment_method,
|
payment_method: payment_method,
|
||||||
coupon_code: payment_details[:coupon],
|
coupon: payment_details[:coupon],
|
||||||
setup_intent_id: intent_id)
|
setup_intent_id: intent_id)
|
||||||
else
|
else
|
||||||
generate_invoice(reservation, operator_profile_id, payment_details, intent_id)
|
generate_invoice(reservation, operator_profile_id, payment_details, intent_id)
|
||||||
end
|
end
|
||||||
WalletService.debit_user_wallet(payment, user, reservation)
|
WalletService.debit_user_wallet(payment, user, reservation)
|
||||||
payment.save
|
|
||||||
reservation.save
|
reservation.save
|
||||||
payment.post_save(intent_id)
|
|
||||||
reservation.post_save
|
reservation.post_save
|
||||||
|
payment.save
|
||||||
|
payment.post_save(intent_id)
|
||||||
end
|
end
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
@ -44,10 +44,9 @@ class Reservations::Reserve
|
|||||||
##
|
##
|
||||||
# Generate the invoice for the given reservation+subscription
|
# Generate the invoice for the given reservation+subscription
|
||||||
##
|
##
|
||||||
def generate_schedule(reservation: nil, total: nil, operator_profile_id: nil, user: nil, payment_method: nil, coupon_code: nil,
|
def generate_schedule(reservation: nil, total: nil, operator_profile_id: nil, user: nil, payment_method: nil, coupon: nil,
|
||||||
setup_intent_id: nil)
|
setup_intent_id: nil)
|
||||||
operator = InvoicingProfile.find(operator_profile_id)&.user
|
operator = InvoicingProfile.find(operator_profile_id)&.user
|
||||||
coupon = Coupon.find_by(code: coupon_code) unless coupon_code.nil?
|
|
||||||
|
|
||||||
PaymentScheduleService.new.create(
|
PaymentScheduleService.new.create(
|
||||||
nil,
|
nil,
|
||||||
|
@ -5,9 +5,8 @@ class StripeService
|
|||||||
class << self
|
class << self
|
||||||
|
|
||||||
# Create the provided PaymentSchedule on Stripe, using the Subscription API
|
# Create the provided PaymentSchedule on Stripe, using the Subscription API
|
||||||
def create_stripe_subscription(payment_schedule_id, setup_intent_id)
|
def create_stripe_subscription(payment_schedule, setup_intent_id)
|
||||||
stripe_key = Setting.get('stripe_secret_key')
|
stripe_key = Setting.get('stripe_secret_key')
|
||||||
payment_schedule = PaymentSchedule.find(payment_schedule_id)
|
|
||||||
first_item = payment_schedule.ordered_items.first
|
first_item = payment_schedule.ordered_items.first
|
||||||
|
|
||||||
case payment_schedule.scheduled_type
|
case payment_schedule.scheduled_type
|
||||||
@ -35,8 +34,8 @@ class StripeService
|
|||||||
stp_subscription = Stripe::Subscription.create({
|
stp_subscription = Stripe::Subscription.create({
|
||||||
customer: payment_schedule.invoicing_profile.user.stp_customer_id,
|
customer: payment_schedule.invoicing_profile.user.stp_customer_id,
|
||||||
cancel_at: subscription.expiration_date.to_i,
|
cancel_at: subscription.expiration_date.to_i,
|
||||||
promotion_code: payment_schedule.coupon&.code,
|
|
||||||
add_invoice_items: items,
|
add_invoice_items: items,
|
||||||
|
coupon: payment_schedule.coupon&.code,
|
||||||
items: [
|
items: [
|
||||||
{ price: price[:id] }
|
{ price: price[:id] }
|
||||||
],
|
],
|
||||||
@ -45,6 +44,25 @@ class StripeService
|
|||||||
payment_schedule.update_attributes(stp_subscription_id: stp_subscription.id)
|
payment_schedule.update_attributes(stp_subscription_id: stp_subscription.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_stripe_coupon(coupon_id)
|
||||||
|
coupon = Coupon.find(coupon_id)
|
||||||
|
stp_coupon = {
|
||||||
|
id: coupon.code,
|
||||||
|
duration: coupon.validity_per_user
|
||||||
|
}
|
||||||
|
if coupon.type == 'percent_off'
|
||||||
|
stp_coupon[:percent_off] = coupon.percent_off
|
||||||
|
elsif coupon.type == 'amount_off'
|
||||||
|
stp_coupon[:amount_off] = coupon.amount_off
|
||||||
|
stp_coupon[:currency] = Setting.get('stripe_currency')
|
||||||
|
end
|
||||||
|
|
||||||
|
stp_coupon[:redeem_by] = coupon.valid_until.to_i unless coupon.valid_until.nil?
|
||||||
|
stp_coupon[:max_redemptions] = coupon.max_usages unless coupon.max_usages.nil?
|
||||||
|
|
||||||
|
Stripe::Coupon.create(stp_coupon, api_key: Setting.get('stripe_secret_key'))
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def subscription_invoice_items(payment_schedule, subscription, first_item, reservable_stp_id)
|
def subscription_invoice_items(payment_schedule, subscription, first_item, reservable_stp_id)
|
||||||
|
@ -24,13 +24,15 @@ class Subscriptions::Subscribe
|
|||||||
|
|
||||||
ActiveRecord::Base.transaction do
|
ActiveRecord::Base.transaction do
|
||||||
subscription.init_save
|
subscription.init_save
|
||||||
|
raise InvalidSubscriptionError unless subscription&.persisted?
|
||||||
|
|
||||||
payment = if schedule
|
payment = if schedule
|
||||||
generate_schedule(subscription: subscription,
|
generate_schedule(subscription: subscription,
|
||||||
total: payment_details[:before_coupon],
|
total: payment_details[:before_coupon],
|
||||||
operator_profile_id: operator_profile_id,
|
operator_profile_id: operator_profile_id,
|
||||||
user: user,
|
user: user,
|
||||||
payment_method: payment_method,
|
payment_method: payment_method,
|
||||||
coupon_code: payment_details[:coupon],
|
coupon: payment_details[:coupon],
|
||||||
setup_intent_id: intent_id)
|
setup_intent_id: intent_id)
|
||||||
else
|
else
|
||||||
generate_invoice(subscription, operator_profile_id, payment_details, intent_id)
|
generate_invoice(subscription, operator_profile_id, payment_details, intent_id)
|
||||||
@ -75,10 +77,9 @@ class Subscriptions::Subscribe
|
|||||||
##
|
##
|
||||||
# Generate the invoice for the given subscription
|
# Generate the invoice for the given subscription
|
||||||
##
|
##
|
||||||
def generate_schedule(subscription: nil, total: nil, operator_profile_id: nil, user: nil, payment_method: nil, coupon_code: nil,
|
def generate_schedule(subscription: nil, total: nil, operator_profile_id: nil, user: nil, payment_method: nil, coupon: nil,
|
||||||
setup_intent_id: nil)
|
setup_intent_id: nil)
|
||||||
operator = InvoicingProfile.find(operator_profile_id)&.user
|
operator = InvoicingProfile.find(operator_profile_id)&.user
|
||||||
coupon = Coupon.find_by(code: coupon_code) unless coupon_code.nil?
|
|
||||||
|
|
||||||
PaymentScheduleService.new.create(
|
PaymentScheduleService.new.create(
|
||||||
subscription,
|
subscription,
|
||||||
|
@ -21,28 +21,11 @@ class StripeWorker
|
|||||||
user.update_columns(stp_customer_id: customer.id)
|
user.update_columns(stp_customer_id: customer.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_stripe_coupon(coupon_id)
|
|
||||||
coupon = Coupon.find(coupon_id)
|
|
||||||
stp_coupon = {
|
|
||||||
id: coupon.code,
|
|
||||||
duration: coupon.validity_per_user
|
|
||||||
}
|
|
||||||
if coupon.type == 'percent_off'
|
|
||||||
stp_coupon[:percent_off] = coupon.percent_off
|
|
||||||
elsif coupon.type == 'amount_off'
|
|
||||||
stp_coupon[:amount_off] = coupon.amount_off
|
|
||||||
stp_coupon[:currency] = Setting.get('stripe_currency')
|
|
||||||
end
|
|
||||||
|
|
||||||
stp_coupon[:redeem_by] = coupon.valid_until.to_i unless coupon.valid_until.nil?
|
|
||||||
stp_coupon[:max_redemptions] = coupon.max_usages unless coupon.max_usages.nil?
|
|
||||||
|
|
||||||
Stripe::Coupon.create(stp_coupon, api_key: Setting.get('stripe_secret_key'))
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_stripe_coupon(coupon_code)
|
def delete_stripe_coupon(coupon_code)
|
||||||
cpn = Stripe::Coupon.retrieve(coupon_code, api_key: Setting.get('stripe_secret_key'))
|
cpn = Stripe::Coupon.retrieve(coupon_code, api_key: Setting.get('stripe_secret_key'))
|
||||||
cpn.delete
|
cpn.delete
|
||||||
|
rescue Stripe::InvalidRequestError => e
|
||||||
|
STDERR.puts "WARNING: Unable to delete the coupon on Stripe: #{e}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_or_update_stp_product(class_name, id)
|
def create_or_update_stp_product(class_name, id)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user