@@ -44,6 +44,8 @@ import {
4444 StripePaymentIntentFactory ,
4545 StripeCustomerSessionFactory ,
4646 StripeApiListFactory ,
47+ StripeInvoiceFactory ,
48+ StripeDeletedInvoiceFactory ,
4749} from '@fxa/payments/stripe' ;
4850import {
4951 MockProfileClientConfigProvider ,
@@ -199,6 +201,249 @@ describe('CartService', () => {
199201 paymentMethodManager = moduleRef . get ( PaymentMethodManager ) ;
200202 } ) ;
201203
204+ describe ( 'wrapCartWithCatch' , ( ) => {
205+ it ( 'calls cartManager.finishErrorCart' , async ( ) => {
206+ const mockCart = ResultCartFactory ( {
207+ state : CartState . PROCESSING ,
208+ stripeSubscriptionId : null ,
209+ stripeCustomerId : null ,
210+ } ) ;
211+ jest
212+ . spyOn ( cartManager , 'fetchCartById' )
213+ . mockRejectedValueOnce ( new Error ( 'test' ) )
214+ . mockResolvedValue ( mockCart ) ;
215+ jest . spyOn ( cartManager , 'finishErrorCart' ) . mockResolvedValue ( ) ;
216+
217+ await expect (
218+ cartService . finalizeProcessingCart ( mockCart . id )
219+ ) . rejects . toThrow ( Error ) ;
220+
221+ expect ( cartManager . finishErrorCart ) . toHaveBeenCalled ( ) ;
222+ } ) ;
223+
224+ it ( 'cancels a created subscription' , async ( ) => {
225+ const mockCustomer = StripeResponseFactory ( StripeCustomerFactory ( ) ) ;
226+ const mockSubscription = StripeResponseFactory (
227+ StripeSubscriptionFactory ( {
228+ customer : mockCustomer . id ,
229+ latest_invoice : null ,
230+ } )
231+ ) ;
232+ const mockCart = ResultCartFactory ( {
233+ state : CartState . PROCESSING ,
234+ stripeSubscriptionId : mockSubscription . id ,
235+ stripeCustomerId : mockCustomer . id ,
236+ } ) ;
237+
238+ jest
239+ . spyOn ( cartManager , 'fetchCartById' )
240+ . mockRejectedValueOnce ( new Error ( 'test' ) )
241+ . mockResolvedValue ( mockCart ) ;
242+
243+ jest . spyOn ( cartManager , 'finishErrorCart' ) . mockResolvedValue ( ) ;
244+ jest
245+ . spyOn ( subscriptionManager , 'retrieve' )
246+ . mockResolvedValue ( mockSubscription ) ;
247+ jest
248+ . spyOn ( subscriptionManager , 'getLatestPaymentIntent' )
249+ . mockResolvedValue ( undefined ) ;
250+ jest
251+ . spyOn ( subscriptionManager , 'cancel' )
252+ . mockResolvedValue ( mockSubscription ) ;
253+
254+ await expect (
255+ cartService . finalizeProcessingCart ( mockCart . id )
256+ ) . rejects . toThrow ( Error ) ;
257+
258+ expect ( subscriptionManager . cancel ) . toHaveBeenCalledWith (
259+ mockSubscription . id ,
260+ {
261+ cancellation_details : {
262+ comment : 'Automatic Cancellation: Cart checkout failed.' ,
263+ } ,
264+ }
265+ ) ;
266+ } ) ;
267+
268+ it ( 'deletes a created draft invoice' , async ( ) => {
269+ const mockCustomer = StripeResponseFactory ( StripeCustomerFactory ( ) ) ;
270+ const mockInvoice = StripeResponseFactory (
271+ StripeInvoiceFactory ( { status : 'draft' } )
272+ ) ;
273+ const mockDeletedInvoice = StripeResponseFactory (
274+ StripeDeletedInvoiceFactory ( { id : mockInvoice . id } )
275+ ) ;
276+ const mockSubscription = StripeResponseFactory (
277+ StripeSubscriptionFactory ( {
278+ customer : mockCustomer . id ,
279+ latest_invoice : mockInvoice . id ,
280+ } )
281+ ) ;
282+ const mockCart = ResultCartFactory ( {
283+ state : CartState . PROCESSING ,
284+ stripeSubscriptionId : mockSubscription . id ,
285+ stripeCustomerId : mockCustomer . id ,
286+ } ) ;
287+
288+ jest
289+ . spyOn ( cartManager , 'fetchCartById' )
290+ . mockRejectedValueOnce ( new Error ( 'test' ) )
291+ . mockResolvedValue ( mockCart ) ;
292+
293+ jest . spyOn ( cartManager , 'finishErrorCart' ) . mockResolvedValue ( ) ;
294+ jest
295+ . spyOn ( subscriptionManager , 'retrieve' )
296+ . mockResolvedValue ( mockSubscription ) ;
297+ jest . spyOn ( invoiceManager , 'retrieve' ) . mockResolvedValue ( mockInvoice ) ;
298+ jest
299+ . spyOn ( invoiceManager , 'delete' )
300+ . mockResolvedValue ( mockDeletedInvoice ) ;
301+ jest
302+ . spyOn ( subscriptionManager , 'getLatestPaymentIntent' )
303+ . mockResolvedValue ( undefined ) ;
304+ jest
305+ . spyOn ( subscriptionManager , 'cancel' )
306+ . mockResolvedValue ( mockSubscription ) ;
307+
308+ await expect (
309+ cartService . finalizeProcessingCart ( mockCart . id )
310+ ) . rejects . toThrow ( Error ) ;
311+
312+ expect ( invoiceManager . delete ) . toHaveBeenCalledWith ( mockInvoice . id ) ;
313+ } ) ;
314+
315+ it ( 'voids a created finalized invoice' , async ( ) => {
316+ const mockCustomer = StripeResponseFactory ( StripeCustomerFactory ( ) ) ;
317+ const mockInvoice = StripeResponseFactory (
318+ StripeInvoiceFactory ( { status : 'open' } )
319+ ) ;
320+ const mockSubscription = StripeResponseFactory (
321+ StripeSubscriptionFactory ( {
322+ customer : mockCustomer . id ,
323+ latest_invoice : mockInvoice . id ,
324+ } )
325+ ) ;
326+ const mockCart = ResultCartFactory ( {
327+ state : CartState . PROCESSING ,
328+ stripeSubscriptionId : mockSubscription . id ,
329+ stripeCustomerId : mockCustomer . id ,
330+ } ) ;
331+
332+ jest
333+ . spyOn ( cartManager , 'fetchCartById' )
334+ . mockRejectedValueOnce ( new Error ( 'test' ) )
335+ . mockResolvedValue ( mockCart ) ;
336+
337+ jest . spyOn ( cartManager , 'finishErrorCart' ) . mockResolvedValue ( ) ;
338+ jest
339+ . spyOn ( subscriptionManager , 'retrieve' )
340+ . mockResolvedValue ( mockSubscription ) ;
341+ jest . spyOn ( invoiceManager , 'retrieve' ) . mockResolvedValue ( mockInvoice ) ;
342+ jest . spyOn ( invoiceManager , 'void' ) . mockResolvedValue ( mockInvoice ) ;
343+ jest
344+ . spyOn ( subscriptionManager , 'getLatestPaymentIntent' )
345+ . mockResolvedValue ( undefined ) ;
346+ jest
347+ . spyOn ( subscriptionManager , 'cancel' )
348+ . mockResolvedValue ( mockSubscription ) ;
349+
350+ await expect (
351+ cartService . finalizeProcessingCart ( mockCart . id )
352+ ) . rejects . toThrow ( Error ) ;
353+
354+ expect ( invoiceManager . void ) . toHaveBeenCalledWith ( mockInvoice . id ) ;
355+ } ) ;
356+
357+ it ( 'cancels a created payment intent' , async ( ) => {
358+ const mockCustomer = StripeResponseFactory ( StripeCustomerFactory ( ) ) ;
359+ const mockPaymentIntent = StripeResponseFactory (
360+ StripePaymentIntentFactory ( { status : 'processing' } )
361+ ) ;
362+ const mockSubscription = StripeResponseFactory (
363+ StripeSubscriptionFactory ( {
364+ customer : mockCustomer . id ,
365+ latest_invoice : null ,
366+ } )
367+ ) ;
368+ const mockCart = ResultCartFactory ( {
369+ state : CartState . PROCESSING ,
370+ stripeSubscriptionId : mockSubscription . id ,
371+ stripeCustomerId : mockCustomer . id ,
372+ } ) ;
373+
374+ jest
375+ . spyOn ( cartManager , 'fetchCartById' )
376+ . mockRejectedValueOnce ( new Error ( 'test' ) )
377+ . mockResolvedValue ( mockCart ) ;
378+
379+ jest . spyOn ( cartManager , 'finishErrorCart' ) . mockResolvedValue ( ) ;
380+ jest
381+ . spyOn ( subscriptionManager , 'retrieve' )
382+ . mockResolvedValue ( mockSubscription ) ;
383+ jest
384+ . spyOn ( subscriptionManager , 'getLatestPaymentIntent' )
385+ . mockResolvedValue ( mockPaymentIntent ) ;
386+ jest
387+ . spyOn ( paymentIntentManager , 'cancel' )
388+ . mockResolvedValue ( mockPaymentIntent ) ;
389+ jest
390+ . spyOn ( subscriptionManager , 'cancel' )
391+ . mockResolvedValue ( mockSubscription ) ;
392+
393+ await expect (
394+ cartService . finalizeProcessingCart ( mockCart . id )
395+ ) . rejects . toThrow ( Error ) ;
396+
397+ expect ( paymentIntentManager . cancel ) . toHaveBeenCalledWith (
398+ mockPaymentIntent . id
399+ ) ;
400+ } ) ;
401+
402+ it ( 'does not delete a customer with preexisting subscriptions' , async ( ) => {
403+ const mockCustomer = StripeResponseFactory ( StripeCustomerFactory ( ) ) ;
404+ const mockSubscription = StripeResponseFactory (
405+ StripeSubscriptionFactory ( {
406+ customer : mockCustomer . id ,
407+ latest_invoice : null ,
408+ } )
409+ ) ;
410+ const mockPreviousSubscription = StripeResponseFactory (
411+ StripeSubscriptionFactory ( )
412+ ) ;
413+ const mockCart = ResultCartFactory ( {
414+ state : CartState . PROCESSING ,
415+ stripeSubscriptionId : mockSubscription . id ,
416+ stripeCustomerId : mockCustomer . id ,
417+ } ) ;
418+
419+ jest
420+ . spyOn ( cartManager , 'fetchCartById' )
421+ . mockRejectedValueOnce ( new Error ( 'test' ) )
422+ . mockResolvedValue ( mockCart ) ;
423+
424+ jest . spyOn ( cartManager , 'finishErrorCart' ) . mockResolvedValue ( ) ;
425+ jest
426+ . spyOn ( subscriptionManager , 'retrieve' )
427+ . mockResolvedValue ( mockSubscription ) ;
428+ jest
429+ . spyOn ( subscriptionManager , 'getLatestPaymentIntent' )
430+ . mockResolvedValue ( undefined ) ;
431+ jest
432+ . spyOn ( subscriptionManager , 'cancel' )
433+ . mockResolvedValue ( mockSubscription ) ;
434+ jest
435+ . spyOn ( subscriptionManager , 'listForCustomer' )
436+ . mockResolvedValue ( [ mockPreviousSubscription ] ) ;
437+ jest . spyOn ( customerManager , 'delete' ) ;
438+
439+ await expect (
440+ cartService . finalizeProcessingCart ( mockCart . id )
441+ ) . rejects . toThrow ( Error ) ;
442+
443+ expect ( customerManager . delete ) . not . toHaveBeenCalledWith ( mockCustomer . id ) ;
444+ } ) ;
445+ } ) ;
446+
202447 describe ( 'setupCart' , ( ) => {
203448 const args = {
204449 interval : SubplatInterval . Monthly ,
@@ -314,7 +559,12 @@ describe('CartService', () => {
314559 } ) ;
315560
316561 describe ( 'restartCart' , ( ) => {
562+ const mockStripeCustomerId = faker . string . uuid ( ) ;
563+ const mockAccountCustomer = ResultAccountCustomerFactory ( {
564+ stripeCustomerId : mockStripeCustomerId ,
565+ } ) ;
317566 const mockOldCart = ResultCartFactory ( {
567+ uid : mockAccountCustomer . uid ,
318568 couponCode : faker . word . noun ( ) ,
319569 } ) ;
320570 const mockNewCart = ResultCartFactory ( ) ;
@@ -333,6 +583,9 @@ describe('CartService', () => {
333583 . spyOn ( promotionCodeManager , 'assertValidPromotionCodeNameForPrice' )
334584 . mockResolvedValue ( undefined ) ;
335585 jest . spyOn ( cartManager , 'createCart' ) . mockResolvedValue ( mockNewCart ) ;
586+ jest
587+ . spyOn ( accountCustomerManager , 'getAccountCustomerByUid' )
588+ . mockResolvedValue ( mockAccountCustomer ) ;
336589
337590 const result = await cartService . restartCart ( mockOldCart . id ) ;
338591
@@ -344,7 +597,7 @@ describe('CartService', () => {
344597 couponCode : mockOldCart . couponCode ,
345598 taxAddress : mockOldCart . taxAddress ,
346599 currency : mockOldCart . currency ,
347- stripeCustomerId : mockOldCart . stripeCustomerId ,
600+ stripeCustomerId : mockAccountCustomer . stripeCustomerId ,
348601 email : mockOldCart . email ,
349602 amount : mockOldCart . amount ,
350603 eligibilityStatus : mockOldCart . eligibilityStatus ,
0 commit comments