@defra-fish/sales-api-service 1.62.0-rc.7 → 1.62.0-rc.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra-fish/sales-api-service",
3
- "version": "1.62.0-rc.7",
3
+ "version": "1.62.0-rc.9",
4
4
  "description": "Rod Licensing Sales API",
5
5
  "type": "module",
6
6
  "engines": {
@@ -35,9 +35,9 @@
35
35
  "test": "echo \"Error: run tests from root\" && exit 1"
36
36
  },
37
37
  "dependencies": {
38
- "@defra-fish/business-rules-lib": "1.62.0-rc.7",
39
- "@defra-fish/connectors-lib": "1.62.0-rc.7",
40
- "@defra-fish/dynamics-lib": "1.62.0-rc.7",
38
+ "@defra-fish/business-rules-lib": "1.62.0-rc.9",
39
+ "@defra-fish/connectors-lib": "1.62.0-rc.9",
40
+ "@defra-fish/dynamics-lib": "1.62.0-rc.9",
41
41
  "@hapi/boom": "^9.1.2",
42
42
  "@hapi/hapi": "^20.1.3",
43
43
  "@hapi/inert": "^6.0.3",
@@ -52,5 +52,5 @@
52
52
  "moment-timezone": "^0.5.34",
53
53
  "uuid": "^8.3.2"
54
54
  },
55
- "gitHead": "769c73ef2423de6a672ce76dd831fe5927393373"
55
+ "gitHead": "435e74ebb124eeda4a409f5aa256ba188125f06b"
56
56
  }
@@ -6,6 +6,7 @@ Object {
6
6
  "cancelledDate": null,
7
7
  "cancelledReason": null,
8
8
  "endDate": "2023-11-12T00:00:00.000Z",
9
+ "lastDigitsCardNumbers": "0128",
9
10
  "name": "Fester Tester 2023",
10
11
  "nextDueDate": "2023-11-02T00:00:00.000Z",
11
12
  "publicId": "abcdef99987",
@@ -13,17 +13,31 @@ import {
13
13
  generateRecurringPaymentRecord,
14
14
  processRPResult,
15
15
  findNewestExistingRecurringPaymentInCrm,
16
+ getRecurringPaymentAgreement,
16
17
  cancelRecurringPayment
17
18
  } from '../recurring-payments.service.js'
18
19
  import { calculateEndDate, generatePermissionNumber } from '../permissions.service.js'
19
20
  import { getObfuscatedDob } from '../contacts.service.js'
20
21
  import { createHash } from 'node:crypto'
21
- import { AWS } from '@defra-fish/connectors-lib'
22
+ import { AWS, govUkPayApi } from '@defra-fish/connectors-lib'
22
23
  import { TRANSACTION_STAGING_TABLE, TRANSACTION_QUEUE } from '../../config.js'
23
24
  import { TRANSACTION_STATUS } from '../../services/transactions/constants.js'
24
25
  import { retrieveStagedTransaction } from '../../services/transactions/retrieve-transaction.js'
25
26
  import { createPaymentJournal, getPaymentJournal, updatePaymentJournal } from '../../services/paymentjournals/payment-journals.service.js'
26
27
  import { PAYMENT_JOURNAL_STATUS_CODES, TRANSACTION_SOURCE, PAYMENT_TYPE } from '@defra-fish/business-rules-lib'
28
+ import db from 'debug'
29
+
30
+ jest.mock('ioredis', () => ({
31
+ built: {
32
+ utils: {
33
+ debug: jest.fn()
34
+ }
35
+ }
36
+ }))
37
+
38
+ jest.mock('debug', () => jest.fn(() => jest.fn()))
39
+ const { value: debug } = db.mock.results[db.mock.calls.findIndex(c => c[0] === 'sales:recurring')]
40
+
27
41
  const { docClient, sqs } = AWS.mock.results[0].value
28
42
 
29
43
  jest.mock('@defra-fish/dynamics-lib', () => ({
@@ -46,7 +60,10 @@ jest.mock('@defra-fish/connectors-lib', () => ({
46
60
  sqs: {
47
61
  sendMessage: jest.fn()
48
62
  }
49
- }))
63
+ })),
64
+ govUkPayApi: {
65
+ getRecurringPaymentAgreementInformation: jest.fn()
66
+ }
50
67
  }))
