From e3187460eaed4944f602131867acf4848f4b76a3 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Fri, 30 Apr 2021 16:07:19 +0200 Subject: [PATCH] create payment schedules on payzen Also: make generic the creation of products on remote gateway Also: make generic the call to gateway specific actions --- app/controllers/api/payzen_controller.rb | 5 - app/exceptions/payment_gateway_error.rb | 5 + app/exceptions/payzen_error.rb | 2 +- app/frontend/src/javascript/api/payzen.ts | 5 - .../components/payment/payzen/payzen-form.tsx | 13 +- app/models/coupon.rb | 12 +- app/models/invoice.rb | 2 +- app/models/machine.rb | 8 +- app/models/payment_schedule.rb | 4 +- app/models/plan.rb | 8 +- app/models/space.rb | 8 +- app/models/training.rb | 8 +- app/services/payment_gateway_service.rb | 33 +++++ app/services/plans_service.rb | 2 +- app/services/stripe_service.rb | 113 ---------------- app/workers/payment_schedule_item_worker.rb | 1 + lib/pay_zen/charge.rb | 22 +++ lib/pay_zen/order.rb | 2 - lib/pay_zen/service.rb | 39 ++++++ lib/pay_zen/subscription.rb | 17 +++ lib/pay_zen/token.rb | 2 - lib/payment/service.rb | 16 +++ lib/stripe/service.rb | 126 ++++++++++++++++++ 23 files changed, 287 insertions(+), 166 deletions(-) create mode 100644 app/exceptions/payment_gateway_error.rb create mode 100644 app/services/payment_gateway_service.rb delete mode 100644 app/services/stripe_service.rb create mode 100644 lib/pay_zen/service.rb create mode 100644 lib/pay_zen/subscription.rb create mode 100644 lib/payment/service.rb create mode 100644 lib/stripe/service.rb diff --git a/app/controllers/api/payzen_controller.rb b/app/controllers/api/payzen_controller.rb index cc7ff5e8b..555a3693f 100644 --- a/app/controllers/api/payzen_controller.rb +++ b/app/controllers/api/payzen_controller.rb @@ -61,11 +61,6 @@ class API::PayzenController < API::PaymentsController render json: e, status: :unprocessable_entity end - def confirm_payment_schedule - # TODO - raise NotImplementedError - end - private def on_reservation_success(order_id, details) diff --git a/app/exceptions/payment_gateway_error.rb b/app/exceptions/payment_gateway_error.rb new file mode 100644 index 000000000..5ee6aa66e --- /dev/null +++ b/app/exceptions/payment_gateway_error.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +# Raised when an an error occurred with any payment gateway +class PaymentGatewayError < StandardError +end diff --git a/app/exceptions/payzen_error.rb b/app/exceptions/payzen_error.rb index e0b325111..e449f6965 100644 --- a/app/exceptions/payzen_error.rb +++ b/app/exceptions/payzen_error.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true # Raised when an an error occurred with the PayZen payment gateway -class PayzenError < StandardError +class PayzenError < PaymentGatewayError end diff --git a/app/frontend/src/javascript/api/payzen.ts b/app/frontend/src/javascript/api/payzen.ts index a5e684f89..5d404c180 100644 --- a/app/frontend/src/javascript/api/payzen.ts +++ b/app/frontend/src/javascript/api/payzen.ts @@ -36,9 +36,4 @@ export default class PayzenAPI { const res: AxiosResponse = await apiClient.post('/api/payzen/confirm_payment', { cart_items: cartItems, order_id: orderId }); return res?.data; } - - static async confirmSchedule(orderId: string, cartItems: CartItems): Promise { - const res: AxiosResponse = await apiClient.post('/api/payzen/confirm_payment_schedule', { cart_items: cartItems, order_id: orderId }); - return res?.data; - } } diff --git a/app/frontend/src/javascript/components/payment/payzen/payzen-form.tsx b/app/frontend/src/javascript/components/payment/payzen/payzen-form.tsx index 77cb37aca..acd7ddbd8 100644 --- a/app/frontend/src/javascript/components/payment/payzen/payzen-form.tsx +++ b/app/frontend/src/javascript/components/payment/payzen/payzen-form.tsx @@ -63,7 +63,7 @@ export const PayzenForm: React.FC = ({ onSubmit, onSuccess, on const transaction = event.clientAnswer.transactions[0]; if (event.clientAnswer.orderStatus === 'PAID') { - confirm(event).then((confirmation) => { + PayzenAPI.confirm(event.clientAnswer.orderDetails.orderId, cartItems).then((confirmation) => { PayZenKR.current.removeForms().then(() => { onSuccess(confirmation); }); @@ -77,17 +77,6 @@ export const PayzenForm: React.FC = ({ onSubmit, onSuccess, on return true; }; - /** - * Ask the API to confirm the processed transaction, depending on the current transaction (schedule or not). - */ - const confirm = async (paymentAnswer: ProcessPaymentAnswer): Promise => { - if (paymentSchedule) { - return await PayzenAPI.confirm(paymentAnswer.clientAnswer.orderDetails.orderId, cartItems); - } else { - return await PayzenAPI.confirmSchedule(paymentAnswer.clientAnswer.orderDetails.orderId, cartItems); - } - } - /** * Callback triggered when the PayZen form was entirely loaded and displayed * @see https://docs.lyra.com/fr/rest/V4.0/javascript/features/reference.html#%C3%89v%C3%A9nements diff --git a/app/models/coupon.rb b/app/models/coupon.rb index 36afc2594..b5dd71401 100644 --- a/app/models/coupon.rb +++ b/app/models/coupon.rb @@ -5,8 +5,8 @@ class Coupon < ApplicationRecord has_many :invoices has_many :payment_schedule - after_create :create_stripe_coupon - after_commit :delete_stripe_coupon, on: [:destroy] + after_create :create_gateway_coupon + after_commit :delete_gateway_coupon, on: [:destroy] validates :name, presence: true validates :code, presence: true @@ -97,12 +97,12 @@ class Coupon < ApplicationRecord private - def create_stripe_coupon - StripeService.create_stripe_coupon(id) + def create_gateway_coupon + PaymentGatewayService.create_coupon(id) end - def delete_stripe_coupon - StripeWorker.perform_async(:delete_stripe_coupon, code) + def delete_gateway_coupon + PaymentGatewayService.delete_coupon(id) end end diff --git a/app/models/invoice.rb b/app/models/invoice.rb index b0e2cfa21..adc8cf28f 100644 --- a/app/models/invoice.rb +++ b/app/models/invoice.rb @@ -163,7 +163,7 @@ class Invoice < PaymentDocument end def paid_by_card? - !payment_gateway_object.nil? || %w[stripe payzen].include?(payment_method) + !payment_gateway_object.nil? && payment_method == 'card' end private diff --git a/app/models/machine.rb b/app/models/machine.rb index e69b56ed5..2b5f5ee26 100644 --- a/app/models/machine.rb +++ b/app/models/machine.rb @@ -31,8 +31,8 @@ class Machine < ApplicationRecord after_create :create_statistic_subtype after_create :create_machine_prices - after_create :update_stripe_product - after_update :update_stripe_product, if: :saved_change_to_name? + after_create :update_gateway_product + after_update :update_gateway_product, if: :saved_change_to_name? after_update :update_statistic_subtype, if: :saved_change_to_name? after_destroy :remove_statistic_subtype @@ -79,7 +79,7 @@ class Machine < ApplicationRecord private - def update_stripe_product - StripeWorker.perform_async(:create_or_update_stp_product, Machine.name, id) + def update_gateway_product + PaymentGatewayService.create_or_update_product(Machine.name, id) end end diff --git a/app/models/payment_schedule.rb b/app/models/payment_schedule.rb index c6a2c1b97..6f3cc7724 100644 --- a/app/models/payment_schedule.rb +++ b/app/models/payment_schedule.rb @@ -70,10 +70,10 @@ class PaymentSchedule < PaymentDocument payment_schedule_items end - def post_save(setup_intent_id) + def post_save(gateway_method_id) return unless payment_method == 'card' - StripeService.create_stripe_subscription(self, setup_intent_id) + PaymentGatewayService.new.create_subscription(self, gateway_method_id) end private diff --git a/app/models/plan.rb b/app/models/plan.rb index 0b7a9bf64..33d9b56ab 100644 --- a/app/models/plan.rb +++ b/app/models/plan.rb @@ -24,8 +24,8 @@ class Plan < ApplicationRecord after_create :create_spaces_prices after_create :create_statistic_type after_create :set_name - after_create :update_stripe_product - after_update :update_stripe_product, if: :saved_change_to_base_name? + after_create :update_gateway_product + after_update :update_gateway_product, if: :saved_change_to_base_name? validates :amount, :group, :base_name, presence: true validates :interval_count, numericality: { only_integer: true, greater_than_or_equal_to: 1 } @@ -130,7 +130,7 @@ class Plan < ApplicationRecord update_columns(name: human_readable_name) end - def update_stripe_product - StripeWorker.perform_async(:create_or_update_stp_product, Plan.name, id) + def update_gateway_product + PaymentGatewayService.create_or_update_product(Plan.name, id) end end diff --git a/app/models/space.rb b/app/models/space.rb index 17ca2434e..ea3426f9e 100644 --- a/app/models/space.rb +++ b/app/models/space.rb @@ -28,8 +28,8 @@ class Space < ApplicationRecord after_create :create_statistic_subtype after_create :create_space_prices - after_create :update_stripe_product - after_update :update_stripe_product, if: :saved_change_to_name? + after_create :update_gateway_product + after_update :update_gateway_product, if: :saved_change_to_name? after_update :update_statistic_subtype, if: :saved_change_to_name? after_destroy :remove_statistic_subtype @@ -67,7 +67,7 @@ class Space < ApplicationRecord private - def update_stripe_product - StripeWorker.perform_async(:create_or_update_stp_product, Space.name, id) + def update_gateway_product + PaymentGatewayService.create_or_update_product(Space.name, id) end end diff --git a/app/models/training.rb b/app/models/training.rb index df790af2f..8971b59eb 100644 --- a/app/models/training.rb +++ b/app/models/training.rb @@ -30,8 +30,8 @@ class Training < ApplicationRecord after_create :create_statistic_subtype after_create :create_trainings_pricings - after_create :update_stripe_product - after_update :update_stripe_product, if: :saved_change_to_name? + after_create :update_gateway_product + after_update :update_gateway_product, if: :saved_change_to_name? after_update :update_statistic_subtype, if: :saved_change_to_name? after_destroy :remove_statistic_subtype @@ -69,7 +69,7 @@ class Training < ApplicationRecord end end - def update_stripe_product - StripeWorker.perform_async(:create_or_update_stp_product, Training.name, id) + def update_gateway_product + PaymentGatewayService.create_or_update_product(Training.name, id) end end diff --git a/app/services/payment_gateway_service.rb b/app/services/payment_gateway_service.rb new file mode 100644 index 000000000..771748129 --- /dev/null +++ b/app/services/payment_gateway_service.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +# create remote items on currently active payment gateway +class PaymentGatewayService + def initialize + @gateway = if Stripe::Helper.enabled? + require 'stripe/service' + Stripe::Service + elsif PayZen::Helper.enabled? + require 'pay_zen/service' + PayZen::Service + else + require 'payment/service' + Payment::Service + end + end + + def create_subscription(payment_schedule, gateway_object_id) + @gateway.create_subscription(payment_schedule, gateway_object_id) + end + + def create_coupon(coupon_id) + @gateway.create_coupon(coupon_id) + end + + def delete_coupon(coupon_id) + @gateway.delete_coupon(coupon_id) + end + + def create_or_update_product(klass, id) + @gateway.create_or_update_product(klass, id) + end +end diff --git a/app/services/plans_service.rb b/app/services/plans_service.rb index c3a6c746f..d936c580e 100644 --- a/app/services/plans_service.rb +++ b/app/services/plans_service.rb @@ -20,7 +20,7 @@ class PlansService { errors: plan.errors } end end - rescue Stripe::InvalidRequestError => e + rescue PaymentGatewayError => e { errors: e.message } end end diff --git a/app/services/stripe_service.rb b/app/services/stripe_service.rb deleted file mode 100644 index d98be6d51..000000000 --- a/app/services/stripe_service.rb +++ /dev/null @@ -1,113 +0,0 @@ -# frozen_string_literal: true - -# Helpers and utilities for interactions with the Stripe payment gateway -class StripeService - class << self - - # Create the provided PaymentSchedule on Stripe, using the Subscription API - def create_stripe_subscription(payment_schedule, setup_intent_id) - stripe_key = Setting.get('stripe_secret_key') - first_item = payment_schedule.ordered_items.first - - case payment_schedule.scheduled_type - when Reservation.name - subscription = payment_schedule.scheduled.subscription - reservable_stp_id = payment_schedule.scheduled.reservable&.payment_gateway_object&.gateway_object_id - when Subscription.name - subscription = payment_schedule.scheduled - reservable_stp_id = nil - else - raise InvalidSubscriptionError - end - - handle_wallet_transaction(payment_schedule) - - # setup intent (associates the customer and the payment method) - intent = Stripe::SetupIntent.retrieve(setup_intent_id, api_key: stripe_key) - # subscription (recurring price) - price = create_price(first_item.details['recurring'], - subscription.plan.payment_gateway_object.gateway_object_id, - nil, monthly: true) - # other items (not recurring) - items = subscription_invoice_items(payment_schedule, subscription, first_item, reservable_stp_id) - - stp_subscription = Stripe::Subscription.create({ - customer: payment_schedule.invoicing_profile.user.payment_gateway_object.gateway_object_id, - cancel_at: (payment_schedule.ordered_items.last.due_date + 3.day).to_i, - add_invoice_items: items, - coupon: payment_schedule.coupon&.code, - items: [ - { price: price[:id] } - ], - default_payment_method: intent[:payment_method] - }, { api_key: stripe_key }) - pgo = PaymentGatewayObject.new(item: payment_schedule) - pgo.gateway_object = stp_subscription - pgo.save! - end - - def create_stripe_coupon(coupon_id) - coupon = Coupon.find(coupon_id) - stp_coupon = { id: coupon.code } - 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[:duration] = coupon.validity_per_user == 'always' ? 'forever' : 'once' - 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) - second_item = payment_schedule.ordered_items[1] - - items = [] - if first_item.amount != second_item.amount - unless first_item.details['adjustment']&.zero? - # 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 = create_price(first_item.details['adjustment'], - subscription.plan.payment_gateway_object.gateway_object_id, - "Price adjustment for payment schedule #{payment_schedule.id}") - items.push(price: p1[:id]) - end - unless first_item.details['other_items']&.zero? - # when taking a subscription at the same time of a reservation (space, machine or training), the amount of the - # reservation is invoiced here. - p2 = create_price(first_item.details['other_items'], - reservable_stp_id, - "Reservations for payment schedule #{payment_schedule.id}") - items.push(price: p2[:id]) - end - end - - items - end - - def create_price(amount, stp_product_id, name, monthly: false) - params = { - unit_amount: amount, - currency: Setting.get('stripe_currency'), - product: stp_product_id, - nickname: name - } - params[:recurring] = { interval: 'month', interval_count: 1 } if monthly - - Stripe::Price.create(params, api_key: Setting.get('stripe_secret_key')) - end - - def handle_wallet_transaction(payment_schedule) - return unless payment_schedule.wallet_amount - - customer_id = payment_schedule.invoicing_profile.user.payment_gateway_object.gateway_object_id - Stripe::Customer.update(customer_id, { balance: -payment_schedule.wallet_amount }, { api_key: Setting.get('stripe_secret_key') }) - end - end -end diff --git a/app/workers/payment_schedule_item_worker.rb b/app/workers/payment_schedule_item_worker.rb index f5eac38b0..e0a42c844 100644 --- a/app/workers/payment_schedule_item_worker.rb +++ b/app/workers/payment_schedule_item_worker.rb @@ -18,6 +18,7 @@ class PaymentScheduleItemWorker def check_item(psi) # the following depends on the payment method (stripe/check) + # FIXME if psi.payment_schedule.payment_method == 'card' ### Stripe stripe_key = Setting.get('stripe_secret_key') diff --git a/lib/pay_zen/charge.rb b/lib/pay_zen/charge.rb index e4498a173..71146bd9f 100644 --- a/lib/pay_zen/charge.rb +++ b/lib/pay_zen/charge.rb @@ -46,5 +46,27 @@ class PayZen::Charge < PayZen::Client contrib: contrib, customer: customer) end + + ## + # @see https://payzen.io/fr-FR/rest/V4.0/api/playground/Charge/CreateSubscription + ## + def create_subscription(amount: 0, + currency: Setting.get('payzen_currency'), + effect_date: DateTime.current.to_s, + payment_method_token: nil, + rrule: nil, + order_id: nil, + initial_amount: nil, + initial_amount_number: nil) + post('Charge/CreateSubscription,', + amount: amount, + currency: currency, + effectDate: effect_date, + paymentMethodToken: payment_method_token, + rrule: rrule, + orderId: order_id, + initialAmount: initial_amount, + initialAmountNumber: initial_amount_number) + end end diff --git a/lib/pay_zen/order.rb b/lib/pay_zen/order.rb index a07afb91f..b25817b72 100644 --- a/lib/pay_zen/order.rb +++ b/lib/pay_zen/order.rb @@ -14,6 +14,4 @@ class PayZen::Order < PayZen::Client def get(order_id, operation_type: nil) post('/Order/Get/', orderId: order_id, operationType: operation_type) end - end - diff --git a/lib/pay_zen/service.rb b/lib/pay_zen/service.rb new file mode 100644 index 000000000..62f577715 --- /dev/null +++ b/lib/pay_zen/service.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'payment/service' +require 'pay_zen/charge' +require 'pay_zen/order' + +# PayZen payement gateway +module PayZen; end + +## create remote objects on PayZen +class PayZen::Service < Payment::Service + def create_subscription(payment_schedule, order_id) + first_item = payment_schedule.ordered_items.first + + order = PayZen::Order.new.get(order_id: order_id, operation_type: 'VERIFICATION') + client = PayZen::Charge.new + + params = { + amount: first_item.details['recurring'].to_i, + effect_date: first_item.due_date.to_s, + payment_method_token: order['answer']['transactions'].first['paymentMethodToken'], + rrule: rrule(payment_schedule), + order_id: order_id + } + unless first_item.details['adjustment']&.zero? + params[:initial_amount] = first_item.amount + params[:initial_amount_number] = 1 + end + client.create_subscription(params) + end + + private + + def rrule(payment_schedule) + count = payment_schedule.payment_schedule_items.count + last = payment_schedule.ordered_items.last.due_date.strftime('%Y%m%d') + "RRULE:FREQ=MONTHLY;COUNT=#{count};UNTIL=#{last}" + end +end diff --git a/lib/pay_zen/subscription.rb b/lib/pay_zen/subscription.rb new file mode 100644 index 000000000..6efdf2e1b --- /dev/null +++ b/lib/pay_zen/subscription.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'pay_zen/client' + +# Subscription/* endpoints of the PayZen REST API +class PayZen::Subscription < PayZen::Client + def initialize(base_url: nil, username: nil, password: nil) + super(base_url: base_url, username: username, password: password) + end + + ## + # @see https://payzen.io/fr-FR/rest/V4.0/api/playground/Subscription/Get/ + ## + def get(subscription_id, payment_method_token) + post('/Subscription/Get/', subscriptionId: subscription_id, paymentMethodToken: payment_method_token) + end +end diff --git a/lib/pay_zen/token.rb b/lib/pay_zen/token.rb index f4370c579..9dbbc29b1 100644 --- a/lib/pay_zen/token.rb +++ b/lib/pay_zen/token.rb @@ -14,6 +14,4 @@ class PayZen::Token < PayZen::Client def get(payment_method_token) post('/Token/Get/', paymentMethodToken: payment_method_token) end - end - diff --git a/lib/payment/service.rb b/lib/payment/service.rb new file mode 100644 index 000000000..941f54163 --- /dev/null +++ b/lib/payment/service.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# Payments module +module Payment; end + +# Abstract class that must be implemented by each payment gateway. +# Provides methods to create remote objects on the payment gateway +class Payment::Service + def create_subscription(_payment_schedule, _gateway_object_id); end + + def create_coupon(_coupon_id); end + + def delete_coupon(_coupon_id); end + + def create_or_update_product(_klass, _id); end +end diff --git a/lib/stripe/service.rb b/lib/stripe/service.rb new file mode 100644 index 000000000..de2f3ae1b --- /dev/null +++ b/lib/stripe/service.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +require 'payment/service' + +# Stripe payement gateway +module Stripe; end + +## create remote objects on stripe +class Stripe::Service < Payment::Service + # Create the provided PaymentSchedule on Stripe, using the Subscription API + def create_subscription(payment_schedule, setup_intent_id) + stripe_key = Setting.get('stripe_secret_key') + first_item = payment_schedule.ordered_items.first + + case payment_schedule.scheduled_type + when Reservation.name + subscription = payment_schedule.scheduled.subscription + reservable_stp_id = payment_schedule.scheduled.reservable&.payment_gateway_object&.gateway_object_id + when Subscription.name + subscription = payment_schedule.scheduled + reservable_stp_id = nil + else + raise InvalidSubscriptionError + end + + handle_wallet_transaction(payment_schedule) + + # setup intent (associates the customer and the payment method) + intent = Stripe::SetupIntent.retrieve(setup_intent_id, api_key: stripe_key) + # subscription (recurring price) + price = create_price(first_item.details['recurring'], + subscription.plan.payment_gateway_object.gateway_object_id, + nil, monthly: true) + # other items (not recurring) + items = subscription_invoice_items(payment_schedule, subscription, first_item, reservable_stp_id) + + stp_subscription = Stripe::Subscription.create({ + customer: payment_schedule.invoicing_profile.user.payment_gateway_object.gateway_object_id, + cancel_at: (payment_schedule.ordered_items.last.due_date + 3.day).to_i, + add_invoice_items: items, + coupon: payment_schedule.coupon&.code, + items: [ + { price: price[:id] } + ], + default_payment_method: intent[:payment_method] + }, { api_key: stripe_key }) + pgo = PaymentGatewayObject.new(item: payment_schedule) + pgo.gateway_object = stp_subscription + pgo.save! + end + + def create_coupon(coupon_id) + coupon = Coupon.find(coupon_id) + stp_coupon = { id: coupon.code } + 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[:duration] = coupon.validity_per_user == 'always' ? 'forever' : 'once' + 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_coupon(coupon_id) + coupon = Coupon.find(coupon_id) + StripeWorker.perform_async(:delete_stripe_coupon, coupon.code) + end + + def create_or_update_product(klass, id) + StripeWorker.perform_async(:create_or_update_stp_product, klass, id) + rescue Stripe::InvalidRequestError => e + raise PaymentGatewayError(e) + end + + private + + def subscription_invoice_items(payment_schedule, subscription, first_item, reservable_stp_id) + second_item = payment_schedule.ordered_items[1] + + items = [] + if first_item.amount != second_item.amount + unless first_item.details['adjustment']&.zero? + # 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 = create_price(first_item.details['adjustment'], + subscription.plan.payment_gateway_object.gateway_object_id, + "Price adjustment for payment schedule #{payment_schedule.id}") + items.push(price: p1[:id]) + end + unless first_item.details['other_items']&.zero? + # when taking a subscription at the same time of a reservation (space, machine or training), the amount of the + # reservation is invoiced here. + p2 = create_price(first_item.details['other_items'], + reservable_stp_id, + "Reservations for payment schedule #{payment_schedule.id}") + items.push(price: p2[:id]) + end + end + + items + end + + def create_price(amount, stp_product_id, name, monthly: false) + params = { + unit_amount: amount, + currency: Setting.get('stripe_currency'), + product: stp_product_id, + nickname: name + } + params[:recurring] = { interval: 'month', interval_count: 1 } if monthly + + Stripe::Price.create(params, api_key: Setting.get('stripe_secret_key')) + end + + def handle_wallet_transaction(payment_schedule) + return unless payment_schedule.wallet_amount + + customer_id = payment_schedule.invoicing_profile.user.payment_gateway_object.gateway_object_id + Stripe::Customer.update(customer_id, { balance: -payment_schedule.wallet_amount }, { api_key: Setting.get('stripe_secret_key') }) + end +end