Skip to content

feat: enhance multi-product subscription support - #9866

Merged
chaitanyapotti merged 24 commits into
mainfrom
feat/subscription-multi-product
Aug 14, 2026
Merged

feat: enhance multi-product subscription support#9866
chaitanyapotti merged 24 commits into
mainfrom
feat/subscription-multi-product

Conversation

@tuna1207

@tuna1207 tuna1207 commented Aug 13, 2026

Copy link
Copy Markdown
Member

Explanation

Generalizes @metamask/subscription-controller beyond Shield-only flows so clients can manage multiple subscription products (Shield + Money Account Plus) with product-scoped payment methods, crypto auth methods, and trial logic.

Current state

The subscription controller was built around Shield as the sole product. Messenger actions, crypto approval handling, payment-method lookup, and trial eligibility were all hardcoded to PRODUCT_TYPES.SHIELD. The pricing API now supports multiple products with different crypto auth flows (ERC-20 approval vs. delegation), so the controller needed to be generalized.

Solution

This PR extends the subscription controller and service to support multi-product subscriptions aligned with PR-141 pricing API changes:

New types and constants

  • PRODUCT_TYPES.MONEY_ACCOUNT_PLUS (money_account_plus)
  • CRYPTO_AUTH_METHODS (erc20_approval, delegation)
  • CryptoAuthMethod type exported from the package

Pricing model extensions

  • PricingPaymentMethod gains optional products and cryptoAuthMethod for product-scoped payment rows
  • ChainPaymentInfo gains optional delegateAddress for delegation flows
  • TokenPaymentInfo gains optional isVaultShare, accountantAddress, sources, and makes conversionRate optional (vault share tokens)
  • StartCryptoSubscriptionRequest makes rawTransaction optional for delegation; adds cryptoAuthMethod and delegationHash
  • CachedLastSelectedPaymentMethod tracks per-product cryptoAuthMethod

Controller generalization

  • lastSelectedPaymentMethod is now Partial<Record<ProductType, ...>> (per-product)
  • Crypto payment-method lookup is product- and auth-method-aware via #findCryptoPaymentMethod
  • Trial requests use #getIsTrialRequested — derived from trialPeriodDays and trialedProducts, not a simple "has trialed" boolean
  • Sponsorship checks are product-scoped
  • getCryptoApproveTransactionParams selects the ERC-20 approval payment method for the requested product
  • submitSubscriptionCryptoApproval accepts productType and works for any product (not Shield-only)
  • lastSubscription is synced from the API but not used for active product lookup

Breaking renames

Before After
SubscriptionController:startShieldSubscriptionWithCard SubscriptionController:startSubscriptionWithCard
SubscriptionController:submitShieldSubscriptionCryptoApproval SubscriptionController:submitSubscriptionCryptoApproval (+ required productType as first arg)

Migration for consumers

// Card checkout
- messenger.call('SubscriptionController:startShieldSubscriptionWithCard', request)
+ messenger.call('SubscriptionController:startSubscriptionWithCard', request)

// Crypto approval
- messenger.call('SubscriptionController:submitShieldSubscriptionCryptoApproval', txMeta, isSponsored?, rewardAccountId?)
+ messenger.call('SubscriptionController:submitSubscriptionCryptoApproval', PRODUCT_TYPES.SHIELD, txMeta, isSponsored?, rewardAccountId?)

References

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

Note

High Risk
Breaking messenger/API renames and payment/trial logic changes affect all subscription consumers; incorrect product-scoped crypto lookup or trial overrides could block checkout or grant trials wrongly.

Overview
Generalizes @metamask/subscription-controller beyond Shield so clients can manage Shield and Money Account Plus in parallel, with product-scoped payment rows, crypto auth methods, and trial handling.

New surface area: PRODUCT_TYPES.MONEY_ACCOUNT_PLUS, CRYPTO_AUTH_METHODS (erc20_approval / delegation), and richer pricing types—card vs crypto payment methods, spot vs vault tokens (optional conversionRate, required accountantAddress for vault shares), optional delegateAddress on chains, and StartCryptoSubscriptionRequest as a union of ERC-20 (rawTransaction) vs delegation (delegationHash).

