@defra/forms-engine-plugin 4.0.42 → 4.0.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/.public/javascripts/shared.min.js +1 -1
  2. package/.public/javascripts/shared.min.js.map +1 -1
  3. package/.public/stylesheets/application.min.css +1 -1
  4. package/.public/stylesheets/application.min.css.map +1 -1
  5. package/.server/client/javascripts/location-map.js +8 -4
  6. package/.server/client/javascripts/location-map.js.map +1 -1
  7. package/.server/client/stylesheets/_payment-field.scss +8 -0
  8. package/.server/client/stylesheets/application.scss +2 -0
  9. package/.server/index.js +3 -1
  10. package/.server/index.js.map +1 -1
  11. package/.server/server/constants.d.ts +1 -0
  12. package/.server/server/constants.js +1 -0
  13. package/.server/server/constants.js.map +1 -1
  14. package/.server/server/forms/payment-test.yaml +42 -0
  15. package/.server/server/forms/register-as-a-unicorn-breeder.yaml +14 -0
  16. package/.server/server/plugins/engine/components/FormComponent.d.ts +1 -0
  17. package/.server/server/plugins/engine/components/FormComponent.js +1 -0
  18. package/.server/server/plugins/engine/components/FormComponent.js.map +1 -1
  19. package/.server/server/plugins/engine/components/PaymentField.d.ts +135 -0
  20. package/.server/server/plugins/engine/components/PaymentField.js +228 -0
  21. package/.server/server/plugins/engine/components/PaymentField.js.map +1 -0
  22. package/.server/server/plugins/engine/components/PaymentField.types.d.ts +21 -0
  23. package/.server/server/plugins/engine/components/PaymentField.types.js +2 -0
  24. package/.server/server/plugins/engine/components/PaymentField.types.js.map +1 -0
  25. package/.server/server/plugins/engine/components/UkAddressField.d.ts +1 -1
  26. package/.server/server/plugins/engine/components/UkAddressField.js +3 -1
  27. package/.server/server/plugins/engine/components/UkAddressField.js.map +1 -1
  28. package/.server/server/plugins/engine/components/helpers/components.d.ts +1 -1
  29. package/.server/server/plugins/engine/components/helpers/components.js +3 -0
  30. package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
  31. package/.server/server/plugins/engine/components/index.d.ts +1 -0
  32. package/.server/server/plugins/engine/components/index.js +1 -0
  33. package/.server/server/plugins/engine/components/index.js.map +1 -1
  34. package/.server/server/plugins/engine/configureEnginePlugin.d.ts +1 -1
  35. package/.server/server/plugins/engine/configureEnginePlugin.js +4 -2
  36. package/.server/server/plugins/engine/configureEnginePlugin.js.map +1 -1
  37. package/.server/server/plugins/engine/helpers.d.ts +1 -0
  38. package/.server/server/plugins/engine/models/SummaryViewModel.d.ts +3 -0
  39. package/.server/server/plugins/engine/models/SummaryViewModel.js +7 -0
  40. package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
  41. package/.server/server/plugins/engine/options.js +2 -1
  42. package/.server/server/plugins/engine/options.js.map +1 -1
  43. package/.server/server/plugins/engine/outputFormatters/human/v1.js +34 -1
  44. package/.server/server/plugins/engine/outputFormatters/human/v1.js.map +1 -1
  45. package/.server/server/plugins/engine/outputFormatters/machine/v2.d.ts +22 -0
  46. package/.server/server/plugins/engine/outputFormatters/machine/v2.js +43 -1
  47. package/.server/server/plugins/engine/outputFormatters/machine/v2.js.map +1 -1
  48. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +29 -8
  49. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
  50. package/.server/server/plugins/engine/pageControllers/StartPageController.d.ts +2 -0
  51. package/.server/server/plugins/engine/pageControllers/SummaryPageController.d.ts +17 -0
  52. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +173 -51
  53. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
  54. package/.server/server/plugins/engine/pageControllers/errors.d.ts +31 -0
  55. package/.server/server/plugins/engine/pageControllers/errors.js +59 -2
  56. package/.server/server/plugins/engine/pageControllers/errors.js.map +1 -1
  57. package/.server/server/plugins/engine/pageControllers/helpers/submission.d.ts +27 -0
  58. package/.server/server/plugins/engine/pageControllers/helpers/submission.js +77 -0
  59. package/.server/server/plugins/engine/pageControllers/helpers/submission.js.map +1 -0
  60. package/.server/server/plugins/engine/plugin.js +10 -5
  61. package/.server/server/plugins/engine/plugin.js.map +1 -1
  62. package/.server/server/plugins/engine/routes/index.js +8 -4
  63. package/.server/server/plugins/engine/routes/index.js.map +1 -1
  64. package/.server/server/plugins/engine/routes/payment-helper.d.ts +14 -0
  65. package/.server/server/plugins/engine/routes/payment-helper.js +41 -0
  66. package/.server/server/plugins/engine/routes/payment-helper.js.map +1 -0
  67. package/.server/server/plugins/engine/routes/payment-helper.test.js +81 -0
  68. package/.server/server/plugins/engine/routes/payment-helper.test.js.map +1 -0
  69. package/.server/server/plugins/engine/routes/payment.d.ts +8 -0
  70. package/.server/server/plugins/engine/routes/payment.js +140 -0
  71. package/.server/server/plugins/engine/routes/payment.js.map +1 -0
  72. package/.server/server/plugins/engine/routes/payment.test.js +187 -0
  73. package/.server/server/plugins/engine/routes/payment.test.js.map +1 -0
  74. package/.server/server/plugins/engine/services/localFormsService.js +6 -0
  75. package/.server/server/plugins/engine/services/localFormsService.js.map +1 -1
  76. package/.server/server/plugins/engine/types/schema.js +7 -0
  77. package/.server/server/plugins/engine/types/schema.js.map +1 -1
  78. package/.server/server/plugins/engine/types.d.ts +20 -1
  79. package/.server/server/plugins/engine/types.js +4 -0
  80. package/.server/server/plugins/engine/types.js.map +1 -1
  81. package/.server/server/plugins/engine/validationHelpers.d.ts +1 -1
  82. package/.server/server/plugins/engine/validationHelpers.js.map +1 -1
  83. package/.server/server/plugins/engine/views/components/paymentfield.html +42 -0
  84. package/.server/server/plugins/engine/views/index.html +9 -1
  85. package/.server/server/plugins/engine/views/partials/form.html +20 -5
  86. package/.server/server/plugins/engine/views/summary.html +17 -1
  87. package/.server/server/plugins/map/routes/get-os-token.d.ts +6 -0
  88. package/.server/server/plugins/map/routes/get-os-token.js +41 -0
  89. package/.server/server/plugins/map/routes/get-os-token.js.map +1 -0
  90. package/.server/server/plugins/map/routes/get-os-token.test.js +49 -0
  91. package/.server/server/plugins/map/routes/get-os-token.test.js.map +1 -0
  92. package/.server/server/plugins/map/routes/index.d.ts +1 -11
  93. package/.server/server/plugins/map/routes/index.js +60 -16
  94. package/.server/server/plugins/map/routes/index.js.map +1 -1
  95. package/.server/server/plugins/map/types.d.ts +1 -0
  96. package/.server/server/plugins/map/types.js +1 -0
  97. package/.server/server/plugins/map/types.js.map +1 -1
  98. package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
  99. package/.server/server/plugins/payment/helper.d.ts +30 -0
  100. package/.server/server/plugins/payment/helper.js +49 -0
  101. package/.server/server/plugins/payment/helper.js.map +1 -0
  102. package/.server/server/plugins/payment/helper.test.js +37 -0
  103. package/.server/server/plugins/payment/helper.test.js.map +1 -0
  104. package/.server/server/plugins/payment/service.d.ts +40 -0
  105. package/.server/server/plugins/payment/service.js +129 -0
  106. package/.server/server/plugins/payment/service.js.map +1 -0
  107. package/.server/server/plugins/payment/service.test.js +162 -0
  108. package/.server/server/plugins/payment/service.test.js.map +1 -0
  109. package/.server/server/plugins/payment/types.d.ts +172 -0
  110. package/.server/server/plugins/payment/types.js +78 -0
  111. package/.server/server/plugins/payment/types.js.map +1 -0
  112. package/.server/server/types.d.ts +3 -0
  113. package/.server/server/types.js.map +1 -1
  114. package/.server/typings/hapi/index.d.js.map +1 -1
  115. package/README.md +12 -9
  116. package/package.json +2 -2
  117. package/src/client/javascripts/location-map.js +12 -4
  118. package/src/client/stylesheets/_payment-field.scss +8 -0
  119. package/src/client/stylesheets/application.scss +2 -0
  120. package/src/index.ts +5 -1
  121. package/src/server/constants.js +1 -0
  122. package/src/server/forms/payment-test.yaml +42 -0
  123. package/src/server/forms/register-as-a-unicorn-breeder.yaml +14 -0
  124. package/src/server/plugins/engine/components/FormComponent.ts +1 -0
  125. package/src/server/plugins/engine/components/PaymentField.test.ts +611 -0
  126. package/src/server/plugins/engine/components/PaymentField.ts +367 -0
  127. package/src/server/plugins/engine/components/PaymentField.types.ts +21 -0
  128. package/src/server/plugins/engine/components/UkAddressField.ts +2 -1
  129. package/src/server/plugins/engine/components/helpers/components.ts +5 -0
  130. package/src/server/plugins/engine/components/index.ts +1 -0
  131. package/src/server/plugins/engine/configureEnginePlugin.ts +4 -2
  132. package/src/server/plugins/engine/models/SummaryViewModel.ts +8 -0
  133. package/src/server/plugins/engine/options.js +2 -1
  134. package/src/server/plugins/engine/outputFormatters/human/v1.payment.test.ts +147 -0
  135. package/src/server/plugins/engine/outputFormatters/human/v1.test.ts +105 -103
  136. package/src/server/plugins/engine/outputFormatters/human/v1.ts +61 -2
  137. package/src/server/plugins/engine/outputFormatters/machine/v2.payment.test.ts +115 -0
  138. package/src/server/plugins/engine/outputFormatters/machine/v2.ts +60 -1
  139. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +32 -6
  140. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +247 -72
  141. package/src/server/plugins/engine/pageControllers/errors.test.ts +13 -1
  142. package/src/server/plugins/engine/pageControllers/errors.ts +79 -4
  143. package/src/server/plugins/engine/pageControllers/helpers/submission.test.ts +299 -0
  144. package/src/server/plugins/engine/pageControllers/helpers/submission.ts +110 -0
  145. package/src/server/plugins/engine/plugin.ts +17 -10
  146. package/src/server/plugins/engine/routes/index.ts +17 -16
  147. package/src/server/plugins/engine/routes/payment-helper.js +39 -0
  148. package/src/server/plugins/engine/routes/payment-helper.test.js +90 -0
  149. package/src/server/plugins/engine/routes/payment.js +151 -0
  150. package/src/server/plugins/engine/routes/payment.test.js +180 -0
  151. package/src/server/plugins/engine/services/localFormsService.js +7 -0
  152. package/src/server/plugins/engine/types/schema.ts +9 -0
  153. package/src/server/plugins/engine/types.ts +25 -1
  154. package/src/server/plugins/engine/validationHelpers.ts +1 -1
  155. package/src/server/plugins/engine/views/components/paymentfield.html +42 -0
  156. package/src/server/plugins/engine/views/index.html +9 -1
  157. package/src/server/plugins/engine/views/partials/form.html +20 -5
  158. package/src/server/plugins/engine/views/summary.html +17 -1
  159. package/src/server/plugins/map/routes/get-os-token.js +41 -0
  160. package/src/server/plugins/map/routes/get-os-token.test.js +55 -0
  161. package/src/server/plugins/map/routes/index.js +70 -24
  162. package/src/server/plugins/map/types.js +1 -0
  163. package/src/server/plugins/payment/helper.js +56 -0
  164. package/src/server/plugins/payment/helper.test.js +52 -0
  165. package/src/server/plugins/payment/service.js +171 -0
  166. package/src/server/plugins/payment/service.test.js +205 -0
  167. package/src/server/plugins/payment/types.js +77 -0
  168. package/src/server/types.ts +3 -0
  169. package/src/typings/hapi/index.d.ts +1 -0
