Skip to content

Commit c9c25ee

Browse files
Merge pull request #1493 from guardian/afs-distinct-products-by-subscription-id
Fix Payment Method Update Journey with Multiple Subscriptions of the Same Product
2 parents 60dfa0d + 81bc414 commit c9c25ee

File tree

6 files changed

+62
-13
lines changed

6 files changed

+62
-13
lines changed

client/components/mma/paymentUpdate/PaymentDetailUpdate.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ export const PaymentDetailUpdate = (props: WithProductType<ProductType>) => {
333333
),
334334
newSubscriptionData,
335335
isFromApp: isFromApp,
336+
productDetail,
336337
},
337338
});
338339
}
@@ -342,6 +343,7 @@ export const PaymentDetailUpdate = (props: WithProductType<ProductType>) => {
342343
state: {
343344
newPaymentMethodDetailFriendlyName:
344345
newPaymentMethodDetail.friendlyName,
346+
productDetail,
345347
},
346348
});
347349
}
@@ -390,6 +392,7 @@ export const PaymentDetailUpdate = (props: WithProductType<ProductType>) => {
390392
paymentMethodType={
391393
StripeCheckoutSessionPaymentMethodType.Card
392394
}
395+
subscriptionId={subscription.subscriptionId}
393396
/>
394397
) : (
395398
<CardInputForm

client/components/mma/paymentUpdate/PaymentDetailUpdateCheckoutSessionReturn.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,14 @@ export const PaymentDetailUpdateCheckoutSessionReturn = () => {
3939
state: {
4040
newPaymentMethodDetailFriendlyName:
4141
getNewPaymentMethodDetailFriendlyNameFromPaymentMethodType(),
42+
productDetail,
4243
},
4344
});
44-
}, [navigate, getNewPaymentMethodDetailFriendlyNameFromPaymentMethodType]);
45+
}, [
46+
navigate,
47+
getNewPaymentMethodDetailFriendlyNameFromPaymentMethodType,
48+
productDetail,
49+
]);
4550

