diff --git a/apps/payments/next/app/[locale]/subscriptions/manage/page.tsx b/apps/payments/next/app/[locale]/subscriptions/manage/page.tsx index ce8085cf6ad..42ef4162095 100644 --- a/apps/payments/next/app/[locale]/subscriptions/manage/page.tsx +++ b/apps/payments/next/app/[locale]/subscriptions/manage/page.tsx @@ -2,6 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import classNames from 'classnames'; import { headers } from 'next/headers'; import Image from 'next/image'; import Link from 'next/link'; @@ -57,17 +58,10 @@ export default async function Manage({ appleIapSubscriptions, googleIapSubscriptions, } = await getSubManPageContentAction(session.user?.id); - const { - billingAgreementId, - brand, - expMonth, - expYear, - last4, - type, - walletType, - } = defaultPaymentMethod || {}; + const { billingAgreementId, brand, expMonth, expYear, last4, type } = + defaultPaymentMethod || {}; const isPaypalBillingAgreementError = - type === 'external_paypal' && brand === 'paypal' && !billingAgreementId; + type === 'external_paypal' && !billingAgreementId; const expirationDate = expMonth && expYear ? l10n.getLocalizedMonthYearString(expMonth, expYear, locale) @@ -270,7 +264,9 @@ export default async function Manage({ aria-hidden="true" >

{l10n.getString( @@ -305,136 +301,62 @@ export default async function Manage({

)} - {type === 'card' && walletType && ( -
- { - - {l10n.getString( - 'subscription-management-button-manage-payment-method', - 'Manage' - )} - -
- )} - - {type === 'card' && brand && !walletType && ( -
-
-
- {getCardIcon(brand, - {last4 && ( - - {l10n.getString( - 'subscription-management-card-ending-in', - { last4 }, - `Card ending in ${last4}` + )} + > +
+ {brand ? ( +
+
+ {getCardIcon(brand, + {last4 && ( + + {l10n.getString( + 'subscription-management-card-ending-in', + { last4 }, + `Card ending in ${last4}` + )} + )} - - )} -
- {expirationDate && ( -

- {l10n.getString( - 'subscription-management-card-expires-date', - { expirationDate }, - `Expires ${expirationDate}` +

+ {expirationDate && ( +

+ {l10n.getString( + 'subscription-management-card-expires-date', + { expirationDate }, + `Expires ${expirationDate}` + )} +

)} -

- )} -
- - {l10n.getString( - 'subscription-management-button-manage-payment-method', - 'Manage' - )} - -
- )} - - {type === 'link' && ( -
- {l10n.getString( - - {l10n.getString( - 'subscription-management-button-manage-payment-method', - 'Manage' +
+ ) : ( + {getCardIcon(type, )} - -
- )} - - {type === 'external_paypal' && ( -
-
- {l10n.getString( {isPaypalBillingAgreementError && ( -

+

{l10n.getString( 'subscription-management-error-paypal-billing-agreement', 'There is an issue with your PayPal account. Please resolve the issue to maintain your active subscriptions.' @@ -442,33 +364,62 @@ export default async function Manage({

)}
-
- + + + {l10n.getString( + 'subscription-management-button-manage-payment-method', + 'Manage' + )} + + +
+ ) : ( + - - {l10n.getString( - 'subscription-management-button-manage-payment-method', - 'Manage' - )} - - -
+ {l10n.getString( + 'subscription-management-button-manage-payment-method', + 'Manage' + )} + + )}
)} @@ -533,9 +484,9 @@ export default async function Manage({ aria-labelledby={`${sub.productName}-information`} className="leading-6 pb-4" > -
-
-
+
+
+
{sub.productName} -
-
-
+
+
+
{purchase.productName} @@ -747,9 +698,9 @@ export default async function Manage({ aria-labelledby={`${purchase.productName}-heading`} className="leading-6 pb-4" > -
-
-
+
+
+
{purchase.productName} diff --git a/apps/payments/next/app/[locale]/subscriptions/payments/paypal/page.tsx b/apps/payments/next/app/[locale]/subscriptions/payments/paypal/page.tsx index d357d55399c..e39f868c060 100644 --- a/apps/payments/next/app/[locale]/subscriptions/payments/paypal/page.tsx +++ b/apps/payments/next/app/[locale]/subscriptions/payments/paypal/page.tsx @@ -59,8 +59,8 @@ export default async function PaypalPaymentManagementPage({ 'Manage payment methods' )} -
-
+
+
{ const mockUid = faker.string.uuid(); jest.spyOn(paymentMethodManager, 'determineType').mockResolvedValue({ - type: SubPlatPaymentMethodType.Stripe, + type: SubPlatPaymentMethodType.Card, paymentMethodId: 'pm_id', }); jest @@ -114,7 +112,7 @@ describe('PaymentMethodManager', () => { mockUid ); expect(result).toEqual({ - type: mockPaymentMethod.type, + type: SubPlatPaymentMethodType.Card, brand: mockPaymentMethod.card?.brand, last4: mockPaymentMethod.card?.last4, expMonth: mockPaymentMethod.card?.exp_month, @@ -142,26 +140,61 @@ describe('PaymentMethodManager', () => { mockUid ); expect(result).toEqual({ - type: 'external_paypal', + type: SubPlatPaymentMethodType.PayPal, brand: 'paypal', billingAgreementId: mockPaypalBillingAgreementId, }); }); + + it('returns payment method information - apple_pay', async () => { + const mockPaymentMethod = StripeResponseFactory( + StripePaymentMethodFactory({}) + ); + const mockStripeCustomer = StripeCustomerFactory(); + const mockSubscriptions = [StripeSubscriptionFactory()]; + const mockUid = faker.string.uuid(); + + jest.spyOn(paymentMethodManager, 'determineType').mockResolvedValue({ + type: SubPlatPaymentMethodType.ApplePay, + paymentMethodId: 'pm_id', + }); + jest + .spyOn(paymentMethodManager, 'retrieve') + .mockResolvedValue(mockPaymentMethod); + + const result = await paymentMethodManager.getDefaultPaymentMethod( + mockStripeCustomer, + mockSubscriptions, + mockUid + ); + expect(result).toEqual({ + type: SubPlatPaymentMethodType.ApplePay, + brand: mockPaymentMethod.card?.brand, + last4: mockPaymentMethod.card?.last4, + expMonth: mockPaymentMethod.card?.exp_month, + expYear: mockPaymentMethod.card?.exp_year, + walletType: mockPaymentMethod.card?.wallet?.type, + }); + }); }); describe('determineType', () => { it('returns card', async () => { const mockCustomer = StripeCustomerFactory({ invoice_settings: { - custom_fields: null, - default_payment_method: 'any', - footer: null, - rendering_options: null, - }, + custom_fields: null, + default_payment_method: 'any', + footer: null, + rendering_options: null, + }, }); - const mockPaymentMethod = StripeResponseFactory(StripeCardPaymentMethodFactory()); - jest.spyOn(paymentMethodManager, 'retrieve').mockResolvedValue(mockPaymentMethod); + const mockPaymentMethod = StripeResponseFactory( + StripeCardPaymentMethodFactory() + ); + jest + .spyOn(paymentMethodManager, 'retrieve') + .mockResolvedValue(mockPaymentMethod); await expect( paymentMethodManager.determineType(mockCustomer) @@ -198,7 +231,9 @@ describe('PaymentMethodManager', () => { type: 'link', }) ); - jest.spyOn(paymentMethodManager, 'retrieve').mockResolvedValue(mockPaymentMethod); + jest + .spyOn(paymentMethodManager, 'retrieve') + .mockResolvedValue(mockPaymentMethod); await expect( paymentMethodManager.determineType(mockCustomer) @@ -220,10 +255,12 @@ describe('PaymentMethodManager', () => { const mockPaymentMethod = StripeResponseFactory( StripeCardPaymentMethodFactory({ - walletType: 'apple_pay' + walletType: 'apple_pay', }) ); - jest.spyOn(paymentMethodManager, 'retrieve').mockResolvedValue(mockPaymentMethod); + jest + .spyOn(paymentMethodManager, 'retrieve') + .mockResolvedValue(mockPaymentMethod); await expect( paymentMethodManager.determineType(mockCustomer) @@ -245,10 +282,12 @@ describe('PaymentMethodManager', () => { const mockPaymentMethod = StripeResponseFactory( StripeCardPaymentMethodFactory({ - walletType: 'google_pay' + walletType: 'google_pay', }) ); - jest.spyOn(paymentMethodManager, 'retrieve').mockResolvedValue(mockPaymentMethod); + jest + .spyOn(paymentMethodManager, 'retrieve') + .mockResolvedValue(mockPaymentMethod); await expect( paymentMethodManager.determineType(mockCustomer) diff --git a/libs/payments/customer/src/lib/paymentMethod.manager.ts b/libs/payments/customer/src/lib/paymentMethod.manager.ts index c0eea5f0701..1cebd7afcf5 100644 --- a/libs/payments/customer/src/lib/paymentMethod.manager.ts +++ b/libs/payments/customer/src/lib/paymentMethod.manager.ts @@ -51,12 +51,11 @@ export class PaymentMethodManager { paymentMethodType.paymentMethodId ); defaultPaymentMethod = { - type: paymentMethod.type, + type: paymentMethodType.type, brand: paymentMethod.card?.brand, last4: paymentMethod.card?.last4, expMonth: paymentMethod.card?.exp_month, expYear: paymentMethod.card?.exp_year, - walletType: paymentMethod.card?.wallet?.type, }; break; } @@ -64,8 +63,7 @@ export class PaymentMethodManager { const billingAgreementId = await this.paypalBillingAgreementManager.retrieveActiveId(uid); defaultPaymentMethod = { - type: 'external_paypal', - brand: 'paypal', + type: SubPlatPaymentMethodType.PayPal, billingAgreementId, }; break; diff --git a/libs/payments/customer/src/lib/types.ts b/libs/payments/customer/src/lib/types.ts index 4575c1ba7ed..5d6f8dd3ca8 100644 --- a/libs/payments/customer/src/lib/types.ts +++ b/libs/payments/customer/src/lib/types.ts @@ -75,13 +75,12 @@ export type PaymentProvidersType = | 'external_paypal'; export interface DefaultPaymentMethod { - type: PaymentProvidersType; + type: SubPlatPaymentMethodType; brand?: string; last4?: string; expMonth?: number; expYear?: number; billingAgreementId?: string; - walletType?: string; } export interface PricingForCurrency { diff --git a/libs/payments/ui/src/lib/client/components/PaymentMethodManagement/index.tsx b/libs/payments/ui/src/lib/client/components/PaymentMethodManagement/index.tsx index 432fafff9d1..d428f3d4060 100644 --- a/libs/payments/ui/src/lib/client/components/PaymentMethodManagement/index.tsx +++ b/libs/payments/ui/src/lib/client/components/PaymentMethodManagement/index.tsx @@ -195,7 +195,7 @@ export function PaymentMethodManagement({ 'Manage payment methods' )} -
+
{!isReady && (
@@ -204,7 +204,7 @@ export function PaymentMethodManagement({ {isInputNewCardDetails && ( <> diff --git a/libs/payments/ui/src/lib/client/components/SubscriptionContent/index.tsx b/libs/payments/ui/src/lib/client/components/SubscriptionContent/index.tsx index 27745e04b9e..47acf1cdcfe 100644 --- a/libs/payments/ui/src/lib/client/components/SubscriptionContent/index.tsx +++ b/libs/payments/ui/src/lib/client/components/SubscriptionContent/index.tsx @@ -531,7 +531,7 @@ export const SubscriptionContent = ({ {canResubscribe ? ( <> @@ -562,7 +562,7 @@ export const SubscriptionContent = ({ {nextInvoiceTotal !== undefined && nextInvoiceTotal >= 0 ? ( <> diff --git a/libs/payments/ui/src/lib/utils/getCardIcon.ts b/libs/payments/ui/src/lib/utils/getCardIcon.ts index b394313754b..2eac254b8a7 100644 --- a/libs/payments/ui/src/lib/utils/getCardIcon.ts +++ b/libs/payments/ui/src/lib/utils/getCardIcon.ts @@ -22,61 +22,86 @@ export function getCardIcon(cardBrand: string, l10n: LocalizerRsc) { return { img: Amex, altText: l10n.getString('amex-logo-alt-text', 'American Express logo'), + width: 32, + height: 20, }; case 'apple_pay': return { img: ApplePay, altText: l10n.getString('apple-pay-logo-alt-text', 'Apple Pay logo'), + width: 45, + height: 24, }; case 'diners': return { img: Diners, altText: l10n.getString('diners-logo-alt-text', 'Diners logo'), + width: 32, + height: 20, }; case 'discover': return { img: Discover, altText: l10n.getString('discover-logo-alt-text', 'Discover logo'), + width: 32, + height: 20, }; case 'google_pay': return { img: GooglePay, altText: l10n.getString('google-pay-logo-alt-text', 'Google Pay logo'), + width: 45, + height: 24, }; case 'jcb': return { img: Jcb, altText: l10n.getString('jcb-logo-alt-text', 'JCB logo'), + width: 32, + height: 20, }; case 'link': return { img: Link, altText: l10n.getString('link-logo-alt-text', 'Link logo'), - } + width: 72, + height: 24, + }; case 'mastercard': return { img: Mastercard, altText: l10n.getString('mastercard-logo-alt-text', 'Mastercard logo'), + width: 32, + height: 20, }; + case 'external_paypal': case 'paypal': return { img: Paypal, altText: l10n.getString('paypal-logo-alt-text', 'PayPal logo'), + width: 91, + height: 24, }; case 'unionpay': return { img: UnionPay, altText: l10n.getString('unionpay-logo-alt-text', 'Union Pay logo'), + width: 32, + height: 20, }; case 'visa': return { img: Visa, altText: l10n.getString('visa-logo-alt-text', 'Visa logo'), + width: 32, + height: 20, }; default: return { img: Unbranded, altText: l10n.getString('unbranded-logo-alt-text', 'Unbranded logo'), + width: 32, + height: 20, }; } }