Skip to content

Commit fea7391

Browse files
authored
Merge branch 'main' into feat/add-infura-networks
2 parents e0bbdc7 + c25c554 commit fea7391

File tree

8 files changed

+448
-33
lines changed

8 files changed

+448
-33
lines changed

packages/subscription-controller/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- Added the new controller state, `lastSelectedPaymentMethod`. ([#6946](https://github.com/MetaMask/core/pull/6946))
1313
- We will use this in the UI state persistence between navigation.
1414
- We will use this to query user subscription plan details in subscribe methods internally.
15+
- Added new public method, `submitSponsorshipIntents`, to submit sponsorship intents for the new subscription with crypto. ([#6898](https://github.com/MetaMask/core/pull/6898))
1516

1617
## [3.0.0]
1718

packages/subscription-controller/src/SubscriptionController.test.ts

Lines changed: 222 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ import type {
3131
UpdatePaymentMethodOpts,
3232
Product,
3333
SubscriptionEligibility,
34-
CachedLastSelectedPaymentMethods,
34+
CachedLastSelectedPaymentMethod,
35+
SubmitSponsorshipIntentsMethodParams,
36+
ProductType,
3537
} from './types';
3638
import {
3739
PAYMENT_TYPES,
@@ -82,6 +84,14 @@ const MOCK_PRODUCT_PRICE: ProductPricing = {
8284
unitAmount: 900,
8385
unitDecimals: 2,
8486
trialPeriodDays: 0,
87+
minBillingCycles: 12,
88+
},
89+
{
90+
interval: 'year',
91+
unitAmount: 8000,
92+
unitDecimals: 2,
93+
currency: 'usd',
94+
trialPeriodDays: 14,
8595
minBillingCycles: 1,
8696
},
8797
],
@@ -199,6 +209,7 @@ function createMockSubscriptionService() {
199209
const mockGetBillingPortalUrl = jest.fn();
200210
const mockGetSubscriptionsEligibilities = jest.fn();
201211
const mockSubmitUserEvent = jest.fn();
212+
const mockSubmitSponsorshipIntents = jest.fn();
202213

203214
const mockService = {
204215
getSubscriptions: mockGetSubscriptions,
@@ -212,6 +223,7 @@ function createMockSubscriptionService() {
212223
getBillingPortalUrl: mockGetBillingPortalUrl,
213224
getSubscriptionsEligibilities: mockGetSubscriptionsEligibilities,
214225
submitUserEvent: mockSubmitUserEvent,
226+
submitSponsorshipIntents: mockSubmitSponsorshipIntents,
215227
};
216228

217229
return {
@@ -224,6 +236,7 @@ function createMockSubscriptionService() {
224236
mockStartSubscriptionWithCrypto,
225237
mockUpdatePaymentMethodCard,
226238
mockUpdatePaymentMethodCrypto,
239+
mockSubmitSponsorshipIntents,
227240
};
228241
}
229242

@@ -988,7 +1001,7 @@ describe('SubscriptionController', () => {
9881001
});
9891002

9901003
expect(result).toStrictEqual({
991-
approveAmount: '9000000000000000000',
1004+
approveAmount: '108000000000000000000',
9921005
paymentAddress: '0xspender',
9931006
paymentTokenAddress: '0xtoken',
9941007
chainId: '0x1',
@@ -1424,6 +1437,13 @@ describe('SubscriptionController', () => {
14241437
});
14251438

14261439
describe('cacheLastSelectedPaymentMethod', () => {
1440+
const MOCK_CACHED_PAYMENT_METHOD: CachedLastSelectedPaymentMethod = {
1441+
type: PAYMENT_TYPES.byCrypto,
1442+
paymentTokenAddress: '0x123',
1443+
paymentTokenSymbol: 'USDT',
1444+
plan: RECURRING_INTERVALS.month,
1445+
};
1446+
14271447
it('should cache last selected payment method successfully', async () => {
14281448
await withController(async ({ controller }) => {
14291449
controller.cacheLastSelectedPaymentMethod(PRODUCT_TYPES.SHIELD, {
@@ -1460,18 +1480,13 @@ describe('SubscriptionController', () => {
14601480
},
14611481
});
14621482

1463-
controller.cacheLastSelectedPaymentMethod(PRODUCT_TYPES.SHIELD, {
1464-
type: PAYMENT_TYPES.byCrypto,
1465-
paymentTokenAddress: '0x123',
1466-
plan: RECURRING_INTERVALS.month,
1467-
});
1483+
controller.cacheLastSelectedPaymentMethod(
1484+
PRODUCT_TYPES.SHIELD,
1485+
MOCK_CACHED_PAYMENT_METHOD,
1486+
);
14681487

14691488
expect(controller.state.lastSelectedPaymentMethod).toStrictEqual({
1470-
[PRODUCT_TYPES.SHIELD]: {
1471-
type: PAYMENT_TYPES.byCrypto,
1472-
paymentTokenAddress: '0x123',
1473-
plan: RECURRING_INTERVALS.month,
1474-
},
1489+
[PRODUCT_TYPES.SHIELD]: MOCK_CACHED_PAYMENT_METHOD,
14751490
});
14761491
},
14771492
);
@@ -1483,11 +1498,204 @@ describe('SubscriptionController', () => {
14831498
controller.cacheLastSelectedPaymentMethod(PRODUCT_TYPES.SHIELD, {
14841499
type: PAYMENT_TYPES.byCrypto,
14851500
plan: RECURRING_INTERVALS.month,
1486-
} as CachedLastSelectedPaymentMethods),
1501+
} as CachedLastSelectedPaymentMethod),
14871502
).toThrow(
1488-
SubscriptionControllerErrorMessage.PaymentTokenAddressRequiredForCrypto,
1503+
SubscriptionControllerErrorMessage.PaymentTokenAddressAndSymbolRequiredForCrypto,
1504+
);
1505+
});
1506+
});
1507+
});
1508+
1509+
describe('submitSponsorshipIntents', () => {
1510+
const MOCK_SUBMISSION_INTENTS_REQUEST: SubmitSponsorshipIntentsMethodParams =
1511+
{
1512+
chainId: '0x1',
1513+
address: '0x1234567890123456789012345678901234567890',
1514+
products: [PRODUCT_TYPES.SHIELD],
1515+
};
1516+
const MOCK_CACHED_PAYMENT_METHOD: Record<
1517+
ProductType,
1518+
CachedLastSelectedPaymentMethod
1519+
> = {
1520+
[PRODUCT_TYPES.SHIELD]: {
1521+
type: PAYMENT_TYPES.byCrypto,
1522+
paymentTokenAddress: '0xtoken',
1523+
paymentTokenSymbol: 'USDT',
1524+
plan: RECURRING_INTERVALS.month,
1525+
},
1526+
};
1527+
1528+
it('should submit sponsorship intents successfully', async () => {
1529+
await withController(
1530+
{
1531+
state: {
1532+
lastSelectedPaymentMethod: MOCK_CACHED_PAYMENT_METHOD,
1533+
pricing: MOCK_PRICE_INFO_RESPONSE,
1534+
},
1535+
},
1536+
async ({ controller, mockService }) => {
1537+
const submitSponsorshipIntentsSpy = jest
1538+
.spyOn(mockService, 'submitSponsorshipIntents')
1539+
.mockResolvedValue(undefined);
1540+
1541+
await controller.submitSponsorshipIntents(
1542+
MOCK_SUBMISSION_INTENTS_REQUEST,
1543+
);
1544+
expect(submitSponsorshipIntentsSpy).toHaveBeenCalledWith({
1545+
...MOCK_SUBMISSION_INTENTS_REQUEST,
1546+
paymentTokenSymbol: 'USDT',
1547+
billingCycles: 12,
1548+
recurringInterval: RECURRING_INTERVALS.month,
1549+
});
1550+
},
1551+
);
1552+
});
1553+
1554+
it('should throw error when products array is empty', async () => {
1555+
await withController(async ({ controller }) => {
1556+
await expect(
1557+
controller.submitSponsorshipIntents({
1558+
...MOCK_SUBMISSION_INTENTS_REQUEST,
1559+
products: [],
1560+
}),
1561+
).rejects.toThrow(
1562+
SubscriptionControllerErrorMessage.SubscriptionProductsEmpty,
1563+
);
1564+
});
1565+
});
1566+
1567+
it('should throw error when user is already subscribed', async () => {
1568+
await withController(
1569+
{
1570+
state: {
1571+
subscriptions: [MOCK_SUBSCRIPTION],
1572+
},
1573+
},
1574+
async ({ controller, mockService }) => {
1575+
await expect(
1576+
controller.submitSponsorshipIntents(
1577+
MOCK_SUBMISSION_INTENTS_REQUEST,
1578+
),
1579+
).rejects.toThrow(
1580+
SubscriptionControllerErrorMessage.UserAlreadySubscribed,
1581+
);
1582+
1583+
// Verify the subscription service was not called
1584+
expect(mockService.submitSponsorshipIntents).not.toHaveBeenCalled();
1585+
},
1586+
);
1587+
});
1588+
1589+
it('should not submit sponsorship intents if the user has trailed the products before', async () => {
1590+
await withController(
1591+
{
1592+
state: {
1593+
subscriptions: [
1594+
{
1595+
...MOCK_SUBSCRIPTION,
1596+
status: SUBSCRIPTION_STATUSES.canceled,
1597+
},
1598+
],
1599+
trialedProducts: [PRODUCT_TYPES.SHIELD],
1600+
},
1601+
},
1602+
async ({ controller, mockService }) => {
1603+
mockService.submitSponsorshipIntents.mockResolvedValue(undefined);
1604+
1605+
await controller.submitSponsorshipIntents(
1606+
MOCK_SUBMISSION_INTENTS_REQUEST,
1607+
);
1608+
expect(mockService.submitSponsorshipIntents).not.toHaveBeenCalled();
1609+
},
1610+
);
1611+
});
1612+
1613+
it('should throw error when no cached payment method is found', async () => {
1614+
await withController(async ({ controller }) => {
1615+
await expect(
1616+
controller.submitSponsorshipIntents(MOCK_SUBMISSION_INTENTS_REQUEST),
1617+
).rejects.toThrow(
1618+
SubscriptionControllerErrorMessage.PaymentMethodNotCrypto,
14891619
);
14901620
});
14911621
});
1622+
1623+
it('should throw error when payment method is not crypto', async () => {
1624+
await withController(
1625+
{
1626+
state: {
1627+
lastSelectedPaymentMethod: {
1628+
[PRODUCT_TYPES.SHIELD]: {
1629+
type: PAYMENT_TYPES.byCard,
1630+
plan: RECURRING_INTERVALS.month,
1631+
},
1632+
},
1633+
},
1634+
},
1635+
async ({ controller }) => {
1636+
await expect(
1637+
controller.submitSponsorshipIntents(
1638+
MOCK_SUBMISSION_INTENTS_REQUEST,
1639+
),
1640+
).rejects.toThrow(
1641+
SubscriptionControllerErrorMessage.PaymentMethodNotCrypto,
1642+
);
1643+
},
1644+
);
1645+
});
1646+
1647+
it('should throw error when product price is not found', async () => {
1648+
await withController(
1649+
{
1650+
state: {
1651+
lastSelectedPaymentMethod: MOCK_CACHED_PAYMENT_METHOD,
1652+
},
1653+
},
1654+
async ({ controller }) => {
1655+
await expect(
1656+
controller.submitSponsorshipIntents(
1657+
MOCK_SUBMISSION_INTENTS_REQUEST,
1658+
),
1659+
).rejects.toThrow(
1660+
SubscriptionControllerErrorMessage.ProductPriceNotFound,
1661+
);
1662+
},
1663+
);
1664+
});
1665+
1666+
it('should handle subscription service errors', async () => {
1667+
await withController(
1668+
{
1669+
state: {
1670+
lastSelectedPaymentMethod: {
1671+
[PRODUCT_TYPES.SHIELD]: {
1672+
...MOCK_CACHED_PAYMENT_METHOD[PRODUCT_TYPES.SHIELD],
1673+
plan: RECURRING_INTERVALS.year,
1674+
},
1675+
},
1676+
pricing: MOCK_PRICE_INFO_RESPONSE,
1677+
},
1678+
},
1679+
async ({ controller, mockService }) => {
1680+
mockService.submitSponsorshipIntents.mockRejectedValue(
1681+
new SubscriptionServiceError(
1682+
'Failed to submit sponsorship intents',
1683+
),
1684+
);
1685+
1686+
await expect(
1687+
controller.submitSponsorshipIntents(
1688+
MOCK_SUBMISSION_INTENTS_REQUEST,
1689+
),
1690+
).rejects.toThrow(SubscriptionServiceError);
1691+
expect(mockService.submitSponsorshipIntents).toHaveBeenCalledWith({
1692+
...MOCK_SUBMISSION_INTENTS_REQUEST,
1693+
paymentTokenSymbol: 'USDT',
1694+
billingCycles: 1,
1695+
recurringInterval: RECURRING_INTERVALS.year,
1696+
});
1697+
},
1698+
);
1699+
});
14921700
});
14931701
});

0 commit comments

Comments
 (0)