@defra-fish/recurring-payments-job 1.64.0-rc.1 → 1.64.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/recurring-payments-job",
3
- "version": "1.64.0-rc.1",
3
+ "version": "1.64.0-rc.3",
4
4
  "description": "Rod Licensing Recurring Payments Job",
5
5
  "type": "module",
6
6
  "engines": {
@@ -36,11 +36,11 @@
36
36
  "test": "echo \"Error: run tests from root\" && exit 1"
37
37
  },
38
38
  "dependencies": {
39
- "@defra-fish/business-rules-lib": "1.64.0-rc.1",
40
- "@defra-fish/connectors-lib": "1.64.0-rc.1",
39
+ "@defra-fish/business-rules-lib": "1.64.0-rc.3",
40
+ "@defra-fish/connectors-lib": "1.64.0-rc.3",
41
41
  "commander": "^7.2.0",
42
42
  "debug": "^4.3.3",
43
43
  "moment-timezone": "^0.5.34"
44
44
  },
45
- "gitHead": "2d114ca497f84cd7ede12c2f93965a7044d2f39a"
45
+ "gitHead": "709cc3d3ded64bf33d0c68bfd7484c829a226ab2"
46
46
  }
@@ -70,6 +70,12 @@ const getMockDueRecurringPayment = ({ agreementId = 'test-agreement-id', id = 'a
70
70
  expanded: { activePermission: { entity: { referenceNumber } } }
71
71
  })
72
72
 