51
68
 
52
69
  jest.mock('node:crypto', () => ({
@@ -91,6 +108,7 @@ jest.mock('@defra-fish/business-rules-lib', () => ({
91
108
  debit: Symbol('debit')
92
109
  }
93
110
  }))
111
+ global.structuredClone = obj => JSON.parse(JSON.stringify(obj))
94
112
 
95
113
  const getMockRecurringPayment = (overrides = {}) => ({
96
114
  name: 'Test Name',
@@ -254,6 +272,11 @@ describe('recurring payments service', () => {
254
272
  beforeAll(() => {
255
273
  TRANSACTION_QUEUE.Url = 'TestUrl'
256
274
  TRANSACTION_STAGING_TABLE.TableName = 'TestTable'
275
+ const mockResponse = {
276
+ ok: true,
277
+ json: jest.fn().mockResolvedValue({ success: true, payment_instrument: { card_details: { last_digits_card_number: '1234' } } })
278
+ }
279
+ govUkPayApi.getRecurringPaymentAgreementInformation.mockResolvedValue(mockResponse)
257
280
  })
258
281
 
259
282
  describe('getRecurringPayments', () => {
@@ -296,7 +319,8 @@ describe('recurring payments service', () => {
296
319
  cancelledReason: null,
297
320
  endDate: '2023-11-12T00:00:00.000Z',
298
321
  agreementId: '435678',
299
- status: 0
322
+ status: 0,
323
+ last_digits_card_number: '0128'
300
324
  }
301
325
  },
302
326
  permissions: [getMockPermission()]
@@ -355,7 +379,7 @@ describe('recurring payments service', () => {
355
379
  })
356
380
 
357
381
  describe('generateRecurringPaymentRecord', () => {
358
- const createFinalisedSampleTransaction = (agreementId, permission) => ({
382
+ const createFinalisedSampleTransaction = (agreementId, permission, lastDigitsCardNumbers) => ({
359
383
  expires: 1732892402,
360
384
  cost: 35.8,
361
385
  isRecurringPaymentSupported: true,
@@ -378,7 +402,8 @@ describe('recurring payments service', () => {
378
402
  id: 'd26d646f-ed0f-4cf1-b6c1-ccfbbd611757',
379
403
  dataSource: 'Web Sales',
380
404
  transactionId: 'd26d646f-ed0f-4cf1-b6c1-ccfbbd611757',
381
- status: { id: 'FINALISED' }
405
+ status: { id: 'FINALISED' },
406
+ lastDigitsCardNumbers
382
407
  })
383
408
 
384
409
  it.each([
@@ -390,7 +415,8 @@ describe('recurring payments service', () => {
390
415
  issueDate: '2024-11-22T15:00:45.922Z',
391
416
  endDate: '2025-11-21T23:59:59.999Z'
392
417
  },
393
- '2025-11-12T00:00:00.000Z'
418
+ '2025-11-12T00:00:00.000Z',
419
+ '1234'
394
420
  ],
395
421
  [
396
422
  'next day start - next due on end date minus ten days',
@@ -400,7 +426,8 @@ describe('recurring payments service', () => {
400
426
  issueDate: '2024-11-22T15:00:45.922Z',
401
427
  endDate: '2025-11-22T23:59:59.999Z'
402
428
  },
403
- '2025-11-12T00:00:00.000Z'
429
+ '2025-11-12T00:00:00.000Z',
430
+ '5678'
404
431
  ],
405
432
  [
406
433
  'starts ten days after issue - next due on issue date plus one year',
@@ -410,7 +437,8 @@ describe('recurring payments service', () => {
410
437
  issueDate: '2024-11-12T15:00:45.922Z',
411
438
  endDate: '2025-11-21T23:59:59.999Z'
412
439
  },
413
- '2025-11-12T00:00:00.000Z'
440
+ '2025-11-12T00:00:00.000Z',
441
+ '9012'
414
442
  ],
415
443
  [
416
444
  'starts twenty days after issue - next due on issue date plus one year',
@@ -420,7 +448,8 @@ describe('recurring payments service', () => {
420
448
  issueDate: '2024-11-12T15:00:45.922Z',
421
449
  endDate: '2025-01-30T23:59:59.999Z'
422
450
  },
423
- '2025-11-12T00:00:00.000Z'
451
+ '2025-11-12T00:00:00.000Z',
452
+ '3456'
424
453
  ],
425
454
  [
426
455
  "issued on 29th Feb '24, starts on 30th March '24 - next due on 28th Feb '25",
@@ -430,7 +459,8 @@ describe('recurring payments service', () => {
430
459
  issueDate: '2024-02-29T12:38:24.123Z',
431
460
  endDate: '2025-03-29T23:59:59.999Z'
432
461
  },
433
- '2025-02-28T00:00:00.000Z'
462
+ '2025-02-28T00:00:00.000Z',
463
+ '7890'
434
464
  ],
435
465
  [
436
466
  "issued on 30th March '25 at 1am, starts at 1:30am - next due on 20th March '26",
@@ -440,13 +470,21 @@ describe('recurring payments service', () => {
440
470
  issueDate: '2025-03-30T01:00:00.000Z',
441
471
  endDate: '2026-03-29T23:59:59.999Z'
442
472
  },
443
- '2026-03-20T00:00:00.000Z'
473
+ '2026-03-20T00:00:00.000Z',
474
+ '1199'
444
475
  ]
445
- ])('creates record from transaction with %s', (_d, agreementId, permissionData, expectedNextDueDate) => {
446
- const sampleTransaction = createFinalisedSampleTransaction(agreementId, permissionData)
476
+ ])('creates record from transaction with %s', async (_d, agreementId, permissionData, expectedNextDueDate, lastDigitsCardNumbers) => {
477
+ const mockResponse = {
478
+ ok: true,
479
+ json: jest
480
+ .fn()
481
+ .mockResolvedValue({ success: true, payment_instrument: { card_details: { last_digits_card_number: lastDigitsCardNumbers } } })
482
+ }
483
+ govUkPayApi.getRecurringPaymentAgreementInformation.mockResolvedValue(mockResponse)
484
+ const sampleTransaction = createFinalisedSampleTransaction(agreementId, permissionData, lastDigitsCardNumbers)
447
485
  const permission = createSamplePermission(permissionData)
448
486
 
449
- const rpRecord = generateRecurringPaymentRecord(sampleTransaction, permission)
487
+ const rpRecord = await generateRecurringPaymentRecord(sampleTransaction, permission)
450
488
 
451
489
  expect(rpRecord).toEqual(
452
490
  expect.objectContaining({
@@ -458,7 +496,8 @@ describe('recurring payments service', () => {
458
496
  cancelledReason: null,
459
497
  endDate: permissionData.endDate,
460
498
  agreementId,
461
- status: 1
499
+ status: 1,
500
+ last_digits_card_number: lastDigitsCardNumbers
462
501
  })
463
502
  }),
464
503
  permissions: expect.arrayContaining([permission])
@@ -483,22 +522,26 @@ describe('recurring payments service', () => {
483
522
  endDate: '2025-11-10T23:59:59.999Z'
484
523
  }
485
524
  ]
486
- ])('throws an error for invalid dates when %s', (_d, permission) => {
525
+ ])('throws an error for invalid dates when %s', async (_d, permission) => {
487
526
  const sampleTransaction = createFinalisedSampleTransaction('hyu78ijhyu78ijuhyu78ij9iu6', permission)
488
527
 
489
- expect(() => generateRecurringPaymentRecord(sampleTransaction)).toThrow('Invalid dates provided for permission')
528
+ await expect(generateRecurringPaymentRecord(sampleTransaction)).rejects.toThrow('Invalid dates provided for permission')
490
529
  })
491
530
 
492
- it('returns a false flag when agreementId is not present', () => {
493
- const sampleTransaction = createFinalisedSampleTransaction(null, {
494
- startDate: '2024-11-22T15:30:45.922Z',
495
- issueDate: '2024-11-22T15:00:45.922Z',
496
- endDate: '2025-11-21T23:59:59.999Z'
497
- })
531
+ it('returns a false flag when agreementId is not present', async () => {
532
+ const sampleTransaction = createFinalisedSampleTransaction(
533
+ null,
534
+ {
535
+ startDate: '2024-11-22T15:30:45.922Z',
536
+ issueDate: '2024-11-22T15:00:45.922Z',
537
+ endDate: '2025-11-21T23:59:59.999Z'
538
+ },
539
+ '0123'
540
+ )
498
541
 
499
- const rpRecord = generateRecurringPaymentRecord(sampleTransaction)
542
+ const rpRecord = await generateRecurringPaymentRecord(sampleTransaction)
500
543
 
501
- expect(rpRecord.payment.recurring).toBeFalsy()
544
+ expect(rpRecord.payment?.recurring).toBeFalsy()
502
545
  })
503
546
  })
504
547
 
@@ -756,6 +799,64 @@ describe('recurring payments service', () => {
756
799
  })
757
800
  })
758
801
 
802
+ describe('getRecurringPaymentAgreement', () => {
803
+ const agreementId = '1234'
804
+
805
+ it('should send provided agreement id data to Gov.UK Pay', async () => {
806
+ const mockResponse = {
807
+ ok: true,
808
+ json: jest.fn().mockResolvedValue({ success: true, payment_instrument: { card_details: { last_digits_card_number: '1234' } } })
809
+ }
810
+ govUkPayApi.getRecurringPaymentAgreementInformation.mockResolvedValue(mockResponse)
811
+ await getRecurringPaymentAgreement(agreementId)
812
+ expect(govUkPayApi.getRecurringPaymentAgreementInformation).toHaveBeenCalledWith(agreementId)
813
+ })
814
+
815
+ it('should return response body when payment creation is successful', async () => {
816
+ const mockResponse = {
817
+ ok: true,
818
+ json: jest.fn().mockResolvedValue({ success: true, payment_instrument: { card_details: { last_digits_card_number: '1234' } } })
819
+ }
820
+ govUkPayApi.getRecurringPaymentAgreementInformation.mockResolvedValue(mockResponse)
821
+
822
+ const result = await getRecurringPaymentAgreement(agreementId)
823
+
824
+ expect(result).toEqual({
825
+ success: true,
826
+ payment_instrument: {
827
+ card_details: {
828
+ last_digits_card_number: '1234'
829
+ }
830
+ }
831
+ })
832
+ })
833
+
834
+ it('debug should output message when response.ok is true without card details', async () => {
835
+ const mockResponse = {
836
+ ok: true,
837
+ json: jest.fn().mockResolvedValue({ success: true, payment_instrument: { card_details: { last_digits_card_number: '1234' } } })
838
+ }
839
+ govUkPayApi.getRecurringPaymentAgreementInformation.mockResolvedValue(mockResponse)
840
+
841
+ await getRecurringPaymentAgreement(agreementId)
842
+
843
+ expect(debug).toHaveBeenCalledWith('Successfully got recurring payment agreement information: %o', {
844
+ success: true,
845
+ payment_instrument: {}
846
+ })
847
+ })
848
+
849
+ it('should throw an error when the response is not ok', async () => {
850
+ const mockResponse = {
851
+ ok: false,
852
+ json: jest.fn().mockResolvedValue({ success: true, payment_instrument: { card_details: { last_digits_card_number: '1234' } } })
853
+ }
854
+ govUkPayApi.getRecurringPaymentAgreementInformation.mockResolvedValue(mockResponse)
855
+
856
+ await expect(getRecurringPaymentAgreement(agreementId)).rejects.toThrow('Failure getting agreement in the GOV.UK API service')
857
+ })
858
+ })
859
+
759
860
  describe('cancelRecurringPayment', () => {
760
861
  it('should call findById with RecurringPayment and the provided id', async () => {
761
862
  const id = 'abc123'
@@ -15,7 +15,9 @@ import { TRANSACTION_STATUS } from '../services/transactions/constants.js'
15
15
  import { retrieveStagedTransaction } from '../services/transactions/retrieve-transaction.js'
16
16
  import { createPaymentJournal, getPaymentJournal, updatePaymentJournal } from '../services/paymentjournals/payment-journals.service.js'
17
17
  import moment from 'moment'
18
- import { AWS } from '@defra-fish/connectors-lib'
18
+ import { AWS, govUkPayApi } from '@defra-fish/connectors-lib'
19
+ import db from 'debug'
20
+ const debug = db('sales:recurring')
19
21
  const { sqs, docClient } = AWS()
20
22
 
21
23
  export const getRecurringPayments = date => executeQuery(findDueRecurringPayments(date))
@@ -34,8 +36,10 @@ const getNextDueDate = (startDate, issueDate, endDate) => {
34
36
  throw new Error('Invalid dates provided for permission')
35
37
  }
36
38
 
37
- export const generateRecurringPaymentRecord = (transactionRecord, permission) => {
39
+ export const generateRecurringPaymentRecord = async (transactionRecord, permission) => {
38
40
  if (transactionRecord.agreementId) {
41
+ const agreementResponse = await getRecurringPaymentAgreement(transactionRecord.agreementId)
42
+ const lastDigitsCardNumbers = agreementResponse.payment_instrument?.card_details?.last_digits_card_number
39
43
  const [{ startDate, issueDate, endDate }] = transactionRecord.permissions
40
44
  return {
41
45
  payment: {
@@ -46,7 +50,8 @@ export const generateRecurringPaymentRecord = (transactionRecord, permission) =>
46
50
  cancelledReason: null,
47
51
  endDate,
48
52
  agreementId: transactionRecord.agreementId,
49
- status: 1
53
+ status: 1,
54
+ last_digits_card_number: lastDigitsCardNumbers
50
55
  }
51
56
  },
52
57
  permissions: [permission]
@@ -73,6 +78,7 @@ export const processRecurringPayment = async (transactionRecord, contact) => {
73
78
  recurringPayment.agreementId = transactionRecord.payment.recurring.agreementId
74
79
  recurringPayment.publicId = hash.digest('base64')
75
80
  recurringPayment.status = transactionRecord.payment.recurring.status
81
+ recurringPayment.lastDigitsCardNumbers = transactionRecord.payment.recurring.last_digits_card_number
76
82
  const [permission] = transactionRecord.permissions
77
83
  recurringPayment.bindToEntity(RecurringPayment.definition.relationships.activePermission, permission)
78
84
  recurringPayment.bindToEntity(RecurringPayment.definition.relationships.contact, contact)
@@ -81,6 +87,22 @@ export const processRecurringPayment = async (transactionRecord, contact) => {
81
87
  return { recurringPayment: null }
82
88
  }
83
89
 
90
+ export const getRecurringPaymentAgreement = async agreementId => {
91
+ const response = await govUkPayApi.getRecurringPaymentAgreementInformation(agreementId)
92
+ if (response.ok) {
93
+ const resBody = await response.json()
94
+ const resBodyNoCardDetails = structuredClone(resBody)
95
+
96
+ if (resBodyNoCardDetails.payment_instrument?.card_details) {
97
+ delete resBodyNoCardDetails.payment_instrument.card_details
98
+ }
99
+ debug('Successfully got recurring payment agreement information: %o', resBodyNoCardDetails)
100
+ return resBody
101
+ } else {
102
+ throw new Error('Failure getting agreement in the GOV.UK API service')
103
+ }
104
+ }
105
+
84
106
  export const processRPResult = async (transactionId, paymentId, createdDate) => {
85
107
  const transactionRecord = await retrieveStagedTransaction(transactionId)
86
108
  if (await getPaymentJournal(transactionId)) {
@@ -77,7 +77,7 @@ export async function processQueue ({ id }) {
77
77
 
78
78
  entities.push(contact, permission)
79
79
 
80
- const { recurringPayment } = await processRecurringPayment(generateRecurringPaymentRecord(transactionRecord, permission), contact)
80
+ const { recurringPayment } = await processRecurringPayment(await generateRecurringPaymentRecord(transactionRecord, permission), contact)
81
81
 
82
82
  if (recurringPayment && permit.isRecurringPaymentSupported) {
83
83
  entities.push(recurringPayment)