@@ -0,0 +1,611 @@
1
+ import {
2
+ ComponentType,
3
+ type FormMetadata,
4
+ type PaymentFieldComponent
5
+ } from '@defra/forms-model'
6
+
7
+ import { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'
8
+ import { PaymentField } from '~/src/server/plugins/engine/components/PaymentField.js'
9
+ import {
10
+ getAnswer,
11
+ type Field
12
+ } from '~/src/server/plugins/engine/components/helpers/components.js'
13
+ import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
14
+ import { PaymentPreAuthError } from '~/src/server/plugins/engine/pageControllers/errors.js'
15
+ import {
16
+ type FormContext,
17
+ type FormValue
18
+ } from '~/src/server/plugins/engine/types.js'
19
+ import {
20
+ type FormRequestPayload,
21
+ type FormResponseToolkit
22
+ } from '~/src/server/routes/types.js'
23
+ import { get, post, postJson } from '~/src/server/services/httpService.js'
24
+ import definition from '~/test/form/definitions/blank.js'
25
+ import { getFormData, getFormState } from '~/test/helpers/component-helpers.js'
26
+
27
+ jest.mock('~/src/server/services/httpService.ts')
28
+
29
+ describe('PaymentField', () => {
30
+ let model: FormModel
31
+
32
+ beforeEach(() => {
33
+ model = new FormModel(definition, {
34
+ basePath: 'test'
35
+ })
36
+ })
37
+
38
+ describe('Defaults', () => {
39
+ let def: PaymentFieldComponent
40
+ let collection: ComponentCollection
41
+ let field: Field
42
+
43
+ beforeEach(() => {
44
+ def = {
45
+ title: 'Example payment field',
46
+ name: 'myComponent',
47
+ type: ComponentType.PaymentField,
48
+ options: {
49
+ amount: 100,
50
+ description: 'Test payment description'
51
+ }
52
+ } satisfies PaymentFieldComponent
53
+
54
+ collection = new ComponentCollection([def], { model })
55
+ field = collection.fields[0]
56
+ })
57
+
58
+ describe('Schema', () => {
59
+ it('uses component title as label as default', () => {
60
+ const { formSchema } = collection
61
+ const { keys } = formSchema.describe()
62
+
63
+ expect(keys).toHaveProperty(
64
+ 'myComponent',
65
+ expect.objectContaining({
66
+ flags: expect.objectContaining({
67
+ label: 'Example payment field'
68
+ })
69
+ })
70
+ )
71
+ })
72
+
73
+ it('uses component name as keys', () => {
74
+ const { formSchema } = collection
75
+ const { keys } = formSchema.describe()
76
+
77
+ expect(field.keys).toEqual(['myComponent'])
78
+ expect(field.collection).toBeUndefined()
79
+
80
+ for (const key of field.keys) {
81
+ expect(keys).toHaveProperty(key)
82
+ }
83
+ })
84
+
85
+ it('is required by default', () => {
86
+ const { formSchema } = collection
87
+ const { keys } = formSchema.describe()
88
+
89
+ expect(keys).toHaveProperty(
90
+ 'myComponent',
91
+ expect.objectContaining({
92
+ keys: expect.objectContaining({
93
+ amount: expect.objectContaining({
94
+ flags: expect.objectContaining({
95
+ presence: 'required'
96
+ })
97
+ })
98
+ })
99
+ })
100
+ )
101
+ })
102
+
103
+ it('adds errors for empty value', () => {
104
+ const payment = {
105
+ paymentId: '',
106
+ reference: '',
107
+ amount: 0,
108
+ description: '',
109
+ uuid: '',
110
+ formId: '',
111
+ isLivePayment: false
112
+ }
113
+ const result = collection.validate(
114
+ getFormData(payment as unknown as FormValue)
115
+ )
116
+
117
+ const errors = result.errors ?? []
118
+
119
+ expect(errors[0]).toEqual(
120
+ expect.objectContaining({
121
+ text: 'Enter myComponent.paymentId'
122
+ })
123
+ )
124
+
125
+ expect(errors[1]).toEqual(
126
+ expect.objectContaining({
127
+ text: 'Enter myComponent.reference'
128
+ })
129
+ )
130
+
131
+ expect(errors[2]).toEqual(
132
+ expect.objectContaining({
133
+ text: 'Enter myComponent.description'
134
+ })
135
+ )
136
+
137
+ expect(errors[3]).toEqual(
138
+ expect.objectContaining({
139
+ text: 'Enter myComponent.uuid'
140
+ })
141
+ )
142
+
143
+ expect(errors[4]).toEqual(
144
+ expect.objectContaining({
145
+ text: 'Enter myComponent.formId'
146
+ })
147
+ )
148
+
149
+ expect(errors[5]).toEqual(
150
+ expect.objectContaining({
151
+ text: 'Select myComponent.preAuth'
152
+ })
153
+ )
154
+ })
155
+
156
+ it('adds errors for invalid values', () => {
157
+ const result1 = collection.validate(getFormData(['invalid']))
158
+ const result2 = collection.validate(
159
+ // @ts-expect-error - Allow invalid param for test
160
+ getFormData({ unknown: 'invalid' })
161
+ )
162
+
163
+ expect(result1.errors).toBeTruthy()
164
+ expect(result2.errors).toBeTruthy()
165
+ })
166
+ })
167
+
168
+ describe('State', () => {
169
+ const paymentForState = {
170
+ paymentId: 'payment-id',
171
+ reference: 'payment-ref',
172
+ amount: 150,
173
+ description: 'payment description',
174
+ uuid: 'ee501106-4ce1-4947-91a7-7cc1a335ccd8',
175
+ formId: 'formid',
176
+ isLivePayment: false
177
+ }
178
+ it('returns text from state', () => {
179
+ const state1 = getFormState(paymentForState as unknown as FormValue)
180
+ const state2 = getFormState(null)
181
+
182
+ const answer1 = getAnswer(field, state1)
183
+ const answer2 = getAnswer(field, state2)
184
+
185
+ expect(answer1).toBe('£150.00 - payment description')
186
+ expect(answer2).toBe('')
187
+ })
188
+ })
189
+
190
+ describe('View model', () => {
191
+ it('sets Nunjucks component defaults', () => {
192
+ const viewModel = field.getViewModel(getFormData(undefined))
193
+
194
+ expect(viewModel).toEqual(
195
+ expect.objectContaining({
196
+ label: { text: def.title },
197
+ name: 'myComponent',
198
+ id: 'myComponent',
199
+ amount: '100.00',
200
+ attributes: {},
201
+ description: 'Test payment description'
202
+ })
203
+ )
204
+ })
205
+
206
+ it('sets Nunjucks component values', () => {
207
+ const paymentForViewModel = {
208
+ paymentId: 'payment-id',
209
+ reference: 'payment-ref',
210
+ uuid: 'ee501106-4ce1-4947-91a7-7cc1a335ccd8',
211
+ formId: 'formid',
212
+ amount: 100,
213
+ description: 'Test payment description',
214
+ isLivePayment: false
215
+ } as unknown as FormValue
216
+ const viewModel = field.getViewModel(getFormData(paymentForViewModel))
217
+
218
+ expect(viewModel).toEqual(
219
+ expect.objectContaining({
220
+ label: { text: def.title },
221
+ name: 'myComponent',
222
+ id: 'myComponent',
223
+ amount: '100.00',
224
+ attributes: {},
225
+ description: 'Test payment description'
226
+ })
227
+ )
228
+ })
229
+ })
230
+
231
+ describe('AllPossibleErrors', () => {
232
+ it('should return errors', () => {
233
+ const errors = field.getAllPossibleErrors()
234
+ expect(errors.baseErrors).not.toBeEmpty()
235
+ expect(errors.advancedSettingsErrors).toBeEmpty()
236
+ })
237
+ })
238
+ })
239
+
240
+ describe('dispatcher and onSubmit', () => {
241
+ const def = {
242
+ title: 'Example payment field',
243
+ name: 'myComponent',
244
+ type: ComponentType.PaymentField,
245
+ options: {
246
+ amount: 100,
247
+ description: 'Test payment description'
248
+ }
249
+ } satisfies PaymentFieldComponent
250
+
251
+ const collection = new ComponentCollection([def], { model })
252
+ const paymentField = collection.fields[0] as PaymentField
253
+
254
+ describe('dispatcher', () => {
255
+ it('should create payment and redirect to gov pay', async () => {
256
+ const mockYarSet = jest.fn()
257
+ const mockRequest = {
258
+ server: {
259
+ plugins: {
260
+ // eslint-disable-next-line no-useless-computed-key
261
+ ['forms-engine-plugin']: {
262
+ baseUrl: 'base-url'
263
+ }
264
+ }
265
+ },
266
+ yar: {
267
+ set: mockYarSet
268
+ }
269
+ } as unknown as FormRequestPayload
270
+ const mockH = {
271
+ redirect: jest
272
+ .fn()
273
+ .mockReturnValueOnce({ code: jest.fn().mockReturnValueOnce('ok') })
274
+ } as unknown as FormResponseToolkit
275
+ const args = {
276
+ controller: {
277
+ model: {
278
+ formId: 'formid',
279
+ basePath: 'base-path',
280
+ name: 'PaymentModel'
281
+ },
282
+ getState: jest
283
+ .fn()
284
+ .mockResolvedValueOnce({ $$__referenceNumber: 'pay-ref-123' })
285
+ },
286
+ component: paymentField,
287
+ sourceUrl: 'http://localhost:3009/test-payment',
288
+ isLive: false,
289
+ isPreview: true
290
+ }
291
+ // @ts-expect-error - partial mock
292
+ jest.mocked(postJson).mockResolvedValueOnce({
293
+ payload: {
294
+ state: {
295
+ status: 'created'
296
+ },
297
+ payment_id: 'new-payment-id',
298
+ _links: {
299
+ next_url: {
300
+ href: '/next-url'
301
+ }
302
+ }
303
+ }
304
+ })
305
+
306
+ const res = await PaymentField.dispatcher(mockRequest, mockH, args)
307
+ expect(res).toBe('ok')
308
+ expect(mockYarSet).toHaveBeenCalledWith(expect.any(String), {
309
+ amount: 100,
310
+ componentName: 'myComponent',
311
+ description: 'Test payment description',
312
+ failureUrl: 'http://localhost:3009/test-payment',
313
+ formId: 'formid',
314
+ isLivePayment: false,
315
+ paymentId: 'new-payment-id',
316
+ reference: 'pay-ref-123',
317
+ returnUrl: 'base-url/base-path/summary',
318
+ uuid: expect.any(String)
319
+ })
320
+ })
321
+
322
+ it('should redirect to summary if payment is already pre-authorised', async () => {
323
+ const mockRedirectCode = jest.fn().mockReturnValueOnce('redirected')
324
+ const mockH = {
325
+ redirect: jest.fn().mockReturnValueOnce({ code: mockRedirectCode })
326
+ } as unknown as FormResponseToolkit
327
+ const mockRequest = {
328
+ server: {
329
+ plugins: {
330
+ // eslint-disable-next-line no-useless-computed-key
331
+ ['forms-engine-plugin']: {
332
+ baseUrl: 'base-url'
333
+ }
334
+ }
335
+ },
336
+ yar: {
337
+ set: jest.fn()
338
+ }
339
+ } as unknown as FormRequestPayload
340
+ const args = {
341
+ controller: {
342
+ model: {
343
+ formId: 'formid',
344
+ basePath: 'base-path',
345
+ name: 'PaymentModel'
346
+ },
347
+ getState: jest.fn().mockResolvedValueOnce({
348
+ $$__referenceNumber: 'pay-ref-123',
349
+ myComponent: {
350
+ paymentId: 'existing-payment-id',
351
+ amount: 100,
352
+ description: 'Test payment',
353
+ preAuth: {
354
+ status: 'success',
355
+ createdAt: '2026-01-29T12:00:00.000Z'
356
+ }
357
+ }
358
+ })
359
+ },
360
+ component: paymentField,
361
+ sourceUrl: 'http://localhost:3009/test-payment',
362
+ isLive: false,
363
+ isPreview: true
364
+ }
365
+
366
+ const res = await PaymentField.dispatcher(mockRequest, mockH, args)
367
+
368
+ expect(res).toBe('redirected')
369
+ expect(mockH.redirect).toHaveBeenCalledWith(
370
+ 'base-url/base-path/summary'
371
+ )
372
+ expect(mockRedirectCode).toHaveBeenCalledWith(303)
373
+ expect(postJson).not.toHaveBeenCalled()
374
+ })
375
+ })
376
+
377
+ describe('onSubmit', () => {
378
+ it('should throw if missing state', async () => {
379
+ const mockRequest = {} as unknown as FormRequestPayload
380
+
381
+ const error = await paymentField
382
+ .onSubmit(
383
+ mockRequest,
384
+ {} as FormMetadata,
385
+ { state: {} } as FormContext
386
+ )
387
+ .catch((e: unknown) => e)
388
+
389
+ expect(error).toBeInstanceOf(PaymentPreAuthError)
390
+ expect((error as PaymentPreAuthError).component).toBe(paymentField)
391
+ expect((error as PaymentPreAuthError).userMessage).toBe(
392
+ 'Complete the payment to continue'
393
+ )
394
+ })
395
+
396
+ it('should ignore if our state says payment already captured', async () => {
397
+ const mockRequest = {} as unknown as FormRequestPayload
398
+
399
+ await paymentField.onSubmit(
400
+ mockRequest,
401
+ {} as FormMetadata,
402
+ {
403
+ state: {
404
+ myComponent: {
405
+ capture: {
406
+ status: 'success'
407
+ },
408
+ paymentId: 'payment-id',
409
+ amount: 123,
410
+ description: 'Payment desc'
411
+ }
412
+ }
413
+ } as unknown as FormContext
414
+ )
415
+ expect(get).not.toHaveBeenCalled()
416
+ expect(post).not.toHaveBeenCalled()
417
+ })
418
+
419
+ it('should mark payment already captured according to gov pay', async () => {
420
+ const mockRequest = {} as unknown as FormRequestPayload
421
+ jest
422
+ .mocked(get)
423
+ // @ts-expect-error - partial mock
424
+ .mockResolvedValueOnce({
425
+ payload: { amount: 10000, state: { status: 'success' } }
426
+ })
427
+ await paymentField.onSubmit(
428
+ mockRequest,
429
+ {} as FormMetadata,
430
+ {
431
+ state: {
432
+ myComponent: {
433
+ paymentId: 'payment-id',
434
+ amount: 100,
435
+ description: 'Payment desc',
436
+ isLivePayment: false,
437
+ formId: 'formid'
438
+ }
439
+ }
440
+ } as unknown as FormContext
441
+ )
442
+ expect(get).toHaveBeenCalled()
443
+ expect(post).not.toHaveBeenCalled()
444
+ })
445
+
446
+ it('should throw if bad status', async () => {
447
+ const mockRequest = {} as unknown as FormRequestPayload
448
+ jest
449
+ .mocked(get)
450
+ // @ts-expect-error - partial mock
451
+ .mockResolvedValueOnce({
452
+ payload: { amount: 10000, state: { status: 'bad' } }
453
+ })
454
+ const error = await paymentField
455
+ .onSubmit(
456
+ mockRequest,
457
+ {} as FormMetadata,
458
+ {
459
+ state: {
460
+ myComponent: {
461
+ paymentId: 'payment-id',
462
+ amount: 100,
463
+ description: 'Payment desc',
464
+ isLivePayment: false,
465
+ formId: 'formid'
466
+ }
467
+ }
468
+ } as unknown as FormContext
469
+ )
470
+ .catch((e: unknown) => e)
471
+
472
+ expect(error).toBeInstanceOf(PaymentPreAuthError)
473
+ expect((error as PaymentPreAuthError).component).toBe(paymentField)
474
+ expect((error as PaymentPreAuthError).userMessage).toBe(
475
+ 'Your payment authorisation has expired. Please add your payment details again.'
476
+ )
477
+ })
478
+
479
+ it('should throw if error during capture', async () => {
480
+ const mockRequest = {} as unknown as FormRequestPayload
481
+ jest
482
+ .mocked(get)
483
+ // @ts-expect-error - partial mock
484
+ .mockResolvedValueOnce({
485
+ payload: { amount: 10000, state: { status: 'capturable' } }
486
+ })
487
+ // @ts-expect-error - partial mock
488
+ jest.mocked(post).mockResolvedValueOnce({ res: { statusCode: 400 } })
489
+ const error = await paymentField
490
+ .onSubmit(
491
+ mockRequest,
492
+ {} as FormMetadata,
493
+ {
494
+ state: {
495
+ myComponent: {
496
+ paymentId: 'payment-id',
497
+ amount: 123,
498
+ description: 'Payment desc',
499
+ isLivePayment: false,
500
+ formId: 'formid'
501
+ }
502
+ }
503
+ } as unknown as FormContext
504
+ )
505
+ .catch((e: unknown) => e)
506
+
507
+ expect(error).toBeInstanceOf(PaymentPreAuthError)
508
+ expect((error as PaymentPreAuthError).component).toBe(paymentField)
509
+ expect((error as PaymentPreAuthError).userMessage).toBe(
510
+ 'There was a problem and your form was not submitted. Try submitting the form again.'
511
+ )
512
+ })
513
+
514
+ it('should throw if amount mismatch', async () => {
515
+ const mockRequest = {} as unknown as FormRequestPayload
516
+ jest
517
+ .mocked(get)
518
+ // @ts-expect-error - partial mock
519
+ .mockResolvedValueOnce({
520
+ payload: { amount: 5000, state: { status: 'capturable' } }
521
+ })
522
+ // @ts-expect-error - partial mock
523
+ jest.mocked(post).mockResolvedValueOnce({ res: { statusCode: 200 } })
524
+ const error = await paymentField
525
+ .onSubmit(
526
+ mockRequest,
527
+ {} as FormMetadata,
528
+ {
529
+ state: {
530
+ myComponent: {
531
+ paymentId: 'payment-id',
532
+ amount: 123,
533
+ description: 'Payment desc',
534
+ isLivePayment: false,
535
+ formId: 'formid'
536
+ }
537
+ }
538
+ } as unknown as FormContext
539
+ )
540
+ .catch((e: unknown) => e)
541
+
542
+ expect(error).toBeInstanceOf(PaymentPreAuthError)
543
+ expect((error as PaymentPreAuthError).component).toBe(paymentField)
544
+ expect((error as PaymentPreAuthError).userMessage).toBe(
545
+ 'The pre-authorised payment amount is somehow different from that requested. Try adding payment details again.'
546
+ )
547
+ })
548
+
549
+ it('should capture payment if no errors', async () => {
550
+ const mockRequest = {} as unknown as FormRequestPayload
551
+ jest
552
+ .mocked(get)
553
+ // @ts-expect-error - partial mock
554
+ .mockResolvedValueOnce({
555
+ payload: { amount: 10000, state: { status: 'capturable' } }
556
+ })
557
+ // @ts-expect-error - partial mock
558
+ jest.mocked(post).mockResolvedValueOnce({ res: { statusCode: 200 } })
559
+ await paymentField.onSubmit(
560
+ mockRequest,
561
+ {} as FormMetadata,
562
+ {
563
+ state: {
564
+ myComponent: {
565
+ paymentId: 'payment-id',
566
+ amount: 123,
567
+ description: 'Payment desc',
568
+ isLivePayment: false,
569
+ formId: 'formid'
570
+ }
571
+ }
572
+ } as unknown as FormContext
573
+ )
574
+ expect(get).toHaveBeenCalled()
575
+ expect(post).toHaveBeenCalled()
576
+ })
577
+ })
578
+
579
+ describe('getFormValue', () => {
580
+ it('should return undefined', () => {
581
+ expect(paymentField.getFormValue({})).toBeUndefined()
582
+ })
583
+ it('should return value', () => {
584
+ const payment = {
585
+ paymentId: 'payment-id',
586
+ amount: 123,
587
+ description: 'Payment desc',
588
+ isLivePayment: false,
589
+ formId: 'formid'
590
+ }
591
+ expect(paymentField.getFormValue(payment)).toEqual(payment)
592
+ })
593
+ })
594
+
595
+ describe('isState', () => {
596
+ it('should return false if not valid state', () => {
597
+ expect(paymentField.isState({})).toBe(false)
598
+ })
599
+ it('should return value', () => {
600
+ const payment = {
601
+ paymentId: 'payment-id',
602
+ amount: 123,
603
+ description: 'Payment desc',
604
+ isLivePayment: false,
605
+ formId: 'formid'
606
+ }
607
+ expect(paymentField.isState(payment)).toBe(true)
608
+ })
609
+ })
610
+ })
611
+ })