1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2024-12-01 12:24:28 +01:00
fab-manager/lib/stripe/service.rb

253 lines
11 KiB
Ruby

# frozen_string_literal: true
require 'payment/service'
# Stripe payement gateway
module Stripe; end
## create remote objects on stripe
class Stripe::Service < Payment::Service
# Build the subscription base on the given shopping cart and create it on the remote stripe API
def subscribe(payment_method_id, shopping_cart)
price_details = shopping_cart.total
payment_schedule = price_details[:schedule][:payment_schedule]
payment_schedule.payment_schedule_items = price_details[:schedule][:items]
first_item = price_details[:schedule][:items].min_by(&:due_date)
subscription = shopping_cart.items.find { |item| item.class == CartItem::Subscription }.to_object
reservable_stp_id = shopping_cart.items.find { |item| item.is_a?(CartItem::Reservation) }&.to_object
&.reservable&.payment_gateway_object&.gateway_object_id
WalletService.debit_user_wallet(payment_schedule, shopping_cart.customer, transaction: false)
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)
create_remote_subscription(shopping_cart, payment_schedule, items, price, payment_method_id)
end
def create_subscription(payment_schedule, stp_object_id, stp_object_type)
stripe_key = Setting.get('stripe_secret_key')
stp_subscription = Stripe::Item.new(stp_object_type, stp_object_id).retrieve
payment_method_id = Stripe::Customer.retrieve(stp_subscription.customer, api_key: stripe_key).invoice_settings.default_payment_method
payment_method = Stripe::PaymentMethod.retrieve(payment_method_id, api_key: stripe_key)
pgo = PaymentGatewayObject.new(item: payment_schedule)
pgo.gateway_object = payment_method
pgo.save!
end
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
def create_user(user_id)
StripeWorker.perform_async(:create_stripe_customer, user_id)
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 == stripe_amount('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
def stripe_amount(amount)
currency = Setting.get('stripe_currency')
return amount / 100 if zero_decimal_currencies.any? { |s| s.casecmp(currency).zero? }
amount
end
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
if stp_subscription.status == 'canceled'
# the subscription was canceled by the gateway => notify & update the status
notify_payment_schedule_gateway_canceled(payment_schedule_item)
payment_schedule_item.update_attributes(state: 'gateway_canceled')
return
end
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
notify_payment_schedule_item_failed(payment_schedule_item)
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!
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
else
notify_payment_schedule_item_error(payment_schedule_item)
payment_schedule_item.update_attributes(state: 'error')
end
end
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
def attach_method_as_default(payment_method_id, customer_id)
stripe_key = Setting.get('stripe_secret_key')
# attach the payment method to the given customer
method = Stripe::PaymentMethod.attach(
payment_method_id,
{ customer: customer_id },
{ api_key: stripe_key }
)
# then set it as the default payment method for this customer
Stripe::Customer.update(
customer_id,
{ invoice_settings: { default_payment_method: payment_method_id } },
{ api_key: stripe_key }
)
method
end
private
# Create the provided PaymentSchedule on Stripe, using the Subscription API
def create_remote_subscription(shopping_cart, payment_schedule, items, price, payment_method_id)
stripe_key = Setting.get('stripe_secret_key')
if payment_schedule.start_at.nil?
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,
start_date: payment_schedule.start_at.to_i,
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
def subscription_invoice_items(payment_schedule, subscription, first_item, reservable_stp_id)
second_item = payment_schedule.payment_schedule_items.sort_by(&:due_date)[1]
items = []
if second_item && 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: stripe_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
# @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
end