73
+ const getMockSendPaymentResponse = ({ payment_id = 'pay-1', agreementId = 'agr-1', created_date = '2025-01-01T00:00:00.000Z' } = {}) => ({
74
+ payment_id,
75
+ agreementId,
76
+ created_date
77
+ })
78
+
73
79
  describe('recurring-payments-processor', () => {
74
80
  const [{ value: debugLogger }] = db.mock.results
75
81
 
@@ -588,6 +594,139 @@ describe('recurring-payments-processor', () => {
588
594
  expect(debugLogger).toHaveBeenCalledWith(expect.any(String), error)
589
595
  })
590
596
 
597
+ // --- //
598
+
599
+ it('should log errors from await salesApi.processRPResult', async () => {
600
+ salesApi.getDueRecurringPayments.mockResolvedValueOnce([getMockDueRecurringPayment()])
601
+ salesApi.createTransaction.mockResolvedValueOnce({ id: 'trans-1', cost: 30 })
602
+
603
+ const payment = getMockSendPaymentResponse()
604
+ sendPayment.mockResolvedValueOnce(payment)
605
+
606
+ getPaymentStatus.mockResolvedValueOnce(getPaymentStatusSuccess())
607
+
608
+ const boom = new Error('boom')
609
+
610
+ salesApi.processRPResult.mockImplementation(transId => (transId === 'trans-1' ? Promise.reject(boom) : Promise.resolve()))
611
+
612
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
613
+
614
+ await execute()
615
+
616
+ expect(errorSpy).toHaveBeenCalledWith('Failed to process Recurring Payment for trans-1', boom)
617
+
618
+ errorSpy.mockRestore()
619
+ })
620
+
621
+ describe('handling failures for multiple due payments', () => {
622
+ beforeEach(() => {
623
+ salesApi.getDueRecurringPayments.mockResolvedValueOnce([getMockDueRecurringPayment(), getMockDueRecurringPayment()])
624
+
625
+ salesApi.preparePermissionDataForRenewal.mockResolvedValueOnce({ licensee: { countryCode: 'GB-ENG' } })
626
+
627
+ salesApi.createTransaction.mockResolvedValueOnce({ id: 'trans-1', cost: 30 }).mockResolvedValueOnce({ id: 'trans-2', cost: 30 })
628
+ })
629
+
630
+ it('continues when one sendPayment rejects (Promise.allSettled check)', async () => {
631
+ const secondPayment = getMockSendPaymentResponse({
632
+ payment_id: 'test-payment-second',
633
+ agreementId: 'agr-2',
634
+ created_date: '2025-01-01T00:00:00.000Z'
635
+ })
636
+
637
+ const gatewayDown = new Error('gateway down')
638
+ sendPayment.mockRejectedValueOnce(gatewayDown).mockResolvedValueOnce(secondPayment)
639
+ getPaymentStatus.mockResolvedValueOnce(getPaymentStatusSuccess())
640
+ salesApi.processRPResult.mockResolvedValueOnce()
641
+
642
+ await execute()
643
+
644
+ const summary = {
645
+ statusArgs: getPaymentStatus.mock.calls,
646
+ rpResultArgs: salesApi.processRPResult.mock.calls
647
+ }
648
+
649
+ expect(summary).toEqual({
650
+ statusArgs: [[secondPayment.payment_id]],
651
+ rpResultArgs: [['trans-2', secondPayment.payment_id, secondPayment.created_date]]
652
+ })
653
+ })
654
+
655
+ it('continues when processRPResult rejects for one payment', async () => {
656
+ const firstPayment = getMockSendPaymentResponse({
657
+ payment_id: 'pay-1',
658
+ agreementId: 'agr-1',
659
+ created_date: '2025-01-01T00:00:00.000Z'
660
+ })
661
+ const secondPayment = getMockSendPaymentResponse({
662
+ payment_id: 'pay-2',
663
+ agreementId: 'agr-2',
664
+ created_date: '2025-01-01T00:01:00.000Z'
665
+ })
666
+
667
+ sendPayment.mockResolvedValueOnce(firstPayment).mockResolvedValueOnce(secondPayment)
668
+ getPaymentStatus.mockResolvedValueOnce(getPaymentStatusSuccess()).mockResolvedValueOnce(getPaymentStatusSuccess())
669
+
670
+ const boom = new Error('boom')
671
+ salesApi.processRPResult.mockImplementation(transId => (transId === 'trans-1' ? Promise.reject(boom) : Promise.resolve()))
672
+
673
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
674
+
675
+ await execute()
676
+
677
+ const summary = {
678
+ rpResultArgs: salesApi.processRPResult.mock.calls,
679
+ rpCount: salesApi.processRPResult.mock.calls.length,
680
+ firstError: errorSpy.mock.calls[0]
681
+ }
682
+
683
+ errorSpy.mockRestore()
684
+
685
+ expect(summary).toEqual({
686
+ rpResultArgs: expect.arrayContaining([
687
+ ['trans-1', firstPayment.payment_id, firstPayment.created_date],
688
+ ['trans-2', secondPayment.payment_id, secondPayment.created_date]
689
+ ]),
690
+ rpCount: 2,
691
+ firstError: ['Failed to process Recurring Payment for trans-1', boom]
692
+ })
693
+ })
694
+
695
+ it('does not abort when getPaymentStatus rejects for one payment (allSettled at status stage)', async () => {
696
+ const p1 = getMockSendPaymentResponse({ payment_id: 'pay-1', created_date: '2025-01-01T00:00:00.000Z' })
697
+ const p2 = getMockSendPaymentResponse({ payment_id: 'pay-2', created_date: '2025-01-01T00:01:00.000Z' })
698
+
699
+ sendPayment.mockResolvedValueOnce(p1).mockResolvedValueOnce(p2)
700
+
701
+ getPaymentStatus.mockImplementation(async id => {
702
+ if (id === p1.payment_id) {
703
+ throw Object.assign(new Error('HTTP 500'), { response: { status: 500, data: 'boom' } })
704
+ }
705
+ return getPaymentStatusSuccess()
706
+ })
707
+
708
+ salesApi.processRPResult.mockResolvedValueOnce()
709
+
710
+ await execute()
711
+
712
+ const summary = {
713
+ statusArgs: getPaymentStatus.mock.calls,
714
+ statusCount: getPaymentStatus.mock.calls.length,
715
+ rpResultArgs: salesApi.processRPResult.mock.calls,
716
+ rpCount: salesApi.processRPResult.mock.calls.length
717
+ }
718
+
719
+ expect(summary).toEqual({
720
+ statusArgs: expect.arrayContaining([[p1.payment_id], [p2.payment_id]]),
721
+ statusCount: 2,
722
+ rpResultArgs: expect.arrayContaining([['trans-2', p2.payment_id, p2.created_date]]),
723
+ rpCount: 1
724
+ })
725
+ })
726
+ })
727
+
728
+ // --- //
729
+
591
730
  it.each([
592
731
  [400, 'Failed to fetch status for payment test-payment-id, error 400'],
593
732
  [486, 'Failed to fetch status for payment test-payment-id, error 486'],
@@ -155,8 +155,13 @@ const processRecurringPaymentStatus = async payment => {
155
155
  debug(`Payment status for ${payment.paymentId}: ${status}`)
156
156
 
157
157
  if (status === PAYMENT_STATUS.Success) {
158
- await salesApi.processRPResult(payment.transaction.id, payment.paymentId, payment.created_date)
159
- debug(`Processed Recurring Payment for ${payment.transaction.id}`)
158
+ try {
159
+ await salesApi.processRPResult(payment.transaction.id, payment.paymentId, payment.created_date)
160
+ debug(`Processed Recurring Payment for ${payment.transaction.id}`)
161
+ } catch (err) {
162
+ console.error(`Failed to process Recurring Payment for ${payment.transaction.id}`, err)
163
+ throw err
164
+ }
160
165
  }
161
166
  if (status === PAYMENT_STATUS.Failure || status === PAYMENT_STATUS.Error) {
162
167
  console.error(