@@ -31,7 +31,9 @@ import type {
3131 UpdatePaymentMethodOpts ,
3232 Product ,
3333 SubscriptionEligibility ,
34- CachedLastSelectedPaymentMethods ,
34+ CachedLastSelectedPaymentMethod ,
35+ SubmitSponsorshipIntentsMethodParams ,
36+ ProductType ,
3537} from './types' ;
3638import {
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