@@ -12,6 +12,7 @@ import {
1212 CartManager ,
1313 CartService ,
1414 InvalidInvoiceStateCheckoutError ,
15+ NewAccountPrepaidCardFreeTrialNotAllowedError ,
1516 ResultCartFactory ,
1617 SubscriptionAttributionFactory ,
1718 UnexpectedSubscriptionStatusForTrialError ,
@@ -33,6 +34,7 @@ import {
3334 ResultPaypalCustomerFactory ,
3435} from '@fxa/payments/paypal' ;
3536import {
37+ ConfirmationTokenManager ,
3638 CustomerManager ,
3739 CustomerSessionManager ,
3840 InvoiceManager ,
@@ -67,6 +69,8 @@ import {
6769 AccountCustomerManager ,
6870 AccountCustomerNotFoundError ,
6971 StripeConfirmationTokenFactory ,
72+ StripeConfirmationTokenPaymentMethodPreviewCardFactory ,
73+ StripeConfirmationTokenPaymentMethodPreviewFactory ,
7074 StripeSetupIntentFactory ,
7175} from '@fxa/payments/stripe' ;
7276import {
@@ -152,6 +156,7 @@ describe('CheckoutService', () => {
152156 let asyncLocalStorage : AsyncLocalStorage < CartStore > ;
153157 let cartManager : CartManager ;
154158 let checkoutService : CheckoutService ;
159+ let confirmationTokenManager : ConfirmationTokenManager ;
155160 let customerManager : CustomerManager ;
156161 let eligibilityService : EligibilityService ;
157162 let invoiceManager : InvoiceManager ;
@@ -185,6 +190,7 @@ describe('CheckoutService', () => {
185190 CartManager ,
186191 CartService ,
187192 CheckoutService ,
193+ ConfirmationTokenManager ,
188194 CustomerManager ,
189195 CustomerSessionManager ,
190196 CurrencyManager ,
@@ -251,6 +257,7 @@ describe('CheckoutService', () => {
251257 asyncLocalStorage = moduleRef . get ( AsyncLocalStorageCart ) ;
252258 cartManager = moduleRef . get ( CartManager ) ;
253259 checkoutService = moduleRef . get ( CheckoutService ) ;
260+ confirmationTokenManager = moduleRef . get ( ConfirmationTokenManager ) ;
254261 customerManager = moduleRef . get ( CustomerManager ) ;
255262 eligibilityService = moduleRef . get ( EligibilityService ) ;
256263 invoiceManager = moduleRef . get ( InvoiceManager ) ;
@@ -1403,6 +1410,257 @@ describe('CheckoutService', () => {
14031410 } ) ;
14041411 } ) ;
14051412
1413+ it ( 'throws NewAccountPrepaidCardFreeTrialNotAllowedError when new account uses a prepaid card for a free trial' , async ( ) => {
1414+ const mockNewAccountPrePayStepsResult = PrePayStepsResultFactory ( {
1415+ uid : mockCart . uid ,
1416+ accountCreatedAt : Date . now ( ) - 60 * 60 * 1000 ,
1417+ customer : mockCustomer ,
1418+ promotionCode : mockPromotionCode ,
1419+ price : mockPrice ,
1420+ eligibility : mockEligibilityResult ,
1421+ freeTrial : mockFreeTrial ,
1422+ } ) ;
1423+ const mockPrepaidConfirmationToken = StripeResponseFactory (
1424+ StripeConfirmationTokenFactory ( {
1425+ payment_method_preview :
1426+ StripeConfirmationTokenPaymentMethodPreviewFactory ( {
1427+ card : StripeConfirmationTokenPaymentMethodPreviewCardFactory ( {
1428+ funding : 'prepaid' ,
1429+ } ) ,
1430+ } ) ,
1431+ } )
1432+ ) ;
1433+
1434+ jest
1435+ . spyOn ( checkoutService , 'prePaySteps' )
1436+ . mockResolvedValue ( mockNewAccountPrePayStepsResult ) ;
1437+ jest
1438+ . spyOn ( priceManager , 'retrievePricingForCurrency' )
1439+ . mockResolvedValue ( mockPricingForCurrency ) ;
1440+ jest
1441+ . spyOn ( checkoutService , 'getFreeTrialEligibility' )
1442+ . mockResolvedValue ( mockFreeTrial ) ;
1443+ jest
1444+ . spyOn ( confirmationTokenManager , 'retrieve' )
1445+ . mockResolvedValue ( mockPrepaidConfirmationToken ) ;
1446+ jest . spyOn ( subscriptionManager , 'create' ) ;
1447+ jest . spyOn ( statsd , 'increment' ) ;
1448+
1449+ await expect (
1450+ checkoutService . payWithStripe (
1451+ mockCart ,
1452+ mockConfirmationToken . id ,
1453+ mockAttributionData ,
1454+ mockRequestArgs ,
1455+ mockCart . uid
1456+ )
1457+ ) . rejects . toBeInstanceOf ( NewAccountPrepaidCardFreeTrialNotAllowedError ) ;
1458+
1459+ expect ( subscriptionManager . create ) . not . toHaveBeenCalled ( ) ;
1460+ } ) ;
1461+
1462+ it ( 'does not block prepaid card for free trial when account is older than 24 hours' , async ( ) => {
1463+ const mockOldAccountPrePayStepsResult = PrePayStepsResultFactory ( {
1464+ uid : mockCart . uid ,
1465+ accountCreatedAt : Date . now ( ) - 25 * 60 * 60 * 1000 ,
1466+ customer : mockCustomer ,
1467+ promotionCode : mockPromotionCode ,
1468+ price : mockPrice ,
1469+ eligibility : mockEligibilityResult ,
1470+ freeTrial : mockFreeTrial ,
1471+ } ) ;
1472+ const mockTrialSubscription = StripeResponseFactory (
1473+ StripeSubscriptionFactory ( { status : 'trialing' } )
1474+ ) ;
1475+ const mockTrialInvoice = StripeResponseFactory (
1476+ StripeInvoiceFactory ( {
1477+ payment_intent : null ,
1478+ amount_due : 0 ,
1479+ } )
1480+ ) ;
1481+ const mockSetupIntent = StripeResponseFactory (
1482+ StripeSetupIntentFactory ( {
1483+ status : 'succeeded' ,
1484+ payment_method : mockPaymentMethod . id ,
1485+ } )
1486+ ) ;
1487+
1488+ jest
1489+ . spyOn ( checkoutService , 'prePaySteps' )
1490+ . mockResolvedValue ( mockOldAccountPrePayStepsResult ) ;
1491+ jest
1492+ . spyOn ( priceManager , 'retrievePricingForCurrency' )
1493+ . mockResolvedValue ( mockPricingForCurrency ) ;
1494+ jest
1495+ . spyOn ( checkoutService , 'getFreeTrialEligibility' )
1496+ . mockResolvedValue ( mockFreeTrial ) ;
1497+ jest
1498+ . spyOn ( subscriptionManager , 'create' )
1499+ . mockResolvedValue ( mockTrialSubscription ) ;
1500+ jest . spyOn ( cartManager , 'updateFreshCart' ) . mockResolvedValue ( ) ;
1501+ jest
1502+ . spyOn ( invoiceManager , 'retrieve' )
1503+ . mockResolvedValue ( mockTrialInvoice ) ;
1504+ jest
1505+ . spyOn ( setupIntentManager , 'createAndConfirm' )
1506+ . mockResolvedValue ( mockSetupIntent ) ;
1507+ jest . spyOn ( customerManager , 'update' ) . mockResolvedValue ( mockCustomer ) ;
1508+ jest . spyOn ( checkoutService , 'postPaySteps' ) . mockResolvedValue ( ) ;
1509+ jest
1510+ . spyOn ( paymentMethodManager , 'retrieve' )
1511+ . mockResolvedValue ( mockPaymentMethod ) ;
1512+ jest . spyOn ( freeTrialManager , 'recordFreeTrial' ) . mockResolvedValue ( ) ;
1513+ jest . spyOn ( statsd , 'increment' ) ;
1514+
1515+ await checkoutService . payWithStripe (
1516+ mockCart ,
1517+ mockConfirmationToken . id ,
1518+ mockAttributionData ,
1519+ mockRequestArgs ,
1520+ mockCart . uid
1521+ ) ;
1522+
1523+ expect ( subscriptionManager . create ) . toHaveBeenCalled ( ) ;
1524+ } ) ;
1525+
1526+ it ( 'does not block new account with a non-prepaid card for a free trial' , async ( ) => {
1527+ const mockNewAccountPrePayStepsResult = PrePayStepsResultFactory ( {
1528+ uid : mockCart . uid ,
1529+ accountCreatedAt : Date . now ( ) - 60 * 60 * 1000 ,
1530+ customer : mockCustomer ,
1531+ promotionCode : mockPromotionCode ,
1532+ price : mockPrice ,
1533+ eligibility : mockEligibilityResult ,
1534+ freeTrial : mockFreeTrial ,
1535+ } ) ;
1536+ const mockCreditConfirmationToken = StripeResponseFactory (
1537+ StripeConfirmationTokenFactory ( {
1538+ payment_method_preview :
1539+ StripeConfirmationTokenPaymentMethodPreviewFactory ( {
1540+ card : StripeConfirmationTokenPaymentMethodPreviewCardFactory ( {
1541+ funding : 'credit' ,
1542+ } ) ,
1543+ } ) ,
1544+ } )
1545+ ) ;
1546+ const mockTrialSubscription = StripeResponseFactory (
1547+ StripeSubscriptionFactory ( { status : 'trialing' } )
1548+ ) ;
1549+ const mockTrialInvoice = StripeResponseFactory (
1550+ StripeInvoiceFactory ( {
1551+ payment_intent : null ,
1552+ amount_due : 0 ,
1553+ } )
1554+ ) ;
1555+ const mockSetupIntent = StripeResponseFactory (
1556+ StripeSetupIntentFactory ( {
1557+ status : 'succeeded' ,
1558+ payment_method : mockPaymentMethod . id ,
1559+ } )
1560+ ) ;
1561+
1562+ jest
1563+ . spyOn ( checkoutService , 'prePaySteps' )
1564+ . mockResolvedValue ( mockNewAccountPrePayStepsResult ) ;
1565+ jest
1566+ . spyOn ( priceManager , 'retrievePricingForCurrency' )
1567+ . mockResolvedValue ( mockPricingForCurrency ) ;
1568+ jest
1569+ . spyOn ( checkoutService , 'getFreeTrialEligibility' )
1570+ . mockResolvedValue ( mockFreeTrial ) ;
1571+ jest
1572+ . spyOn ( confirmationTokenManager , 'retrieve' )
1573+ . mockResolvedValue ( mockCreditConfirmationToken ) ;
1574+ jest
1575+ . spyOn ( subscriptionManager , 'create' )
1576+ . mockResolvedValue ( mockTrialSubscription ) ;
1577+ jest . spyOn ( cartManager , 'updateFreshCart' ) . mockResolvedValue ( ) ;
1578+ jest
1579+ . spyOn ( invoiceManager , 'retrieve' )
1580+ . mockResolvedValue ( mockTrialInvoice ) ;
1581+ jest
1582+ . spyOn ( setupIntentManager , 'createAndConfirm' )
1583+ . mockResolvedValue ( mockSetupIntent ) ;
1584+ jest . spyOn ( customerManager , 'update' ) . mockResolvedValue ( mockCustomer ) ;
1585+ jest . spyOn ( checkoutService , 'postPaySteps' ) . mockResolvedValue ( ) ;
1586+ jest
1587+ . spyOn ( paymentMethodManager , 'retrieve' )
1588+ . mockResolvedValue ( mockPaymentMethod ) ;
1589+ jest . spyOn ( freeTrialManager , 'recordFreeTrial' ) . mockResolvedValue ( ) ;
1590+ jest . spyOn ( statsd , 'increment' ) ;
1591+
1592+ await checkoutService . payWithStripe (
1593+ mockCart ,
1594+ mockConfirmationToken . id ,
1595+ mockAttributionData ,
1596+ mockRequestArgs ,
1597+ mockCart . uid
1598+ ) ;
1599+
1600+ expect ( subscriptionManager . create ) . toHaveBeenCalled ( ) ;
1601+ } ) ;
1602+
1603+ it ( 'does not retrieve the confirmation token when new account with prepaid card is not on a free trial' , async ( ) => {
1604+ const mockNewAccountPrePayStepsResult = PrePayStepsResultFactory ( {
1605+ uid : mockCart . uid ,
1606+ accountCreatedAt : Date . now ( ) - 60 * 60 * 1000 ,
1607+ customer : mockCustomer ,
1608+ promotionCode : mockPromotionCode ,
1609+ price : mockPrice ,
1610+ eligibility : mockEligibilityResult ,
1611+ } ) ;
1612+ const mockSubscription = StripeResponseFactory (
1613+ StripeSubscriptionFactory ( )
1614+ ) ;
1615+ const mockPaymentIntent = StripeResponseFactory (
1616+ StripePaymentIntentFactory ( {
1617+ status : 'succeeded' ,
1618+ payment_method : mockPaymentMethod . id ,
1619+ } )
1620+ ) ;
1621+ const mockInvoice = StripeResponseFactory (
1622+ StripeInvoiceFactory ( {
1623+ payment_intent : mockPaymentIntent . id ,
1624+ } )
1625+ ) ;
1626+
1627+ jest
1628+ . spyOn ( checkoutService , 'prePaySteps' )
1629+ . mockResolvedValue ( mockNewAccountPrePayStepsResult ) ;
1630+ jest
1631+ . spyOn ( priceManager , 'retrievePricingForCurrency' )
1632+ . mockResolvedValue ( mockPricingForCurrency ) ;
1633+ jest
1634+ . spyOn ( checkoutService , 'getFreeTrialEligibility' )
1635+ . mockResolvedValue ( null ) ;
1636+ jest . spyOn ( confirmationTokenManager , 'retrieve' ) ;
1637+ jest
1638+ . spyOn ( subscriptionManager , 'create' )
1639+ . mockResolvedValue ( mockSubscription ) ;
1640+ jest . spyOn ( cartManager , 'updateFreshCart' ) . mockResolvedValue ( ) ;
1641+ jest . spyOn ( invoiceManager , 'retrieve' ) . mockResolvedValue ( mockInvoice ) ;
1642+ jest
1643+ . spyOn ( paymentIntentManager , 'confirm' )
1644+ . mockResolvedValue ( mockPaymentIntent ) ;
1645+ jest . spyOn ( customerManager , 'update' ) . mockResolvedValue ( mockCustomer ) ;
1646+ jest . spyOn ( checkoutService , 'postPaySteps' ) . mockResolvedValue ( ) ;
1647+ jest
1648+ . spyOn ( paymentMethodManager , 'retrieve' )
1649+ . mockResolvedValue ( mockPaymentMethod ) ;
1650+ jest . spyOn ( statsd , 'increment' ) ;
1651+
1652+ await checkoutService . payWithStripe (
1653+ mockCart ,
1654+ mockConfirmationToken . id ,
1655+ mockAttributionData ,
1656+ mockRequestArgs ,
1657+ mockCart . uid
1658+ ) ;
1659+
1660+ expect ( confirmationTokenManager . retrieve ) . not . toHaveBeenCalled ( ) ;
1661+ expect ( subscriptionManager . create ) . toHaveBeenCalled ( ) ;
1662+ } ) ;
1663+
14061664 it ( 'throws UnexpectedSubscriptionStatusForTrialError when trial subscription is not trialing' , async ( ) => {
14071665 const mockNonTrialingSubscription = StripeResponseFactory (
14081666 StripeSubscriptionFactory ( { status : 'active' } )
0 commit comments