Skip to content

Commit

Permalink
include additional information in D365 invalid bank alerts (#221)
Browse files Browse the repository at this point in the history
  • Loading branch information
samplackett authored Jan 3, 2025
1 parent 17761d2 commit bf98abd
Show file tree
Hide file tree
Showing 9 changed files with 66 additions and 54 deletions.
4 changes: 2 additions & 2 deletions app/acknowledgement/process-acknowledgement.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ const processAcknowledgement = async (acknowledgement) => {
if (acknowledgement.success) {
await sendAckEvent(acknowledgement)
} else if (!acknowledgement.message?.toLowerCase().includes('duplicate')) {
const { schemeId, paymentRequestId, frn, sourceSystem } = await getPaymentRequest(acknowledgement.invoiceNumber)
await processInvalid(schemeId, paymentRequestId, frn, sourceSystem, acknowledgement)
const paymentRequest = await getPaymentRequest(acknowledgement.invoiceNumber)
await processInvalid(paymentRequest, acknowledgement)
}
}

Expand Down
5 changes: 3 additions & 2 deletions app/acknowledgement/process-invalid.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ const { getHoldCategoryId } = require('../holds')
const { holdAndReschedule } = require('../reschedule')
const { sendAcknowledgementErrorEvent } = require('../event')

const processInvalid = async (schemeId, paymentRequestId, frn, sourceSystem, acknowledgement) => {
const processInvalid = async (paymentRequest, acknowledgement) => {
const transaction = await db.sequelize.transaction()
try {
const { schemeId, paymentRequestId, frn } = paymentRequest
await resetPaymentRequestById(paymentRequestId, transaction)
const holdCategoryName = getHoldCategoryName(acknowledgement.message)
const holdCategoryId = await getHoldCategoryId(schemeId, holdCategoryName, transaction)
await holdAndReschedule(paymentRequestId, holdCategoryId, frn, transaction)
await sendAcknowledgementErrorEvent(holdCategoryName, acknowledgement, frn, sourceSystem)
await sendAcknowledgementErrorEvent(holdCategoryName, acknowledgement, paymentRequest)

await transaction.commit()
} catch (error) {
Expand Down
15 changes: 11 additions & 4 deletions app/event/send-ack-invalid-bank-details-error-event.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,27 @@ const { EventPublisher } = require('ffc-pay-event-publisher')
const { SOURCE } = require('../constants/source')
const { PAYMENT_INVALID_BANK } = require('../constants/events')

const sendAckInvalidBankDetailsErrorEvent = async (frn, sourceSystem) => {
const sendAckInvalidBankDetailsErrorEvent = async (paymentRequest) => {
if (processingConfig.useV2Events) {
await sendV2AckInvalidBankDetailsErrorEvent(frn, sourceSystem)
await sendV2AckInvalidBankDetailsErrorEvent(paymentRequest)
}
}

const sendV2AckInvalidBankDetailsErrorEvent = async (frn, sourceSystem) => {
const sendV2AckInvalidBankDetailsErrorEvent = async (paymentRequest) => {
const { frn, sourceSystem, contractNumber, agreementNumber, batch, claimDate, value, sbi } = paymentRequest
const event = {
source: SOURCE,
type: PAYMENT_INVALID_BANK,
data: {
message: 'No valid bank details held',
frn,
sourceSystem
sourceSystem,
contractNumber,
agreementNumber,
batch,
claimDate,
value,
sbi
}
}
const eventPublisher = new EventPublisher(messageConfig.eventsTopic)
Expand Down
4 changes: 2 additions & 2 deletions app/event/send-acknowledgement-error-event.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ const { sendAckInvalidBankDetailsErrorEvent } = require('./send-ack-invalid-bank
const { sendProcessingAckErrorEvent } = require('./send-ack-error-event')
const { BANK_ACCOUNT_ANOMALY } = require('../constants/hold-categories-names')

const sendAcknowledgementErrorEvent = async (holdCategoryName, acknowledgement, frn, sourceSystem) => {
const sendAcknowledgementErrorEvent = async (holdCategoryName, acknowledgement, paymentRequest) => {
if (holdCategoryName === BANK_ACCOUNT_ANOMALY) {
await sendAckInvalidBankDetailsErrorEvent(frn, sourceSystem)
await sendAckInvalidBankDetailsErrorEvent(paymentRequest)
} else {
await sendProcessingAckErrorEvent(acknowledgement)
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ffc-pay-processing",
"version": "2.55.33",
"version": "2.55.34",
"description": "Payment Hub processing service",
"homepage": "https://github.com/DEFRA/ffc-pay-processing",
"main": "app/index.js",
Expand Down
10 changes: 5 additions & 5 deletions test/unit/acknowledgement/process-acknowledgement.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ const { getPaymentRequest: mockGetPaymentRequest } = require('../../../app/ackno
jest.mock('../../../app/acknowledgement/process-invalid')
const { processInvalid: mockProcessInvalid } = require('../../../app/acknowledgement/process-invalid')

const paymentRequest = require('../../mocks/payment-requests/payment-request')

const { processAcknowledgement } = require('../../../app/acknowledgement/process-acknowledgement')

let acknowledgement
let paymentRequest

describe('process acknowledgement', () => {
beforeEach(() => {
jest.clearAllMocks()
acknowledgement = JSON.parse(JSON.stringify(require('../../mocks/acknowledgement')))
paymentRequest = JSON.parse(JSON.stringify(require('../../mocks/payment-requests/payment-request')))
mockGetPaymentRequest.mockResolvedValue(paymentRequest)
})

Expand All @@ -39,7 +39,7 @@ describe('process acknowledgement', () => {
expect(mockSendAckEvent).not.toHaveBeenCalled()
})

test('should get associated payment request by invoice number if not successfully acknowledged', async () => {
test('should get associated payment request by invoice number if not successfully acknowledged and message does not contain "Duplicate"', async () => {
acknowledgement.success = false
await processAcknowledgement(acknowledgement)
expect(mockGetPaymentRequest).toHaveBeenCalledWith(acknowledgement.invoiceNumber)
Expand All @@ -50,10 +50,10 @@ describe('process acknowledgement', () => {
expect(mockGetPaymentRequest).not.toHaveBeenCalled()
})

test('should process invalid acknowledgement if not successfully acknowledged', async () => {
test('should process invalid acknowledgement if not successfully acknowledged and message does not contain "Duplicate"', async () => {
acknowledgement.success = false
await processAcknowledgement(acknowledgement)
expect(mockProcessInvalid).toHaveBeenCalledWith(paymentRequest.schemeId, paymentRequest.paymentRequestId, paymentRequest.frn, paymentRequest.sourceSystem, acknowledgement)
expect(mockProcessInvalid).toHaveBeenCalledWith(paymentRequest, acknowledgement)
})

test('should not process invalid acknowledgement if successfully acknowledged', async () => {
Expand Down
39 changes: 16 additions & 23 deletions test/unit/acknowledgement/process-invalid.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,68 +32,61 @@ jest.mock('../../../app/event')
const { sendAcknowledgementErrorEvent: mockSendAcknowledgementErrorEvent } = require('../../../app/event')

const acknowledgement = require('../../mocks/acknowledgement')
const { FRN } = require('../../mocks/values/frn')

const { DAX_REJECTION } = require('../../../app/constants/hold-categories-names')
const { SFI } = require('../../../app/constants/schemes')
const paymentRequest = require('../../mocks/payment-requests/payment-request')

const { processInvalid } = require('../../../app/acknowledgement/process-invalid')

const PAYMENT_REQUEST_ID = 1
const HOLD_CATEGORY_ID = 1
const SOURCE_SYSTEM = 'sourceSystem'

describe('process invalid acknowledgements', () => {
beforeEach(() => {
jest.clearAllMocks()

mockGetHoldCategoryName.mockReturnValue(DAX_REJECTION)
mockGetHoldCategoryId.mockResolvedValue(HOLD_CATEGORY_ID)
mockGetHoldCategoryName.mockReturnValue('DAX_REJECTION')
mockGetHoldCategoryId.mockResolvedValue(1)
})

test('should create new database transaction', async () => {
await processInvalid(SFI, PAYMENT_REQUEST_ID, FRN, SOURCE_SYSTEM, acknowledgement)
await processInvalid(paymentRequest, acknowledgement)
expect(mockTransaction).toHaveBeenCalledTimes(1)
})

test('should reset payment request by id in transaction scope', async () => {
await processInvalid(SFI, PAYMENT_REQUEST_ID, FRN, SOURCE_SYSTEM, acknowledgement)
expect(mockResetPaymentRequestById).toHaveBeenCalledWith(PAYMENT_REQUEST_ID, mockTransactionObject)
await processInvalid(paymentRequest, acknowledgement)
expect(mockResetPaymentRequestById).toHaveBeenCalledWith(paymentRequest.paymentRequestId, mockTransactionObject)
})

test('should get hold category name from acknowledgement message', async () => {
await processInvalid(SFI, PAYMENT_REQUEST_ID, FRN, SOURCE_SYSTEM, acknowledgement)
await processInvalid(paymentRequest, acknowledgement)
expect(mockGetHoldCategoryName).toHaveBeenCalledWith(acknowledgement.message)
})

test('should get hold category id from scheme id and hold category name', async () => {
await processInvalid(SFI, PAYMENT_REQUEST_ID, FRN, SOURCE_SYSTEM, acknowledgement)
expect(mockGetHoldCategoryId).toHaveBeenCalledWith(SFI, DAX_REJECTION, mockTransactionObject)
await processInvalid(paymentRequest, acknowledgement)
expect(mockGetHoldCategoryId).toHaveBeenCalledWith(paymentRequest.schemeId, 'DAX_REJECTION', mockTransactionObject)
})

test('should hold and reschedule payment request', async () => {
await processInvalid(SFI, PAYMENT_REQUEST_ID, FRN, SOURCE_SYSTEM, acknowledgement)
expect(mockHoldAndReschedule).toHaveBeenCalledWith(PAYMENT_REQUEST_ID, HOLD_CATEGORY_ID, FRN, mockTransactionObject)
await processInvalid(paymentRequest, acknowledgement)
expect(mockHoldAndReschedule).toHaveBeenCalledWith(paymentRequest.paymentRequestId, 1, paymentRequest.frn, mockTransactionObject)
})

test('should send acknowledgement error event', async () => {
await processInvalid(SFI, PAYMENT_REQUEST_ID, FRN, SOURCE_SYSTEM, acknowledgement)
expect(mockSendAcknowledgementErrorEvent).toHaveBeenCalledWith(DAX_REJECTION, acknowledgement, FRN, SOURCE_SYSTEM)
await processInvalid(paymentRequest, acknowledgement)
expect(mockSendAcknowledgementErrorEvent).toHaveBeenCalledWith('DAX_REJECTION', acknowledgement, paymentRequest)
})

test('should commit transaction', async () => {
await processInvalid(SFI, PAYMENT_REQUEST_ID, FRN, SOURCE_SYSTEM, acknowledgement)
await processInvalid(paymentRequest, acknowledgement)
expect(mockCommit).toHaveBeenCalledTimes(1)
})

test('should rollback transaction on error', async () => {
mockHoldAndReschedule.mockRejectedValue(new Error('test error'))
await expect(processInvalid(SFI, PAYMENT_REQUEST_ID, FRN, SOURCE_SYSTEM, acknowledgement)).rejects.toThrow('test error')
await expect(processInvalid(paymentRequest, acknowledgement)).rejects.toThrow('test error')
expect(mockRollback).toHaveBeenCalledTimes(1)
})

test('should throw error on error', async () => {
mockHoldAndReschedule.mockRejectedValue(new Error('test error'))
await expect(processInvalid(SFI, PAYMENT_REQUEST_ID, FRN, SOURCE_SYSTEM, acknowledgement)).rejects.toThrow('test error')
await expect(processInvalid(paymentRequest, acknowledgement)).rejects.toThrow('test error')
})
})
28 changes: 20 additions & 8 deletions test/unit/event/send-ack-invalid-bank-details-error-event.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ const { PAYMENT_INVALID_BANK } = require('../../../app/constants/events')

const { sendAckInvalidBankDetailsErrorEvent } = require('../../../app/event/send-ack-invalid-bank-details-error-event')

const frn = require('../../mocks/values/frn')
const SOURCE_SYSTEM = 'sourceSystem'
const paymentRequest = require('../../mocks/payment-requests/payment-request')

describe('send acknowledgement invalid bank details error event', () => {
beforeEach(() => {
Expand All @@ -31,33 +30,46 @@ describe('send acknowledgement invalid bank details error event', () => {
})

test('should send V2 event if V2 events enabled', async () => {
await sendAckInvalidBankDetailsErrorEvent(frn, SOURCE_SYSTEM)
await sendAckInvalidBankDetailsErrorEvent(paymentRequest)
expect(mockPublishEvent).toHaveBeenCalled()
})

test('should not send V2 event if V2 events disabled', async () => {
processingConfig.useV2Events = false
await sendAckInvalidBankDetailsErrorEvent(frn, SOURCE_SYSTEM)
await sendAckInvalidBankDetailsErrorEvent(paymentRequest)
expect(mockPublishEvent).not.toHaveBeenCalled()
})

test('should send event to V2 topic', async () => {
await sendAckInvalidBankDetailsErrorEvent(frn, SOURCE_SYSTEM)
await sendAckInvalidBankDetailsErrorEvent(paymentRequest)
expect(MockEventPublisher.mock.calls[0][0]).toBe(messageConfig.eventsTopic)
})

test('should raise event with processing source', async () => {
await sendAckInvalidBankDetailsErrorEvent(frn, SOURCE_SYSTEM)
await sendAckInvalidBankDetailsErrorEvent(paymentRequest)
expect(mockPublishEvent.mock.calls[0][0].source).toBe(SOURCE)
})

test('should raise payment invalid bank event type', async () => {
await sendAckInvalidBankDetailsErrorEvent(frn, SOURCE_SYSTEM)
await sendAckInvalidBankDetailsErrorEvent(paymentRequest)
expect(mockPublishEvent.mock.calls[0][0].type).toBe(PAYMENT_INVALID_BANK)
})

test('should include no valid bank details message', async () => {
await sendAckInvalidBankDetailsErrorEvent(frn, SOURCE_SYSTEM)
await sendAckInvalidBankDetailsErrorEvent(paymentRequest)
expect(mockPublishEvent.mock.calls[0][0].data.message).toEqual('No valid bank details held')
})

test('should include payment request details in event data', async () => {
await sendAckInvalidBankDetailsErrorEvent(paymentRequest)
const eventData = mockPublishEvent.mock.calls[0][0].data
expect(eventData.frn).toBe(paymentRequest.frn)
expect(eventData.sourceSystem).toBe(paymentRequest.sourceSystem)
expect(eventData.contractNumber).toBe(paymentRequest.contractNumber)
expect(eventData.agreementNumber).toBe(paymentRequest.agreementNumber)
expect(eventData.batch).toBe(paymentRequest.batch)
expect(eventData.claimDate).toBe(paymentRequest.claimDate)
expect(eventData.value).toBe(paymentRequest.value)
expect(eventData.sbi).toBe(paymentRequest.sbi)
})
})
13 changes: 6 additions & 7 deletions test/unit/event/send-acknowledgement-error-event.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,32 @@ const { BANK_ACCOUNT_ANOMALY } = require('../../../app/constants/hold-categories
const { sendAcknowledgementErrorEvent } = require('../../../app/event/send-acknowledgement-error-event')

const acknowledgement = require('../../mocks/acknowledgement')
const frn = require('../../mocks/values/frn')
const SOURCE_SYSTEM = 'sourceSystem'
const paymentRequest = require('../../mocks/payment-requests/payment-request')

describe('send acknowledgement error event', () => {
beforeEach(() => {
jest.clearAllMocks()
})

test('should send invalid bank details error event if hold category is bank account anomaly', async () => {
await sendAcknowledgementErrorEvent(BANK_ACCOUNT_ANOMALY, acknowledgement, frn, SOURCE_SYSTEM)
expect(mockSendAckInvalidBankDetailsErrorEvent).toHaveBeenCalledWith(frn, SOURCE_SYSTEM)
await sendAcknowledgementErrorEvent(BANK_ACCOUNT_ANOMALY, acknowledgement, paymentRequest)
expect(mockSendAckInvalidBankDetailsErrorEvent).toHaveBeenCalledWith(paymentRequest)
})

test('should send processing acknowledgement error event if hold category is not bank account anomaly', async () => {
const holdCategoryName = 'OTHER_CATEGORY'
await sendAcknowledgementErrorEvent(holdCategoryName, acknowledgement, frn, SOURCE_SYSTEM)
await sendAcknowledgementErrorEvent(holdCategoryName, acknowledgement, paymentRequest)
expect(mockSendProcessingAckErrorEvent).toHaveBeenCalledWith(acknowledgement)
})

test('should not send processing acknowledgement error event if hold category is bank account anomaly', async () => {
await sendAcknowledgementErrorEvent(BANK_ACCOUNT_ANOMALY, acknowledgement, frn, SOURCE_SYSTEM)
await sendAcknowledgementErrorEvent(BANK_ACCOUNT_ANOMALY, acknowledgement, paymentRequest)
expect(mockSendProcessingAckErrorEvent).not.toHaveBeenCalled()
})

test('should not send invalid bank details error event if hold category is not bank account anomaly', async () => {
const holdCategoryName = 'OTHER_CATEGORY'
await sendAcknowledgementErrorEvent(holdCategoryName, acknowledgement, frn, SOURCE_SYSTEM)
await sendAcknowledgementErrorEvent(holdCategoryName, acknowledgement, paymentRequest)
expect(mockSendAckInvalidBankDetailsErrorEvent).not.toHaveBeenCalled()
})
})

0 comments on commit bf98abd

Please sign in to comment.