mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-27 01:43:22 +00:00
update CreditCardCloner to find existing clone
This commit is contained in:
@@ -48,8 +48,8 @@ module Spree
|
||||
# NOTE: the name of this method is determined by Spree::Payment::Processing
|
||||
def charge_offline(money, creditcard, gateway_options)
|
||||
options = basic_options(gateway_options)
|
||||
customer_id, payment_method_id = Stripe::CreditCardCloner.new.clone(creditcard,
|
||||
stripe_account_id)
|
||||
customer_id, payment_method_id =
|
||||
Stripe::CreditCardCloner.new.find_or_clone(creditcard, stripe_account_id)
|
||||
|
||||
options[:customer] = customer_id
|
||||
options[:off_session] = true
|
||||
@@ -110,8 +110,8 @@ module Spree
|
||||
options = basic_options(gateway_options)
|
||||
options[:return_url] = full_checkout_path
|
||||
|
||||
customer_id, payment_method_id = Stripe::CreditCardCloner.new.clone(creditcard,
|
||||
stripe_account_id)
|
||||
customer_id, payment_method_id =
|
||||
Stripe::CreditCardCloner.new.find_or_clone(creditcard, stripe_account_id)
|
||||
options[:customer] = customer_id
|
||||
[money, payment_method_id, options]
|
||||
end
|
||||
|
||||
@@ -3,15 +3,13 @@
|
||||
class RecurringPayments
|
||||
def self.setup_for(customer)
|
||||
return unless card = customer.user.default_card
|
||||
return unless stripe_account = customer.enterprise.stripe_account&.stripe_user_id
|
||||
|
||||
stripe_account = customer.enterprise.stripe_account&.stripe_user_id
|
||||
|
||||
customer_id, payment_method_id = Stripe::CreditCardCloner.new.clone(card,
|
||||
stripe_account)
|
||||
setup_intent = Stripe::SetupIntent.create({
|
||||
payment_method: payment_method_id, customer: customer_id }, {
|
||||
stripe_account: stripe_account
|
||||
}
|
||||
customer_id, payment_method_id =
|
||||
Stripe::CreditCardCloner.new.find_or_clone(card, stripe_account)
|
||||
setup_intent = Stripe::SetupIntent.create(
|
||||
{ payment_method: payment_method_id, customer: customer_id },
|
||||
{ stripe_account: stripe_account }
|
||||
)
|
||||
setup_intent.client_secret
|
||||
end
|
||||
|
||||
@@ -1,27 +1,37 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Here we clone
|
||||
# - a card (card_*) or payment_method (pm_*) stored (in a customer) in a platform account
|
||||
# into
|
||||
# Here we clone (or find a clone of)
|
||||
# - a card (card_*) or payment_method (pm_*) stored (in a customer) in a platform account into
|
||||
# - a payment method (pm_*) (in a new customer) in a connected account
|
||||
#
|
||||
# This is required when using the Stripe Payment Intents API:
|
||||
# - the customer and payment methods are stored in the platform account
|
||||
# so that they can be re-used across multiple sellers
|
||||
# - when a card needs to be charged, we need to create it in the seller's stripe account
|
||||
# - when a card needs to be charged, we need to clone (or find the clone)
|
||||
# in the seller's stripe account
|
||||
#
|
||||
# We are doing this process every time the card is charged:
|
||||
# - this means that, if the customer uses the same card on the same seller multiple times,
|
||||
# the card will be created multiple times on the seller's account
|
||||
# - to avoid this, we would have to store the IDs of every card on each seller's stripe account
|
||||
# in our database (this way we only have to store the platform account ID)
|
||||
# To avoid creating a new clone of the card/customer each time the card is charged or
|
||||
# authorized (e.g. for SCA), we attach metadata { clone: true } to the card the first time we
|
||||
# clone it and look for a card with the same fingerprint (hash of the card number) and
|
||||
# that metadata key to avoid cloning it multiple times.
|
||||
|
||||
module Stripe
|
||||
class CreditCardCloner
|
||||
def find_or_clone(credit_card, connected_account_id)
|
||||
if card = find_cloned_card(credit_card, connected_account_id)
|
||||
card
|
||||
else
|
||||
clone(credit_card, connected_account_id)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def clone(credit_card, connected_account_id)
|
||||
new_payment_method = clone_payment_method(credit_card, connected_account_id)
|
||||
|
||||
# If no customer is given, it will clone the payment method only
|
||||
return nil, new_payment_method.id if credit_card.gateway_customer_profile_id.blank?
|
||||
return [nil, new_payment_method.id] if credit_card.gateway_customer_profile_id.blank?
|
||||
|
||||
new_customer = Stripe::Customer.create({ email: credit_card.user.email },
|
||||
stripe_account: connected_account_id)
|
||||
@@ -29,10 +39,63 @@ module Stripe
|
||||
new_customer.id,
|
||||
connected_account_id)
|
||||
|
||||
add_metadata_to_payment_method(new_payment_method.id, connected_account_id)
|
||||
|
||||
[new_customer.id, new_payment_method.id]
|
||||
end
|
||||
|
||||
private
|
||||
def find_cloned_card(card, connected_account_id)
|
||||
matches = []
|
||||
return matches unless fingerprint = fingerprint_for_card(card)
|
||||
|
||||
find_customers(card.user.email, connected_account_id).each do |customer|
|
||||
find_payment_methods(customer.id, connected_account_id).each do |payment_method|
|
||||
if payment_method_is_clone?(payment_method, fingerprint)
|
||||
matches << [customer.id, payment_method.id]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
matches.first
|
||||
end
|
||||
|
||||
def payment_method_is_clone?(payment_method, fingerprint)
|
||||
payment_method.card.fingerprint == fingerprint && payment_method.metadata["ofn-clone"]
|
||||
end
|
||||
|
||||
def fingerprint_for_card(card)
|
||||
Stripe::PaymentMethod.retrieve(card.gateway_payment_profile_id).card.fingerprint
|
||||
end
|
||||
|
||||
def find_customers(email, connected_account_id)
|
||||
starting_after = nil
|
||||
customers = []
|
||||
|
||||
loop do
|
||||
response = Stripe::Customer.list({ email: email, starting_after: starting_after },
|
||||
{ stripe_account: connected_account_id })
|
||||
customers += response.data
|
||||
break unless response.has_more
|
||||
|
||||
starting_after = response.data.last.id
|
||||
end
|
||||
customers
|
||||
end
|
||||
|
||||
def find_payment_methods(customer_id, connected_account_id)
|
||||
starting_after = nil
|
||||
payment_methods = []
|
||||
|
||||
loop do
|
||||
options = { customer: customer_id, type: 'card', starting_after: starting_after }
|
||||
response = Stripe::PaymentMethod.list(options, { stripe_account: connected_account_id })
|
||||
payment_methods += response.data
|
||||
break unless response.has_more
|
||||
|
||||
starting_after = response.data.last.id
|
||||
end
|
||||
payment_methods
|
||||
end
|
||||
|
||||
def clone_payment_method(credit_card, connected_account_id)
|
||||
platform_acct_payment_method_id = credit_card.gateway_payment_profile_id
|
||||
@@ -48,5 +111,11 @@ module Stripe
|
||||
{ customer: customer_id },
|
||||
stripe_account: connected_account_id)
|
||||
end
|
||||
|
||||
def add_metadata_to_payment_method(payment_method_id, connected_account_id)
|
||||
Stripe::PaymentMethod.update(payment_method_id,
|
||||
{ metadata: { "ofn-clone": true } },
|
||||
stripe_account: connected_account_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user