Skip to content

Commit f881ee4

Browse files
committed
fix(payments-next): Fix update button for Link as saved method
Because: * When the user had Link as their saved payment method, the Manage Payment Methods page would show the option to update/save their payment method even when no changes had been made This commit: * Fixes the logic that compares the selected vs default payment method states Closes #PAY-3422
1 parent 607a919 commit f881ee4

5 files changed

Lines changed: 76 additions & 29 deletions

File tree

apps/payments/next/app/[locale]/subscriptions/payments/stripe/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export default async function StripePaymentManagementPage({
4141
throw error;
4242
}
4343
}
44-
const { clientSecret, defaultPaymentMethodId, currency } =
44+
const { clientSecret, defaultPaymentMethod, currency } =
4545
stripeClientSession;
4646

4747
return (
@@ -58,7 +58,7 @@ export default async function StripePaymentManagementPage({
5858
>
5959
<PaymentMethodManagement
6060
uid={session?.user?.id}
61-
defaultPaymentMethodId={defaultPaymentMethodId}
61+
defaultPaymentMethod={defaultPaymentMethod}
6262
sessionEmail={session?.user?.email ?? undefined}
6363
/>
6464
</StripeManagementWrapper>

libs/payments/management/src/lib/subscriptionManagement.service.spec.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,7 +1002,10 @@ describe('SubscriptionManagementService', () => {
10021002
expect(result).toEqual({
10031003
clientSecret: mockCustomerSession.client_secret,
10041004
customer: mockCustomer.id,
1005-
defaultPaymentMethodId: mockPaymentMethod.id,
1005+
defaultPaymentMethod: {
1006+
id: mockPaymentMethod.id,
1007+
type: mockPaymentMethod.type,
1008+
},
10061009
currency: mockCustomer.currency,
10071010
});
10081011
});
@@ -1072,7 +1075,10 @@ describe('SubscriptionManagementService', () => {
10721075
expect(result).toEqual({
10731076
clientSecret: mockCustomerSession.client_secret,
10741077
customer: mockCustomer.id,
1075-
defaultPaymentMethodId: mockPaymentMethod.id,
1078+
defaultPaymentMethod: {
1079+
id: mockPaymentMethod.id,
1080+
type: mockPaymentMethod.type,
1081+
},
10761082
currency: mockCurrency,
10771083
});
10781084
});
@@ -1132,7 +1138,10 @@ describe('SubscriptionManagementService', () => {
11321138
expect(result).toEqual({
11331139
clientSecret: mockCustomerSession.client_secret,
11341140
customer: mockCustomer.id,
1135-
defaultPaymentMethodId: mockPaymentMethod.id,
1141+
defaultPaymentMethod: {
1142+
id: mockPaymentMethod.id,
1143+
type: mockPaymentMethod.type,
1144+
},
11361145
currency: mockCurrency,
11371146
});
11381147
});

libs/payments/management/src/lib/subscriptionManagement.service.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -805,8 +805,15 @@ export class SubscriptionManagementService {
805805
return {
806806
clientSecret: customerSession.client_secret,
807807
customer: customerSession.customer,
808-
defaultPaymentMethodId: defaultPaymentMethod?.id,
809808
currency,
809+
...(defaultPaymentMethod
810+
? {
811+
defaultPaymentMethod: {
812+
type: defaultPaymentMethod?.type,
813+
id: defaultPaymentMethod?.id,
814+
},
815+
}
816+
: {}),
810817
};
811818
}
812819

libs/payments/ui/src/lib/client/components/PaymentMethodManagement/index.tsx

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,14 @@ import {
2828

2929
export function PaymentMethodManagement({
3030
uid,
31-
defaultPaymentMethodId,
31+
defaultPaymentMethod,
3232
sessionEmail,
3333
}: {
3434
uid?: string;
35-
defaultPaymentMethodId?: string;
35+
defaultPaymentMethod?: {
36+
id: string;
37+
type?: string;
38+
};
3639
sessionEmail?: string;
3740
}) {
3841
const { l10n } = useLocalization();
@@ -65,17 +68,36 @@ export function PaymentMethodManagement({
6568
) => {
6669
setIsComplete(event.complete);
6770
setError(null);
68-
setHasPaymentMethod(!!event.value.payment_method);
71+
setHasPaymentMethod(
72+
!!event.value.payment_method ||
73+
(event.value.type === 'link' && defaultPaymentMethod?.type === 'link')
74+
);
6975

7076
if (event.value.type !== 'card') {
7177
setIsNonCardSelected(true);
7278
setIsInputNewCardDetails(false);
73-
if (!!event.value.payment_method) {
74-
if (event.value.payment_method.id !== defaultPaymentMethodId) {
79+
if (
80+
event.value.payment_method?.type === 'link' &&
81+
defaultPaymentMethod?.type === 'link'
82+
) {
83+
/**
84+
* Users can add Link to their account twice if a Link account was not associated with their Stripe
85+
* account email when starting the checkout flow (i.e. this was the customers first time using Link).
86+
* The Payment Element does not currently handle accounts with multiple Link payment methods,
87+
* but the user can still modify their Link payment method settings in-component.
88+
*/
89+
setIsNonDefaultCardSelected(false);
90+
} else if (
91+
!!event.value.payment_method &&
92+
event.value.payment_method.id
93+
) {
94+
if (event.value.payment_method.id !== defaultPaymentMethod?.id) {
7595
setIsNonDefaultCardSelected(true);
7696
} else {
7797
setIsNonDefaultCardSelected(false);
7898
}
99+
} else {
100+
setIsNonDefaultCardSelected(false);
79101
}
80102
return;
81103
}
@@ -86,8 +108,12 @@ export function PaymentMethodManagement({
86108
} else if (event.value.type === 'card' && !!event.value.payment_method) {
87109
setIsInputNewCardDetails(false);
88110

89-
if (event.value.payment_method.id !== defaultPaymentMethodId) {
90-
setIsNonDefaultCardSelected(true);
111+
if (event.value.payment_method.id) {
112+
if (event.value.payment_method.id !== defaultPaymentMethod?.id) {
113+
setIsNonDefaultCardSelected(true);
114+
} else {
115+
setIsNonDefaultCardSelected(false);
116+
}
91117
} else {
92118
setIsNonDefaultCardSelected(false);
93119
}
@@ -156,18 +182,16 @@ export function PaymentMethodManagement({
156182
}
157183

158184
let response;
159-
try {
160-
response = await updateStripePaymentDetails(
185+
try {
186+
response = await updateStripePaymentDetails(
161187
uid ?? '',
162188
confirmationToken.id
163189
);
164190
} catch (error) {
165191
const errorReason = getManagePaymentMethodErrorFtlInfo(error.message);
166-
setError(l10n.getString(
167-
errorReason.messageFtl,
168-
{},
169-
errorReason.message
170-
));
192+
setError(
193+
l10n.getString(errorReason.messageFtl, {}, errorReason.message)
194+
);
171195
return;
172196
}
173197

@@ -247,12 +271,8 @@ export function PaymentMethodManagement({
247271
className="h-10 mt-10 w-full"
248272
type="submit"
249273
variant={ButtonVariant.Primary}
250-
aria-disabled={
251-
!stripe || !isComplete || isLoading
252-
}
253-
disabled={
254-
!stripe || !isComplete || isLoading
255-
}
274+
aria-disabled={!stripe || !isComplete || isLoading}
275+
disabled={!stripe || !isComplete || isLoading}
256276
>
257277
{isLoading ? (
258278
<Image

libs/payments/ui/src/lib/nestapp/validators/GetStripePaymentManagementDetailsResult.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,17 @@
22
* License, v. 2.0. If a copy of the MPL was not distributed with this
33
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44

5-
import { IsOptional, IsString } from 'class-validator';
5+
import { Type } from 'class-transformer';
6+
import { IsOptional, IsString, ValidateNested } from 'class-validator';
7+
8+
export class PaymentMethodDetails {
9+
@IsString()
10+
id!: string;
11+
12+
@IsString()
13+
@IsOptional()
14+
type?: string;
15+
}
616

717
export class GetStripePaymentManagementDetailsResult {
818
@IsString()
@@ -11,9 +21,10 @@ export class GetStripePaymentManagementDetailsResult {
1121
@IsString()
1222
customer!: string;
1323

14-
@IsString()
24+
@ValidateNested({ each: true })
25+
@Type(() => PaymentMethodDetails)
1526
@IsOptional()
16-
defaultPaymentMethodId?: string;
27+
defaultPaymentMethod?: PaymentMethodDetails;
1728

1829
@IsString()
1930
currency!: string;

0 commit comments

Comments
 (0)