33 type FormMetadata ,
44 type PaymentFieldComponent
55} from '@defra/forms-model'
6+ import { StatusCodes } from 'http-status-codes'
67
78import { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'
89import { PaymentField } from '~/src/server/plugins/engine/components/PaymentField.js'
@@ -14,18 +15,26 @@ import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
1415import { PaymentPreAuthError } from '~/src/server/plugins/engine/pageControllers/errors.js'
1516import {
1617 type FormContext ,
17- type FormValue
18+ type FormValue ,
19+ type PaymentExternalArgs
1820} from '~/src/server/plugins/engine/types.js'
1921import {
2022 type FormRequestPayload ,
2123 type FormResponseToolkit
2224} from '~/src/server/routes/types.js'
2325import { get , post , postJson } from '~/src/server/services/httpService.js'
26+ import { type Services } from '~/src/server/types.js'
2427import definition from '~/test/form/definitions/blank.js'
2528import { getFormData , getFormState } from '~/test/helpers/component-helpers.js'
2629
2730jest . mock ( '~/src/server/services/httpService.ts' )
2831
32+ const mockServices = {
33+ formsService : {
34+ getFormSecret : ( ) => 'secret-value'
35+ }
36+ } as unknown as Services
37+
2938describe ( 'PaymentField' , ( ) => {
3039 let model : FormModel
3140
@@ -250,6 +259,7 @@ describe('PaymentField', () => {
250259
251260 const collection = new ComponentCollection ( [ def ] , { model } )
252261 const paymentField = collection . fields [ 0 ] as PaymentField
262+ paymentField . model = { services : mockServices } as unknown as FormModel
253263
254264 describe ( 'dispatcher' , ( ) => {
255265 it ( 'should create payment and redirect to gov pay' , async ( ) => {
@@ -277,7 +287,8 @@ describe('PaymentField', () => {
277287 model : {
278288 formId : 'formid' ,
279289 basePath : 'base-path' ,
280- name : 'PaymentModel'
290+ name : 'PaymentModel' ,
291+ services : mockServices
281292 } ,
282293 getState : jest
283294 . fn ( )
@@ -287,7 +298,7 @@ describe('PaymentField', () => {
287298 sourceUrl : 'http://localhost:3009/test-payment' ,
288299 isLive : false ,
289300 isPreview : true
290- }
301+ } as unknown as PaymentExternalArgs
291302 // @ts -expect-error - partial mock
292303 jest . mocked ( postJson ) . mockResolvedValueOnce ( {
293304 payload : {
@@ -342,7 +353,8 @@ describe('PaymentField', () => {
342353 model : {
343354 formId : 'formid' ,
344355 basePath : 'base-path' ,
345- name : 'PaymentModel'
356+ name : 'PaymentModel' ,
357+ services : mockServices
346358 } ,
347359 getState : jest . fn ( ) . mockResolvedValueOnce ( {
348360 $$__referenceNumber : 'pay-ref-123' ,
@@ -361,7 +373,7 @@ describe('PaymentField', () => {
361373 sourceUrl : 'http://localhost:3009/test-payment' ,
362374 isLive : false ,
363375 isPreview : true
364- }
376+ } as unknown as PaymentExternalArgs
365377
366378 const res = await PaymentField . dispatcher ( mockRequest , mockH , args )
367379
@@ -372,6 +384,128 @@ describe('PaymentField', () => {
372384 expect ( mockRedirectCode ) . toHaveBeenCalledWith ( 303 )
373385 expect ( postJson ) . not . toHaveBeenCalled ( )
374386 } )
387+
388+ it ( 'should display error if create payment fails (e.g. network or bad api key) - test payment' , async ( ) => {
389+ const mockYarSet = jest . fn ( )
390+ const mockYarFlash = jest . fn ( )
391+ const mockRequest = {
392+ server : {
393+ plugins : {
394+ // eslint-disable-next-line no-useless-computed-key
395+ [ 'forms-engine-plugin' ] : {
396+ baseUrl : 'base-url'
397+ }
398+ }
399+ } ,
400+ yar : {
401+ set : mockYarSet ,
402+ flash : mockYarFlash
403+ } ,
404+ url : {
405+ href : '/here'
406+ }
407+ } as unknown as FormRequestPayload
408+ const mockH = {
409+ redirect : jest
410+ . fn ( )
411+ . mockReturnValueOnce ( { code : jest . fn ( ) . mockReturnValueOnce ( 'ok' ) } )
412+ } as unknown as FormResponseToolkit
413+ const args = {
414+ controller : {
415+ model : {
416+ formId : 'formid' ,
417+ basePath : 'base-path' ,
418+ name : 'PaymentModel' ,
419+ services : mockServices
420+ } ,
421+ getState : jest
422+ . fn ( )
423+ . mockResolvedValueOnce ( { $$__referenceNumber : 'pay-ref-123' } )
424+ } ,
425+ component : paymentField ,
426+ sourceUrl : 'http://localhost:3009/test-payment' ,
427+ isLive : false ,
428+ isPreview : true
429+ } as unknown as PaymentExternalArgs
430+ jest . mocked ( postJson ) . mockImplementationOnce ( ( ) => {
431+ // eslint-disable-next-line @typescript-eslint/only-throw-error
432+ throw { output : { statusCode : StatusCodes . UNAUTHORIZED } }
433+ } )
434+
435+ const res = await PaymentField . dispatcher ( mockRequest , mockH , args )
436+ expect ( res ) . toBe ( 'ok' )
437+ expect ( mockYarSet ) . not . toHaveBeenCalled ( )
438+ expect ( mockYarFlash ) . toHaveBeenCalledWith (
439+ 'COMPONENT_STATE_ERROR' ,
440+ {
441+ href : '#myComponent' ,
442+ name : 'myComponent' ,
443+ text : 'Add a valid test API key before you can preview the payment journey.'
444+ } ,
445+ true
446+ )
447+ } )
448+
449+ it ( 'should display error if create payment fails (e.g. network or bad api key) - live payment' , async ( ) => {
450+ const mockYarSet = jest . fn ( )
451+ const mockYarFlash = jest . fn ( )
452+ const mockRequest = {
453+ server : {
454+ plugins : {
455+ // eslint-disable-next-line no-useless-computed-key
456+ [ 'forms-engine-plugin' ] : {
457+ baseUrl : 'base-url'
458+ }
459+ }
460+ } ,
461+ yar : {
462+ set : mockYarSet ,
463+ flash : mockYarFlash
464+ } ,
465+ url : {
466+ href : '/here'
467+ }
468+ } as unknown as FormRequestPayload
469+ const mockH = {
470+ redirect : jest
471+ . fn ( )
472+ . mockReturnValueOnce ( { code : jest . fn ( ) . mockReturnValueOnce ( 'ok' ) } )
473+ } as unknown as FormResponseToolkit
474+ const args = {
475+ controller : {
476+ model : {
477+ formId : 'formid' ,
478+ basePath : 'base-path' ,
479+ name : 'PaymentModel' ,
480+ services : mockServices
481+ } ,
482+ getState : jest
483+ . fn ( )
484+ . mockResolvedValueOnce ( { $$__referenceNumber : 'pay-ref-123' } )
485+ } ,
486+ component : paymentField ,
487+ sourceUrl : 'http://localhost:3009/test-payment' ,
488+ isLive : true ,
489+ isPreview : false
490+ } as unknown as PaymentExternalArgs
491+ jest . mocked ( postJson ) . mockImplementationOnce ( ( ) => {
492+ // eslint-disable-next-line @typescript-eslint/only-throw-error
493+ throw { output : { statusCode : StatusCodes . UNAUTHORIZED } }
494+ } )
495+
496+ const res = await PaymentField . dispatcher ( mockRequest , mockH , args )
497+ expect ( res ) . toBe ( 'ok' )
498+ expect ( mockYarSet ) . not . toHaveBeenCalled ( )
499+ expect ( mockYarFlash ) . toHaveBeenCalledWith (
500+ 'COMPONENT_STATE_ERROR' ,
501+ {
502+ href : '#myComponent' ,
503+ name : 'myComponent' ,
504+ text : 'There is a problem and we cannot take a payment. Contact us (details in the footer of this form) or save your progress and return to the form later.'
505+ } ,
506+ true
507+ )
508+ } )
375509 } )
376510
377511 describe ( 'onSubmit' , ( ) => {
0 commit comments