Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
54d39fe
feat: enhance multi-product subscription support
tuna1207 Aug 13, 2026
3f5c81e
fix: lint
tuna1207 Aug 13, 2026
cd756dc
chore: update changelog for multi-product subscription enhancements
tuna1207 Aug 13, 2026
19736a8
feat(subscription-controller): enhance crypto approval handling for S…
tuna1207 Aug 13, 2026
bf9e0da
fix(subscription-controller): handle Money Account crypto approval ha…
tuna1207 Aug 13, 2026
e35e481
fix(subscription-controller): update product name in subscription tes…
tuna1207 Aug 13, 2026
a4b5409
feat(subscription-controller): add test for trial eligibility based o…
tuna1207 Aug 13, 2026
5522448
fix(subscription-controller): add validation for crypto subscription …
tuna1207 Aug 13, 2026
57b48d1
fix(subscription-controller): update crypto payment method validation…
tuna1207 Aug 13, 2026
c284cc0
feat(subscription-controller): enhance multi-product subscription sup…
tuna1207 Aug 13, 2026
1e55d61
feat(subscription-controller): implement discriminated unions for pay…
tuna1207 Aug 13, 2026
e586606
fix(subscription-controller): improve error handling for sponsorship …
tuna1207 Aug 14, 2026
b3f9697
fix: correct jsdoc
tuna1207 Aug 14, 2026
b7db56a
fix(subscription-controller): crypto subscription starts, ensuring st…
tuna1207 Aug 14, 2026
36c01b7
feat(subscription-controller): add trial request handling in subscrip…
tuna1207 Aug 14, 2026
f9d54e6
feat(subscription-controller): enhance crypto payment method resoluti…
tuna1207 Aug 14, 2026
89b0428
feat(subscription-controller): add validation for empty products arra…
tuna1207 Aug 14, 2026
87bf7c1
chore: update changelog
tuna1207 Aug 14, 2026
48918a8
fix: test
tuna1207 Aug 14, 2026
f181261
feat(subscription-controller): implement discriminated unions for cry…
tuna1207 Aug 14, 2026
35450e4
feat(subscription-controller): refactor subscription methods to use r…
tuna1207 Aug 14, 2026
865c4d9
feat(subscription-controller): add handling for trial eligibility bas…
tuna1207 Aug 14, 2026
e25e830
fix: lint
tuna1207 Aug 14, 2026
6cbac54
fix: test
tuna1207 Aug 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion packages/subscription-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed
### Added