Runtime behavior changes: Card and crypto starts refresh subscriptions first, then override isTrialRequested from pricing trialPeriodDays and trialedProducts (client-supplied trial flags are ignored). Crypto start refetches subscriptions after success. Crypto approve params, sponsorship, and cached payment methods resolve via product + cryptoAuthMethod, with legacy unscoped pricing rows defaulting to Shield + ERC-20 approval only when both products and cryptoAuthMethod are omitted.

Breaking renames / signatures: startShieldSubscriptionWithCardstartSubscriptionWithCard; submitShieldSubscriptionCryptoApprovalsubmitSubscriptionCryptoApproval with a request object (still Shield / shieldSubscriptionApprove only—delegation products use startSubscriptionWithCrypto). cacheLastSelectedPaymentMethod takes { product, paymentMethod }. lastSelectedPaymentMethod is Partial<Record<ProductType, …>>. Consumers must narrow discriminated unions for pricing and token payment info.

Service layer: startSubscriptionWithCrypto validates empty products and exactly one auth combo before POST; pricing responses validated against the new structs.

Reviewed by Cursor Bugbot for commit 6cbac54. Bugbot is set up for automated code reviews on this repo. Configure here.

@tuna1207
tuna1207 marked this pull request as ready for review August 14, 2026 01:54
@tuna1207
tuna1207 requested review from a team as code owners August 14, 2026 01:54
@tuna1207
tuna1207 deployed to default-branch August 14, 2026 01:54 — with GitHub Actions Active
Comment thread packages/subscription-controller/src/SubscriptionController.ts
Comment thread packages/subscription-controller/src/SubscriptionController.ts
if (txMeta.type !== TransactionType.shieldSubscriptionApprove) {
return;
if (
productType !== PRODUCT_TYPES.SHIELD ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove the productType from method params and this?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as #9866 (comment)

this.state.lastSelectedPaymentMethod?.[request.products[0]];
this.#assertIsPaymentMethodCrypto(selectedPaymentMethod);

const cryptoAuthMethod =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like CRYPTO_AUTH_METHODS.ERC20_APPROVAL is being used by default, if last selected payment isn't available.

Then may I know where do we even use the CRYPTO_AUTH_METHODS.DELEGATION?
Or we don't need sponsorship for delegations?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, currently only ERC20_APPROVAL handle sponsorship, for DELEGATION we only receive the delegation hash here, no sponsorship handled

* when omitted. Use `CRYPTO_AUTH_METHODS.DELEGATION` for products such as
* Money Account Plus.
*/
cryptoAuthMethod?: CryptoAuthMethod;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's make it required?

rawTransaction?: Hex;
/**
* Crypto authorization method. Defaults to `CRYPTO_AUTH_METHODS.ERC20_APPROVAL`
* when omitted. Use `CRYPTO_AUTH_METHODS.DELEGATION` for products such as

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I check is CRYPTO_AUTH_METHODS.DELEGATION only for money account?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If CRYPTO_AUTH_METHODS.DELEGATION only for money account, we should add some validation/assertions in startSubscriptionWithCrypto and reject the request when shield subscription comes with DELEGATION method.

Comment thread packages/subscription-controller/src/types.ts
Comment thread packages/subscription-controller/src/types.ts
Comment thread packages/subscription-controller/src/types.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 48918a8. Configure here.

Comment thread packages/subscription-controller/src/SubscriptionController.ts
…pto subscription requests, adding new request types and enhancing validation logic
…equest objects for crypto approval and payment method caching, enhancing type safety and clarity
…ed on subscription state, enhancing tests for trial requests
@chaitanyapotti
chaitanyapotti added this pull request to the merge queue Aug 14, 2026
Merged via the queue into main with commit 9cdb2b3 Aug 14, 2026
58 checks passed
@chaitanyapotti
chaitanyapotti deleted the feat/subscription-multi-product branch August 14, 2026 07:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants