Skip to content

Commit ba37533

Browse files
Merge pull request #19348 from mozilla/PAY-3178-payment-methods-page-and-utils
feat(payments-next):Page and util support for alternate payment methods
2 parents e8c508d + d606779 commit ba37533

30 files changed

Lines changed: 587 additions & 157 deletions

File tree

apps/payments/next/app/[locale]/[offeringId]/[interval]/checkout/[cartId]/success/page.tsx

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,55 @@ export default async function CheckoutSuccess({
148148
cart.latestInvoicePreview?.currency,
149149
locale
150150
)}
151-
{cart.paymentInfo.type === 'external_paypal' ? (
151+
{cart.paymentInfo.walletType === 'apple_pay' ? (
152+
<div className="flex items-center gap-3">
153+
<Image
154+
src={getCardIcon('apple_pay', l10n).img}
155+
alt={l10n.getString('apple-pay-logo-alt-text', 'Apple Pay logo')}
156+
width={40}
157+
height={24}
158+
/>
159+
<span className="flex items-center gap-2">
160+
{cart.paymentInfo.brand && (
161+
<Image
162+
src={getCardIcon(cart.paymentInfo.brand, l10n).img}
163+
alt={getCardIcon(cart.paymentInfo.brand, l10n).altText}
164+
width={40}
165+
height={24}
166+
/>
167+
)}
168+
{l10n.getString(
169+
'next-payment-confirmation-cc-card-ending-in',
170+
{ last4: cart.paymentInfo.last4 ?? '' },
171+
`Card ending in ${cart.paymentInfo.last4}`
172+
)}
173+
</span>
174+
</div>
175+
) : cart.paymentInfo.walletType === 'google_pay' ? (
176+
<div className="flex items-center gap-3">
177+
<Image
178+
src={getCardIcon('google_pay', l10n).img}
179+
alt={l10n.getString('google-pay-logo-alt-text', 'Google Pay logo')}
180+
width={40}
181+
height={24}
182+
/>
183+
<span className="flex items-center gap-2">
184+
{cart.paymentInfo.brand && (
185+
<Image
186+
src={getCardIcon(cart.paymentInfo.brand, l10n).img}
187+
alt={getCardIcon(cart.paymentInfo.brand, l10n).altText}
188+
width={40}
189+
height={24}
190+
/>
191+
)}
192+
{l10n.getString(
193+
'next-payment-confirmation-cc-card-ending-in',
194+
{ last4: cart.paymentInfo.last4 ?? '' },
195+
`Card ending in ${cart.paymentInfo.last4}`
196+
)}
197+
</span>
198+
</div>
199+
) : cart.paymentInfo.type === 'external_paypal' ? (
152200
<Image
153201
src={getCardIcon('paypal', l10n).img}
154202
alt={l10n.getString('paypal-logo-alt-text', 'PayPal logo')}

apps/payments/next/app/[locale]/[offeringId]/[interval]/upgrade/[cartId]/(mainLayout)/success/page.tsx

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,55 @@ export default async function UpgradeSuccess({
149149
cart.latestInvoicePreview?.currency,
150150
locale
151151
)}
152-
{cart.paymentInfo.type === 'external_paypal' ? (
152+
{cart.paymentInfo.walletType === 'apple_pay' ? (
153+
<div className="flex items-center gap-3">
154+
<Image
155+
src={getCardIcon('apple_pay', l10n).img}
156+
alt={l10n.getString('apple-pay-logo-alt-text', 'Apple Pay logo')}
157+
width={40}
158+
height={24}
159+
/>
160+
<span className="flex items-center gap-2">
161+
{cart.paymentInfo.brand && (
162+
<Image
163+
src={getCardIcon(cart.paymentInfo.brand, l10n).img}
164+
alt={getCardIcon(cart.paymentInfo.brand, l10n).altText}
165+
width={40}
166+
height={24}
167+
/>
168+
)}
169+
{l10n.getString(
170+
'next-payment-confirmation-cc-card-ending-in',
171+
{ last4: cart.paymentInfo.last4 ?? '' },
172+
`Card ending in ${cart.paymentInfo.last4}`
173+
)}
174+
</span>
175+
</div>
176+
) : cart.paymentInfo.walletType === 'google_pay' ? (
177+
<div className="flex items-center gap-3">
178+
<Image
179+
src={getCardIcon('google_pay', l10n).img}
180+
alt={l10n.getString('google-pay-logo-alt-text', 'Google Pay logo')}
181+
width={40}
182+
height={24}
183+
/>
184+
<span className="flex items-center gap-2">
185+
{cart.paymentInfo.brand && (
186+
<Image
187+
src={getCardIcon(cart.paymentInfo.brand, l10n).img}
188+
alt={getCardIcon(cart.paymentInfo.brand, l10n).altText}
189+
width={40}
190+
height={24}
191+
/>
192+
)}
193+
{l10n.getString(
194+
'next-payment-confirmation-cc-card-ending-in',
195+
{ last4: cart.paymentInfo.last4 ?? '' },
196+
`Card ending in ${cart.paymentInfo.last4}`
197+
)}
198+
</span>
199+
</div>
200+
) : cart.paymentInfo.type === 'external_paypal' ? (
153201
<Image
154202
src={getCardIcon('paypal', l10n).img}
155203
alt={l10n.getString('paypal-logo-alt-text', 'PayPal logo')}

apps/payments/next/app/[locale]/[offeringId]/[interval]/upgrade/[cartId]/(startLayout)/start/page.tsx

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,55 @@ export default async function Upgrade({
9090

9191
{cart.paymentInfo && (
9292
<div className="flex items-center justify-between mt-4 text-sm">
93-
{cart.paymentInfo.type === 'external_paypal' ? (
93+
{cart.paymentInfo.walletType === 'apple_pay' ? (
94+
<div className="flex items-center gap-3">
95+
<Image
96+
src={getCardIcon('apple_pay', l10n).img}
97+
alt={l10n.getString('apple-pay-logo-alt-text', 'Apple Pay logo')}
98+
width={40}
99+
height={24}
100+
/>
101+
<span className="flex items-center gap-2">
102+
{cart.paymentInfo.brand && (
103+
<Image
104+
src={getCardIcon(cart.paymentInfo.brand, l10n).img}
105+
alt={getCardIcon(cart.paymentInfo.brand, l10n).altText}
106+
width={40}
107+
height={24}
108+
/>
109+
)}
110+
{l10n.getString(
111+
'next-payment-confirmation-cc-card-ending-in',
112+
{ last4: cart.paymentInfo.last4 ?? '' },
113+
`Card ending in ${cart.paymentInfo.last4}`
114+
)}
115+
</span>
116+
</div>
117+
) : cart.paymentInfo.walletType === 'google_pay' ? (
118+
<div className="flex items-center gap-3">
119+
<Image
120+
src={getCardIcon('google_pay', l10n).img}
121+
alt={l10n.getString('google-pay-logo-alt-text', 'Google Pay logo')}
122+
width={40}
123+
height={24}
124+
/>
125+
<span className="flex items-center gap-2">
126+
{cart.paymentInfo.brand && (
127+
<Image
128+
src={getCardIcon(cart.paymentInfo.brand, l10n).img}
129+
alt={getCardIcon(cart.paymentInfo.brand, l10n).altText}
130+
width={40}
131+
height={24}
132+
/>
133+
)}
134+
{l10n.getString(
135+
'next-payment-confirmation-cc-card-ending-in',
136+
{ last4: cart.paymentInfo.last4 ?? '' },
137+
`Card ending in ${cart.paymentInfo.last4}`
138+
)}
139+
</span>
140+
</div>
141+
) : cart.paymentInfo.type === 'external_paypal' ? (
94142
<Image
95143
src={getCardIcon('paypal', l10n).img}
96144
alt={l10n.getString('paypal-logo-alt-text', 'PayPal logo')}

apps/payments/next/app/[locale]/en.ftl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ visa-logo-alt-text = { -brand-visa } logo
1111
# Alt text for generic payment card logo
1212
unbranded-logo-alt-text = Unbranded logo
1313
link-logo-alt-text = { -brand-link } logo
14+
apple-pay-logo-alt-text = { -brand-apple-pay } logo
15+
google-pay-logo-alt-text = { -brand-google-pay } logo
1416
1517
## Error pages - /checkout and /upgrade
1618
## Common strings used in multiple pages

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

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export default async function Manage({
5151
appleIapSubscriptions,
5252
googleIapSubscriptions,
5353
} = await getSubManPageContentAction(session.user?.id);
54-
const { billingAgreementId, brand, expMonth, expYear, last4, type } =
54+
const { billingAgreementId, brand, expMonth, expYear, last4, type, walletType } =
5555
defaultPaymentMethod || {};
5656
const expirationDate =
5757
expMonth && expYear
@@ -152,7 +152,65 @@ export default async function Manage({
152152
)}
153153
</div>
154154

155-
{type === 'card' && brand && (
155+
{type === 'card' && walletType && (
156+
<div className="flex items-center justify-between">
157+
<div className="leading-5 text-sm">
158+
<div className="flex items-center gap-3 py-2">
159+
<Image
160+
src={getCardIcon(walletType === 'apple_pay' ? 'apple_pay' : 'google_pay', l10n).img}
161+
alt={
162+
walletType === 'apple_pay'
163+
? l10n.getString('apple-pay-logo-alt-text', 'Apple Pay logo')
164+
: l10n.getString('google-pay-logo-alt-text', 'Google Pay logo')
165+
}
166+
width={40}
167+
height={24}
168+
/>
169+
{brand && (
170+
<Image
171+
src={getCardIcon(brand, l10n).img}
172+
alt={getCardIcon(brand, l10n).altText}
173+
width={40}
174+
height={24}
175+
/>
176+
)}
177+
{last4 && (
178+
<div>
179+
{l10n.getString(
180+
'subscription-management-card-ending-in',
181+
{ last4 },
182+
`Card ending in ${last4}`
183+
)}
184+
</div>
185+
)}
186+
</div>
187+
{expirationDate && (
188+
<div>
189+
{l10n.getString(
190+
'subscription-management-card-expires-date',
191+
{ expirationDate },
192+
`Expires ${expirationDate}`
193+
)}
194+
</div>
195+
)}
196+
</div>
197+
<Link
198+
className={CSS_SECONDARY_LINK}
199+
href={`${config.paymentsNextHostedUrl}/${locale}/subscriptions/payments/stripe`}
200+
aria-label={l10n.getString(
201+
'subscription-management-button-change-payment-method-aria',
202+
'Change payment method'
203+
)}
204+
>
205+
{l10n.getString(
206+
'subscription-management-button-change-payment-method',
207+
'Change'
208+
)}
209+
</Link>
210+
</div>
211+
)}
212+
213+
{type === 'card' && brand && !walletType && (
156214
<div className="flex items-center justify-between">
157215
<div className="leading-5 text-sm">
158216
<Image

libs/payments/cart/src/lib/cart.service.ts

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ import {
2020
PaymentMethodManager,
2121
CustomerSessionManager,
2222
PaymentIntentManager,
23-
determinePaymentMethodType,
2423
retrieveSubscriptionItem,
2524
TaxAddress,
2625
PriceManager,
2726
getSubplatInterval,
2827
SetupIntentManager,
2928
PromotionCodeError,
29+
SubPlatPaymentMethodType,
3030
} from '@fxa/payments/customer';
3131
import {
3232
EligibilityService,
@@ -877,24 +877,35 @@ export class CartService {
877877
}
878878

879879
let paymentInfo: PaymentInfo | undefined;
880-
const paymentMethodType = determinePaymentMethodType(
880+
const paymentMethodType = await this.paymentMethodManager.determineType(
881881
customer,
882882
subscriptions
883883
);
884-
if (paymentMethodType?.type === 'stripe') {
885-
const paymentMethod = await this.paymentMethodManager.retrieve(
886-
paymentMethodType.paymentMethodId
887-
);
888-
paymentInfo = {
889-
type: paymentMethod.type,
890-
last4: paymentMethod.card?.last4,
891-
brand: paymentMethod.card?.brand,
892-
customerSessionClientSecret: customerSession?.client_secret,
893-
};
894-
} else if (paymentMethodType?.type === 'external_paypal') {
895-
paymentInfo = {
896-
type: 'external_paypal',
897-
};
884+
885+
switch (paymentMethodType?.type) {
886+
case SubPlatPaymentMethodType.PayPal:
887+
paymentInfo = {
888+
type: 'external_paypal',
889+
};
890+
break;
891+
case SubPlatPaymentMethodType.Link:
892+
case SubPlatPaymentMethodType.Card:
893+
case SubPlatPaymentMethodType.ApplePay:
894+
case SubPlatPaymentMethodType.GooglePay:
895+
case SubPlatPaymentMethodType.Stripe: {
896+
const paymentMethod = await this.paymentMethodManager.retrieve(
897+
paymentMethodType.paymentMethodId
898+
);
899+
const walletType = paymentMethod.card?.wallet?.type;
900+
paymentInfo = {
901+
type: paymentMethod.type,
902+
last4: paymentMethod.card?.last4,
903+
brand: paymentMethod.card?.brand,
904+
customerSessionClientSecret: customerSession?.client_secret,
905+
...(walletType ? { walletType } : {}),
906+
};
907+
break;
908+
}
898909
}
899910

900911
// Cart latest invoice data

libs/payments/cart/src/lib/cart.types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export interface PaymentInfo {
5757
last4?: string;
5858
brand?: string;
5959
customerSessionClientSecret?: string;
60+
walletType?: string;
6061
}
6162

6263
export type ResultCart = Readonly<Omit<Cart, 'id' | 'uid'>> & {

libs/payments/cart/src/lib/util/throwIntentFailedError.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ export function throwIntentFailedError(
2323
intentType: 'SetupIntent' | 'PaymentIntent'
2424
) {
2525
switch (errorCode) {
26+
case 'payment_intent_payment_attempt_failed':
27+
case 'payment_method_provider_decline':
2628
case 'card_declined': {
2729
switch (declineCode) {
2830
case 'approve_with_id':

libs/payments/customer/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,4 @@ export * from './lib/factories/tax-address.factory';
1919
export * from './lib/customer.error';
2020
export * from './lib/util/stripeInvoiceToFirstInvoicePreviewDTO';
2121
export * from './lib/util/getSubplatInterval';
22-
export * from './lib/util/determinePaymentMethodType';
2322
export * from './lib/util/retrieveSubscriptionItem';

0 commit comments

Comments
 (0)