@defra/forms-engine-plugin 4.0.61 → 4.1.0
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.
- package/.server/server/plugins/engine/components/PaymentField.d.ts +3 -17
- package/.server/server/plugins/engine/components/PaymentField.js +12 -3
- package/.server/server/plugins/engine/components/PaymentField.js.map +1 -1
- package/.server/server/plugins/engine/helpers.d.ts +1 -0
- package/.server/server/plugins/engine/plugin.js +3 -1
- package/.server/server/plugins/engine/plugin.js.map +1 -1
- package/.server/server/plugins/engine/routes/payment-helper.d.ts +3 -1
- package/.server/server/plugins/engine/routes/payment-helper.js +5 -5
- package/.server/server/plugins/engine/routes/payment-helper.js.map +1 -1
- package/.server/server/plugins/engine/routes/payment-helper.test.js +4 -2
- package/.server/server/plugins/engine/routes/payment-helper.test.js.map +1 -1
- package/.server/server/plugins/engine/routes/payment.js +7 -1
- package/.server/server/plugins/engine/routes/payment.js.map +1 -1
- package/.server/server/plugins/engine/services/formsService.d.ts +7 -0
- package/.server/server/plugins/engine/services/formsService.js +11 -0
- package/.server/server/plugins/engine/services/formsService.js.map +1 -1
- package/.server/server/plugins/engine/services/formsService.test.js +4 -1
- package/.server/server/plugins/engine/services/formsService.test.js.map +1 -1
- package/.server/server/plugins/engine/types.d.ts +5 -2
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/.server/server/plugins/payment/helper.d.ts +4 -11
- package/.server/server/plugins/payment/helper.js +11 -19
- package/.server/server/plugins/payment/helper.js.map +1 -1
- package/.server/server/plugins/payment/helper.test.js +1 -22
- package/.server/server/plugins/payment/helper.test.js.map +1 -1
- package/.server/server/plugins/payment/service.d.ts +3 -3
- package/.server/server/plugins/payment/service.js +25 -15
- package/.server/server/plugins/payment/service.js.map +1 -1
- package/.server/server/plugins/payment/service.test.js +8 -6
- package/.server/server/plugins/payment/service.test.js.map +1 -1
- package/.server/server/types.d.ts +1 -0
- package/.server/server/types.js.map +1 -1
- package/.server/server/utils/file-form-service.js +10 -0
- package/.server/server/utils/file-form-service.js.map +1 -1
- package/.server/server/utils/file-form-service.test.js +5 -0
- package/.server/server/utils/file-form-service.test.js.map +1 -1
- package/.server/typings/hapi/index.d.js.map +1 -1
- package/package.json +1 -1
- package/src/server/plugins/engine/beta/form-context.test.ts +2 -1
- package/src/server/plugins/engine/components/PaymentField.test.ts +139 -5
- package/src/server/plugins/engine/components/PaymentField.ts +29 -21
- package/src/server/plugins/engine/plugin.ts +3 -1
- package/src/server/plugins/engine/routes/payment-helper.js +9 -5
- package/src/server/plugins/engine/routes/payment-helper.test.js +4 -1
- package/src/server/plugins/engine/routes/payment.js +8 -1
- package/src/server/plugins/engine/services/formsService.js +11 -0
- package/src/server/plugins/engine/services/formsService.test.js +6 -1
- package/src/server/plugins/engine/types.ts +6 -1
- package/src/server/plugins/payment/helper.js +15 -23
- package/src/server/plugins/payment/helper.test.js +1 -32
- package/src/server/plugins/payment/service.js +42 -28
- package/src/server/plugins/payment/service.test.js +22 -24
- package/src/server/types.ts +1 -0
- package/src/server/utils/file-form-service.js +11 -0
- package/src/server/utils/file-form-service.test.js +13 -0
- package/src/typings/hapi/index.d.ts +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-form-service.test.js","names":["join","FormStatus","FileFormService","describe","service","beforeEach","now","Date","user","id","displayName","author","createdAt","createdBy","updatedAt","updatedBy","metadata","organisation","teamName","teamEmail","submissionGuidance","notificationEmail","live","addForm","import","meta","dirname","title","slug","it","getFormMetadata","expect","toBe","toThrow","getFormMetadataById","form","getFormDefinition","name","startPage","interfaceImpl","toFormsService","res1","res2","res3","Draft","readForm","rejects"],"sources":["../../../src/server/utils/file-form-service.test.js"],"sourcesContent":["import { join } from 'node:path'\n\nimport { FormStatus } from '~/src/server/routes/types.js'\nimport { FileFormService } from '~/src/server/utils/file-form-service.js'\n\ndescribe('File-form-service', () => {\n /** @type {FileFormService} */\n let service\n beforeEach(async () => {\n const now = new Date()\n const user = { id: 'user', displayName: 'Username' }\n const author = {\n createdAt: now,\n createdBy: user,\n updatedAt: now,\n updatedBy: user\n }\n service = new FileFormService()\n const metadata = {\n organisation: 'Defra',\n teamName: 'Team name',\n teamEmail: 'team@defra.gov.uk',\n submissionGuidance: \"Thanks for your submission, we'll be in touch\",\n notificationEmail: 'email@domain.com',\n ...author,\n live: author\n }\n await service.addForm(\n `${join(import.meta.dirname, '../../../test/form/definitions')}/components.json`,\n {\n ...metadata,\n id: '95e92559-968d-44ae-8666-2b1ad3dffd31',\n title: 'Form test',\n slug: 'form-test'\n }\n )\n })\n\n describe('metadata by slug', () => {\n it('should get form metadata by slug', () => {\n const meta = service.getFormMetadata('form-test')\n expect(meta.id).toBe('95e92559-968d-44ae-8666-2b1ad3dffd31')\n expect(meta.title).toBe('Form test')\n })\n\n it('should throw if not found', () => {\n expect(() => service.getFormMetadata('form-test-missing')).toThrow(\n \"Form metadata 'form-test-missing' not found\"\n )\n })\n })\n\n describe('metadata by id', () => {\n it('should get form metadata by id', () => {\n const meta = service.getFormMetadataById(\n '95e92559-968d-44ae-8666-2b1ad3dffd31'\n )\n expect(meta.id).toBe('95e92559-968d-44ae-8666-2b1ad3dffd31')\n expect(meta.title).toBe('Form test')\n })\n\n it('should throw if not found', () => {\n expect(() => service.getFormMetadataById('id-missing')).toThrow(\n \"Form metadata id 'id-missing' not found\"\n )\n })\n })\n\n describe('definition by id', () => {\n it('should get form definition by id', () => {\n const form = service.getFormDefinition(\n '95e92559-968d-44ae-8666-2b1ad3dffd31'\n )\n expect(form.name).toBe('All components')\n expect(form.startPage).toBe('/all-components')\n })\n\n it('should throw if not found', () => {\n expect(() => service.getFormDefinition('id-missing')).toThrow(\n \"Form definition 'id-missing' not found\"\n )\n })\n })\n\n describe('toFormsService', () => {\n it('should create interface', async () => {\n const interfaceImpl = service.toFormsService()\n const res1 = await interfaceImpl.getFormMetadata('form-test')\n expect(res1.id).toBe('95e92559-968d-44ae-8666-2b1ad3dffd31')\n expect(res1.title).toBe('Form test')\n\n const res2 = await interfaceImpl.getFormMetadataById(\n '95e92559-968d-44ae-8666-2b1ad3dffd31'\n )\n expect(res2.id).toBe('95e92559-968d-44ae-8666-2b1ad3dffd31')\n expect(res2.title).toBe('Form test')\n\n const res3 = await interfaceImpl.getFormDefinition(\n '95e92559-968d-44ae-8666-2b1ad3dffd31',\n FormStatus.Draft\n )\n expect(res3?.name).toBe('All components')\n expect(res3?.startPage).toBe('/all-components')\n })\n })\n\n describe('readForm', () => {\n it('should throw if invalid extension', async () => {\n await expect(\n service.readForm('/some-folder/some-file.bad')\n ).rejects.toThrow(\"Invalid file extension '.bad'\")\n })\n })\n})\n"],"mappings":"AAAA,SAASA,IAAI,QAAQ,WAAW;AAEhC,SAASC,UAAU;AACnB,SAASC,eAAe;AAExBC,QAAQ,CAAC,mBAAmB,EAAE,MAAM;EAClC;EACA,IAAIC,OAAO;EACXC,UAAU,CAAC,YAAY;IACrB,MAAMC,GAAG,GAAG,IAAIC,IAAI,CAAC,CAAC;IACtB,MAAMC,IAAI,GAAG;MAAEC,EAAE,EAAE,MAAM;MAAEC,WAAW,EAAE;IAAW,CAAC;IACpD,MAAMC,MAAM,GAAG;MACbC,SAAS,EAAEN,GAAG;MACdO,SAAS,EAAEL,IAAI;MACfM,SAAS,EAAER,GAAG;MACdS,SAAS,EAAEP;IACb,CAAC;IACDJ,OAAO,GAAG,IAAIF,eAAe,CAAC,CAAC;IAC/B,MAAMc,QAAQ,GAAG;MACfC,YAAY,EAAE,OAAO;MACrBC,QAAQ,EAAE,WAAW;MACrBC,SAAS,EAAE,mBAAmB;MAC9BC,kBAAkB,EAAE,+CAA+C;MACnEC,iBAAiB,EAAE,kBAAkB;MACrC,GAAGV,MAAM;MACTW,IAAI,EAAEX;IACR,CAAC;IACD,MAAMP,OAAO,CAACmB,OAAO,CACnB,GAAGvB,IAAI,CAACwB,MAAM,CAACC,IAAI,CAACC,OAAO,EAAE,gCAAgC,CAAC,kBAAkB,EAChF;MACE,GAAGV,QAAQ;MACXP,EAAE,EAAE,sCAAsC;MAC1CkB,KAAK,EAAE,WAAW;MAClBC,IAAI,EAAE;IACR,CACF,CAAC;EACH,CAAC,CAAC;EAEFzB,QAAQ,CAAC,kBAAkB,EAAE,MAAM;IACjC0B,EAAE,CAAC,kCAAkC,EAAE,MAAM;MAC3C,MAAMJ,IAAI,GAAGrB,OAAO,CAAC0B,eAAe,CAAC,WAAW,CAAC;MACjDC,MAAM,CAACN,IAAI,CAAChB,EAAE,CAAC,CAACuB,IAAI,CAAC,sCAAsC,CAAC;MAC5DD,MAAM,CAACN,IAAI,CAACE,KAAK,CAAC,CAACK,IAAI,CAAC,WAAW,CAAC;IACtC,CAAC,CAAC;IAEFH,EAAE,CAAC,2BAA2B,EAAE,MAAM;MACpCE,MAAM,CAAC,MAAM3B,OAAO,CAAC0B,eAAe,CAAC,mBAAmB,CAAC,CAAC,CAACG,OAAO,CAChE,6CACF,CAAC;IACH,CAAC,CAAC;EACJ,CAAC,CAAC;EAEF9B,QAAQ,CAAC,gBAAgB,EAAE,MAAM;IAC/B0B,EAAE,CAAC,gCAAgC,EAAE,MAAM;MACzC,MAAMJ,IAAI,GAAGrB,OAAO,CAAC8B,mBAAmB,CACtC,sCACF,CAAC;MACDH,MAAM,CAACN,IAAI,CAAChB,EAAE,CAAC,CAACuB,IAAI,CAAC,sCAAsC,CAAC;MAC5DD,MAAM,CAACN,IAAI,CAACE,KAAK,CAAC,CAACK,IAAI,CAAC,WAAW,CAAC;IACtC,CAAC,CAAC;IAEFH,EAAE,CAAC,2BAA2B,EAAE,MAAM;MACpCE,MAAM,CAAC,MAAM3B,OAAO,CAAC8B,mBAAmB,CAAC,YAAY,CAAC,CAAC,CAACD,OAAO,CAC7D,yCACF,CAAC;IACH,CAAC,CAAC;EACJ,CAAC,CAAC;EAEF9B,QAAQ,CAAC,kBAAkB,EAAE,MAAM;IACjC0B,EAAE,CAAC,kCAAkC,EAAE,MAAM;MAC3C,MAAMM,IAAI,GAAG/B,OAAO,CAACgC,iBAAiB,CACpC,sCACF,CAAC;MACDL,MAAM,CAACI,IAAI,CAACE,IAAI,CAAC,CAACL,IAAI,CAAC,gBAAgB,CAAC;MACxCD,MAAM,CAACI,IAAI,CAACG,SAAS,CAAC,CAACN,IAAI,CAAC,iBAAiB,CAAC;IAChD,CAAC,CAAC;IAEFH,EAAE,CAAC,2BAA2B,EAAE,MAAM;MACpCE,MAAM,CAAC,MAAM3B,OAAO,CAACgC,iBAAiB,CAAC,YAAY,CAAC,CAAC,CAACH,OAAO,CAC3D,wCACF,CAAC;IACH,CAAC,CAAC;EACJ,CAAC,CAAC;EAEF9B,QAAQ,CAAC,gBAAgB,EAAE,MAAM;IAC/B0B,EAAE,CAAC,yBAAyB,EAAE,YAAY;MACxC,MAAMU,aAAa,GAAGnC,OAAO,CAACoC,cAAc,CAAC,CAAC;MAC9C,MAAMC,IAAI,GAAG,MAAMF,aAAa,CAACT,eAAe,CAAC,WAAW,CAAC;MAC7DC,MAAM,CAACU,IAAI,CAAChC,EAAE,CAAC,CAACuB,IAAI,CAAC,sCAAsC,CAAC;MAC5DD,MAAM,CAACU,IAAI,CAACd,KAAK,CAAC,CAACK,IAAI,CAAC,WAAW,CAAC;MAEpC,MAAMU,IAAI,GAAG,MAAMH,aAAa,CAACL,mBAAmB,CAClD,sCACF,CAAC;MACDH,MAAM,CAACW,IAAI,CAACjC,EAAE,CAAC,CAACuB,IAAI,CAAC,sCAAsC,CAAC;MAC5DD,MAAM,CAACW,IAAI,CAACf,KAAK,CAAC,CAACK,IAAI,CAAC,WAAW,CAAC;MAEpC,MAAMW,IAAI,GAAG,MAAMJ,aAAa,CAACH,iBAAiB,CAChD,sCAAsC,EACtCnC,UAAU,CAAC2C,KACb,CAAC;MACDb,MAAM,CAACY,IAAI,EAAEN,IAAI,CAAC,CAACL,IAAI,CAAC,gBAAgB,CAAC;MACzCD,MAAM,CAACY,IAAI,EAAEL,SAAS,CAAC,CAACN,IAAI,CAAC,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"file-form-service.test.js","names":["join","FormStatus","FileFormService","describe","service","beforeEach","now","Date","user","id","displayName","author","createdAt","createdBy","updatedAt","updatedBy","metadata","organisation","teamName","teamEmail","submissionGuidance","notificationEmail","live","addForm","import","meta","dirname","title","slug","it","getFormMetadata","expect","toBe","toThrow","getFormMetadataById","form","getFormDefinition","name","startPage","interfaceImpl","toFormsService","res1","res2","res3","Draft","res4","getFormSecret","process","env","PAYMENT_PROVIDER_API_KEY_TEST","res5","readForm","rejects"],"sources":["../../../src/server/utils/file-form-service.test.js"],"sourcesContent":["import { join } from 'node:path'\n\nimport { FormStatus } from '~/src/server/routes/types.js'\nimport { FileFormService } from '~/src/server/utils/file-form-service.js'\n\ndescribe('File-form-service', () => {\n /** @type {FileFormService} */\n let service\n beforeEach(async () => {\n const now = new Date()\n const user = { id: 'user', displayName: 'Username' }\n const author = {\n createdAt: now,\n createdBy: user,\n updatedAt: now,\n updatedBy: user\n }\n service = new FileFormService()\n const metadata = {\n organisation: 'Defra',\n teamName: 'Team name',\n teamEmail: 'team@defra.gov.uk',\n submissionGuidance: \"Thanks for your submission, we'll be in touch\",\n notificationEmail: 'email@domain.com',\n ...author,\n live: author\n }\n await service.addForm(\n `${join(import.meta.dirname, '../../../test/form/definitions')}/components.json`,\n {\n ...metadata,\n id: '95e92559-968d-44ae-8666-2b1ad3dffd31',\n title: 'Form test',\n slug: 'form-test'\n }\n )\n })\n\n describe('metadata by slug', () => {\n it('should get form metadata by slug', () => {\n const meta = service.getFormMetadata('form-test')\n expect(meta.id).toBe('95e92559-968d-44ae-8666-2b1ad3dffd31')\n expect(meta.title).toBe('Form test')\n })\n\n it('should throw if not found', () => {\n expect(() => service.getFormMetadata('form-test-missing')).toThrow(\n \"Form metadata 'form-test-missing' not found\"\n )\n })\n })\n\n describe('metadata by id', () => {\n it('should get form metadata by id', () => {\n const meta = service.getFormMetadataById(\n '95e92559-968d-44ae-8666-2b1ad3dffd31'\n )\n expect(meta.id).toBe('95e92559-968d-44ae-8666-2b1ad3dffd31')\n expect(meta.title).toBe('Form test')\n })\n\n it('should throw if not found', () => {\n expect(() => service.getFormMetadataById('id-missing')).toThrow(\n \"Form metadata id 'id-missing' not found\"\n )\n })\n })\n\n describe('definition by id', () => {\n it('should get form definition by id', () => {\n const form = service.getFormDefinition(\n '95e92559-968d-44ae-8666-2b1ad3dffd31'\n )\n expect(form.name).toBe('All components')\n expect(form.startPage).toBe('/all-components')\n })\n\n it('should throw if not found', () => {\n expect(() => service.getFormDefinition('id-missing')).toThrow(\n \"Form definition 'id-missing' not found\"\n )\n })\n })\n\n describe('toFormsService', () => {\n it('should create interface', async () => {\n const interfaceImpl = service.toFormsService()\n const res1 = await interfaceImpl.getFormMetadata('form-test')\n expect(res1.id).toBe('95e92559-968d-44ae-8666-2b1ad3dffd31')\n expect(res1.title).toBe('Form test')\n\n const res2 = await interfaceImpl.getFormMetadataById(\n '95e92559-968d-44ae-8666-2b1ad3dffd31'\n )\n expect(res2.id).toBe('95e92559-968d-44ae-8666-2b1ad3dffd31')\n expect(res2.title).toBe('Form test')\n\n const res3 = await interfaceImpl.getFormDefinition(\n '95e92559-968d-44ae-8666-2b1ad3dffd31',\n FormStatus.Draft\n )\n expect(res3?.name).toBe('All components')\n expect(res3?.startPage).toBe('/all-components')\n\n const res4 = await interfaceImpl.getFormSecret(\n '95e92559-968d-44ae-8666-2b1ad3dffd31',\n 'my-secret-name'\n )\n expect(res4).toBe('test-api-key')\n\n delete process.env.PAYMENT_PROVIDER_API_KEY_TEST\n const res5 = await interfaceImpl.getFormSecret(\n '95e92559-968d-44ae-8666-2b1ad3dffd31',\n 'my-secret-name'\n )\n expect(res5).toBe('')\n })\n })\n\n describe('readForm', () => {\n it('should throw if invalid extension', async () => {\n await expect(\n service.readForm('/some-folder/some-file.bad')\n ).rejects.toThrow(\"Invalid file extension '.bad'\")\n })\n })\n})\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.js","names":[],"sources":["../../../src/typings/hapi/index.d.ts"],"sourcesContent":["import { type Plugin } from '@hapi/hapi'\nimport { type ServerYar, type Yar } from '@hapi/yar'\nimport { type Logger } from 'pino'\n\nimport {\n type COMPONENT_STATE_ERROR,\n type EXTERNAL_STATE_APPENDAGE,\n type EXTERNAL_STATE_PAYLOAD\n} from '~/src/server/constants.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {\n type AnyFormRequest,\n type FormSubmissionError,\n type PluginOptions\n} from '~/src/server/plugins/engine/types.ts'\nimport { type CacheService } from '~/src/server/services/index.js'\n\ndeclare module '@hapi/yar' {\n interface YarFlashes {\n [EXTERNAL_STATE_APPENDAGE]: object\n [EXTERNAL_STATE_PAYLOAD]: object\n [COMPONENT_STATE_ERROR]: string\n [key: string]: { errors: FormSubmissionError[] }\n }\n}\n\ndeclare module '@hapi/hapi' {\n // Here we are decorating Hapi interface types with\n // props from plugins which doesn't export @types\n interface PluginProperties {\n crumb: {\n generate?: (request: AnyRequest) => string\n }\n 'forms-engine-plugin': {\n baseLayoutPath: string\n cacheService: CacheService\n viewContext?: (\n request: AnyFormRequest | null\n ) => Record<string, unknown> | Promise<Record<string, unknown>>\n saveAndExit?: PluginOptions['saveAndExit']\n baseUrl: string\n }\n }\n\n interface Request {\n logger: Logger\n yar: Yar\n }\n\n interface RequestApplicationState {\n model?: FormModel\n }\n\n interface Server {\n logger: Logger\n yar: ServerYar\n }\n\n interface ServerApplicationState {\n model?: FormModel\n models: Map<string, { model: FormModel; updatedAt: Date }>\n }\n}\n\ndeclare module 'blankie' {\n declare const blankie: {\n plugin: Plugin<Record<string, boolean | string | string[]>>\n }\n\n export = blankie\n}\n\ndeclare module 'blipp' {\n declare const blipp: {\n plugin: Plugin\n }\n\n export = blipp\n}\n\ndeclare module 'hapi-pulse' {\n declare const hapiPulse: {\n plugin: Plugin<{\n timeout: number\n }>\n }\n\n export = hapiPulse\n}\n"],"mappings":"","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"index.d.js","names":[],"sources":["../../../src/typings/hapi/index.d.ts"],"sourcesContent":["import { type Plugin } from '@hapi/hapi'\nimport { type ServerYar, type Yar } from '@hapi/yar'\nimport { type Logger } from 'pino'\n\nimport {\n type COMPONENT_STATE_ERROR,\n type EXTERNAL_STATE_APPENDAGE,\n type EXTERNAL_STATE_PAYLOAD\n} from '~/src/server/constants.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {\n type AnyFormRequest,\n type FormSubmissionError,\n type PluginOptions\n} from '~/src/server/plugins/engine/types.ts'\nimport { type CacheService } from '~/src/server/services/index.js'\n\ndeclare module '@hapi/yar' {\n interface YarFlashes {\n [EXTERNAL_STATE_APPENDAGE]: object\n [EXTERNAL_STATE_PAYLOAD]: object\n [COMPONENT_STATE_ERROR]: string\n [key: string]: { errors: FormSubmissionError[] }\n }\n}\n\ndeclare module '@hapi/hapi' {\n // Here we are decorating Hapi interface types with\n // props from plugins which doesn't export @types\n interface PluginProperties {\n crumb: {\n generate?: (request: AnyRequest) => string\n }\n 'forms-engine-plugin': {\n baseLayoutPath: string\n cacheService: CacheService\n viewContext?: (\n request: AnyFormRequest | null\n ) => Record<string, unknown> | Promise<Record<string, unknown>>\n saveAndExit?: PluginOptions['saveAndExit']\n baseUrl: string\n services: PluginOptions['services']\n }\n }\n\n interface Request {\n logger: Logger\n yar: Yar\n }\n\n interface RequestApplicationState {\n model?: FormModel\n }\n\n interface Server {\n logger: Logger\n yar: ServerYar\n }\n\n interface ServerApplicationState {\n model?: FormModel\n models: Map<string, { model: FormModel; updatedAt: Date }>\n }\n}\n\ndeclare module 'blankie' {\n declare const blankie: {\n plugin: Plugin<Record<string, boolean | string | string[]>>\n }\n\n export = blankie\n}\n\ndeclare module 'blipp' {\n declare const blipp: {\n plugin: Plugin\n }\n\n export = blipp\n}\n\ndeclare module 'hapi-pulse' {\n declare const hapiPulse: {\n plugin: Plugin<{\n timeout: number\n }>\n }\n\n export = hapiPulse\n}\n"],"mappings":"","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -150,7 +150,8 @@ describe('getFormModel helper', () => {
|
|
|
150
150
|
formsService: {
|
|
151
151
|
getFormMetadata: jest.fn().mockResolvedValue(metadata),
|
|
152
152
|
getFormMetadataById: jest.fn(),
|
|
153
|
-
getFormDefinition: jest.fn().mockResolvedValue(definition)
|
|
153
|
+
getFormDefinition: jest.fn().mockResolvedValue(definition),
|
|
154
|
+
getFormSecret: jest.fn()
|
|
154
155
|
},
|
|
155
156
|
formSubmissionService: {
|
|
156
157
|
persistFiles: jest.fn(),
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
type FormMetadata,
|
|
4
4
|
type PaymentFieldComponent
|
|
5
5
|
} from '@defra/forms-model'
|
|
6
|
+
import { StatusCodes } from 'http-status-codes'
|
|
6
7
|
|
|
7
8
|
import { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'
|
|
8
9
|
import { PaymentField } from '~/src/server/plugins/engine/components/PaymentField.js'
|
|
@@ -14,18 +15,26 @@ import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
|
|
|
14
15
|
import { PaymentPreAuthError } from '~/src/server/plugins/engine/pageControllers/errors.js'
|
|
15
16
|
import {
|
|
16
17
|
type FormContext,
|
|
17
|
-
type FormValue
|
|
18
|
+
type FormValue,
|
|
19
|
+
type PaymentExternalArgs
|
|
18
20
|
} from '~/src/server/plugins/engine/types.js'
|
|
19
21
|
import {
|
|
20
22
|
type FormRequestPayload,
|
|
21
23
|
type FormResponseToolkit
|
|
22
24
|
} from '~/src/server/routes/types.js'
|
|
23
25
|
import { get, post, postJson } from '~/src/server/services/httpService.js'
|
|
26
|
+
import { type Services } from '~/src/server/types.js'
|
|
24
27
|
import definition from '~/test/form/definitions/blank.js'
|
|
25
28
|
import { getFormData, getFormState } from '~/test/helpers/component-helpers.js'
|
|
26
29
|
|
|
27
30
|
jest.mock('~/src/server/services/httpService.ts')
|
|
28
31
|
|
|
32
|
+
const mockServices = {
|
|
33
|
+
formsService: {
|
|
34
|
+
getFormSecret: () => 'secret-value'
|
|
35
|
+
}
|
|
36
|
+
} as unknown as Services
|
|
37
|
+
|
|
29
38
|
describe('PaymentField', () => {
|
|
30
39
|
let model: FormModel
|
|
31
40
|
|
|
@@ -250,6 +259,7 @@ describe('PaymentField', () => {
|
|
|
250
259
|
|
|
251
260
|
const collection = new ComponentCollection([def], { model })
|
|
252
261
|
const paymentField = collection.fields[0] as PaymentField
|
|
262
|
+
paymentField.model = { services: mockServices } as unknown as FormModel
|
|
253
263
|
|
|
254
264
|
describe('dispatcher', () => {
|
|
255
265
|
it('should create payment and redirect to gov pay', async () => {
|
|
@@ -277,7 +287,8 @@ describe('PaymentField', () => {
|
|
|
277
287
|
model: {
|
|
278
288
|
formId: 'formid',
|
|
279
289
|
basePath: 'base-path',
|
|
280
|
-
name: 'PaymentModel'
|
|
290
|
+
name: 'PaymentModel',
|
|
291
|
+
services: mockServices
|
|
281
292
|
},
|
|
282
293
|
getState: jest
|
|
283
294
|
.fn()
|
|
@@ -287,7 +298,7 @@ describe('PaymentField', () => {
|
|
|
287
298
|
sourceUrl: 'http://localhost:3009/test-payment',
|
|
288
299
|
isLive: false,
|
|
289
300
|
isPreview: true
|
|
290
|
-
}
|
|
301
|
+
} as unknown as PaymentExternalArgs
|
|
291
302
|
// @ts-expect-error - partial mock
|
|
292
303
|
jest.mocked(postJson).mockResolvedValueOnce({
|
|
293
304
|
payload: {
|
|
@@ -342,7 +353,8 @@ describe('PaymentField', () => {
|
|
|
342
353
|
model: {
|
|
343
354
|
formId: 'formid',
|
|
344
355
|
basePath: 'base-path',
|
|
345
|
-
name: 'PaymentModel'
|
|
356
|
+
name: 'PaymentModel',
|
|
357
|
+
services: mockServices
|
|
346
358
|
},
|
|
347
359
|
getState: jest.fn().mockResolvedValueOnce({
|
|
348
360
|
$$__referenceNumber: 'pay-ref-123',
|
|
@@ -361,7 +373,7 @@ describe('PaymentField', () => {
|
|
|
361
373
|
sourceUrl: 'http://localhost:3009/test-payment',
|
|
362
374
|
isLive: false,
|
|
363
375
|
isPreview: true
|
|
364
|
-
}
|
|
376
|
+
} as unknown as PaymentExternalArgs
|
|
365
377
|
|
|
366
378
|
const res = await PaymentField.dispatcher(mockRequest, mockH, args)
|
|
367
379
|
|
|
@@ -372,6 +384,128 @@ describe('PaymentField', () => {
|
|
|
372
384
|
expect(mockRedirectCode).toHaveBeenCalledWith(303)
|
|
373
385
|
expect(postJson).not.toHaveBeenCalled()
|
|
374
386
|
})
|
|
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
|
+
})
|
|
375
509
|
})
|
|
376
510
|
|
|
377
511
|
describe('onSubmit', () => {
|
|
@@ -7,16 +7,19 @@ import {
|
|
|
7
7
|
import { StatusCodes } from 'http-status-codes'
|
|
8
8
|
import joi, { type ObjectSchema } from 'joi'
|
|
9
9
|
|
|
10
|
+
import { COMPONENT_STATE_ERROR } from '~/src/server/constants.js'
|
|
10
11
|
import { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'
|
|
11
12
|
import { type PaymentState } from '~/src/server/plugins/engine/components/PaymentField.types.js'
|
|
12
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
createError,
|
|
15
|
+
getPluginOptions
|
|
16
|
+
} from '~/src/server/plugins/engine/helpers.js'
|
|
13
17
|
import {
|
|
14
18
|
PaymentErrorTypes,
|
|
15
19
|
PaymentPreAuthError,
|
|
16
20
|
PaymentSubmissionError
|
|
17
21
|
} from '~/src/server/plugins/engine/pageControllers/errors.js'
|
|
18
22
|
import {
|
|
19
|
-
type AnyFormRequest,
|
|
20
23
|
type FormContext,
|
|
21
24
|
type FormRequestPayload,
|
|
22
25
|
type FormResponseToolkit
|
|
@@ -27,7 +30,8 @@ import {
|
|
|
27
30
|
type FormState,
|
|
28
31
|
type FormStateValue,
|
|
29
32
|
type FormSubmissionError,
|
|
30
|
-
type FormSubmissionState
|
|
33
|
+
type FormSubmissionState,
|
|
34
|
+
type PaymentExternalArgs
|
|
31
35
|
} from '~/src/server/plugins/engine/types.js'
|
|
32
36
|
import {
|
|
33
37
|
createPaymentService,
|
|
@@ -186,7 +190,7 @@ export class PaymentField extends FormComponent {
|
|
|
186
190
|
static async dispatcher(
|
|
187
191
|
request: FormRequestPayload,
|
|
188
192
|
h: FormResponseToolkit,
|
|
189
|
-
args:
|
|
193
|
+
args: PaymentExternalArgs
|
|
190
194
|
): Promise<unknown> {
|
|
191
195
|
const { options, name: componentName } = args.component
|
|
192
196
|
const { model } = args.controller
|
|
@@ -205,7 +209,12 @@ export class PaymentField extends FormComponent {
|
|
|
205
209
|
|
|
206
210
|
const isLivePayment = args.isLive && !args.isPreview
|
|
207
211
|
const formId = args.controller.model.formId
|
|
208
|
-
const
|
|
212
|
+
const formsService = model.services.formsService
|
|
213
|
+
const paymentService = await createPaymentService(
|
|
214
|
+
isLivePayment,
|
|
215
|
+
formId,
|
|
216
|
+
formsService
|
|
217
|
+
)
|
|
209
218
|
|
|
210
219
|
const uuid = randomUUID()
|
|
211
220
|
|
|
@@ -229,6 +238,15 @@ export class PaymentField extends FormComponent {
|
|
|
229
238
|
{ formId, slug }
|
|
230
239
|
)
|
|
231
240
|
|
|
241
|
+
if (!payment) {
|
|
242
|
+
const message = isLivePayment
|
|
243
|
+
? '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.'
|
|
244
|
+
: 'Add a valid test API key before you can preview the payment journey.'
|
|
245
|
+
const govukError = createError(componentName, message)
|
|
246
|
+
request.yar.flash(COMPONENT_STATE_ERROR, govukError, true)
|
|
247
|
+
return h.redirect(request.url.href).code(StatusCodes.SEE_OTHER)
|
|
248
|
+
}
|
|
249
|
+
|
|
232
250
|
const sessionData: PaymentSessionData = {
|
|
233
251
|
uuid,
|
|
234
252
|
formId,
|
|
@@ -272,7 +290,12 @@ export class PaymentField extends FormComponent {
|
|
|
272
290
|
}
|
|
273
291
|
|
|
274
292
|
const { paymentId, isLivePayment, formId } = paymentState
|
|
275
|
-
const
|
|
293
|
+
const formsService = this.model.services.formsService
|
|
294
|
+
const paymentService = await createPaymentService(
|
|
295
|
+
isLivePayment,
|
|
296
|
+
formId,
|
|
297
|
+
formsService
|
|
298
|
+
)
|
|
276
299
|
|
|
277
300
|
/**
|
|
278
301
|
* @see https://docs.payments.service.gov.uk/api_reference/#payment-status-lifecycle
|
|
@@ -343,21 +366,6 @@ export class PaymentField extends FormComponent {
|
|
|
343
366
|
}
|
|
344
367
|
}
|
|
345
368
|
|
|
346
|
-
export interface PaymentDispatcherArgs {
|
|
347
|
-
controller: {
|
|
348
|
-
model: {
|
|
349
|
-
formId: string
|
|
350
|
-
basePath: string
|
|
351
|
-
name: string
|
|
352
|
-
}
|
|
353
|
-
getState: (request: AnyFormRequest) => Promise<FormSubmissionState>
|
|
354
|
-
}
|
|
355
|
-
component: PaymentField
|
|
356
|
-
sourceUrl: string
|
|
357
|
-
isLive: boolean
|
|
358
|
-
isPreview: boolean
|
|
359
|
-
}
|
|
360
|
-
|
|
361
369
|
/**
|
|
362
370
|
* Session data stored when dispatching to GOV.UK Pay
|
|
363
371
|
*/
|
|
@@ -41,7 +41,8 @@ export const plugin = {
|
|
|
41
41
|
onRequest,
|
|
42
42
|
ordnanceSurveyApiKey,
|
|
43
43
|
baseUrl,
|
|
44
|
-
ordnanceSurveyApiSecret
|
|
44
|
+
ordnanceSurveyApiSecret,
|
|
45
|
+
services
|
|
45
46
|
} = options
|
|
46
47
|
|
|
47
48
|
const cacheService =
|
|
@@ -77,6 +78,7 @@ export const plugin = {
|
|
|
77
78
|
server.expose('cacheService', cacheService)
|
|
78
79
|
server.expose('saveAndExit', saveAndExit)
|
|
79
80
|
server.expose('baseUrl', baseUrl)
|
|
81
|
+
server.expose('services', services)
|
|
80
82
|
|
|
81
83
|
server.app.model = model
|
|
82
84
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import Boom from '@hapi/boom'
|
|
2
2
|
|
|
3
3
|
import { PAYMENT_SESSION_PREFIX } from '~/src/server/plugins/engine/routes/payment.js'
|
|
4
|
-
import {
|
|
5
|
-
import { PaymentService } from '~/src/server/plugins/payment/service.js'
|
|
4
|
+
import { createPaymentService } from '~/src/server/plugins/payment/helper.js'
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Validates session data and retrieves payment status
|
|
9
8
|
* @param {Request} request - the request
|
|
10
9
|
* @param {string} uuid - the payment UUID
|
|
10
|
+
* @param {FormsService} formsService - the forms service
|
|
11
11
|
* @returns {Promise<{ session: PaymentSessionData, sessionKey: string, paymentStatus: GetPaymentResponse }>}
|
|
12
12
|
*/
|
|
13
|
-
export async function getPaymentContext(request, uuid) {
|
|
13
|
+
export async function getPaymentContext(request, uuid, formsService) {
|
|
14
14
|
const sessionKey = `${PAYMENT_SESSION_PREFIX}${uuid}`
|
|
15
15
|
const session = /** @type {PaymentSessionData | null} */ (
|
|
16
16
|
request.yar.get(sessionKey)
|
|
@@ -26,8 +26,11 @@ export async function getPaymentContext(request, uuid) {
|
|
|
26
26
|
throw Boom.badRequest('No paymentId in session')
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const
|
|
30
|
-
|
|
29
|
+
const paymentService = await createPaymentService(
|
|
30
|
+
isLivePayment,
|
|
31
|
+
formId,
|
|
32
|
+
formsService
|
|
33
|
+
)
|
|
31
34
|
const paymentStatus = await paymentService.getPaymentStatus(
|
|
32
35
|
paymentId,
|
|
33
36
|
isLivePayment
|
|
@@ -73,4 +76,5 @@ export function convertPenceToPounds(amount) {
|
|
|
73
76
|
/**
|
|
74
77
|
* @import { Request } from '@hapi/hapi'
|
|
75
78
|
* @import { GetPaymentResponse, PaymentSessionData } from '~/src/server/plugins/payment/types.js'
|
|
79
|
+
* @import { FormsService } from '~/src/server/types.js'
|
|
76
80
|
*/
|
|
@@ -64,8 +64,11 @@ describe('payment helper', () => {
|
|
|
64
64
|
error: undefined
|
|
65
65
|
})
|
|
66
66
|
|
|
67
|
+
const mockFormsService = {
|
|
68
|
+
getFormSecret: () => 'secret-value'
|
|
69
|
+
}
|
|
67
70
|
// @ts-expect-error - partial request mock
|
|
68
|
-
const res = await getPaymentContext(mockRequest, uuid)
|
|
71
|
+
const res = await getPaymentContext(mockRequest, uuid, mockFormsService)
|
|
69
72
|
expect(res).toEqual({
|
|
70
73
|
paymentStatus: {
|
|
71
74
|
paymentId: 'payment-id-12345',
|
|
@@ -4,6 +4,7 @@ import Joi from 'joi'
|
|
|
4
4
|
|
|
5
5
|
import { createLogger } from '~/src/server/common/helpers/logging/logger.js'
|
|
6
6
|
import { EXTERNAL_STATE_APPENDAGE } from '~/src/server/constants.js'
|
|
7
|
+
import { getPluginOptions } from '~/src/server/plugins/engine/helpers.js'
|
|
7
8
|
import {
|
|
8
9
|
buildPaymentInfo,
|
|
9
10
|
convertPenceToPounds,
|
|
@@ -128,9 +129,13 @@ function getReturnRoute() {
|
|
|
128
129
|
path: PAYMENT_RETURN_PATH,
|
|
129
130
|
async handler(request, h) {
|
|
130
131
|
const { uuid } = /** @type {{ uuid: string }} */ (request.query)
|
|
132
|
+
|
|
133
|
+
const { services } = getPluginOptions(request.server)
|
|
134
|
+
|
|
131
135
|
const { session, sessionKey, paymentStatus } = await getPaymentContext(
|
|
132
136
|
request,
|
|
133
|
-
uuid
|
|
137
|
+
uuid,
|
|
138
|
+
/** @type {FormsService} */ (services?.formsService)
|
|
134
139
|
)
|
|
135
140
|
|
|
136
141
|
/**
|
|
@@ -193,4 +198,6 @@ function getReturnRoute() {
|
|
|
193
198
|
* @import { GetPaymentResponse, PaymentSessionData } from '~/src/server/plugins/payment/types.js'
|
|
194
199
|
* @import { PaymentState } from '~/src/server/plugins/engine/components/PaymentField.types.js'
|
|
195
200
|
* @import { ExternalStateAppendage, FormState } from '~/src/server/plugins/engine/types.js'
|
|
201
|
+
* @import { PluginOptions } from '~/src/server/plugins/engine/types.js'
|
|
202
|
+
* @import { FormsService } from '~/src/server/types.js'
|
|
196
203
|
*/
|
|
@@ -33,6 +33,17 @@ export function getFormDefinition(_id, _state) {
|
|
|
33
33
|
throw error
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
// eslint-disable-next-line jsdoc/require-returns-check
|
|
37
|
+
/**
|
|
38
|
+
* Dummy function to get a form secret.
|
|
39
|
+
* @param {string} _id - the id of the form
|
|
40
|
+
* @param {string} _secretName - the name of the secret
|
|
41
|
+
* @returns {Promise<string>}
|
|
42
|
+
*/
|
|
43
|
+
export function getFormSecret(_id, _secretName) {
|
|
44
|
+
throw error
|
|
45
|
+
}
|
|
46
|
+
|
|
36
47
|
/**
|
|
37
48
|
* @import { FormStatus, FormDefinition, FormMetadata } from '@defra/forms-model'
|
|
38
49
|
*/
|
|
@@ -3,7 +3,8 @@ import { FormStatus } from '@defra/forms-model'
|
|
|
3
3
|
import {
|
|
4
4
|
getFormDefinition,
|
|
5
5
|
getFormMetadata,
|
|
6
|
-
getFormMetadataById
|
|
6
|
+
getFormMetadataById,
|
|
7
|
+
getFormSecret
|
|
7
8
|
} from '~/src/server/plugins/engine/services/formsService.js'
|
|
8
9
|
|
|
9
10
|
describe('formsService', () => {
|
|
@@ -18,4 +19,8 @@ describe('formsService', () => {
|
|
|
18
19
|
it('getFormDefinition should throw error', () => {
|
|
19
20
|
expect(() => getFormDefinition('id', FormStatus.Draft)).toThrow()
|
|
20
21
|
})
|
|
22
|
+
|
|
23
|
+
it('getFormSecret should throw error', () => {
|
|
24
|
+
expect(() => getFormSecret('id', 'my-secret-name')).toThrow()
|
|
25
|
+
})
|
|
21
26
|
})
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type Item,
|
|
6
6
|
type List,
|
|
7
7
|
type Page,
|
|
8
|
+
type PaymentFieldComponent,
|
|
8
9
|
type UkAddressFieldComponent
|
|
9
10
|
} from '@defra/forms-model'
|
|
10
11
|
import {
|
|
@@ -397,7 +398,7 @@ export interface ExternalArgs {
|
|
|
397
398
|
component: ComponentDef
|
|
398
399
|
controller: QuestionPageController
|
|
399
400
|
sourceUrl: string
|
|
400
|
-
actionArgs
|
|
401
|
+
actionArgs?: Record<string, string>
|
|
401
402
|
isLive: boolean
|
|
402
403
|
isPreview: boolean
|
|
403
404
|
}
|
|
@@ -407,6 +408,10 @@ export interface PostcodeLookupExternalArgs extends ExternalArgs {
|
|
|
407
408
|
actionArgs: { step: string }
|
|
408
409
|
}
|
|
409
410
|
|
|
411
|
+
export interface PaymentExternalArgs extends ExternalArgs {
|
|
412
|
+
component: PaymentFieldComponent
|
|
413
|
+
}
|
|
414
|
+
|
|
410
415
|
export interface ExternalStateAppendage {
|
|
411
416
|
component: string
|
|
412
417
|
data: FormStateValue | FormState
|
|
@@ -5,35 +5,23 @@ import { PaymentService } from '~/src/server/plugins/payment/service.js'
|
|
|
5
5
|
export const DEFAULT_PAYMENT_HELP_URL =
|
|
6
6
|
'https://www.gov.uk/government/organisations/department-for-environment-food-rural-affairs'
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* If a draft preview form or a live preview form, read the TEST API key value specific to that form.
|
|
11
|
-
* If a live (non-preview) form, read the LIVE API key value specific to that form.
|
|
12
|
-
* @param {boolean} isLivePayment - true if this is a live payment (as opposed to a test one)
|
|
13
|
-
* @param {string} formId - id of the form
|
|
14
|
-
* @returns {string}
|
|
15
|
-
*/
|
|
16
|
-
export function getPaymentApiKey(isLivePayment, formId) {
|
|
17
|
-
const apiKeyValue = isLivePayment
|
|
18
|
-
? process.env[`PAYMENT_PROVIDER_API_KEY_LIVE_${formId}`]
|
|
19
|
-
: process.env[`PAYMENT_PROVIDER_API_KEY_TEST_${formId}`]
|
|
20
|
-
|
|
21
|
-
if (!apiKeyValue) {
|
|
22
|
-
throw new Error(
|
|
23
|
-
`[payment] Missing payment api key for ${isLivePayment ? 'live' : 'test'} form id ${formId}`
|
|
24
|
-
)
|
|
25
|
-
}
|
|
26
|
-
return apiKeyValue
|
|
27
|
-
}
|
|
8
|
+
const PAYMENT_TEST_API_KEY = 'payment-test-api-key'
|
|
9
|
+
const PAYMENT_LIVE_API_KEY = 'payment-live-api-key'
|
|
28
10
|
|
|
29
11
|
/**
|
|
30
12
|
* Creates a PaymentService instance with the appropriate API key
|
|
31
13
|
* @param {boolean} isLivePayment - true if this is a live payment
|
|
32
14
|
* @param {string} formId - id of the form
|
|
33
|
-
* @
|
|
15
|
+
* @param {FormsService} formsService - service to handle form data operations
|
|
16
|
+
* @returns {Promise<PaymentService>}
|
|
34
17
|
*/
|
|
35
|
-
export function createPaymentService(
|
|
36
|
-
|
|
18
|
+
export async function createPaymentService(
|
|
19
|
+
isLivePayment,
|
|
20
|
+
formId,
|
|
21
|
+
formsService
|
|
22
|
+
) {
|
|
23
|
+
const secretName = isLivePayment ? PAYMENT_LIVE_API_KEY : PAYMENT_TEST_API_KEY
|
|
24
|
+
const apiKey = await formsService.getFormSecret(formId, secretName)
|
|
37
25
|
return new PaymentService(apiKey)
|
|
38
26
|
}
|
|
39
27
|
|
|
@@ -61,3 +49,7 @@ export function formatCurrency(amount, locale = 'en-GB', currency = 'GBP') {
|
|
|
61
49
|
|
|
62
50
|
return formatter.format(amount)
|
|
63
51
|
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @import { FormsService } from '~/src/server/types.js'
|
|
55
|
+
*/
|
|
@@ -1,39 +1,8 @@
|
|
|
1
|
-
import { config } from '~/src/config/index.js'
|
|
2
1
|
import {
|
|
3
2
|
formatCurrency,
|
|
4
|
-
formatPaymentDate
|
|
5
|
-
getPaymentApiKey
|
|
3
|
+
formatPaymentDate
|
|
6
4
|
} from '~/src/server/plugins/payment/helper.js'
|
|
7
5
|
|
|
8
|
-
describe('getPaymentApiKey', () => {
|
|
9
|
-
config.set('paymentProviderApiKeyTest', 'TEST-API-KEY')
|
|
10
|
-
const formId = 'form-id'
|
|
11
|
-
process.env['PAYMENT_PROVIDER_API_KEY_LIVE_form-id'] = 'LIVE-API-KEY'
|
|
12
|
-
process.env['PAYMENT_PROVIDER_API_KEY_TEST_form-id'] = 'TEST-API-KEY'
|
|
13
|
-
|
|
14
|
-
it('should read test key when non-live form', () => {
|
|
15
|
-
const apiKey = getPaymentApiKey(false, formId)
|
|
16
|
-
expect(apiKey).toBe('TEST-API-KEY')
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
it('should read live key when live form', () => {
|
|
20
|
-
const apiKey = getPaymentApiKey(true, formId)
|
|
21
|
-
expect(apiKey).toBe('LIVE-API-KEY')
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
it('should throw if TEST key is missing', () => {
|
|
25
|
-
expect(() => getPaymentApiKey(false, 'form-id-missing')).toThrow(
|
|
26
|
-
'Missing payment api key for test form id form-id-missing'
|
|
27
|
-
)
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
it('should throw if LIVE key is missing', () => {
|
|
31
|
-
expect(() => getPaymentApiKey(true, 'form-id-missing')).toThrow(
|
|
32
|
-
'Missing payment api key for live form id form-id-missing'
|
|
33
|
-
)
|
|
34
|
-
})
|
|
35
|
-
})
|
|
36
|
-
|
|
37
6
|
describe('formatPaymentDate', () => {
|
|
38
7
|
it('should format ISO date string to en-GB format', () => {
|
|
39
8
|
const result = formatPaymentDate('2025-11-10T17:01:29.000Z')
|