- Add multi-product and delegation-based crypto subscription types and pricing fields. ([#9866](https://github.com/MetaMask/core/pull/9866))
- `PRODUCT_TYPES.MONEY_ACCOUNT_PLUS`
- `CRYPTO_AUTH_METHODS` (`erc20_approval`, `delegation`) and `CryptoAuthMethod`
- `StartErc20CryptoSubscriptionRequest` and `StartDelegationCryptoSubscriptionRequest`
- `PricingCardPaymentMethod` and `PricingCryptoPaymentMethod` variants, with optional `products` and (crypto only) `cryptoAuthMethod`
- Optional `ChainPaymentInfo.delegateAddress`
- `SpotTokenPaymentInfo` and `VaultTokenPaymentInfo` variants, with optional `sources`; vault shares require `accountantAddress`

### Changed

- **BREAKING:** Model `PricingPaymentMethod` as a discriminated union of card vs crypto. `chains` and `cryptoAuthMethod` exist only on the crypto variant; narrow with `type === 'crypto'` before reading them. ([#9866](https://github.com/MetaMask/core/pull/9866))
- **BREAKING:** Model `TokenPaymentInfo` as a discriminated union of vault vs spot. `accountantAddress` is required when `isVaultShare` is true and is not present on spot tokens; narrow with `isVaultShare === true` before reading `accountantAddress`. ([#9866](https://github.com/MetaMask/core/pull/9866))
- **BREAKING:** Rename `startShieldSubscriptionWithCard` to `startSubscriptionWithCard`. ([#9866](https://github.com/MetaMask/core/pull/9866))
- Rename `SubscriptionController.startShieldSubscriptionWithCard` to `startSubscriptionWithCard`.
- Rename the messenger action `SubscriptionController:startShieldSubscriptionWithCard` to `SubscriptionController:startSubscriptionWithCard`.
- Rename the exported action type `SubscriptionControllerStartShieldSubscriptionWithCardAction` to `SubscriptionControllerStartSubscriptionWithCardAction`.
- **BREAKING:** Rename `submitShieldSubscriptionCryptoApproval` to `submitSubscriptionCryptoApproval` and take a request object instead of positional arguments. ([#9866](https://github.com/MetaMask/core/pull/9866))
- Rename `SubscriptionController.submitShieldSubscriptionCryptoApproval` to `submitSubscriptionCryptoApproval`.
- Rename the messenger action `SubscriptionController:submitShieldSubscriptionCryptoApproval` to `SubscriptionController:submitSubscriptionCryptoApproval`.
- Rename the exported action type `SubscriptionControllerSubmitShieldSubscriptionCryptoApprovalAction` to `SubscriptionControllerSubmitSubscriptionCryptoApprovalAction`.
- Callers pass `{ productType, txMeta, isSponsored?, rewardAccountId? }` (`SubmitSubscriptionCryptoApprovalRequest`).
- This handler is Shield ERC-20 approve only. `productType` is typed as `typeof PRODUCT_TYPES.SHIELD` (not `ProductType`); `txMeta.type` must be `TransactionType.shieldSubscriptionApprove`. Other products should use `startSubscriptionWithCrypto`.
- **BREAKING:** Make `TokenPaymentInfo.conversionRate` optional. Consumers that access `.conversionRate.usd` without optional chaining will fail typecheck. ([#9866](https://github.com/MetaMask/core/pull/9866))
- **BREAKING:** Change `SubscriptionControllerState.lastSelectedPaymentMethod` from `Record<ProductType, CachedLastSelectedPaymentMethod>` to `Partial<Record<ProductType, CachedLastSelectedPaymentMethod>>`. Product keys may be absent; consumers must handle missing entries. ([#9866](https://github.com/MetaMask/core/pull/9866))
- **BREAKING:** `SubscriptionController.cacheLastSelectedPaymentMethod` now takes a request object instead of positional arguments. ([#9866](https://github.com/MetaMask/core/pull/9866))
- Callers pass `{ product, paymentMethod }` (`CacheLastSelectedPaymentMethodRequest`).
- **BREAKING:** Model `StartCryptoSubscriptionRequest` as a discriminated union of ERC-20 approval vs delegation. ([#9866](https://github.com/MetaMask/core/pull/9866))
- ERC-20: required `rawTransaction`; optional `cryptoAuthMethod: 'erc20_approval'` (the default when omitted).
- Delegation: required `cryptoAuthMethod: 'delegation'` and `delegationHash`.
- Combining or omitting both auth fields is a type error. Runtime validation in `startSubscriptionWithCrypto` remains for unsound callers.
- New exports: `StartErc20CryptoSubscriptionRequest`, `StartDelegationCryptoSubscriptionRequest`.
- Generalize subscription controller flows for multiple products: product-scoped crypto payment-method lookup, and trial requests derived from `trialPeriodDays` plus `trialedProducts`. ([#9866](https://github.com/MetaMask/core/pull/9866))
- Bump `@metamask/transaction-controller` from `^69.5.1` to `^69.5.2` ([#9823](https://github.com/MetaMask/core/pull/9823))

## [7.0.0]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,29 +52,64 @@ export type SubscriptionControllerUnCancelSubscriptionAction = {
handler: SubscriptionController['unCancelSubscription'];
};

export type SubscriptionControllerStartShieldSubscriptionWithCardAction = {
type: `SubscriptionController:startShieldSubscriptionWithCard`;
handler: SubscriptionController['startShieldSubscriptionWithCard'];
/**
* Starts a card-paid subscription checkout session for the requested products
* (e.g. Shield or Money Account Plus).
*
* `isTrialRequested` on the request is ignored and overwritten from pricing
* (`trialPeriodDays > 0`) and `trialedProducts`.
*
* @param request - The start subscription request.
* @returns The checkout session response.
*/
export type SubscriptionControllerStartSubscriptionWithCardAction = {
type: `SubscriptionController:startSubscriptionWithCard`;
handler: SubscriptionController['startSubscriptionWithCard'];
};

/**
* Starts a crypto-paid subscription for the requested products
* (e.g. Shield or Money Account Plus). Unlike card checkout, this
* creates the subscription immediately, so local state is refreshed
* afterwards.
*
* `isTrialRequested` on the request is ignored and overwritten from pricing
* (`trialPeriodDays > 0`) and `trialedProducts`.
*
* @param request - The start crypto subscription request.
* @returns The start crypto subscription response.
* @throws If `products` is empty.
*/
export type SubscriptionControllerStartSubscriptionWithCryptoAction = {
type: `SubscriptionController:startSubscriptionWithCrypto`;
handler: SubscriptionController['startSubscriptionWithCrypto'];
};

/**
* Handles shield subscription crypto approval transactions.
* Submits a Shield ERC-20 crypto approval transaction to start or update a
* crypto subscription.
*
* @param txMeta - The transaction metadata.
* @param isSponsored - Whether the transaction is sponsored.
* @param rewardAccountId - The account ID of the reward subscription to link to the shield subscription.
* This handler is Shield / `TransactionType.shieldSubscriptionApprove` only.
* Delegation-based products (e.g. Money Account) must call
* `startSubscriptionWithCrypto` instead.
*
* @param request - The crypto approval request.
* @param request.productType - The subscription product. Typed as
* `typeof PRODUCT_TYPES.SHIELD` only at the moment (future might support more
* product).
* @param request.txMeta - The transaction metadata. Must have type
* `TransactionType.shieldSubscriptionApprove`.
* @param request.isSponsored - Whether the transaction is sponsored.
* @param request.rewardAccountId - The account ID of the reward subscription
* to link.
* @throws If `productType` is not Shield or `txMeta.type` is not
* `shieldSubscriptionApprove`.
* @returns void
*/
export type SubscriptionControllerSubmitShieldSubscriptionCryptoApprovalAction =
{
type: `SubscriptionController:submitShieldSubscriptionCryptoApproval`;
handler: SubscriptionController['submitShieldSubscriptionCryptoApproval'];
};
export type SubscriptionControllerSubmitSubscriptionCryptoApprovalAction = {
Comment thread
chaitanyapotti marked this conversation as resolved.
type: `SubscriptionController:submitSubscriptionCryptoApproval`;
handler: SubscriptionController['submitSubscriptionCryptoApproval'];
};

/**
* Get transaction params to create crypto approve transaction for subscription payment
Expand Down Expand Up @@ -109,12 +144,12 @@ export type SubscriptionControllerGetBillingPortalUrlAction = {
/**
* Cache the last selected payment method for a specific product.
*
* @param product - The product to cache the payment method for.
* @param paymentMethod - The payment method to cache.
* @param paymentMethod.type - The type of the payment method.
* @param paymentMethod.paymentTokenAddress - The payment token address.
* @param paymentMethod.plan - The plan of the payment method.
* @param paymentMethod.product - The product of the payment method.
* @param request - The request object.
* @param request.product - The product to cache the payment method for.
* @param request.paymentMethod - The payment method to cache.
* @param request.paymentMethod.type - The type of the payment method.
* @param request.paymentMethod.paymentTokenAddress - The payment token address.
* @param request.paymentMethod.plan - The plan of the payment method.
*/
export type SubscriptionControllerCacheLastSelectedPaymentMethodAction = {
type: `SubscriptionController:cacheLastSelectedPaymentMethod`;
Expand Down Expand Up @@ -144,7 +179,8 @@ export type SubscriptionControllerClearLastSelectedPaymentMethodAction = {
* recurringInterval: RecurringInterval.Month,
* billingCycles: 1,
* }
* @returns resolves to true if the sponsorship is supported and intents were submitted successfully, false otherwise
* @returns resolves to true if the sponsorship is supported and intents were submitted successfully, false if the chain does not support sponsorship or the user has already trialed
* @throws If the crypto payment method or chain is missing from pricing
*/
export type SubscriptionControllerSubmitSponsorshipIntentsAction = {
type: `SubscriptionController:submitSponsorshipIntents`;
Expand Down Expand Up @@ -242,9 +278,9 @@ export type SubscriptionControllerMethodActions =
| SubscriptionControllerGetSubscriptionsEligibilitiesAction
| SubscriptionControllerCancelSubscriptionAction
| SubscriptionControllerUnCancelSubscriptionAction
| SubscriptionControllerStartShieldSubscriptionWithCardAction
| SubscriptionControllerStartSubscriptionWithCardAction
| SubscriptionControllerStartSubscriptionWithCryptoAction
| SubscriptionControllerSubmitShieldSubscriptionCryptoApprovalAction
| SubscriptionControllerSubmitSubscriptionCryptoApprovalAction
| SubscriptionControllerGetCryptoApproveTransactionParamsAction
| SubscriptionControllerUpdatePaymentMethodAction
| SubscriptionControllerGetBillingPortalUrlAction
Expand Down
Loading