diff --git a/packages/subscription-controller/CHANGELOG.md b/packages/subscription-controller/CHANGELOG.md index bb8fee31064..9d7f18371dd 100644 --- a/packages/subscription-controller/CHANGELOG.md +++ b/packages/subscription-controller/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Added SubscriptionController `clearLastSelectedPaymentMethod` method ([#7768](https://github.com/MetaMask/core/pull/7768)) + ### Changed - Bump `@metamask/transaction-controller` from `^62.9.2` to `^62.11.0` ([#7737](https://github.com/MetaMask/core/pull/7737), [#7760](https://github.com/MetaMask/core/pull/7760)) diff --git a/packages/subscription-controller/src/SubscriptionController.test.ts b/packages/subscription-controller/src/SubscriptionController.test.ts index 2377584f8b9..ef11fb3636c 100644 --- a/packages/subscription-controller/src/SubscriptionController.test.ts +++ b/packages/subscription-controller/src/SubscriptionController.test.ts @@ -1762,6 +1762,81 @@ describe('SubscriptionController', () => { }); }); + describe('clearLastSelectedPaymentMethod', () => { + it('should clear last selected payment method successfully', async () => { + await withController( + { + state: { + lastSelectedPaymentMethod: { + [PRODUCT_TYPES.SHIELD]: { + type: PAYMENT_TYPES.byCard, + plan: RECURRING_INTERVALS.month, + }, + }, + }, + }, + async ({ controller }) => { + expect(controller.state.lastSelectedPaymentMethod).toStrictEqual({ + [PRODUCT_TYPES.SHIELD]: { + type: PAYMENT_TYPES.byCard, + plan: RECURRING_INTERVALS.month, + }, + }); + + controller.clearLastSelectedPaymentMethod(PRODUCT_TYPES.SHIELD); + + expect(controller.state.lastSelectedPaymentMethod).toStrictEqual({}); + }, + ); + }); + + it('should do nothing when lastSelectedPaymentMethod is undefined', async () => { + await withController(async ({ controller }) => { + expect(controller.state.lastSelectedPaymentMethod).toBeUndefined(); + + controller.clearLastSelectedPaymentMethod(PRODUCT_TYPES.SHIELD); + + expect(controller.state.lastSelectedPaymentMethod).toBeUndefined(); + }); + }); + + it('should remove the product key while preserving the state object', async () => { + await withController( + { + state: { + lastSelectedPaymentMethod: { + [PRODUCT_TYPES.SHIELD]: { + type: PAYMENT_TYPES.byCrypto, + paymentTokenAddress: '0x123', + paymentTokenSymbol: 'USDT', + plan: RECURRING_INTERVALS.month, + }, + 'test-product-type': { + type: PAYMENT_TYPES.byCard, + }, + } as Record, + }, + }, + async ({ controller }) => { + expect( + controller.state.lastSelectedPaymentMethod?.[PRODUCT_TYPES.SHIELD], + ).toBeDefined(); + + controller.clearLastSelectedPaymentMethod(PRODUCT_TYPES.SHIELD); + + expect( + controller.state.lastSelectedPaymentMethod?.[ + 'test-product-type' as ProductType + ], + ).toBeDefined(); + expect( + controller.state.lastSelectedPaymentMethod?.[PRODUCT_TYPES.SHIELD], + ).toBeUndefined(); + }, + ); + }); + }); + describe('submitSponsorshipIntents', () => { const MOCK_SUBMISSION_INTENTS_REQUEST: SubmitSponsorshipIntentsMethodParams = { diff --git a/packages/subscription-controller/src/SubscriptionController.ts b/packages/subscription-controller/src/SubscriptionController.ts index 43c8e17ec70..2801b04d364 100644 --- a/packages/subscription-controller/src/SubscriptionController.ts +++ b/packages/subscription-controller/src/SubscriptionController.ts @@ -111,6 +111,16 @@ export type SubscriptionControllerSubmitSponsorshipIntentsAction = { handler: SubscriptionController['submitSponsorshipIntents']; }; +export type SubscriptionControllerCacheLastSelectedPaymentMethodAction = { + type: `${typeof controllerName}:cacheLastSelectedPaymentMethod`; + handler: SubscriptionController['cacheLastSelectedPaymentMethod']; +}; + +export type SubscriptionControllerClearLastSelectedPaymentMethodAction = { + type: `${typeof controllerName}:clearLastSelectedPaymentMethod`; + handler: SubscriptionController['clearLastSelectedPaymentMethod']; +}; + export type SubscriptionControllerLinkRewardsAction = { type: `${typeof controllerName}:linkRewards`; handler: SubscriptionController['linkRewards']; @@ -139,7 +149,9 @@ export type SubscriptionControllerActions = | SubscriptionControllerGetBillingPortalUrlAction | SubscriptionControllerSubmitSponsorshipIntentsAction | SubscriptionControllerSubmitShieldSubscriptionCryptoApprovalAction - | SubscriptionControllerLinkRewardsAction; + | SubscriptionControllerLinkRewardsAction + | SubscriptionControllerCacheLastSelectedPaymentMethodAction + | SubscriptionControllerClearLastSelectedPaymentMethodAction; export type AllowedActions = | AuthenticationController.AuthenticationControllerGetBearerToken @@ -353,6 +365,16 @@ export class SubscriptionController extends StaticIntervalPollingController()< `${controllerName}:linkRewards`, this.linkRewards.bind(this), ); + + this.messenger.registerActionHandler( + `${controllerName}:cacheLastSelectedPaymentMethod`, + this.cacheLastSelectedPaymentMethod.bind(this), + ); + + this.messenger.registerActionHandler( + `${controllerName}:clearLastSelectedPaymentMethod`, + this.clearLastSelectedPaymentMethod.bind(this), + ); } /** @@ -714,6 +736,21 @@ export class SubscriptionController extends StaticIntervalPollingController()< }); } + /** + * Clear the last selected payment method for a specific product. + * + * @param product - The product to clear the payment method for. + */ + clearLastSelectedPaymentMethod(product: ProductType): void { + this.update((state) => { + if (state.lastSelectedPaymentMethod) { + const { [product]: _, ...rest } = state.lastSelectedPaymentMethod; + state.lastSelectedPaymentMethod = + rest as typeof state.lastSelectedPaymentMethod; + } + }); + } + /** * Submit sponsorship intents to the Subscription Service backend. * diff --git a/packages/subscription-controller/src/index.ts b/packages/subscription-controller/src/index.ts index feea002d74b..f219651b544 100644 --- a/packages/subscription-controller/src/index.ts +++ b/packages/subscription-controller/src/index.ts @@ -16,6 +16,8 @@ export type { SubscriptionControllerOptions, SubscriptionControllerStateChangeEvent, SubscriptionControllerSubmitSponsorshipIntentsAction, + SubscriptionControllerCacheLastSelectedPaymentMethodAction, + SubscriptionControllerClearLastSelectedPaymentMethodAction, SubscriptionControllerLinkRewardsAction, SubscriptionControllerSubmitShieldSubscriptionCryptoApprovalAction, AllowedActions,