From 849aa490a840cca48154358e055144e885d9fad1 Mon Sep 17 00:00:00 2001 From: Christopher Rogers Date: Thu, 21 Nov 2024 13:39:21 -0800 Subject: [PATCH] Uses stripe.handleNextAction for payment intents --- .../risk/three-d-secure/strategy/stripe.js | 32 ++++++++++++++++--- .../three-d-secure/strategy/stripe.test.js | 8 ++--- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/lib/recurly/risk/three-d-secure/strategy/stripe.js b/lib/recurly/risk/three-d-secure/strategy/stripe.js index 9cfe639c5..3a35b832e 100644 --- a/lib/recurly/risk/three-d-secure/strategy/stripe.js +++ b/lib/recurly/risk/three-d-secure/strategy/stripe.js @@ -7,6 +7,7 @@ const debug = require('debug')('recurly:risk:three-d-secure:stripe'); export default class StripeStrategy extends ThreeDSecureStrategy { static libUrl = 'https://js.stripe.com/v3/'; static strategyName = 'stripe'; + static PAYMENT_INTENT_STATUS_SUCCEEDED = 'succeeded'; constructor (...args) { super(...args); @@ -40,15 +41,38 @@ export default class StripeStrategy extends ThreeDSecureStrategy { this.whenReady(() => { const isPaymentIntent = this.stripeClientSecret.indexOf('pi') === 0; - const handleAction = isPaymentIntent ? this.stripe.handleCardAction : this.stripe.confirmCardSetup; - - handleAction(this.stripeClientSecret).then(result => { + const handleResult = result => { if (result.error) { throw result.error; } const { id } = result.paymentIntent || result.setupIntent; this.emit('done', { id }); - }).catch(err => this.threeDSecure.error('3ds-auth-error', { cause: err })); + }; + const handleError = err => this.threeDSecure.error('3ds-auth-error', { cause: err }); + + (() => ( + isPaymentIntent + ? this.stripe.handleNextAction({ clientSecret: this.stripeClientSecret }) + : this.stripe.confirmCardSetup(this.stripeClientSecret) + ))() + .then(handleResult) + .catch(err => { + // Handle a Payment Intent which has already had its action handled and succeeded + if (err.name === 'IntegrationError') { + this.stripe.retrievePaymentIntent(this.stripeClientSecret) + .then(result => { + if (result.error) { + throw result.error; + } + + const { next_action: nextAction, status } = result.paymentIntent || result.setupIntent; + if (!nextAction && status === StripeStrategy.PAYMENT_INTENT_STATUS_SUCCEEDED) { + return handleResult(result); + } + }) + .catch(handleError); + } + }); }); } diff --git a/test/unit/risk/three-d-secure/strategy/stripe.test.js b/test/unit/risk/three-d-secure/strategy/stripe.test.js index a8b930582..44fc3113c 100644 --- a/test/unit/risk/three-d-secure/strategy/stripe.test.js +++ b/test/unit/risk/three-d-secure/strategy/stripe.test.js @@ -25,7 +25,7 @@ describe('StripeStrategy', function () { setupIntent: { id: 'seti-test-id', test: 'result', consistingOf: 'arbitrary-values' } }; this.stripe = { - handleCardAction: sinon.stub().resolves(this.paymentIntentResult), + handleNextAction: sinon.stub().resolves(this.paymentIntentResult), confirmCardSetup: sinon.stub().resolves(this.setupIntentResult) }; window.Stripe = sinon.spy(publishableKey => this.stripe); @@ -78,8 +78,8 @@ describe('StripeStrategy', function () { it('instructs Stripe.js to handle the card action using the client secret', function () { const { strategy, target, stripe } = this; strategy.attach(target); - assert(stripe.handleCardAction.calledOnce); - assert(stripe.handleCardAction.calledWithExactly('pi-test-stripe-client-secret')); + assert(stripe.handleNextAction.calledOnce); + assert(stripe.handleNextAction.calledWithExactly({ clientSecret: 'pi-test-stripe-client-secret' })); }); it('emits done with the paymentIntent result', function (done) { @@ -95,7 +95,7 @@ describe('StripeStrategy', function () { beforeEach(function () { const { strategy } = this; this.exampleResult = { error: { example: 'error', for: 'testing' } }; - strategy.stripe.handleCardAction = sinon.stub().resolves(this.exampleResult); + strategy.stripe.handleNextAction = sinon.stub().resolves(this.exampleResult); }); it('emits an error on threeDSecure', function (done) {