UNPKG

@defra-fish/recurring-payments-job

Version:

Rod Licensing Recurring Payments Job

521 lines (425 loc) 17.7 kB
import { salesApi } from '@defra-fish/connectors-lib' import { processRecurringPayments } from '../recurring-payments-processor.js' import { getPaymentStatus, sendPayment } from '../services/govuk-pay-service.js' jest.mock('@defra-fish/business-rules-lib') jest.mock('@defra-fish/connectors-lib', () => ({ salesApi: { getDueRecurringPayments: jest.fn(() => []), preparePermissionDataForRenewal: jest.fn(() => ({ licensee: { countryCode: 'GB-ENG' } })), createTransaction: jest.fn(() => ({ id: 'test-transaction-id', cost: 30 })), processRPResult: jest.fn() } })) jest.mock('../services/govuk-pay-service.js', () => ({ sendPayment: jest.fn(), getPaymentStatus: jest.fn() })) const PAYMENT_STATUS_DELAY = 60000 const getPaymentStatusSuccess = () => ({ state: { status: 'success' } }) const getMockPaymentRequestResponse = () => [ { entity: { agreementId: 'agreement-1' }, expanded: { activePermission: { entity: { referenceNumber: 'ref-1' } } } } ] describe('recurring-payments-processor', () => { beforeEach(() => { jest.clearAllMocks() process.env.RUN_RECURRING_PAYMENTS = 'true' global.setTimeout = jest.fn((cb, ms) => cb()) }) it('console log displays "Recurring Payments job disabled" when env is false', async () => { process.env.RUN_RECURRING_PAYMENTS = 'false' const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(jest.fn()) await processRecurringPayments() expect(consoleLogSpy).toHaveBeenCalledWith('Recurring Payments job disabled') }) it('console log displays "Recurring Payments job enabled" when env is true', async () => { const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(jest.fn()) await processRecurringPayments() expect(consoleLogSpy).toHaveBeenCalledWith('Recurring Payments job enabled') }) it('get recurring payments is called when env is true', async () => { const date = new Date().toISOString().split('T')[0] await processRecurringPayments() expect(salesApi.getDueRecurringPayments).toHaveBeenCalledWith(date) }) it('console log displays "Recurring Payments found: " when env is true', async () => { const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(jest.fn()) await processRecurringPayments() expect(consoleLogSpy).toHaveBeenCalledWith('Recurring Payments found: ', []) }) it('prepares the data for found recurring payments', async () => { const referenceNumber = Symbol('reference') salesApi.getDueRecurringPayments.mockReturnValueOnce([getMockDueRecurringPayment(referenceNumber)]) const mockPaymentResponse = { payment_id: 'test-payment-id', created_date: '2025-01-01T00:00:00.000Z' } sendPayment.mockResolvedValueOnce(mockPaymentResponse) getPaymentStatus.mockResolvedValueOnce(getPaymentStatusSuccess()) await processRecurringPayments() expect(salesApi.preparePermissionDataForRenewal).toHaveBeenCalledWith(referenceNumber) }) it('creates a transaction with the correct data', async () => { salesApi.getDueRecurringPayments.mockReturnValueOnce([getMockDueRecurringPayment()]) const isLicenceForYou = Symbol('isLicenceForYou') const isRenewal = Symbol('isRenewal') const country = Symbol('country') const permitId = Symbol('permitId') const firstName = Symbol('firstName') const lastName = Symbol('lastName') salesApi.preparePermissionDataForRenewal.mockReturnValueOnce({ isLicenceForYou, isRenewal, licensee: { firstName, lastName, country, countryCode: 'GB-ENG' }, licenceStartDate: '2020-01-01', licenceStartTime: 3, permitId }) const expectedData = { dataSource: 'Recurring Payment', agreementId: 'test-agreement-id', permissions: [ { isLicenceForYou, isRenewal, issueDate: null, licensee: { firstName, lastName, country }, permitId, startDate: '2020-01-01T03:00:00.000Z' } ] } const mockPaymentResponse = { payment_id: 'test-payment-id', agreementId: 'test-agreement-id' } sendPayment.mockResolvedValueOnce(mockPaymentResponse) getPaymentStatus.mockResolvedValueOnce(getPaymentStatusSuccess()) await processRecurringPayments() expect(salesApi.createTransaction).toHaveBeenCalledWith(expectedData) }) it('strips the concession name returned by preparePermissionDataForRenewal before passing to createTransaction', async () => { salesApi.getDueRecurringPayments.mockReturnValueOnce([getMockDueRecurringPayment()]) salesApi.preparePermissionDataForRenewal.mockReturnValueOnce({ licensee: { countryCode: 'GB-ENG' }, concessions: [ { id: 'abc-123', name: 'concession-type-1', proof: { type: 'NO-PROOF' } } ] }) const mockPaymentResponse = { payment_id: 'test-payment-id' } sendPayment.mockResolvedValueOnce(mockPaymentResponse) getPaymentStatus.mockResolvedValueOnce(getPaymentStatusSuccess()) await processRecurringPayments() expect(salesApi.createTransaction).toHaveBeenCalledWith( expect.objectContaining({ permissions: expect.arrayContaining([ expect.objectContaining({ concessions: expect.arrayContaining([ expect.not.objectContaining({ name: 'concession-type-1' }) ]) }) ]) }) ) }) it('assigns the correct startDate when licenceStartTime is present', async () => { salesApi.getDueRecurringPayments.mockReturnValueOnce([getMockDueRecurringPayment()]) salesApi.preparePermissionDataForRenewal.mockReturnValueOnce({ licensee: { countryCode: 'GB-ENG' }, licenceStartDate: '2020-03-14', licenceStartTime: 15 }) const mockPaymentResponse = { payment_id: 'test-payment-id' } sendPayment.mockResolvedValueOnce(mockPaymentResponse) getPaymentStatus.mockResolvedValueOnce(getPaymentStatusSuccess()) await processRecurringPayments() expect(salesApi.createTransaction).toHaveBeenCalledWith( expect.objectContaining({ permissions: [expect.objectContaining({ startDate: '2020-03-14T15:00:00.000Z' })] }) ) }) it('assigns the correct startDate when licenceStartTime is not present', async () => { salesApi.getDueRecurringPayments.mockReturnValueOnce([getMockDueRecurringPayment()]) salesApi.preparePermissionDataForRenewal.mockReturnValueOnce({ licensee: { countryCode: 'GB-ENG' }, licenceStartDate: '2020-03-14' }) const mockPaymentResponse = { payment_id: 'test-payment-id' } sendPayment.mockResolvedValueOnce(mockPaymentResponse) getPaymentStatus.mockResolvedValueOnce(getPaymentStatusSuccess()) await processRecurringPayments() expect(salesApi.createTransaction).toHaveBeenCalledWith( expect.objectContaining({ permissions: [expect.objectContaining({ startDate: '2020-03-14T00:00:00.000Z' })] }) ) }) it('raises an error if createTransaction fails', async () => { salesApi.getDueRecurringPayments.mockReturnValueOnce([getMockDueRecurringPayment()]) const error = 'Wuh-oh!' salesApi.createTransaction.mockImplementationOnce(() => { throw new Error(error) }) await expect(processRecurringPayments()).rejects.toThrowError(error) }) it('prepares and sends the payment request', async () => { const agreementId = Symbol('agreementId') const transactionId = 'transactionId' salesApi.getDueRecurringPayments.mockReturnValueOnce([getMockDueRecurringPayment('foo', agreementId)]) salesApi.preparePermissionDataForRenewal.mockReturnValueOnce({ licensee: { countryCode: 'GB-ENG' } }) salesApi.createTransaction.mockReturnValueOnce({ cost: 50, id: transactionId }) const mockPaymentResponse = { payment_id: 'test-payment-id', agreementId } sendPayment.mockResolvedValueOnce(mockPaymentResponse) getPaymentStatus.mockResolvedValueOnce(getPaymentStatusSuccess()) const expectedData = { amount: 5000, description: 'The recurring card payment for your rod fishing licence', reference: transactionId, authorisation_mode: 'agreement', agreement_id: agreementId } await processRecurringPayments() expect(sendPayment).toHaveBeenCalledWith(expectedData) }) it('should call getPaymentStatus with payment id', async () => { const mockResponse = [ { entity: { agreementId: 'agreement-1' }, expanded: { activePermission: { entity: { referenceNumber: 'ref-1' } } } } ] salesApi.getDueRecurringPayments.mockResolvedValueOnce(mockResponse) salesApi.createTransaction.mockResolvedValueOnce({ id: 'payment-id-1' }) getPaymentStatus.mockResolvedValueOnce(getPaymentStatusSuccess()) const mockPaymentResponse = { payment_id: 'test-payment-id', agreementId: 'agreement-1' } sendPayment.mockResolvedValueOnce(mockPaymentResponse) await processRecurringPayments() expect(getPaymentStatus).toHaveBeenCalledWith('test-payment-id') }) it('should log payment status for recurring payment', async () => { const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(jest.fn()) const mockPaymentId = 'test-payment-id' const mockResponse = [ { entity: { agreementId: 'agreement-1' }, expanded: { activePermission: { entity: { referenceNumber: 'ref-1' } } } } ] salesApi.getDueRecurringPayments.mockResolvedValueOnce(mockResponse) salesApi.createTransaction.mockResolvedValueOnce({ id: mockPaymentId }) const mockPaymentResponse = { payment_id: mockPaymentId, agreementId: 'agreement-1' } sendPayment.mockResolvedValueOnce(mockPaymentResponse) getPaymentStatus.mockResolvedValueOnce(getPaymentStatusSuccess()) await processRecurringPayments() expect(consoleLogSpy).toHaveBeenCalledWith(`Payment status for ${mockPaymentId}: success`) }) it('should call setTimeout with correct delay when there are recurring payments', async () => { const referenceNumber = Symbol('reference') salesApi.getDueRecurringPayments.mockResolvedValueOnce([getMockDueRecurringPayment(referenceNumber)]) const mockPaymentResponse = { payment_id: 'test-payment-id' } sendPayment.mockResolvedValueOnce(mockPaymentResponse) getPaymentStatus.mockResolvedValueOnce(getPaymentStatusSuccess()) const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(cb => cb()) await processRecurringPayments() expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), PAYMENT_STATUS_DELAY) }) it('should not call setTimeout when there are no recurring payments', async () => { salesApi.getDueRecurringPayments.mockResolvedValueOnce([]) const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(cb => cb()) await processRecurringPayments() expect(setTimeoutSpy).not.toHaveBeenCalled() }) it('calls processRPResult with transaction id, payment id and created date when payment is successful', async () => { const mockTransactionId = 'test-transaction-id' const mockPaymentId = 'test-payment-id' const mockPaymentCreatedDate = '2025-01-01T00:00:00.000Z' salesApi.getDueRecurringPayments.mockResolvedValueOnce(getMockPaymentRequestResponse()) salesApi.createTransaction.mockResolvedValueOnce({ id: mockTransactionId, cost: 30 }) sendPayment.mockResolvedValueOnce({ payment_id: mockPaymentId, agreementId: 'agreement-1', created_date: mockPaymentCreatedDate }) getPaymentStatus.mockResolvedValueOnce(getPaymentStatusSuccess()) await processRecurringPayments() expect(salesApi.processRPResult).toHaveBeenCalledWith(mockTransactionId, mockPaymentId, mockPaymentCreatedDate) }) it("doesn't call processRPResult if payment status is not successful", async () => { const mockPaymentId = 'test-payment-id' salesApi.getDueRecurringPayments.mockResolvedValueOnce(getMockPaymentRequestResponse()) salesApi.createTransaction.mockResolvedValueOnce({ id: mockPaymentId, cost: 30 }) sendPayment.mockResolvedValueOnce({ payment_id: mockPaymentId, agreementId: 'agreement-1' }) getPaymentStatus.mockResolvedValueOnce({ state: { status: 'Pending' } }) await processRecurringPayments() expect(salesApi.processRPResult).not.toHaveBeenCalledWith() }) describe.each([2, 3, 10])('if there are %d recurring payments', count => { it('prepares the data for each one', async () => { const references = [] for (let i = 0; i < count; i++) { references.push(Symbol('reference' + i)) } const mockGetDueRecurringPayments = [] references.forEach(reference => { mockGetDueRecurringPayments.push(getMockDueRecurringPayment(reference)) }) salesApi.getDueRecurringPayments.mockReturnValueOnce(mockGetDueRecurringPayments) const mockPaymentResponse = { payment_id: 'test-payment-id' } sendPayment.mockResolvedValue(mockPaymentResponse) const mockPaymentStatus = getPaymentStatusSuccess() getPaymentStatus.mockResolvedValue(mockPaymentStatus) const expectedData = [] references.forEach(reference => { expectedData.push([reference]) }) await processRecurringPayments() expect(salesApi.preparePermissionDataForRenewal.mock.calls).toEqual(expectedData) }) it('creates a transaction for each one', async () => { const mockGetDueRecurringPayments = [] for (let i = 0; i < count; i++) { mockGetDueRecurringPayments.push(getMockDueRecurringPayment(i)) } salesApi.getDueRecurringPayments.mockReturnValueOnce(mockGetDueRecurringPayments) const permits = [] for (let i = 0; i < count; i++) { permits.push(Symbol(`permit${i}`)) } permits.forEach(permit => { salesApi.preparePermissionDataForRenewal.mockReturnValueOnce({ licensee: { countryCode: 'GB-ENG' }, permitId: permit }) }) const expectedData = [] permits.forEach(permit => { expectedData.push([ { dataSource: 'Recurring Payment', agreementId: 'test-agreement-id', permissions: [expect.objectContaining({ permitId: permit })] } ]) }) await processRecurringPayments() expect(salesApi.createTransaction.mock.calls).toEqual(expectedData) }) it('sends a payment for each one', async () => { const mockGetDueRecurringPayments = [] const agreementIds = [] for (let i = 0; i < count; i++) { const agreementId = Symbol(`agreementId${1}`) agreementIds.push(agreementId) mockGetDueRecurringPayments.push(getMockDueRecurringPayment(i, agreementId)) } salesApi.getDueRecurringPayments.mockReturnValueOnce(mockGetDueRecurringPayments) const permits = [] for (let i = 0; i < count; i++) { permits.push(Symbol(`permit${i}`)) } permits.forEach((permit, i) => { salesApi.preparePermissionDataForRenewal.mockReturnValueOnce({ licensee: { countryCode: 'GB-ENG' } }) salesApi.createTransaction.mockReturnValueOnce({ cost: i, id: permit }) }) const expectedData = [] permits.forEach((permit, i) => { expectedData.push([ { amount: i * 100, description: 'The recurring card payment for your rod fishing licence', reference: permit, authorisation_mode: 'agreement', agreement_id: agreementIds[i] } ]) }) await processRecurringPayments() expect(sendPayment.mock.calls).toEqual(expectedData) }) it('gets the payment status for each one', async () => { const mockGetDueRecurringPayments = [] const agreementIds = [] for (let i = 0; i < count; i++) { const agreementId = Symbol(`agreementId${1}`) agreementIds.push(agreementId) mockGetDueRecurringPayments.push(getMockDueRecurringPayment(i, agreementId)) } salesApi.getDueRecurringPayments.mockReturnValueOnce(mockGetDueRecurringPayments) const permits = [] for (let i = 0; i < count; i++) { permits.push(Symbol(`permit${i}`)) } permits.forEach((permit, i) => { salesApi.preparePermissionDataForRenewal.mockReturnValueOnce({ licensee: { countryCode: 'GB-ENG' } }) salesApi.createTransaction.mockReturnValueOnce({ cost: i, id: permit }) }) const expectedData = [] permits.forEach((_, index) => { const paymentId = `payment-id-${index}` expectedData.push(paymentId) const mockPaymentResponse = { payment_id: paymentId } sendPayment.mockResolvedValueOnce(mockPaymentResponse) }) await processRecurringPayments() expectedData.forEach(paymentId => { expect(getPaymentStatus).toHaveBeenCalledWith(paymentId) }) }) }) }) const getMockDueRecurringPayment = (referenceNumber = '123', agreementId = 'test-agreement-id') => ({ entity: { agreementId }, expanded: { activePermission: { entity: { referenceNumber } } } })