@defra-fish/sales-api-service 1.44.0-rc.9 → 1.44.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra-fish/sales-api-service",
3
- "version": "1.44.0-rc.9",
3
+ "version": "1.44.0",
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.44.0-rc.9",
39
- "@defra-fish/connectors-lib": "1.44.0-rc.9",
40
- "@defra-fish/dynamics-lib": "1.44.0-rc.9",
38
+ "@defra-fish/business-rules-lib": "1.44.0",
39
+ "@defra-fish/connectors-lib": "1.44.0",
40
+ "@defra-fish/dynamics-lib": "1.44.0",
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": "a1b20131086b1233fecb04ebd90e188b74356d37"
55
+ "gitHead": "c1a99ab268a24df0eacdf151bf62a549e7e971b2"
56
56
  }
@@ -106,9 +106,23 @@ const finaliseTransactionRequestSchemaContent = {
106
106
  channelId: Joi.string().trim().optional().description('Channel specific identifier'),
107
107
  method: buildJoiOptionSetValidator('defra_paymenttype', 'Debit card'),
108
108
  recurring: Joi.object({
109
- payer: contactRequestSchema,
110
- referenceNumber: Joi.string().required().description('The reference number associated with the recurring payment').example(uuidv4()),
111
- mandate: Joi.string().required().description('The mandate identifier associated with the recurring payment').example(uuidv4())
109
+ name: Joi.string().required().description('The default name associated with the recurring payment').example(uuidv4()),
110
+ nextDueDate: Joi.string()
111
+ .isoDate()
112
+ .required()
113
+ .description('The date of payment for a renewed permission')
114
+ .example(new Date().toISOString()),
115
+ cancelledDate: Joi.string()
116
+ .isoDate()
117
+ .optional()
118
+ .description('Optional field for when recurring payment cancelled')
119
+ .example(new Date().toISOString()),
120
+ cancelledReason: buildJoiOptionSetValidator('defra_cancelledreason', 'User Cancelled'),
121
+ endDate: Joi.string().isoDate().required().description('End of recurring payment').example(new Date().toISOString()),
122
+ agreementId: Joi.string().required().description('Agreement identification number, Gov.UK Pay field').example(uuidv4()),
123
+ publicId: Joi.string().required().description('SHA-256 hash of id').example(uuidv4()),
124
+ contact: contactRequestSchema,
125
+ activePermission: finalisePermissionResponseSchema
112
126
  })
113
127
  .label('finalise-transaction-recurring-payment-details')
114
128
  .description('Used to establish a recurring payment (e.g. via Direct Debit)')
@@ -0,0 +1,31 @@
1
+ import dueRecurringPayments from '../recurring-payments.js'
2
+ import { getRecurringPayments } from '../../../services/recurring-payments.service.js'
3
+
4
+ jest.mock('../../../services/recurring-payments.service.js', () => ({
5
+ getRecurringPayments: jest.fn()
6
+ }))
7
+
8
+ const getMockRequest = ({ date = '2023-10-19' }) => ({
9
+ params: { date }
10
+ })
11
+
12
+ const getMockResponseToolkit = () => ({
13
+ response: jest.fn()
14
+ })
15
+
16
+ describe('recurring payments', () => {
17
+ beforeEach(jest.clearAllMocks)
18
+
19
+ it('handler should return continue response', async () => {
20
+ const request = getMockRequest({})
21
+ const responseToolkit = getMockResponseToolkit()
22
+ expect(await dueRecurringPayments[0].handler(request, responseToolkit)).toEqual(responseToolkit.continue)
23
+ })
24
+
25
+ it('should call getRecurringPayments with date', async () => {
26
+ const date = Symbol('date')
27
+ const request = getMockRequest({ date })
28
+ await dueRecurringPayments[0].handler(request, getMockResponseToolkit())
29
+ expect(getRecurringPayments).toHaveBeenCalledWith(date)
30
+ })
31
+ })
@@ -6,6 +6,7 @@ import PaymentJournals from './payment-journals.js'
6
6
  import StagingExceptions from './staging-exceptions.js'
7
7
  import Authenticate from './authenticate.js'
8
8
  import Users from './system-users.js'
9
+ import RecurringPayment from './recurring-payments.js'
9
10
 
10
11
  import Static from './static.js'
11
12
 
@@ -18,5 +19,6 @@ export default [
18
19
  ...PaymentJournals,
19
20
  ...StagingExceptions,
20
21
  ...Authenticate,
21
- ...Users
22
+ ...Users,
23
+ ...RecurringPayment
22
24
  ]
@@ -0,0 +1,12 @@
1
+ import { getRecurringPayments } from '../../services/recurring-payments.service.js'
2
+ export default [
3
+ {
4
+ method: 'GET',
5
+ path: '/dueRecurringPayments/{date}',
6
+ handler: async (request, h) => {
7
+ const { date } = request.params
8
+ const result = await getRecurringPayments(date)
9
+ return h.response(result)
10
+ }
11
+ }
12
+ ]
@@ -0,0 +1,14 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`recurring payments service processRecurringPayment should return a valid recurringPayment when transactionRecord.payment.recurring is present 1`] = `
4
+ Object {
5
+ "agreementId": "435678",
6
+ "cancelledDate": null,
7
+ "cancelledReason": null,
8
+ "endDate": 2023-11-12T00:00:00.000Z,
9
+ "name": "Test Name",
10
+ "nextDueDate": 2023-11-02T00:00:00.000Z,
11
+ "publicId": "1234456",
12
+ "status": 0,
13
+ }
14
+ `;
@@ -0,0 +1,137 @@
1
+ import { findDueRecurringPayments } from '@defra-fish/dynamics-lib'
2
+ import { getRecurringPayments, processRecurringPayment } from '../recurring-payments.service.js'
3
+
4
+ jest.mock('@defra-fish/dynamics-lib', () => ({
5
+ ...jest.requireActual('@defra-fish/dynamics-lib'),
6
+ executeQuery: jest.fn(),
7
+ findById: jest.fn(),
8
+ findDueRecurringPayments: jest.fn()
9
+ }))
10
+
11
+ const dynamicsLib = jest.requireMock('@defra-fish/dynamics-lib')
12
+
13
+ const getMockRecurringPayment = () => ({
14
+ name: 'Test Name',
15
+ nextDueDate: '2019-12-14T00:00:00Z',
16
+ cancelledDate: null,
17
+ cancelledReason: null,
18
+ endDate: '2019-12-15T00:00:00Z',
19
+ agreementId: 'c9267c6e-573d-488b-99ab-ea18431fc472',
20
+ publicId: '649-213',
21
+ status: 1,
22
+ expanded: {
23
+ contact: {
24
+ entity: getMockContact()
25
+ },
26
+ activePermission: {
27
+ entity: getMockPermission()
28
+ }
29
+ }
30
+ })
31
+
32
+ const getMockRPContactPermission = (contact, permission) => ({
33
+ name: 'Test Name',
34
+ nextDueDate: '2019-12-14T00:00:00Z',
35
+ cancelledDate: null,
36
+ cancelledReason: null,
37
+ endDate: '2019-12-15T00:00:00Z',
38
+ agreementId: 'c9267c6e-573d-488b-99ab-ea18431fc472',
39
+ publicId: '649-213',
40
+ status: 1,
41
+ expanded: {
42
+ contact,
43
+ activePermission: permission
44
+ }
45
+ })
46
+
47
+ const getMockContact = () => ({
48
+ firstName: 'Fester',
49
+ lastName: 'Tester',
50
+ birthDate: '2000-01-01',
51
+ email: 'person@example.com',
52
+ mobilePhone: '+44 7700 900088',
53
+ premises: 'Example House',
54
+ street: 'Example Street',
55
+ locality: 'Near Sample',
56
+ town: 'Exampleton',
57
+ postcode: 'AB12 3CD',
58
+ country: 'GB-ENG',
59
+ preferredMethodOfConfirmation: 'Text',
60
+ preferredMethodOfNewsletter: 'Email',
61
+ preferredMethodOfReminder: 'Letter',
62
+ postalFulfilment: true
63
+ })
64
+
65
+ const getMockPermission = () => ({
66
+ referenceNumber: '12345678',
67
+ licenceLength: '12M',
68
+ isLicenceForYou: true,
69
+ licensee: {
70
+ firstName: 'Fester',
71
+ lastName: 'Tester',
72
+ premises: '14 Howecroft Court',
73
+ street: 'Eastmead Lane',
74
+ town: 'Bristol',
75
+ postcode: 'BS9 1HJ',
76
+ email: 'fester@tester.com',
77
+ mobilePhone: '01234567890',
78
+ birthDate: '1996-01-01',
79
+ postalFulfilment: true
80
+ }
81
+ })
82
+
83
+ describe('recurring payments service', () => {
84
+ beforeEach(jest.clearAllMocks)
85
+ describe('getRecurringPayments', () => {
86
+ it('should equal result of findDueRecurringPayments query', async () => {
87
+ const mockRecurringPayments = [getMockRecurringPayment()]
88
+ const mockContact = mockRecurringPayments[0].expanded.contact
89
+ const mockPermission = mockRecurringPayments[0].expanded.activePermission
90
+
91
+ dynamicsLib.executeQuery.mockResolvedValueOnce(mockRecurringPayments)
92
+
93
+ const result = await getRecurringPayments(new Date())
94
+
95
+ expect(result).toEqual([getMockRPContactPermission(mockContact, mockPermission)])
96
+ })
97
+
98
+ it('executeQuery is called with findDueRecurringPayments with a date', async () => {
99
+ const mockRecurringPayments = [getMockRecurringPayment()]
100
+ const mockDate = new Date()
101
+ dynamicsLib.executeQuery.mockResolvedValueOnce(mockRecurringPayments)
102
+
103
+ await getRecurringPayments(mockDate)
104
+
105
+ expect(dynamicsLib.executeQuery).toHaveBeenCalledWith(findDueRecurringPayments(mockDate))
106
+ })
107
+ })
108
+
109
+ describe('processRecurringPayment', () => {
110
+ it('should return null when transactionRecord.payment.recurring is not present', async () => {
111
+ const transactionRecord = { payment: null }
112
+ const result = await processRecurringPayment(transactionRecord, getMockContact())
113
+ expect(result.recurringPayment).toBeNull()
114
+ })
115
+
116
+ it('should return a valid recurringPayment when transactionRecord.payment.recurring is present', async () => {
117
+ const transactionRecord = {
118
+ payment: {
119
+ recurring: {
120
+ name: 'Test Name',
121
+ nextDueDate: new Date('2023-11-02'),
122
+ cancelledDate: null,
123
+ cancelledReason: null,
124
+ endDate: new Date('2023-11-12'),
125
+ agreementId: '435678',
126
+ publicId: '1234456',
127
+ status: 0
128
+ }
129
+ },
130
+ permissions: [getMockPermission()]
131
+ }
132
+ const contact = getMockContact()
133
+ const result = await processRecurringPayment(transactionRecord, contact)
134
+ expect(result.recurringPayment).toMatchSnapshot()
135
+ })
136
+ })
137
+ })
@@ -0,0 +1,27 @@
1
+ import { executeQuery, findDueRecurringPayments, RecurringPayment } from '@defra-fish/dynamics-lib'
2
+
3
+ export const getRecurringPayments = date => executeQuery(findDueRecurringPayments(date))
4
+
5
+ /**
6
+ * Process a recurring payment instruction
7
+ * @param transactionRecord
8
+ * @returns {Promise<{recurringPayment: RecurringPayment | null}>}
9
+ */
10
+ export const processRecurringPayment = async (transactionRecord, contact) => {
11
+ if (transactionRecord.payment?.recurring) {
12
+ const recurringPayment = new RecurringPayment()
13
+ recurringPayment.name = transactionRecord.payment.recurring.name
14
+ recurringPayment.nextDueDate = transactionRecord.payment.recurring.nextDueDate
15
+ recurringPayment.cancelledDate = transactionRecord.payment.recurring.cancelledDate
16
+ recurringPayment.cancelledReason = transactionRecord.payment.recurring.cancelledReason
17
+ recurringPayment.endDate = transactionRecord.payment.recurring.endDate
18
+ recurringPayment.agreementId = transactionRecord.payment.recurring.agreementId
19
+ recurringPayment.publicId = transactionRecord.payment.recurring.publicId
20
+ recurringPayment.status = transactionRecord.payment.recurring.status
21
+ const [permission] = transactionRecord.permissions
22
+ recurringPayment.bindToEntity(RecurringPayment.definition.relationships.activePermission, permission)
23
+ recurringPayment.bindToEntity(RecurringPayment.definition.relationships.contact, contact)
24
+ return { recurringPayment }
25
+ }
26
+ return { recurringPayment: null }
27
+ }
@@ -181,7 +181,7 @@ describe('transaction service', () => {
181
181
  type: 'Gov Pay',
182
182
  method: 'Debit card',
183
183
  recurring: {
184
- payer: {
184
+ contact: {
185
185
  firstName: 'Fester',
186
186
  lastName: 'Tester',
187
187
  birthDate: '2000-01-01',
@@ -7,10 +7,10 @@ import {
7
7
  FulfilmentRequest,
8
8
  Permission,
9
9
  PoclFile,
10
- RecurringPayment,
11
10
  RecurringPaymentInstruction,
12
11
  Transaction,
13
- TransactionJournal
12
+ TransactionJournal,
13
+ RecurringPayment
14
14
  } from '@defra-fish/dynamics-lib'
15
15
  import {
16
16
  mockFinalisedTransactionRecord,
@@ -126,12 +126,17 @@ describe('transaction service', () => {
126
126
  'licences with a recurring payment',
127
127
  () => {
128
128
  const mockRecord = mockFinalisedTransactionRecord()
129
- mockRecord.permissions[0].permitId = MOCK_12MONTH_SENIOR_PERMIT.id
130
129
  mockRecord.payment.recurring = {
131
- referenceNumber: 'Test Reference Number',
132
- mandate: 'Test Mandate',
130
+ name: 'Test name',
131
+ nextDueDate: new Date('2020/01/11'),
132
+ endDate: new Date('2022/01/16'),
133
+ agreementId: '123446jjng',
134
+ publicId: 'sdf-123',
135
+ status: 1,
136
+ activePermission: mockRecord.permissions[0],
133
137
  contact: Object.assign(mockContactPayload(), { firstName: 'Esther' })
134
138
  }
139
+ mockRecord.permissions[0].permitId = MOCK_12MONTH_SENIOR_PERMIT.id
135
140
  return mockRecord
136
141
  },
137
142
  [
@@ -140,7 +145,6 @@ describe('transaction service', () => {
140
145
  expect.any(TransactionJournal),
141
146
  expect.any(RecurringPayment),
142
147
  expect.any(Contact),
143
- expect.any(Contact),
144
148
  expect.any(Permission),
145
149
  expect.any(RecurringPaymentInstruction),
146
150
  expect.any(ConcessionProof)
@@ -8,17 +8,17 @@ import {
8
8
  Transaction,
9
9
  TransactionCurrency,
10
10
  TransactionJournal,
11
- RecurringPayment,
12
11
  RecurringPaymentInstruction
13
12
  } from '@defra-fish/dynamics-lib'
14
13
  import { DDE_DATA_SOURCE, FULFILMENT_SWITCHOVER_DATE, POCL_TRANSACTION_SOURCES } from '@defra-fish/business-rules-lib'
15
14
  import { getReferenceDataForEntityAndId, getGlobalOptionSetValue, getReferenceDataForEntity } from '../reference-data.service.js'
15
+ import { processRecurringPayment } from '../recurring-payments.service.js'
16
16
  import { resolveContactPayload } from '../contacts.service.js'
17
17
  import { retrieveStagedTransaction } from './retrieve-transaction.js'
18
18
  import { TRANSACTION_STAGING_TABLE, TRANSACTION_STAGING_HISTORY_TABLE } from '../../config.js'
19
- import moment from 'moment'
20
19
  import { AWS } from '@defra-fish/connectors-lib'
21
20
  import db from 'debug'
21
+ import moment from 'moment'
22
22
  const { docClient } = AWS()
23
23
  const debug = db('sales:transactions')
24
24
 
@@ -38,9 +38,6 @@ export async function processQueue ({ id }) {
38
38
  const { transaction, chargeJournal, paymentJournal } = await createTransactionEntities(transactionRecord)
39
39
  entities.push(transaction, chargeJournal, paymentJournal)
40
40
 
41
- const { recurringPayment, payer } = await processRecurringPayment(transactionRecord)
42
- recurringPayment && entities.push(recurringPayment, payer)
43
-
44
41
  const totalTransactionValue = transactionRecord.payment.amount
45
42
  const dataSource = await getGlobalOptionSetValue(Permission.definition.mappings.dataSource.ref, transactionRecord.dataSource)
46
43
  for (const {
@@ -68,6 +65,11 @@ export async function processQueue ({ id }) {
68
65
  isRenewal
69
66
  )
70
67
 
68
+ const { recurringPayment } = await processRecurringPayment(transactionRecord, contact)
69
+ if (recurringPayment) {
70
+ entities.push(recurringPayment)
71
+ }
72
+
71
73
  permission.bindToEntity(Permission.definition.relationships.licensee, contact)
72
74
  permission.bindToEntity(Permission.definition.relationships.permit, permit)
73
75
  permission.bindToEntity(Permission.definition.relationships.transaction, transaction)
@@ -142,27 +144,6 @@ const mapToPermission = async (
142
144
  return permission
143
145
  }
144
146
 
145
- /**
146
- * Process a recurring payment instruction
147
- * @param transactionRecord
148
- * @returns {Promise<{recurringPayment: null, payer: null}>}
149
- */
150
- const processRecurringPayment = async transactionRecord => {
151
- let recurringPayment = null
152
- let payer = null
153
- if (transactionRecord.payment.recurring) {
154
- const inceptionMoment = moment(transactionRecord.payment.timestamp, true).utc()
155
- recurringPayment = new RecurringPayment()
156
- recurringPayment.referenceNumber = transactionRecord.payment.recurring.referenceNumber
157
- recurringPayment.mandate = transactionRecord.payment.recurring.mandate
158
- recurringPayment.inceptionDay = inceptionMoment.date()
159
- recurringPayment.inceptionMonth = inceptionMoment.month()
160
- payer = await resolveContactPayload(transactionRecord.payment.recurring.payer)
161
- recurringPayment.bindToEntity(RecurringPayment.definition.relationships.contact, payer)
162
- }
163
- return { recurringPayment, payer }
164
- }
165
-
166
147
  /**
167
148
  * Create transaction entities required to represent this transaction payload
168
149
  *