mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 13:13:45 +01:00
Merge pull request #34602 from overleaf/td-stripe-3ds1-redirect
Fix for Stripe redirecting to incorrect page GitOrigin-RevId: ac90b2e501c4bf5f2738d61866ea72ba861e5539
This commit is contained in:
+7
-1
@@ -360,6 +360,10 @@ function PreviewSubscriptionChange() {
|
||||
}
|
||||
|
||||
async function payNow(preview: SubscriptionChangePreview) {
|
||||
const successPath =
|
||||
preview.change.type === 'premium-subscription'
|
||||
? '/user/subscription/thank-you?upgrade=true'
|
||||
: '/user/subscription/thank-you'
|
||||
try {
|
||||
if (preview.change.type === 'add-on-purchase') {
|
||||
await postJSON(
|
||||
@@ -375,7 +379,9 @@ async function payNow(preview: SubscriptionChangePreview) {
|
||||
)
|
||||
}
|
||||
} catch (e) {
|
||||
const { handled } = await handleStripePaymentAction(e as FetchError)
|
||||
const { handled } = await handleStripePaymentAction(e as FetchError, {
|
||||
successPath,
|
||||
})
|
||||
if (!handled) {
|
||||
throw e
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ import getMeta from '@/utils/meta'
|
||||
import { loadStripe } from '@stripe/stripe-js/pure'
|
||||
|
||||
export default async function handleStripePaymentAction(
|
||||
error: FetchError
|
||||
error: FetchError,
|
||||
options?: { successPath?: string }
|
||||
): Promise<{ handled: boolean }> {
|
||||
const clientSecret = error?.data?.clientSecret
|
||||
const publicKey = error?.data?.publicKey
|
||||
@@ -11,14 +12,15 @@ export default async function handleStripePaymentAction(
|
||||
if (clientSecret && publicKey) {
|
||||
const stripe = await loadStripe(publicKey)
|
||||
if (stripe) {
|
||||
const currentPath = window.location.pathname
|
||||
const currentPath = window.location.pathname + window.location.search
|
||||
const returnUrl = new URL(
|
||||
'/user/subscription/offsite',
|
||||
getMeta('ol-ExposedSettings').siteUrl
|
||||
)
|
||||
const returnParams = new URLSearchParams({
|
||||
path: currentPath,
|
||||
})
|
||||
const returnParams = new URLSearchParams({ path: currentPath })
|
||||
if (options?.successPath) {
|
||||
returnParams.set('successPath', options.successPath)
|
||||
}
|
||||
returnUrl.search = returnParams.toString()
|
||||
|
||||
const manualConfirmationFlow = await stripe.confirmPayment({
|
||||
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
import sinon from 'sinon'
|
||||
import { expect } from 'chai'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import * as stripeModule from '@stripe/stripe-js/pure'
|
||||
import { FetchError } from '@/infrastructure/fetch-json'
|
||||
import handleStripePaymentAction from '../../../../../frontend/js/features/subscription/util/handle-stripe-payment-action'
|
||||
|
||||
describe('handleStripePaymentAction', function () {
|
||||
let sandbox: sinon.SinonSandbox
|
||||
let confirmPaymentStub: sinon.SinonStub
|
||||
|
||||
beforeEach(function () {
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
confirmPaymentStub = sandbox.stub()
|
||||
sandbox.stub(stripeModule, 'loadStripe').resolves({
|
||||
confirmPayment: confirmPaymentStub,
|
||||
} as any)
|
||||
|
||||
window.history.pushState(
|
||||
{},
|
||||
'',
|
||||
'/user/subscription/preview?planCode=professional-annual'
|
||||
)
|
||||
|
||||
window.metaAttributesCache.set('ol-ExposedSettings', {
|
||||
siteUrl: 'https://www.overleaf.com',
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore()
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
window.history.pushState({}, '', '/')
|
||||
})
|
||||
|
||||
describe('when error has no clientSecret or publicKey', function () {
|
||||
it('returns { handled: false } without calling Stripe', async function () {
|
||||
const error = new FetchError('error', 'url', undefined, undefined, {})
|
||||
const result = await handleStripePaymentAction(error)
|
||||
expect(result).to.deep.equal({ handled: false })
|
||||
sinon.assert.notCalled(
|
||||
stripeModule.loadStripe as unknown as sinon.SinonStub
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when error has clientSecret and publicKey', function () {
|
||||
let error: FetchError
|
||||
|
||||
beforeEach(function () {
|
||||
error = new FetchError(
|
||||
'Payment action required',
|
||||
'url',
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
clientSecret: 'cs_test_123',
|
||||
publicKey: 'pk_test_abc',
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('passes return_url with full path including query string', async function () {
|
||||
confirmPaymentStub.resolves({})
|
||||
fetchMock.post('/user/subscription/sync', 200)
|
||||
|
||||
await handleStripePaymentAction(error)
|
||||
|
||||
sinon.assert.calledOnce(confirmPaymentStub)
|
||||
const { confirmParams } = confirmPaymentStub.firstCall.args[0]
|
||||
const returnUrl = new URL(confirmParams.return_url)
|
||||
expect(returnUrl.searchParams.get('path')).to.equal(
|
||||
'/user/subscription/preview?planCode=professional-annual'
|
||||
)
|
||||
})
|
||||
|
||||
it('includes successPath in return_url when provided', async function () {
|
||||
confirmPaymentStub.resolves({})
|
||||
fetchMock.post('/user/subscription/sync', 200)
|
||||
|
||||
await handleStripePaymentAction(error, {
|
||||
successPath: '/user/subscription/thank-you?upgrade=true',
|
||||
})
|
||||
|
||||
const { confirmParams } = confirmPaymentStub.firstCall.args[0]
|
||||
const returnUrl = new URL(confirmParams.return_url)
|
||||
expect(returnUrl.searchParams.get('successPath')).to.equal(
|
||||
'/user/subscription/thank-you?upgrade=true'
|
||||
)
|
||||
})
|
||||
|
||||
it('uses redirect: if_required', async function () {
|
||||
confirmPaymentStub.resolves({})
|
||||
fetchMock.post('/user/subscription/sync', 200)
|
||||
|
||||
await handleStripePaymentAction(error)
|
||||
|
||||
expect(confirmPaymentStub.firstCall.args[0].redirect).to.equal(
|
||||
'if_required'
|
||||
)
|
||||
})
|
||||
|
||||
describe('when confirmPayment succeeds', function () {
|
||||
it('returns { handled: true }', async function () {
|
||||
confirmPaymentStub.resolves({})
|
||||
fetchMock.post('/user/subscription/sync', 200)
|
||||
|
||||
const result = await handleStripePaymentAction(error)
|
||||
|
||||
expect(result).to.deep.equal({ handled: true })
|
||||
})
|
||||
})
|
||||
|
||||
describe('when confirmPayment returns an error', function () {
|
||||
it('returns { handled: false }', async function () {
|
||||
confirmPaymentStub.resolves({
|
||||
error: { payment_intent: { id: 'pi_test_456' } },
|
||||
})
|
||||
fetchMock.post(
|
||||
'/user/subscription/void-change?payment_intent_id=pi_test_456',
|
||||
200
|
||||
)
|
||||
|
||||
const result = await handleStripePaymentAction(error)
|
||||
|
||||
expect(result).to.deep.equal({ handled: false })
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user