2021-04-30 16:07:19 +02:00
# frozen_string_literal: true
require 'payment/service'
# Stripe payement gateway
module Stripe ; end
## create remote objects on stripe
class Stripe :: Service < Payment :: Service
2021-10-18 10:15:48 +02:00
# Build the subscription base on the given shopping cart and create it on the remote stripe API
2021-10-06 17:09:35 +02:00
def subscribe ( payment_method_id , shopping_cart )
price_details = shopping_cart . total
2021-09-08 18:57:10 +02:00
2021-10-06 17:09:35 +02:00
payment_schedule = price_details [ :schedule ] [ :payment_schedule ]
2021-10-07 17:07:46 +02:00
payment_schedule . payment_schedule_items = price_details [ :schedule ] [ :items ]
2021-10-06 17:09:35 +02:00
first_item = price_details [ :schedule ] [ :items ] . min_by ( & :due_date )
2021-10-15 17:31:01 +02:00
subscription = shopping_cart . items . find { | item | item . class == CartItem :: Subscription } . to_object
2021-10-14 18:20:10 +02:00
reservable_stp_id = shopping_cart . items . find { | item | item . is_a? ( CartItem :: Reservation ) } & . to_object
& . reservable & . payment_gateway_object & . gateway_object_id
2021-04-30 16:07:19 +02:00
2021-10-07 17:07:46 +02:00
WalletService . debit_user_wallet ( payment_schedule , shopping_cart . customer , transaction : false )
2021-04-30 16:07:19 +02:00
handle_wallet_transaction ( payment_schedule )
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 )
2021-10-18 15:19:58 +02:00
create_remote_subscription ( shopping_cart , payment_schedule , items , price , payment_method_id )
2021-10-06 17:09:35 +02:00
end
2021-10-15 17:31:01 +02:00
def create_subscription ( payment_schedule , stp_object_id , stp_object_type )
2021-10-06 17:09:35 +02:00
stripe_key = Setting . get ( 'stripe_secret_key' )
2021-10-15 17:31:01 +02:00
stp_subscription = Stripe :: Item . new ( stp_object_type , stp_object_id ) . retrieve
2021-10-07 18:04:38 +02:00
2021-10-15 17:31:01 +02:00
payment_method_id = Stripe :: Customer . retrieve ( stp_subscription . customer , api_key : stripe_key ) . invoice_settings . default_payment_method
2021-10-07 18:04:38 +02:00
payment_method = Stripe :: PaymentMethod . retrieve ( payment_method_id , api_key : stripe_key )
2021-10-15 11:55:30 +02:00
pgo = PaymentGatewayObject . new ( item : payment_schedule )
pgo . gateway_object = payment_method
pgo . save!
2021-04-30 16:07:19 +02:00
end
2022-01-03 17:13:35 +01:00
def cancel_subscription ( payment_schedule )
stripe_key = Setting . get ( 'stripe_secret_key' )
stp_subscription = payment_schedule . gateway_subscription . retrieve
res = Stripe :: Subscription . delete ( stp_subscription . id , { } , api_key : stripe_key )
res . status == 'canceled'
end
2021-06-14 15:14:14 +02:00
def create_user ( user_id )
StripeWorker . perform_async ( :create_stripe_customer , user_id )
end
2021-04-30 16:07:19 +02:00
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
2021-05-26 14:00:51 +02:00
elsif coupon . type == stripe_amount ( 'amount_off' )
2021-04-30 16:07:19 +02:00
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
2021-05-28 09:10:34 +02:00
raise :: PaymentGatewayError , e
2021-04-30 16:07:19 +02:00
end
2021-05-26 14:00:51 +02:00
def stripe_amount ( amount )
currency = Setting . get ( 'stripe_currency' )
return amount / 100 if zero_decimal_currencies . any? { | s | s . casecmp ( currency ) . zero? }
amount
end
2021-06-03 12:22:37 +02:00
def process_payment_schedule_item ( payment_schedule_item )
stripe_key = Setting . get ( 'stripe_secret_key' )
stp_subscription = payment_schedule_item . payment_schedule . gateway_subscription . retrieve
2022-01-05 17:16:25 +01:00
if stp_subscription . status == 'canceled'
2022-01-11 12:37:06 +01:00
# the subscription was canceled by the gateway => notify & update the status
notify_payment_schedule_gateway_canceled ( payment_schedule_item )
2022-01-05 17:16:25 +01:00
payment_schedule_item . update_attributes ( state : 'gateway_canceled' )
return
end
2021-06-03 12:22:37 +02:00
stp_invoice = Stripe :: Invoice . retrieve ( stp_subscription . latest_invoice , api_key : stripe_key )
if stp_invoice . status == 'paid'
##### Successfully paid
PaymentScheduleService . new . generate_invoice ( payment_schedule_item ,
payment_method : 'card' ,
payment_id : stp_invoice . payment_intent ,
payment_type : 'Stripe::PaymentIntent' )
payment_schedule_item . update_attributes ( state : 'paid' , payment_method : 'card' )
pgo = PaymentGatewayObject . find_or_initialize_by ( item : payment_schedule_item )
pgo . gateway_object = stp_invoice
pgo . save!
elsif stp_subscription . status == 'past_due' || stp_invoice . status == 'open'
##### Payment error
2022-01-11 12:37:06 +01:00
notify_payment_schedule_item_failed ( payment_schedule_item )
2021-06-03 12:22:37 +02:00
stp_payment_intent = Stripe :: PaymentIntent . retrieve ( stp_invoice . payment_intent , api_key : stripe_key )
payment_schedule_item . update_attributes ( state : stp_payment_intent . status ,
client_secret : stp_payment_intent . client_secret )
pgo = PaymentGatewayObject . find_or_initialize_by ( item : payment_schedule_item )
pgo . gateway_object = stp_invoice
pgo . save!
2022-04-06 16:44:31 +02:00
elsif stp_invoice . status == 'draft'
return # Could be that the stripe invoice does not yet reflect the payment made by the member, because we called that service just after payment is made. We call return here and PaymentScheduleItemWorker will anyway call that method every hour
2021-06-03 12:22:37 +02:00
else
2022-01-11 12:37:06 +01:00
notify_payment_schedule_item_error ( payment_schedule_item )
2021-06-03 12:22:37 +02:00
payment_schedule_item . update_attributes ( state : 'error' )
end
end
2021-06-03 16:25:42 +02:00
def pay_payment_schedule_item ( payment_schedule_item )
stripe_key = Setting . get ( 'stripe_secret_key' )
stp_invoice = Stripe :: Invoice . pay ( @payment_schedule_item . payment_gateway_object . gateway_object_id , { } , { api_key : stripe_key } )
PaymentScheduleItemWorker . new . perform ( @payment_schedule_item . id )
{ status : stp_invoice . status }
rescue Stripe :: StripeError = > e
stripe_key = Setting . get ( 'stripe_secret_key' )
stp_invoice = Stripe :: Invoice . retrieve ( @payment_schedule_item . payment_gateway_object . gateway_object_id , api_key : stripe_key )
PaymentScheduleItemWorker . new . perform ( @payment_schedule_item . id )
{ status : stp_invoice . status , error : e }
end
2021-10-06 09:42:58 +02:00
def attach_method_as_default ( payment_method_id , customer_id )
2021-10-06 17:09:35 +02:00
stripe_key = Setting . get ( 'stripe_secret_key' )
2021-10-06 09:42:58 +02:00
# attach the payment method to the given customer
2021-10-06 17:09:35 +02:00
method = Stripe :: PaymentMethod . attach (
2021-10-06 09:42:58 +02:00
payment_method_id ,
2021-10-06 17:09:35 +02:00
{ customer : customer_id } ,
{ api_key : stripe_key }
2021-10-06 09:42:58 +02:00
)
# then set it as the default payment method for this customer
Stripe :: Customer . update (
2021-10-06 17:09:35 +02:00
customer_id ,
{ invoice_settings : { default_payment_method : payment_method_id } } ,
{ api_key : stripe_key }
2021-10-06 09:42:58 +02:00
)
2021-10-06 17:09:35 +02:00
method
2021-10-06 09:42:58 +02:00
end
2021-04-30 16:07:19 +02:00
private
2021-10-18 10:15:48 +02:00
# Create the provided PaymentSchedule on Stripe, using the Subscription API
2021-10-18 15:19:58 +02:00
def create_remote_subscription ( shopping_cart , payment_schedule , items , price , payment_method_id )
2021-10-18 10:15:48 +02:00
stripe_key = Setting . get ( 'stripe_secret_key' )
2021-10-18 15:19:58 +02:00
if payment_schedule . start_at . nil?
2021-10-18 10:15:48 +02:00
Stripe :: Subscription . create ( {
customer : shopping_cart . customer . payment_gateway_object . gateway_object_id ,
cancel_at : ( payment_schedule . payment_schedule_items . max_by ( & :due_date ) . due_date + 1 . month ) . to_i ,
add_invoice_items : items ,
coupon : payment_schedule . coupon & . code ,
items : [
{ price : price [ :id ] }
] ,
default_payment_method : payment_method_id ,
expand : %w[ latest_invoice.payment_intent ]
} , { api_key : stripe_key } )
else
Stripe :: SubscriptionSchedule . create ( {
customer : shopping_cart . customer . payment_gateway_object . gateway_object_id ,
2021-10-18 15:19:58 +02:00
start_date : payment_schedule . start_at . to_i ,
2021-10-18 10:15:48 +02:00
end_behavior : 'cancel' ,
phases : [
{
items : [
{ price : price [ :id ] }
] ,
add_invoice_items : items ,
coupon : payment_schedule . coupon & . code ,
default_payment_method : payment_method_id ,
end_date : (
payment_schedule . payment_schedule_items . max_by ( & :due_date ) . due_date + 1 . month
) . to_i
}
]
} , { api_key : stripe_key } )
end
end
2021-04-30 16:07:19 +02:00
def subscription_invoice_items ( payment_schedule , subscription , first_item , reservable_stp_id )
2021-09-08 18:57:10 +02:00
second_item = payment_schedule . payment_schedule_items . sort_by ( & :due_date ) [ 1 ]
2021-04-30 16:07:19 +02:00
items = [ ]
2021-09-08 18:57:10 +02:00
if second_item && first_item . amount != second_item . amount
2021-04-30 16:07:19 +02:00
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 = {
2021-05-26 14:00:51 +02:00
unit_amount : stripe_amount ( amount ) ,
2021-04-30 16:07:19 +02:00
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
2021-05-26 14:00:51 +02:00
# @see https://stripe.com/docs/currencies#zero-decimal
def zero_decimal_currencies
%w[ BIF CLP DJF GNF JPY KMF KRW MGA PYG RWF UGX VND VUV XAF XOF XPF ]
end
2021-04-30 16:07:19 +02:00
end