@defra-fish/sales-api-service 1.75.0-rc.2 → 1.75.0-rc.3
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.75.0-rc.
|
|
3
|
+
"version": "1.75.0-rc.3",
|
|
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.75.0-rc.
|
|
39
|
-
"@defra-fish/connectors-lib": "1.75.0-rc.
|
|
40
|
-
"@defra-fish/dynamics-lib": "1.75.0-rc.
|
|
38
|
+
"@defra-fish/business-rules-lib": "1.75.0-rc.3",
|
|
39
|
+
"@defra-fish/connectors-lib": "1.75.0-rc.3",
|
|
40
|
+
"@defra-fish/dynamics-lib": "1.75.0-rc.3",
|
|
41
41
|
"@hapi/boom": "9.1.2",
|
|
42
42
|
"@hapi/hapi": "20.1.3",
|
|
43
43
|
"@hapi/inert": "6.0.3",
|
|
@@ -53,5 +53,5 @@
|
|
|
53
53
|
"moment-timezone": "0.5.34",
|
|
54
54
|
"uuid": "8.3.2"
|
|
55
55
|
},
|
|
56
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "0f6912bc7a73c58e2d3494529adc62ce8d65e9e5"
|
|
57
57
|
}
|
|
@@ -168,50 +168,29 @@ describe('cancelRecurringPaymentRequestParamsSchema', () => {
|
|
|
168
168
|
})
|
|
169
169
|
})
|
|
170
170
|
|
|
171
|
-
describe('cancelRecurringPaymentRequestParamsSchema', () => {
|
|
172
|
-
it('validates expected object', async () => {
|
|
173
|
-
const sampleData = { id: 'abc123' }
|
|
174
|
-
expect(() => cancelRecurringPaymentRequestParamsSchema.validateAsync(sampleData)).not.toThrow()
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
it('throws an error if id missing', async () => {
|
|
178
|
-
expect(() => cancelRecurringPaymentRequestParamsSchema.validateAsync({}).rejects.toThrow())
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
it('throws an error if id is not the correct type', async () => {
|
|
182
|
-
const sampleData = { id: 99 }
|
|
183
|
-
expect(() => cancelRecurringPaymentRequestParamsSchema.validateAsync(sampleData).rejects.toThrow())
|
|
184
|
-
})
|
|
185
|
-
})
|
|
186
|
-
|
|
187
171
|
describe('cancelRecurringPaymentRequestQuerySchema', () => {
|
|
188
|
-
it('
|
|
189
|
-
const sampleData = { reason
|
|
190
|
-
expect(
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
it('validates expected object if the reason is User Cancelled', async () => {
|
|
194
|
-
const sampleData = { reason: 'User Cancelled' }
|
|
195
|
-
expect(() => cancelRecurringPaymentRequestQuerySchema.validateAsync(sampleData)).not.toThrow()
|
|
172
|
+
it.each(['Payment Failure', 'User Cancelled', 'Business Cancelled'])('successfully validates when reason is %s', async reason => {
|
|
173
|
+
const sampleData = { reason }
|
|
174
|
+
await expect(cancelRecurringPaymentRequestQuerySchema.validateAsync(sampleData)).resolves.toEqual(sampleData)
|
|
196
175
|
})
|
|
197
176
|
|
|
198
177
|
it('throws an error if query is empty', async () => {
|
|
199
178
|
const sampleData = {}
|
|
200
|
-
expect(
|
|
179
|
+
await expect(cancelRecurringPaymentRequestQuerySchema.validateAsync(sampleData)).rejects.toThrow()
|
|
201
180
|
})
|
|
202
181
|
|
|
203
182
|
it('throws an error if reason is not the correct type', async () => {
|
|
204
183
|
const sampleData = { reason: 99 }
|
|
205
|
-
expect(
|
|
184
|
+
await expect(cancelRecurringPaymentRequestQuerySchema.validateAsync(sampleData)).rejects.toThrow()
|
|
206
185
|
})
|
|
207
186
|
|
|
208
|
-
it('throws an error if reason is
|
|
209
|
-
const sampleData = { reason: '
|
|
210
|
-
expect(
|
|
187
|
+
it('throws an error if reason is invalid', async () => {
|
|
188
|
+
const sampleData = { reason: 'Not A Valid Reason' }
|
|
189
|
+
await expect(cancelRecurringPaymentRequestQuerySchema.validateAsync(sampleData)).rejects.toThrow()
|
|
211
190
|
})
|
|
212
191
|
|
|
213
192
|
it('throws an error if an incorrect key is provided', async () => {
|
|
214
193
|
const sampleData = { foo: 'Payment Failure' }
|
|
215
|
-
expect(
|
|
194
|
+
await expect(cancelRecurringPaymentRequestQuerySchema.validateAsync(sampleData)).rejects.toThrow()
|
|
216
195
|
})
|
|
217
196
|
})
|
|
@@ -35,5 +35,5 @@ export const cancelRecurringPaymentRequestParamsSchema = Joi.object({
|
|
|
35
35
|
})
|
|
36
36
|
|
|
37
37
|
export const cancelRecurringPaymentRequestQuerySchema = Joi.object({
|
|
38
|
-
reason: Joi.string().required().valid('Payment Failure', 'User Cancelled')
|
|
38
|
+
reason: Joi.string().required().valid('Payment Failure', 'User Cancelled', 'Business Cancelled')
|
|
39
39
|
})
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
executeQuery,
|
|
4
4
|
findDueRecurringPayments,
|
|
5
5
|
findRecurringPaymentsByAgreementId,
|
|
6
|
-
|
|
6
|
+
findRecurringPaymentById,
|
|
7
7
|
Permission,
|
|
8
8
|
persist,
|
|
9
9
|
RecurringPayment,
|
|
@@ -48,7 +48,6 @@ const { docClient, sqs } = AWS.mock.results[0].value
|
|
|
48
48
|
jest.mock('@defra-fish/dynamics-lib', () => ({
|
|
49
49
|
...jest.requireActual('@defra-fish/dynamics-lib'),
|
|
50
50
|
executeQuery: jest.fn(),
|
|
51
|
-
findById: jest.fn(),
|
|
52
51
|
findDueRecurringPayments: jest.fn(),
|
|
53
52
|
findRecurringPaymentsByAgreementId: jest.fn(() => ({ toRetrieveRequest: () => {} })),
|
|
54
53
|
dynamicsClient: {
|
|
@@ -56,6 +55,7 @@ jest.mock('@defra-fish/dynamics-lib', () => ({
|
|
|
56
55
|
},
|
|
57
56
|
persist: jest.fn(),
|
|
58
57
|
findRecurringPaymentByPermissionId: jest.fn(() => ({ toRetrieveRequest: () => {} })),
|
|
58
|
+
findRecurringPaymentById: jest.fn(() => ({ toRetrieveRequest: () => {} })),
|
|
59
59
|
retrieveGlobalOptionSets: jest.fn()
|
|
60
60
|
}))
|
|
61
61
|
|
|
@@ -70,7 +70,8 @@ jest.mock('@defra-fish/connectors-lib', () => ({
|
|
|
70
70
|
}
|
|
71
71
|
})),
|
|
72
72
|
govUkPayApi: {
|
|
73
|
-
getRecurringPaymentAgreementInformation: jest.fn()
|
|
73
|
+
getRecurringPaymentAgreementInformation: jest.fn(),
|
|
74
|
+
cancelRecurringPaymentAgreement: jest.fn()
|
|
74
75
|
}
|
|
75
76
|
}))
|
|
76
77
|
|
|
@@ -895,93 +896,237 @@ describe('recurring payments service', () => {
|
|
|
895
896
|
})
|
|
896
897
|
|
|
897
898
|
describe('cancelRecurringPayment', () => {
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
899
|
+
const createLinkedPermission = (overrides = {}) => {
|
|
900
|
+
const permission = new Permission()
|
|
901
|
+
permission.isRecurringPayment = true
|
|
902
|
+
return Object.assign(permission, overrides)
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
const arrangeCancelRecurringPayment = ({
|
|
906
|
+
recurringPayment = getMockRecurringPayment(),
|
|
907
|
+
permissionOverrides = {},
|
|
908
|
+
govUkPayResponse = { ok: true, status: 204 },
|
|
909
|
+
linkedPermission = createLinkedPermission(permissionOverrides)
|
|
910
|
+
} = {}) => {
|
|
911
|
+
govUkPayApi.cancelRecurringPaymentAgreement.mockResolvedValueOnce(govUkPayResponse)
|
|
912
|
+
executeQuery.mockResolvedValueOnce([{ entity: recurringPayment, expanded: { activePermission: { entity: linkedPermission } } }])
|
|
913
|
+
return { recurringPayment, linkedPermission }
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
it.each(['92207e2c-0aa2-4600-a9c4-a7eb2276e404', 'f794dd0d-b80a-4d51-b738-479c87b4765b', 'b437d543-430f-4eb1-830d-539148c8b440'])(
|
|
917
|
+
'passes recurring payment id to findRecurringPaymentById when id is %s',
|
|
918
|
+
async id => {
|
|
919
|
+
arrangeCancelRecurringPayment()
|
|
920
|
+
|
|
921
|
+
await cancelRecurringPayment(id, 'Payment Failure')
|
|
922
|
+
|
|
923
|
+
expect(findRecurringPaymentById).toHaveBeenCalledWith(id)
|
|
924
|
+
}
|
|
925
|
+
)
|
|
926
|
+
|
|
927
|
+
it('passes query created by findRecurringPaymentById to executeQuery', async () => {
|
|
928
|
+
const query = Symbol('query')
|
|
929
|
+
findRecurringPaymentById.mockReturnValueOnce(query)
|
|
930
|
+
arrangeCancelRecurringPayment()
|
|
931
|
+
|
|
932
|
+
await cancelRecurringPayment('id', 'User Cancelled')
|
|
933
|
+
|
|
934
|
+
expect(executeQuery).toHaveBeenCalledWith(query)
|
|
904
935
|
})
|
|
905
936
|
|
|
906
|
-
it('
|
|
907
|
-
|
|
908
|
-
findById.mockReturnValueOnce(getMockRecurringPayment())
|
|
937
|
+
it('passes reason to getGlobalOptionSetValue', async () => {
|
|
938
|
+
arrangeCancelRecurringPayment()
|
|
909
939
|
const reason = Symbol('unique-reason')
|
|
940
|
+
|
|
910
941
|
await cancelRecurringPayment('abc123', reason)
|
|
942
|
+
|
|
911
943
|
expect(getGlobalOptionSetValue).toHaveBeenCalledWith(RecurringPayment.definition.mappings.cancelledReason.ref, reason)
|
|
912
944
|
})
|
|
913
945
|
|
|
914
|
-
it('
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
946
|
+
it('sets cancelledReason on the persisted recurring payment', async () => {
|
|
947
|
+
const cancelledReason = { description: 'User Cancelled', id: 910400003, label: 'User Cancelled' }
|
|
948
|
+
const recurringPayment = { ...getMockRecurringPayment(), cancelledDate: null }
|
|
949
|
+
const { linkedPermission } = arrangeCancelRecurringPayment({ recurringPayment })
|
|
950
|
+
|
|
951
|
+
getGlobalOptionSetValue.mockReturnValueOnce(cancelledReason)
|
|
952
|
+
|
|
953
|
+
await cancelRecurringPayment('id', 'User Cancelled')
|
|
954
|
+
|
|
955
|
+
expect(persist).toHaveBeenCalledWith([
|
|
956
|
+
expect.objectContaining({
|
|
957
|
+
...recurringPayment,
|
|
958
|
+
cancelledReason
|
|
959
|
+
}),
|
|
960
|
+
linkedPermission
|
|
961
|
+
])
|
|
962
|
+
})
|
|
963
|
+
|
|
964
|
+
it('returns the updated recurring payment', async () => {
|
|
965
|
+
const recurringPayment = getMockRecurringPayment()
|
|
966
|
+
arrangeCancelRecurringPayment({ recurringPayment })
|
|
967
|
+
|
|
968
|
+
const result = await cancelRecurringPayment('id', 'User Cancelled')
|
|
969
|
+
|
|
970
|
+
expect(result).toEqual(
|
|
971
|
+
expect.objectContaining({
|
|
972
|
+
...recurringPayment,
|
|
973
|
+
cancelledDate: expect.any(String),
|
|
974
|
+
cancelledReason: expect.any(Object)
|
|
926
975
|
})
|
|
927
|
-
|
|
976
|
+
)
|
|
977
|
+
})
|
|
978
|
+
|
|
979
|
+
it.each(['2026-05-20T09:10:11.123Z', '2027-01-01T00:00:00.000Z'])(
|
|
980
|
+
'sets cancelledDate to current ISO date-time when now is %s',
|
|
981
|
+
async nowIsoString => {
|
|
982
|
+
const recurringPayment = getMockRecurringPayment()
|
|
983
|
+
const { linkedPermission } = arrangeCancelRecurringPayment({ recurringPayment })
|
|
984
|
+
jest.useFakeTimers().setSystemTime(new Date(nowIsoString))
|
|
985
|
+
|
|
986
|
+
try {
|
|
987
|
+
await cancelRecurringPayment('id', 'User Cancelled')
|
|
988
|
+
|
|
989
|
+
expect(persist).toHaveBeenCalledWith([
|
|
990
|
+
expect.objectContaining({
|
|
991
|
+
...recurringPayment,
|
|
992
|
+
cancelledDate: nowIsoString
|
|
993
|
+
}),
|
|
994
|
+
linkedPermission
|
|
995
|
+
])
|
|
996
|
+
} finally {
|
|
997
|
+
jest.useRealTimers()
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
)
|
|
928
1001
|
|
|
1002
|
+
it('calls GovUKPay cancellation with agreement id', async () => {
|
|
929
1003
|
const recurringPayment = getMockRecurringPayment()
|
|
930
|
-
|
|
1004
|
+
arrangeCancelRecurringPayment({ recurringPayment })
|
|
931
1005
|
|
|
932
|
-
|
|
1006
|
+
await cancelRecurringPayment('id', 'User Cancelled')
|
|
933
1007
|
|
|
934
|
-
|
|
1008
|
+
expect(govUkPayApi.cancelRecurringPaymentAgreement).toHaveBeenCalledWith(recurringPayment.agreementId)
|
|
1009
|
+
})
|
|
1010
|
+
|
|
1011
|
+
it('debug outputs success message with agreement id when GovUKPay cancellation succeeds', async () => {
|
|
1012
|
+
const recurringPayment = getMockRecurringPayment()
|
|
1013
|
+
arrangeCancelRecurringPayment({ recurringPayment })
|
|
1014
|
+
|
|
1015
|
+
await cancelRecurringPayment('id', 'User Cancelled')
|
|
1016
|
+
|
|
1017
|
+
expect(debug).toHaveBeenCalledWith('Successfully cancelled GovUkPay agreement: %s', recurringPayment.agreementId)
|
|
1018
|
+
})
|
|
1019
|
+
|
|
1020
|
+
it.each([
|
|
1021
|
+
['404 not found', 404, 'GovUkPay agreement not found (already cancelled or does not exist): %s'],
|
|
1022
|
+
['400 bad request', 400, 'GovUkPay agreement cannot be cancelled (invalid state): %s']
|
|
1023
|
+
])('debug outputs message with agreement id when GovUKPay returns %s', async (_description, status, expectedMessage) => {
|
|
1024
|
+
const recurringPayment = getMockRecurringPayment()
|
|
1025
|
+
arrangeCancelRecurringPayment({ recurringPayment, govUkPayResponse: { ok: false, status } })
|
|
1026
|
+
|
|
1027
|
+
await cancelRecurringPayment('id', 'User Cancelled')
|
|
1028
|
+
|
|
1029
|
+
expect(debug).toHaveBeenCalledWith(expectedMessage, recurringPayment.agreementId)
|
|
1030
|
+
})
|
|
1031
|
+
|
|
1032
|
+
it('throws when recurring payment has no agreement id', async () => {
|
|
1033
|
+
const recurringPayment = getMockRecurringPayment({ agreementId: undefined })
|
|
1034
|
+
|
|
1035
|
+
executeQuery.mockResolvedValueOnce([
|
|
1036
|
+
{ entity: recurringPayment, expanded: { activePermission: { entity: createLinkedPermission() } } }
|
|
1037
|
+
])
|
|
1038
|
+
|
|
1039
|
+
await expect(cancelRecurringPayment('recurring-payment-id', 'Payment Failure')).rejects.toThrow(
|
|
1040
|
+
'Cannot cancel a recurring payment without an agreement ID: recurring-payment-id'
|
|
1041
|
+
)
|
|
1042
|
+
})
|
|
1043
|
+
|
|
1044
|
+
it.each([
|
|
1045
|
+
['404 not found', 404, 'Not Found'],
|
|
1046
|
+
['400 bad request', 400, 'Bad Request']
|
|
1047
|
+
])('does not throw when GovUKPay returns %s', async (_description, status, statusText) => {
|
|
1048
|
+
arrangeCancelRecurringPayment({ govUkPayResponse: { ok: false, status, statusText } })
|
|
1049
|
+
|
|
1050
|
+
await expect(cancelRecurringPayment('id', 'User Cancelled')).resolves.toBeDefined()
|
|
1051
|
+
})
|
|
1052
|
+
|
|
1053
|
+
it.each([
|
|
1054
|
+
['404 not found', 404, 'Not Found'],
|
|
1055
|
+
['400 bad request', 400, 'Bad Request']
|
|
1056
|
+
])('persists cancellation in CRM when GovUKPay returns %s', async (_description, status, statusText) => {
|
|
1057
|
+
const recurringPayment = getMockRecurringPayment()
|
|
1058
|
+
const { linkedPermission } = arrangeCancelRecurringPayment({ recurringPayment, govUkPayResponse: { ok: false, status, statusText } })
|
|
1059
|
+
|
|
1060
|
+
await cancelRecurringPayment('id', 'User Cancelled')
|
|
935
1061
|
|
|
936
1062
|
expect(persist).toHaveBeenCalledWith([
|
|
937
1063
|
expect.objectContaining({
|
|
938
1064
|
...recurringPayment,
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
})
|
|
1065
|
+
cancelledDate: expect.any(String),
|
|
1066
|
+
cancelledReason: expect.any(Object)
|
|
1067
|
+
}),
|
|
1068
|
+
linkedPermission
|
|
942
1069
|
])
|
|
943
1070
|
})
|
|
944
1071
|
|
|
945
|
-
it('
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
label: 'User Cancelled',
|
|
953
|
-
description: 'User Cancelled'
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
})
|
|
1072
|
+
it('throws when GovUKPay returns unexpected status', async () => {
|
|
1073
|
+
arrangeCancelRecurringPayment()
|
|
1074
|
+
govUkPayApi.cancelRecurringPaymentAgreement.mockReset().mockResolvedValueOnce({
|
|
1075
|
+
ok: false,
|
|
1076
|
+
status: 500,
|
|
1077
|
+
statusText: 'Internal Server Error',
|
|
1078
|
+
text: jest.fn().mockResolvedValue('Server error')
|
|
958
1079
|
})
|
|
959
1080
|
|
|
960
|
-
|
|
1081
|
+
await expect(cancelRecurringPayment('id', 'User Cancelled')).rejects.toThrow('Failed to cancel GovUkPay agreement')
|
|
1082
|
+
})
|
|
961
1083
|
|
|
962
|
-
|
|
963
|
-
|
|
1084
|
+
it('sets linked permission isRecurringPayment to false', async () => {
|
|
1085
|
+
const { linkedPermission } = arrangeCancelRecurringPayment()
|
|
964
1086
|
|
|
965
|
-
|
|
1087
|
+
await cancelRecurringPayment('id', 'User Cancelled')
|
|
1088
|
+
|
|
1089
|
+
expect(linkedPermission.isRecurringPayment).toBe(false)
|
|
1090
|
+
})
|
|
1091
|
+
|
|
1092
|
+
it('persists linked permission alongside recurring payment', async () => {
|
|
1093
|
+
const recurringPayment = getMockRecurringPayment()
|
|
1094
|
+
const { linkedPermission } = arrangeCancelRecurringPayment({ recurringPayment })
|
|
966
1095
|
|
|
967
1096
|
await cancelRecurringPayment('id', 'User Cancelled')
|
|
968
1097
|
|
|
969
1098
|
expect(persist).toHaveBeenCalledWith([
|
|
970
1099
|
expect.objectContaining({
|
|
971
1100
|
...recurringPayment,
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
})
|
|
1101
|
+
cancelledDate: expect.any(String),
|
|
1102
|
+
cancelledReason: expect.any(Object)
|
|
1103
|
+
}),
|
|
1104
|
+
linkedPermission
|
|
975
1105
|
])
|
|
976
1106
|
})
|
|
977
1107
|
|
|
978
|
-
it('
|
|
979
|
-
|
|
1108
|
+
it('throws when recurring payment id is invalid', async () => {
|
|
1109
|
+
executeQuery.mockResolvedValueOnce([])
|
|
980
1110
|
|
|
981
|
-
await expect(cancelRecurringPayment('id', 'Payment Failure')).rejects.toThrow(
|
|
982
|
-
'Invalid id provided for recurring payment cancellation'
|
|
1111
|
+
await expect(cancelRecurringPayment('recurring-payment-id', 'Payment Failure')).rejects.toThrow(
|
|
1112
|
+
'Invalid id provided for recurring payment cancellation: recurring-payment-id'
|
|
983
1113
|
)
|
|
984
1114
|
})
|
|
1115
|
+
|
|
1116
|
+
it('throws when no active permission is linked to the recurring payment', async () => {
|
|
1117
|
+
executeQuery.mockResolvedValueOnce([{ entity: getMockRecurringPayment(), expanded: {} }])
|
|
1118
|
+
|
|
1119
|
+
await expect(cancelRecurringPayment('recurring-payment-id', 'Payment Failure')).rejects.toThrow(
|
|
1120
|
+
'No active permission linked to recurring payment: recurring-payment-id'
|
|
1121
|
+
)
|
|
1122
|
+
})
|
|
1123
|
+
|
|
1124
|
+
it('propagates persist error after successful GovUKPay cancellation', async () => {
|
|
1125
|
+
arrangeCancelRecurringPayment()
|
|
1126
|
+
persist.mockRejectedValueOnce(new Error('CRM unavailable'))
|
|
1127
|
+
|
|
1128
|
+
await expect(cancelRecurringPayment('id', 'User Cancelled')).rejects.toThrow('CRM unavailable')
|
|
1129
|
+
})
|
|
985
1130
|
})
|
|
986
1131
|
|
|
987
1132
|
describe('findLinkedRecurringPayment', () => {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
dynamicsClient,
|
|
3
3
|
executeQuery,
|
|
4
|
-
findById,
|
|
5
4
|
findDueRecurringPayments,
|
|
6
5
|
findRecurringPaymentsByAgreementId,
|
|
6
|
+
findRecurringPaymentById,
|
|
7
7
|
persist,
|
|
8
8
|
RecurringPayment,
|
|
9
9
|
findRecurringPaymentByPermissionId,
|
|
@@ -21,6 +21,7 @@ import { getGlobalOptionSetValue } from './reference-data.service.js'
|
|
|
21
21
|
import moment from 'moment'
|
|
22
22
|
import { AWS, govUkPayApi } from '@defra-fish/connectors-lib'
|
|
23
23
|
import db from 'debug'
|
|
24
|
+
import { StatusCodes } from 'http-status-codes'
|
|
24
25
|
const debug = db('sales:recurring')
|
|
25
26
|
const { sqs, docClient } = AWS()
|
|
26
27
|
|
|
@@ -167,21 +168,43 @@ export const findNewestExistingRecurringPaymentInCrm = async agreementId => {
|
|
|
167
168
|
}
|
|
168
169
|
|
|
169
170
|
export const cancelRecurringPayment = async (id, reason) => {
|
|
170
|
-
const
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
-
|
|
171
|
+
const [result] = await executeQuery(findRecurringPaymentById(id))
|
|
172
|
+
if (!result) {
|
|
173
|
+
throw new Error(`Invalid id provided for recurring payment cancellation: ${id}`)
|
|
174
|
+
}
|
|
174
175
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
const recurringPayment = result.entity
|
|
177
|
+
const linkedPermission = result.expanded?.activePermission?.entity
|
|
178
|
+
if (!linkedPermission) {
|
|
179
|
+
throw new Error(`No active permission linked to recurring payment: ${id}`)
|
|
180
|
+
}
|
|
181
|
+
if (!recurringPayment.agreementId) {
|
|
182
|
+
throw new Error(`Cannot cancel a recurring payment without an agreement ID: ${id}`)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
recurringPayment.cancelledDate = new Date().toISOString()
|
|
186
|
+
recurringPayment.cancelledReason = await getGlobalOptionSetValue(RecurringPayment.definition.mappings.cancelledReason.ref, reason)
|
|
178
187
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
188
|
+
await cancelGovUkPayAgreement(recurringPayment.agreementId)
|
|
189
|
+
|
|
190
|
+
const updatedRecurringPayment = Object.assign(new RecurringPayment(), recurringPayment)
|
|
191
|
+
linkedPermission.isRecurringPayment = false
|
|
192
|
+
|
|
193
|
+
await persist([updatedRecurringPayment, linkedPermission])
|
|
194
|
+
return updatedRecurringPayment
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const cancelGovUkPayAgreement = async agreementId => {
|
|
198
|
+
const response = await govUkPayApi.cancelRecurringPaymentAgreement(agreementId)
|
|
199
|
+
if (response.ok) {
|
|
200
|
+
debug('Successfully cancelled GovUkPay agreement: %s', agreementId)
|
|
201
|
+
} else if (response.status === StatusCodes.NOT_FOUND) {
|
|
202
|
+
debug('GovUkPay agreement not found (already cancelled or does not exist): %s', agreementId)
|
|
203
|
+
} else if (response.status === StatusCodes.BAD_REQUEST) {
|
|
204
|
+
debug('GovUkPay agreement cannot be cancelled (invalid state): %s', agreementId)
|
|
183
205
|
} else {
|
|
184
|
-
|
|
206
|
+
const body = await response.text().catch(() => 'Unable to read response body')
|
|
207
|
+
throw new Error(`Failed to cancel GovUkPay agreement ${agreementId}: ${response.status} ${response.statusText} - ${body}`)
|
|
185
208
|
}
|
|
186
209
|
}
|
|
187
210
|
|