From b8319a51248e0da9eb1ee050a0c51bed1a595810 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 30 Dec 2020 10:16:39 +0100 Subject: [PATCH] handle percent coupons w/ stripe subscriptions --- CHANGELOG.md | 1 + app/models/coupon.rb | 4 ++-- app/models/payment_schedule.rb | 2 +- app/models/reservation.rb | 4 +--- app/services/payment_schedule_service.rb | 12 ++++++------ app/services/reservations/reserve.rb | 9 ++++----- app/services/stripe_service.rb | 24 +++++++++++++++++++++--- app/services/subscriptions/subscribe.rb | 7 ++++--- app/workers/stripe_worker.rb | 21 ++------------------- 9 files changed, 42 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e3baefbb..3500ee589 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Refactored theme builder to use scss files - Updated stripe gem to 5.21.0 - Architecture documentation +- Improved coupon creation/deletion workflow - Fix a bug: unable to access embedded plan views - 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 diff --git a/app/models/coupon.rb b/app/models/coupon.rb index d335c56f3..4e0a7d13b 100644 --- a/app/models/coupon.rb +++ b/app/models/coupon.rb @@ -5,7 +5,7 @@ class Coupon < ApplicationRecord has_many :invoices 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] validates :name, presence: true @@ -94,7 +94,7 @@ class Coupon < ApplicationRecord private def create_stripe_coupon - StripeWorker.perform_async(:create_stripe_coupon, id) + StripeService.create_stripe_coupon(id) end def delete_stripe_coupon diff --git a/app/models/payment_schedule.rb b/app/models/payment_schedule.rb index 9f5ce5fb0..0d541a7fd 100644 --- a/app/models/payment_schedule.rb +++ b/app/models/payment_schedule.rb @@ -52,7 +52,7 @@ class PaymentSchedule < PaymentDocument def post_save(setup_intent_id) return unless payment_method == 'stripe' - StripeService.create_stripe_subscription(id, setup_intent_id) + StripeService.create_stripe_subscription(self, setup_intent_id) end private diff --git a/app/models/reservation.rb b/app/models/reservation.rb index f7794f181..18457b00d 100644 --- a/app/models/reservation.rb +++ b/app/models/reservation.rb @@ -45,9 +45,7 @@ class Reservation < ApplicationRecord def generate_subscription return unless plan_id - self.subscription = Subscription.find_or_initialize_by(statistic_profile_id: statistic_profile_id) - subscription.attributes = { plan_id: plan_id, statistic_profile_id: statistic_profile_id, expiration_date: nil } - + self.subscription = Subscription.new(plan_id: plan_id, statistic_profile_id: statistic_profile_id, expiration_date: nil) subscription.init_save subscription end diff --git a/app/services/payment_schedule_service.rb b/app/services/payment_schedule_service.rb index 071f2e6bd..98dd52a59 100644 --- a/app/services/payment_schedule_service.rb +++ b/app/services/payment_schedule_service.rb @@ -20,8 +20,8 @@ class PaymentScheduleService ps = PaymentSchedule.new(scheduled: plan, total: price + other_items, coupon: coupon) deadlines = plan.duration / 1.month per_month = (price / deadlines).truncate - adjustment = if per_month * deadlines != price - price - (per_month * deadlines) + adjustment = if per_month * deadlines + other_items.truncate != ps.total + ps.total - (per_month * deadlines + other_items.truncate) else 0 end @@ -30,9 +30,9 @@ class PaymentScheduleService 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 + details[:adjustment] = adjustment.truncate + details[:other_items] = other_items.truncate + per_month + adjustment.truncate + other_items.truncate else per_month 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) subscription = reservation.generate_subscription if !subscription && reservation&.plan_id - raise InvalidSubscriptionError unless subscription + raise InvalidSubscriptionError unless subscription&.persisted? schedule = compute(subscription.plan, total, coupon) ps = schedule[:payment_schedule] diff --git a/app/services/reservations/reserve.rb b/app/services/reservations/reserve.rb index 6c4bdce56..5c5009579 100644 --- a/app/services/reservations/reserve.rb +++ b/app/services/reservations/reserve.rb @@ -25,16 +25,16 @@ class Reservations::Reserve operator_profile_id: operator_profile_id, user: user, payment_method: payment_method, - coupon_code: payment_details[:coupon], + coupon: payment_details[:coupon], setup_intent_id: intent_id) else generate_invoice(reservation, operator_profile_id, payment_details, intent_id) end WalletService.debit_user_wallet(payment, user, reservation) - payment.save reservation.save - payment.post_save(intent_id) reservation.post_save + payment.save + payment.post_save(intent_id) end true end @@ -44,10 +44,9 @@ class Reservations::Reserve ## # 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) operator = InvoicingProfile.find(operator_profile_id)&.user - coupon = Coupon.find_by(code: coupon_code) unless coupon_code.nil? PaymentScheduleService.new.create( nil, diff --git a/app/services/stripe_service.rb b/app/services/stripe_service.rb index 17498328d..0af774793 100644 --- a/app/services/stripe_service.rb +++ b/app/services/stripe_service.rb @@ -5,9 +5,8 @@ class StripeService class << self # 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') - payment_schedule = PaymentSchedule.find(payment_schedule_id) first_item = payment_schedule.ordered_items.first case payment_schedule.scheduled_type @@ -35,8 +34,8 @@ class StripeService stp_subscription = Stripe::Subscription.create({ customer: payment_schedule.invoicing_profile.user.stp_customer_id, cancel_at: subscription.expiration_date.to_i, - promotion_code: payment_schedule.coupon&.code, add_invoice_items: items, + coupon: payment_schedule.coupon&.code, items: [ { price: price[:id] } ], @@ -45,6 +44,25 @@ class StripeService payment_schedule.update_attributes(stp_subscription_id: stp_subscription.id) 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 def subscription_invoice_items(payment_schedule, subscription, first_item, reservable_stp_id) diff --git a/app/services/subscriptions/subscribe.rb b/app/services/subscriptions/subscribe.rb index fae4bc07d..8e4bef2d9 100644 --- a/app/services/subscriptions/subscribe.rb +++ b/app/services/subscriptions/subscribe.rb @@ -24,13 +24,15 @@ class Subscriptions::Subscribe ActiveRecord::Base.transaction do subscription.init_save + raise InvalidSubscriptionError unless subscription&.persisted? + payment = if schedule generate_schedule(subscription: subscription, total: payment_details[:before_coupon], operator_profile_id: operator_profile_id, user: user, payment_method: payment_method, - coupon_code: payment_details[:coupon], + coupon: payment_details[:coupon], setup_intent_id: intent_id) else generate_invoice(subscription, operator_profile_id, payment_details, intent_id) @@ -75,10 +77,9 @@ class Subscriptions::Subscribe ## # 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) operator = InvoicingProfile.find(operator_profile_id)&.user - coupon = Coupon.find_by(code: coupon_code) unless coupon_code.nil? PaymentScheduleService.new.create( subscription, diff --git a/app/workers/stripe_worker.rb b/app/workers/stripe_worker.rb index 76a3cb6ca..3df6c087b 100644 --- a/app/workers/stripe_worker.rb +++ b/app/workers/stripe_worker.rb @@ -21,28 +21,11 @@ class StripeWorker user.update_columns(stp_customer_id: customer.id) 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) cpn = Stripe::Coupon.retrieve(coupon_code, api_key: Setting.get('stripe_secret_key')) cpn.delete + rescue Stripe::InvalidRequestError => e + STDERR.puts "WARNING: Unable to delete the coupon on Stripe: #{e}" end def create_or_update_stp_product(class_name, id)