diff --git a/app/models/spree/gateway/stripe_sca.rb b/app/models/spree/gateway/stripe_sca.rb index 78fff7d0cd..de383f3b64 100644 --- a/app/models/spree/gateway/stripe_sca.rb +++ b/app/models/spree/gateway/stripe_sca.rb @@ -71,11 +71,9 @@ module Spree options[:currency] = gateway_options[:currency] options[:stripe_account] = stripe_account_id - Stripe::CreditCardCloner.new.clone!(creditcard, stripe_account_id) - options[:customer] = creditcard.gateway_customer_profile_id - payment_method = creditcard.gateway_payment_profile_id - - [money, payment_method, options] + connected_acct_customer_id, connected_acct_payment_method_id = Stripe::CreditCardCloner.new.clone(creditcard, stripe_account_id) + options[:customer] = connected_acct_customer_id + [money, connected_acct_payment_method_id, options] end def failed_activemerchant_billing_response(error_message) diff --git a/lib/stripe/credit_card_cloner.rb b/lib/stripe/credit_card_cloner.rb index 021029a83e..80b094fe84 100644 --- a/lib/stripe/credit_card_cloner.rb +++ b/lib/stripe/credit_card_cloner.rb @@ -1,44 +1,49 @@ # frozen_string_literal: true # Here we clone -# - a card (card_*) stored in a customer in a platform account +# - 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 +# - a payment method (pm_*) in a new customer in a connected account # -# This process is used in the migration between using the Stripe Charges API (stripe_connect) -# and the Stripe Payment Intents API (stripe_sca) +# 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 # -# This process can be deleted once all hubs are running on the new stripe_sca method and all cards in the system have been migrated to the new payment_methods -# Basically, when all DBs have no card_* values in credit_card.gateway_payment_profile_id +# 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) module Stripe class CreditCardCloner - def clone!(credit_card, stripe_account_id) - return unless credit_card.gateway_payment_profile_id.starts_with?('card_') + def clone(credit_card, connected_account_id) + new_payment_method = clone_payment_method(credit_card, connected_account_id) - new_payment_method = clone_payment_method(credit_card, stripe_account_id) new_customer = Stripe::Customer.create({ email: credit_card.user.email }, - stripe_account: stripe_account_id) - attach_payment_method_to_customer(new_payment_method.id, new_customer.id, stripe_account_id) + stripe_account: connected_account_id) + attach_payment_method_to_customer(new_payment_method.id, + new_customer.id, + connected_account_id) - credit_card.update_attributes gateway_customer_profile_id: new_customer.id, - gateway_payment_profile_id: new_payment_method.id - credit_card + [new_customer.id, new_payment_method.id] end private - def clone_payment_method(credit_card, stripe_account_id) - card_id = credit_card.gateway_payment_profile_id + def clone_payment_method(credit_card, connected_account_id) + platform_acct_payment_method_id = credit_card.gateway_payment_profile_id customer_id = credit_card.gateway_customer_profile_id - Stripe::PaymentMethod.create({ customer: customer_id, payment_method: card_id }, - stripe_account: stripe_account_id) + Stripe::PaymentMethod.create({ customer: customer_id, + payment_method: platform_acct_payment_method_id }, + stripe_account: connected_account_id) end - def attach_payment_method_to_customer(payment_method_id, customer_id, stripe_account_id) + def attach_payment_method_to_customer(payment_method_id, customer_id, connected_account_id) Stripe::PaymentMethod.attach(payment_method_id, { customer: customer_id }, - stripe_account: stripe_account_id) + stripe_account: connected_account_id) end end end diff --git a/spec/lib/stripe/credit_card_cloner_spec.rb b/spec/lib/stripe/credit_card_cloner_spec.rb index 771db58c21..07872c0259 100644 --- a/spec/lib/stripe/credit_card_cloner_spec.rb +++ b/spec/lib/stripe/credit_card_cloner_spec.rb @@ -5,7 +5,7 @@ require 'stripe/credit_card_cloner' module Stripe describe CreditCardCloner do - describe "#clone!" do + describe "#clone" do let(:cloner) { Stripe::CreditCardCloner.new } let(:customer_id) { "cus_A123" } @@ -14,24 +14,32 @@ module Stripe let(:new_payment_method_id) { "pm_4567" } let(:stripe_account_id) { "acct_456" } let(:customer_response_mock) { { status: 200, body: customer_response_body } } + let(:payment_method_response_mock) { { status: 200, body: payment_method_response_body } } let(:credit_card) { create(:credit_card, user: create(:user)) } before do allow(Stripe).to receive(:api_key) { "sk_test_12345" } + stub_request(:post, "https://api.stripe.com/v1/payment_methods") + .with(basic_auth: ["sk_test_12345", ""]) + .to_return(payment_method_response_mock) + stub_request(:post, "https://api.stripe.com/v1/customers") .with(basic_auth: ["sk_test_12345", ""], body: { email: credit_card.user.email }) .to_return(customer_response_mock) end context "when called with a credit_card with valid id (card_*)" do + let(:payment_method_response_body) { + JSON.generate(id: new_payment_method_id, default_card: card_id) + } let(:customer_response_body) { JSON.generate(id: customer_id, default_card: card_id) } it "clones the card successefully" do - cloner.clone!(credit_card, stripe_account_id) + cloner.clone(credit_card, stripe_account_id) expect(credit_card.gateway_customer_profile_id).to eq new_customer_id expect(credit_card.gateway_payment_profile_id).to eq new_payment_method_id