UNPKG

@defra-fish/payment-mop-up-job

Version:

Process incomplete web-sales

229 lines (211 loc) • 8.04 kB
import { salesApi, govUkPayApi } from '@defra-fish/connectors-lib' import { execute } from '../processor.js' import { GOVUK_PAY_ERROR_STATUS_CODES, PAYMENT_JOURNAL_STATUS_CODES } from '@defra-fish/business-rules-lib' import moment from 'moment' jest.mock('@defra-fish/connectors-lib') const journalEntries = [ { id: '4fa393ab-07f4-407e-b233-89be2a6f5f77', paymentStatus: 'In Progress', paymentReference: '05nioqikvvnuu5l8m2qeaj0qap', paymentTimestamp: '2020-06-01T10:35:56.873Z' }, { id: 'aaced854-d337-47ee-8d5e-75b26aeb90fb', paymentStatus: 'In Progress', paymentReference: '0f3dr9ugp7u68qq18vt9h8ma85', paymentTimestamp: '2020-06-02T07:17:23.169Z' }, { id: 'a0e0e5c3-1004-4271-80ba-d05eda3e8213', paymentStatus: 'In Progress', paymentReference: '7lufvi9sbh077rvrrmnqo63vme', paymentTimestamp: '2020-06-04T12:04:30.802Z' }, { id: 'a0e0e5c3-1004-4271-80ba-d05eda3e8214', paymentStatus: 'In Progress', paymentReference: '7lufvi9sbh077rvrrmnqo63vmf', paymentTimestamp: '2020-06-04T12:04:30.802Z' }, { id: 'a0e0e5c3-1004-4271-80ba-d05eda3e8215', paymentStatus: 'In Progress', paymentReference: '7lufvi9sbh077rvrrmnqo63vmg', paymentTimestamp: '2020-06-04T12:04:30.802Z' } ] const govUkPayStatusEntries = [ { payment_id: '05nioqikvvnuu5l8m2qeaj0qap', state: { status: 'success', finished: true }, amount: 8200 }, { payment_id: '0f3dr9ugp7u68qq18vt9h8ma85', state: { code: GOVUK_PAY_ERROR_STATUS_CODES.REJECTED, status: 'failed', finished: true } }, { payment_id: '7lufvi9sbh077rvrrmnqo63vme', state: { status: 'error', finished: true } }, { payment_id: '7lufvi9sbh077rvrrmnqo63vmf', state: { code: GOVUK_PAY_ERROR_STATUS_CODES.USER_CANCELLED, status: 'cancelled', finished: true } }, { payment_id: '7lufvi9sbh077rvrrmnqo63vmg', state: { code: GOVUK_PAY_ERROR_STATUS_CODES.EXPIRED, status: 'failed', finished: false } } ] const govUkPayStatusNotFound = { code: 'P0200', description: 'Not found' } const createPaymentEventsEntry = paymentStatus => { return { events: [ { payment_id: paymentStatus.payment_id, state: { status: 'created', finished: false }, updated: 'INTERIM_PAYMENT_EVENT_TIMESTAMP' }, { payment_id: paymentStatus.payment_id, state: { status: 'started', finished: false }, updated: 'INTERIM_PAYMENT_EVENT_TIMESTAMP' }, { payment_id: paymentStatus.payment_id, state: { status: 'submitted', finished: false }, updated: 'INTERIM_PAYMENT_EVENT_TIMESTAMP' }, { payment_id: paymentStatus.payment_id, state: paymentStatus.state, updated: 'FINAL_PAYMENT_EVENT_TIMESTAMP' } ] } } describe('processor', () => { beforeEach(jest.clearAllMocks) it('completes normally where there are no journal records retrieved', async () => { salesApi.paymentJournals.getAll.mockReturnValue([]) govUkPayApi.fetchPaymentStatus.mockImplementation(jest.fn()) await execute(1, 1) expect(govUkPayApi.fetchPaymentStatus).not.toHaveBeenCalled() }) it('completes normally where there are journal records retrieved', async () => { salesApi.paymentJournals.getAll.mockReturnValue(journalEntries) salesApi.updatePaymentJournal.mockImplementation(jest.fn()) salesApi.finaliseTransaction.mockImplementation(jest.fn()) govUkPayStatusEntries.forEach(status => { govUkPayApi.fetchPaymentStatus.mockReturnValueOnce({ json: async () => status }) govUkPayApi.fetchPaymentEvents.mockReturnValueOnce({ json: async () => createPaymentEventsEntry(status) }) }) await execute(1, 1) expect(govUkPayApi.fetchPaymentStatus).toHaveBeenCalledTimes(govUkPayStatusEntries.length) expect(govUkPayApi.fetchPaymentEvents).toHaveBeenCalledTimes(govUkPayStatusEntries.filter(s => s.state.status === 'success').length) expect(salesApi.finaliseTransaction).toHaveBeenCalledTimes(govUkPayStatusEntries.filter(s => s.state.status === 'success').length) expect(salesApi.finaliseTransaction).toHaveBeenCalledWith('4fa393ab-07f4-407e-b233-89be2a6f5f77', { payment: { amount: 82, method: 'Debit card', source: 'Gov Pay', timestamp: 'FINAL_PAYMENT_EVENT_TIMESTAMP' } }) expect(salesApi.updatePaymentJournal).toHaveBeenCalledWith('4fa393ab-07f4-407e-b233-89be2a6f5f77', { paymentStatus: PAYMENT_JOURNAL_STATUS_CODES.Completed }) expect(salesApi.updatePaymentJournal).toHaveBeenCalledWith('aaced854-d337-47ee-8d5e-75b26aeb90fb', { paymentStatus: PAYMENT_JOURNAL_STATUS_CODES.Failed }) expect(salesApi.updatePaymentJournal).toHaveBeenCalledWith('a0e0e5c3-1004-4271-80ba-d05eda3e8213', { paymentStatus: PAYMENT_JOURNAL_STATUS_CODES.Failed }) expect(salesApi.updatePaymentJournal).toHaveBeenCalledWith('a0e0e5c3-1004-4271-80ba-d05eda3e8214', { paymentStatus: PAYMENT_JOURNAL_STATUS_CODES.Cancelled }) expect(salesApi.updatePaymentJournal).toHaveBeenCalledWith('a0e0e5c3-1004-4271-80ba-d05eda3e8215', { paymentStatus: PAYMENT_JOURNAL_STATUS_CODES.Expired }) }) describe('Result not present in GovPay', () => { const NOT_FOUND_ID = journalEntries[2].id const NOT_FOUND_PAYMENT_REFERENCE = journalEntries[2].paymentReference beforeEach(() => { salesApi.paymentJournals.getAll.mockReturnValue(journalEntries) salesApi.updatePaymentJournal.mockImplementation(() => {}) salesApi.finaliseTransaction.mockImplementation(() => {}) govUkPayApi.fetchPaymentEvents.mockImplementation(paymentReference => { if (paymentReference === NOT_FOUND_PAYMENT_REFERENCE) { return { json: async () => govUkPayStatusNotFound } } return { json: async () => createPaymentEventsEntry(govUkPayStatusEntries.find(se => se.payment_id === paymentReference)) } }) govUkPayApi.fetchPaymentStatus.mockImplementation(paymentReference => { if (paymentReference === NOT_FOUND_PAYMENT_REFERENCE) { return { json: async () => govUkPayStatusNotFound } } return { json: async () => govUkPayStatusEntries.find(se => se.payment_id === paymentReference) } }) }) it("When a payment isn't present in GovPay, no error is thrown", async () => { await expect(execute(1, 1)).resolves.toBeUndefined() }) it("when a payment isn't present in GovPay, other results process", async () => { await execute(1, 1) const foundIds = journalEntries.map(j => j.id).filter(id => id !== NOT_FOUND_ID) for (const foundId of foundIds) { expect(salesApi.updatePaymentJournal).toHaveBeenCalledWith(foundId, expect.any(Object)) } }) it("when a payment isn't present in GovPay, it's marked as expired after 3 hours", async () => { const missingJournalEntry = journalEntries.find(je => je.id === NOT_FOUND_ID) missingJournalEntry.paymentTimestamp = moment() .subtract(3, 'hours') .toISOString() await execute(1, 1) expect(salesApi.updatePaymentJournal).toHaveBeenCalledWith( NOT_FOUND_ID, expect.objectContaining({ paymentStatus: PAYMENT_JOURNAL_STATUS_CODES.Expired }) ) }) it("when a payment isn't present in GovPay, it's not marked as expired if 3 hours haven't passed", async () => { const missingJournalEntry = journalEntries.find(je => je.id === NOT_FOUND_ID) missingJournalEntry.paymentTimestamp = moment() .subtract(2, 'hours') .toISOString() await execute(1, 1) expect(salesApi.updatePaymentJournal).not.toHaveBeenCalledWith( NOT_FOUND_ID, expect.objectContaining({ paymentStatus: PAYMENT_JOURNAL_STATUS_CODES.Expired }) ) }) }) })