4651
const obtainCheckoutSessionDetails = useCallback(
4752
async (id: string): Promise<StripeCheckoutSession> => {
@@ -100,6 +105,7 @@ export const PaymentDetailUpdateCheckoutSessionReturn = () => {
100105
paymentMethodInfo:
101106
checkoutSession?.setup_intent?.payment_method,
102107
paymentMethodType,
108+
productDetail,
103109
},
104110
});
105111
})
@@ -120,6 +126,7 @@ export const PaymentDetailUpdateCheckoutSessionReturn = () => {
120126
}, [
121127
sessionId,
122128
paymentMethodType,
129+
productDetail,
123130
navigate,
124131
navigateToFailedPage,
125132
obtainCheckoutSessionDetails,

client/components/mma/paymentUpdate/PaymentDetailUpdateContainer.tsx

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import type { Context } from 'react';
22
import { createContext } from 'react';
3-
import { Navigate, Outlet, useLocation } from 'react-router-dom';
3+
import {
4+
Navigate,
5+
Outlet,
6+
useLocation,
7+
useSearchParams,
8+
} from 'react-router-dom';
49
import type {
510
MembersDataApiResponse,
611
ProductDetail,
@@ -23,7 +28,8 @@ export interface PaymentUpdateContextInterface {
2328
}
2429

2530
const renderContextAndOutletContainer =
26-
(isFromApp?: boolean) => (mdapiResponse: MembersDataApiResponse) => {
31+
(isFromApp?: boolean, subscriptionId?: string) =>
32+
(mdapiResponse: MembersDataApiResponse) => {
2733
const filteredProductDetails = mdapiResponse.products
2834
.filter(isProduct)
2935
.filter(
@@ -42,6 +48,26 @@ const renderContextAndOutletContainer =
4248
<Outlet />
4349
</PaymentUpdateContext.Provider>
4450
);
51+
} else if (subscriptionId) {
52+
const filteredProductDetailsWithSubscriptionId =
53+
filteredProductDetails.filter(
54+
(productDetail) =>
55+
productDetail.subscription.subscriptionId ===
56+
subscriptionId,
57+
);
58+
if (filteredProductDetailsWithSubscriptionId.length === 1) {
59+
return (
60+
<PaymentUpdateContext.Provider
61+
value={{
62+
productDetail:
63+
filteredProductDetailsWithSubscriptionId[0],
64+
isFromApp,
65+
}}
66+
>
67+
<Outlet />
68+
</PaymentUpdateContext.Provider>
69+
);
70+
}
4571
}
4672
return <Navigate to="/" />;
4773
};
@@ -61,6 +87,7 @@ export const PaymentDetailUpdateContainer = (
6187
isFromApp?: boolean;
6288
}
6389

90+
const [queryParameters] = useSearchParams();
6491
const location = useLocation();
6592
const routerState = location.state as LocationState;
6693
const productDetail = routerState?.productDetail;
@@ -86,7 +113,10 @@ export const PaymentDetailUpdateContainer = (
86113
fetch={createProductDetailFetcher(
87114
props.productType.allProductsProductTypeFilterString,
88115
)}
89-
render={renderContextAndOutletContainer(isFromApp)}
116+
render={renderContextAndOutletContainer(
117+
isFromApp,
118+
queryParameters.get('subscriptionId') ?? undefined, // for internal links we use the context instead of query params
119+
)}
90120
loadingMessage={`Retrieving current payment details for your ${props.productType.friendlyName}...`}
91121
/>
92122
)}

client/components/mma/paymentUpdate/card/StripeCheckoutSessionButton.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
import * as Sentry from '@sentry/browser';
88
import type { PaymentMethod } from '@stripe/stripe-js';
99
import { useState } from 'react';
10+
import type { StripeCreateCheckoutSessionRequest } from '@/shared/requests/stripe-create-checkout-session';
1011
import { STRIPE_PUBLIC_KEY_HEADER } from '../../../../../shared/stripeSetupIntent';
1112
import { LoadingCircleIcon } from '../../shared/assets/LoadingCircleIcon';
1213

@@ -18,6 +19,7 @@ export interface StripeCheckoutSessionButtonProps {
1819
stripeApiKey: string;
1920
productTypeUrlPart: string;
2021
paymentMethodType: StripeCheckoutSessionPaymentMethodType;
22+
subscriptionId: string;
2123
}
2224

2325
/**
@@ -48,16 +50,18 @@ export const StripeCheckoutSessionButton = (
4850
setPreparingCheckout(true);
4951

5052
// Create Checkout Session on the server
53+
const body: StripeCreateCheckoutSessionRequest = {
54+
paymentMethodType: props.paymentMethodType,
55+
productTypeUrlPart: props.productTypeUrlPart,
56+
subscriptionId: props.subscriptionId,
57+
};
5158
fetch('/api/payment/checkout-session', {
5259
method: 'POST',
5360
credentials: 'include',
5461
headers: {
5562
[STRIPE_PUBLIC_KEY_HEADER]: props.stripeApiKey,
5663
},
57-
body: JSON.stringify({
58-
paymentMethodType: props.paymentMethodType,
59-
productTypeUrlPart: props.productTypeUrlPart,
60-
}),
64+
body: JSON.stringify(body),
6165
})
6266
.then((response) => {
6367
if (response.ok) {

server/stripeCreateCheckoutSessionHandler.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as Sentry from '@sentry/node';
22
import type express from 'express';
33
import fetch from 'node-fetch';
4+
import type { StripeCreateCheckoutSessionRequest } from '@/shared/requests/stripe-create-checkout-session';
45
import { STRIPE_PUBLIC_KEY_HEADER } from '../shared/stripeSetupIntent';
56
import { conf } from './config';
67
import { log, putMetric } from './log';
@@ -18,10 +19,9 @@ export const stripeCreateCheckoutSessionHandler = async (
1819
}
1920

2021
// Map request
21-
const clientRequestBody: {
22-
paymentMethodType: 'card';
23-
productTypeUrlPart: string;
24-
} = JSON.parse(clientRequestBodyData);
22+
const clientRequestBody: StripeCreateCheckoutSessionRequest = JSON.parse(
23+
clientRequestBodyData,
24+
);
2525

2626
// Get Stripe Secret Key
2727
stripeSetupIntentConfigPromise
@@ -48,7 +48,7 @@ export const stripeCreateCheckoutSessionHandler = async (
4848
const outgoingURL = 'https://api.stripe.com/v1/checkout/sessions';
4949
const requestBody = new URLSearchParams({
5050
mode: 'setup',
51-
success_url: `https://manage.${conf.DOMAIN}/payment/${clientRequestBody.productTypeUrlPart}/checkout-session-return?id={CHECKOUT_SESSION_ID}&paymentMethodType=${clientRequestBody.paymentMethodType}`,
51+
success_url: `https://manage.${conf.DOMAIN}/payment/${clientRequestBody.productTypeUrlPart}/checkout-session-return?id={CHECKOUT_SESSION_ID}&paymentMethodType=${clientRequestBody.paymentMethodType}&subscriptionId=${clientRequestBody.subscriptionId}`,
5252
cancel_url: `https://manage.${conf.DOMAIN}/payment/${clientRequestBody.productTypeUrlPart}`,
5353
'payment_method_types[0]': clientRequestBody.paymentMethodType,
5454
/**
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface StripeCreateCheckoutSessionRequest {
2+
paymentMethodType: 'card';
3+
productTypeUrlPart: string;
4+
subscriptionId: string;
5+
}

0 commit comments

Comments
 (0)