@defra-fish/connectors-lib 1.62.0-rc.8 → 1.62.0-rst.1
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/README.md +2 -1
- package/package.json +2 -3
- package/src/__tests__/document-client-decorator.spec.js +21 -14
- package/src/__tests__/govuk-pay-api.spec.js +48 -11
- package/src/__tests__/sales-api-connector.spec.js +40 -0
- package/src/documentclient-decorator.js +6 -5
- package/src/govuk-pay-api.js +9 -0
- package/src/sales-api-connector.js +11 -0
package/README.md
CHANGED
|
@@ -20,7 +20,8 @@ Provides connectivity to the resources/infrastructure used in the rod licensing
|
|
|
20
20
|
| GOV_PAY_APIKEY | GOV pay access identifier | yes | | | |
|
|
21
21
|
| GOV_PAY_RECURRING_APIKEY | GOV pay access identifier for recurring payments | yes | | | |
|
|
22
22
|
| GOV_PAY_REQUEST_TIMEOUT_MS | Timeout in milliseconds for API requests | no | 10000 | | |
|
|
23
|
-
| GOV_PAY_RCP_API_URL | The GOV.UK Pay API url for agreements | yes | |
|
|
23
|
+
| GOV_PAY_RCP_API_URL | The GOV.UK Pay API url for agreements | yes | | | |
|
|
24
|
+
| GOV_PAY_HEALTH_CHECK_URL | The Gov.UK Pay health check url | yes | | | |
|
|
24
25
|
|
|
25
26
|
# Prerequisites
|
|
26
27
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defra-fish/connectors-lib",
|
|
3
|
-
"version": "1.62.0-
|
|
3
|
+
"version": "1.62.0-rst.1",
|
|
4
4
|
"description": "Shared connectors",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -45,6 +45,5 @@
|
|
|
45
45
|
"ioredis": "^4.28.5",
|
|
46
46
|
"node-fetch": "^2.7.0",
|
|
47
47
|
"redlock": "^4.2.0"
|
|
48
|
-
}
|
|
49
|
-
"gitHead": "5d9e947d9ce87f6f43aa6ca7ac8d78f797ecebdf"
|
|
48
|
+
}
|
|
50
49
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createDocumentClient } from '../documentclient-decorator'
|
|
2
2
|
import { DynamoDB } from '@aws-sdk/client-dynamodb'
|
|
3
|
-
import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb'
|
|
3
|
+
import { DynamoDBDocument, QueryCommand, ScanCommand } from '@aws-sdk/lib-dynamodb'
|
|
4
4
|
|
|
5
5
|
jest.mock('@aws-sdk/client-dynamodb')
|
|
6
6
|
jest.mock('@aws-sdk/lib-dynamodb')
|
|
@@ -9,6 +9,7 @@ describe('document client decorations', () => {
|
|
|
9
9
|
beforeAll(() => {
|
|
10
10
|
jest.spyOn(global, 'setTimeout').mockImplementation(cb => cb())
|
|
11
11
|
DynamoDBDocument.from.mockReturnValue({
|
|
12
|
+
send: jest.fn().mockResolvedValue({ Items: [], lastEvaluatedKey: false }),
|
|
12
13
|
query: jest.fn().mockResolvedValue({ Items: [], lastEvaluatedKey: false }),
|
|
13
14
|
scan: jest.fn().mockResolvedValue({ Items: [], lastEvaluatedKey: false }),
|
|
14
15
|
batchWrite: jest.fn().mockResolvedValue({ UnprocessedItems: {} })
|
|
@@ -46,23 +47,30 @@ describe('document client decorations', () => {
|
|
|
46
47
|
})
|
|
47
48
|
|
|
48
49
|
describe.each`
|
|
49
|
-
aggregateMethod |
|
|
50
|
-
${'queryAllPromise'} | ${
|
|
51
|
-
${'scanAllPromise'} | ${
|
|
52
|
-
`('$aggregateMethod', ({ aggregateMethod,
|
|
50
|
+
aggregateMethod | commandType
|
|
51
|
+
${'queryAllPromise'} | ${QueryCommand}
|
|
52
|
+
${'scanAllPromise'} | ${ScanCommand}
|
|
53
|
+
`('$aggregateMethod', ({ aggregateMethod, commandType }) => {
|
|
53
54
|
it('is added to document client', () => {
|
|
54
55
|
const docClient = createDocumentClient()
|
|
55
56
|
expect(docClient[aggregateMethod]).toBeDefined()
|
|
56
57
|
})
|
|
57
58
|
|
|
58
|
-
it(`passes arguments provided for ${aggregateMethod} to ${
|
|
59
|
+
it(`passes arguments provided for ${aggregateMethod} to ${commandType.name}`, async () => {
|
|
59
60
|
const params = { TableName: 'TEST', KeyConditionExpression: 'id = :id', ExpressionAttributeValues: { ':id': 1 } }
|
|
60
61
|
const docClient = createDocumentClient()
|
|
61
62
|
await docClient[aggregateMethod](params)
|
|
62
|
-
expect(
|
|
63
|
+
expect(commandType).toHaveBeenCalledWith(params)
|
|
63
64
|
})
|
|
64
65
|
|
|
65
|
-
it(`
|
|
66
|
+
it(`passes created command ${commandType.name} to docClient.send`, async () => {
|
|
67
|
+
const docClient = createDocumentClient()
|
|
68
|
+
await docClient[aggregateMethod]()
|
|
69
|
+
const [command] = commandType.mock.instances
|
|
70
|
+
expect(docClient.send).toHaveBeenCalledWith(command)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('calls send repeatedly until LastEvaluatedKey evaluates to false, concatenating all returned items', async () => {
|
|
66
74
|
const expectedItems = [
|
|
67
75
|
{ id: 1, data: Symbol('data1') },
|
|
68
76
|
{ id: 2, data: Symbol('data2') },
|
|
@@ -71,7 +79,7 @@ describe('document client decorations', () => {
|
|
|
71
79
|
{ id: 5, data: Symbol('data5') }
|
|
72
80
|
]
|
|
73
81
|
const docClient = createDocumentClient()
|
|
74
|
-
docClient
|
|
82
|
+
docClient.send
|
|
75
83
|
.mockResolvedValueOnce({ Items: expectedItems.slice(0, 2), LastEvaluatedKey: true })
|
|
76
84
|
.mockResolvedValueOnce({ Items: expectedItems.slice(2, 4), LastEvaluatedKey: true })
|
|
77
85
|
.mockResolvedValueOnce({ Items: expectedItems.slice(4), LastEvaluatedKey: false })
|
|
@@ -79,13 +87,12 @@ describe('document client decorations', () => {
|
|
|
79
87
|
expect(actualItems).toEqual(expectedItems)
|
|
80
88
|
})
|
|
81
89
|
|
|
82
|
-
it(`whilst concatenating ${
|
|
90
|
+
it(`whilst concatenating ${commandType.name} results, passes ExclusiveStartKey param`, async () => {
|
|
83
91
|
const expectedKey = Symbol('🔑')
|
|
84
92
|
const docClient = createDocumentClient()
|
|
85
|
-
docClient
|
|
93
|
+
docClient.send.mockResolvedValueOnce({ Items: [], LastEvaluatedKey: expectedKey }).mockResolvedValueOnce({ Items: [] })
|
|
86
94
|
await docClient[aggregateMethod]()
|
|
87
|
-
expect(
|
|
88
|
-
2,
|
|
95
|
+
expect(commandType).toHaveBeenLastCalledWith(
|
|
89
96
|
expect.objectContaining({
|
|
90
97
|
ExclusiveStartKey: expectedKey
|
|
91
98
|
})
|
|
@@ -95,7 +102,7 @@ describe('document client decorations', () => {
|
|
|
95
102
|
it("omits ExclusiveStartKey if previous LastEvaluatedKey isn't available", async () => {
|
|
96
103
|
const docClient = createDocumentClient()
|
|
97
104
|
await docClient[aggregateMethod]()
|
|
98
|
-
expect(docClient
|
|
105
|
+
expect(docClient.send).toHaveBeenNthCalledWith(
|
|
99
106
|
1,
|
|
100
107
|
expect.not.objectContaining({
|
|
101
108
|
ExclusiveStartKey: expect.anything()
|
|
@@ -24,7 +24,7 @@ describe('govuk-pay-api-connector', () => {
|
|
|
24
24
|
|
|
25
25
|
describe('createPayment', () => {
|
|
26
26
|
it('creates new payments', async () => {
|
|
27
|
-
fetch.
|
|
27
|
+
fetch.mockReturnValueOnce({ ok: true, status: 200 })
|
|
28
28
|
await expect(govUkPayApi.createPayment({ cost: 0 })).resolves.toEqual({ ok: true, status: 200 })
|
|
29
29
|
expect(fetch).toHaveBeenCalledWith('http://0.0.0.0/payment', {
|
|
30
30
|
body: JSON.stringify({ cost: 0 }),
|
|
@@ -36,7 +36,7 @@ describe('govuk-pay-api-connector', () => {
|
|
|
36
36
|
|
|
37
37
|
it('logs and throws errors', async () => {
|
|
38
38
|
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(jest.fn())
|
|
39
|
-
fetch.
|
|
39
|
+
fetch.mockImplementationOnce(() => {
|
|
40
40
|
throw new Error('')
|
|
41
41
|
})
|
|
42
42
|
expect(govUkPayApi.createPayment({ cost: 0 })).rejects.toEqual(Error(''))
|
|
@@ -50,7 +50,7 @@ describe('govuk-pay-api-connector', () => {
|
|
|
50
50
|
})
|
|
51
51
|
|
|
52
52
|
it('uses the correct API key if recurring arg is set to true', async () => {
|
|
53
|
-
fetch.
|
|
53
|
+
fetch.mockReturnValueOnce({ ok: true, status: 200 })
|
|
54
54
|
await expect(govUkPayApi.createPayment({ cost: 0 }, true)).resolves.toEqual({ ok: true, status: 200 })
|
|
55
55
|
expect(fetch).toHaveBeenCalledWith('http://0.0.0.0/payment', {
|
|
56
56
|
body: JSON.stringify({ cost: 0 }),
|
|
@@ -63,7 +63,7 @@ describe('govuk-pay-api-connector', () => {
|
|
|
63
63
|
|
|
64
64
|
describe('fetchPaymentStatus', () => {
|
|
65
65
|
it('retrieves payment status', async () => {
|
|
66
|
-
fetch.
|
|
66
|
+
fetch.mockReturnValueOnce({ ok: true, status: 200, json: () => {} })
|
|
67
67
|
await expect(govUkPayApi.fetchPaymentStatus(123)).resolves.toEqual(expect.objectContaining({ ok: true, status: 200 }))
|
|
68
68
|
expect(fetch).toHaveBeenCalledWith('http://0.0.0.0/payment/123', {
|
|
69
69
|
headers,
|
|
@@ -74,7 +74,7 @@ describe('govuk-pay-api-connector', () => {
|
|
|
74
74
|
|
|
75
75
|
it('logs and throws errors', async () => {
|
|
76
76
|
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(jest.fn())
|
|
77
|
-
fetch.
|
|
77
|
+
fetch.mockImplementationOnce(() => {
|
|
78
78
|
throw new Error('')
|
|
79
79
|
})
|
|
80
80
|
await expect(govUkPayApi.fetchPaymentStatus(123)).rejects.toEqual(Error(''))
|
|
@@ -83,7 +83,7 @@ describe('govuk-pay-api-connector', () => {
|
|
|
83
83
|
})
|
|
84
84
|
|
|
85
85
|
it('uses the correct API key if recurring arg is set to true', async () => {
|
|
86
|
-
fetch.
|
|
86
|
+
fetch.mockReturnValueOnce({ ok: true, status: 200, json: () => {} })
|
|
87
87
|
await expect(govUkPayApi.fetchPaymentStatus(123, true)).resolves.toEqual(expect.objectContaining({ ok: true, status: 200 }))
|
|
88
88
|
expect(fetch).toHaveBeenCalledWith('http://0.0.0.0/payment/123', {
|
|
89
89
|
headers: recurringHeaders,
|
|
@@ -95,14 +95,14 @@ describe('govuk-pay-api-connector', () => {
|
|
|
95
95
|
|
|
96
96
|
describe('fetchPaymentEvents', () => {
|
|
97
97
|
it('retrieves payment events', async () => {
|
|
98
|
-
fetch.
|
|
98
|
+
fetch.mockReturnValueOnce({ ok: true, status: 200, json: () => {} })
|
|
99
99
|
await expect(govUkPayApi.fetchPaymentEvents(123)).resolves.toEqual(expect.objectContaining({ ok: true, status: 200 }))
|
|
100
100
|
expect(fetch).toHaveBeenCalledWith('http://0.0.0.0/payment/123/events', { headers, method: 'get', timeout: 10000 })
|
|
101
101
|
})
|
|
102
102
|
|
|
103
103
|
it('logs and throws errors', async () => {
|
|
104
104
|
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(jest.fn())
|
|
105
|
-
fetch.
|
|
105
|
+
fetch.mockImplementationOnce(() => {
|
|
106
106
|
throw new Error('test event error')
|
|
107
107
|
})
|
|
108
108
|
await expect(govUkPayApi.fetchPaymentEvents(123)).rejects.toEqual(Error('test event error'))
|
|
@@ -110,7 +110,7 @@ describe('govuk-pay-api-connector', () => {
|
|
|
110
110
|
})
|
|
111
111
|
|
|
112
112
|
it('uses the correct API key if recurring arg is set to true', async () => {
|
|
113
|
-
fetch.
|
|
113
|
+
fetch.mockReturnValueOnce({ ok: true, status: 200, json: () => {} })
|
|
114
114
|
await expect(govUkPayApi.fetchPaymentEvents(123, true)).resolves.toEqual(expect.objectContaining({ ok: true, status: 200 }))
|
|
115
115
|
expect(fetch).toHaveBeenCalledWith('http://0.0.0.0/payment/123/events', {
|
|
116
116
|
headers: recurringHeaders,
|
|
@@ -122,7 +122,7 @@ describe('govuk-pay-api-connector', () => {
|
|
|
122
122
|
|
|
123
123
|
describe('createRecurringPaymentAgreement', () => {
|
|
124
124
|
it('creates new payments', async () => {
|
|
125
|
-
fetch.
|
|
125
|
+
fetch.mockReturnValueOnce({ ok: true, status: 200 })
|
|
126
126
|
await expect(govUkPayApi.createRecurringPaymentAgreement({ cost: 0 })).resolves.toEqual({ ok: true, status: 200 })
|
|
127
127
|
expect(fetch).toHaveBeenCalledWith('http://0.0.0.0/agreement', {
|
|
128
128
|
body: JSON.stringify({ cost: 0 }),
|
|
@@ -134,7 +134,7 @@ describe('govuk-pay-api-connector', () => {
|
|
|
134
134
|
|
|
135
135
|
it('logs and throws errors', async () => {
|
|
136
136
|
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(jest.fn())
|
|
137
|
-
fetch.
|
|
137
|
+
fetch.mockImplementationOnce(() => {
|
|
138
138
|
throw new Error('')
|
|
139
139
|
})
|
|
140
140
|
expect(govUkPayApi.createRecurringPaymentAgreement({ reference: '123' })).rejects.toEqual(Error(''))
|
|
@@ -148,6 +148,43 @@ describe('govuk-pay-api-connector', () => {
|
|
|
148
148
|
})
|
|
149
149
|
})
|
|
150
150
|
|
|
151
|
+
describe('isGovPayUp', () => {
|
|
152
|
+
it.each(['http://gov.uk.pay/health/check/url', 'https://gov-uk-pay?health-check-url'])(
|
|
153
|
+
'calls healthy endpoint %s',
|
|
154
|
+
async healthCheckURL => {
|
|
155
|
+
process.env.GOV_PAY_HEALTH_CHECK_URL = healthCheckURL
|
|
156
|
+
await govUkPayApi.isGovPayUp()
|
|
157
|
+
expect(fetch).toHaveBeenCalledWith(healthCheckURL)
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
it('returns the fetch response', async () => {
|
|
162
|
+
const response = Symbol('response')
|
|
163
|
+
fetch.mockReturnValueOnce(response)
|
|
164
|
+
expect(await govUkPayApi.isGovPayUp()).toBe(response)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('throws errors that are thrown by fetch', async () => {
|
|
168
|
+
const error = new Error('Fail')
|
|
169
|
+
fetch.mockImplementationOnce(() => {
|
|
170
|
+
throw error
|
|
171
|
+
})
|
|
172
|
+
await expect(govUkPayApi.isGovPayUp()).rejects.toBe(error)
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('logs errors', async () => {
|
|
176
|
+
const error = new Error('Fail')
|
|
177
|
+
const consoleErrorSpy = jest.spyOn(console, 'error')
|
|
178
|
+
fetch.mockImplementationOnce(() => {
|
|
179
|
+
throw error
|
|
180
|
+
})
|
|
181
|
+
try {
|
|
182
|
+
await govUkPayApi.isGovPayUp()
|
|
183
|
+
} catch {}
|
|
184
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('Error retrieving GovPay health status', error)
|
|
185
|
+
})
|
|
186
|
+
})
|
|
187
|
+
|
|
151
188
|
describe('getRecurringPaymentAgreementInformation', () => {
|
|
152
189
|
it('retrieves recurring payment agreement information', async () => {
|
|
153
190
|
fetch.mockReturnValue({ ok: true, status: 200, json: () => {} })
|
|
@@ -746,4 +746,44 @@ describe('sales-api-connector', () => {
|
|
|
746
746
|
await expect(salesApi.cancelRecurringPayment('id')).rejects.toThrow('Internal Server Error')
|
|
747
747
|
})
|
|
748
748
|
})
|
|
749
|
+
|
|
750
|
+
describe('retrieveStagedTransaction', () => {
|
|
751
|
+
describe.each([['id'], ['abc-123']])("Retrieving staged transaction id '%s'", id => {
|
|
752
|
+
beforeEach(() => {
|
|
753
|
+
fetch.mockReturnValue({
|
|
754
|
+
ok: true,
|
|
755
|
+
status: 200,
|
|
756
|
+
statusText: 'OK',
|
|
757
|
+
text: async () => JSON.stringify({ id })
|
|
758
|
+
})
|
|
759
|
+
})
|
|
760
|
+
|
|
761
|
+
it('calls the endpoint with the correct parameters', async () => {
|
|
762
|
+
await salesApi.retrieveStagedTransaction(id)
|
|
763
|
+
|
|
764
|
+
expect(fetch).toHaveBeenCalledWith(`http://0.0.0.0:4000/retrieveStagedTransaction/${id}`, {
|
|
765
|
+
method: 'get',
|
|
766
|
+
headers: expect.any(Object),
|
|
767
|
+
timeout: 20000
|
|
768
|
+
})
|
|
769
|
+
})
|
|
770
|
+
|
|
771
|
+
it('returns the expected response data', async () => {
|
|
772
|
+
const processedResult = await salesApi.retrieveStagedTransaction(id)
|
|
773
|
+
|
|
774
|
+
expect(processedResult).toEqual({ id })
|
|
775
|
+
})
|
|
776
|
+
})
|
|
777
|
+
|
|
778
|
+
it('throws an error on non-2xx response', async () => {
|
|
779
|
+
fetch.mockReturnValue({
|
|
780
|
+
ok: false,
|
|
781
|
+
status: 500,
|
|
782
|
+
statusText: 'Internal Server Error',
|
|
783
|
+
text: async () => 'Server Error'
|
|
784
|
+
})
|
|
785
|
+
|
|
786
|
+
await expect(salesApi.retrieveStagedTransaction('id')).rejects.toThrow('Internal Server Error')
|
|
787
|
+
})
|
|
788
|
+
})
|
|
749
789
|
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import db from 'debug'
|
|
2
2
|
import { DynamoDB } from '@aws-sdk/client-dynamodb'
|
|
3
|
-
import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb'
|
|
3
|
+
import { DynamoDBDocument, QueryCommand, ScanCommand } from '@aws-sdk/lib-dynamodb'
|
|
4
4
|
const debug = db('connectors:aws')
|
|
5
5
|
|
|
6
6
|
export const createDocumentClient = options => {
|
|
@@ -13,23 +13,24 @@ export const createDocumentClient = options => {
|
|
|
13
13
|
})
|
|
14
14
|
|
|
15
15
|
// Support for large query/scan operations which return results in pages
|
|
16
|
-
const wrapPagedDocumentClientOperation =
|
|
16
|
+
const wrapPagedDocumentClientOperation = CommandType => {
|
|
17
17
|
return async params => {
|
|
18
18
|
const items = []
|
|
19
19
|
let lastEvaluatedKey = null
|
|
20
20
|
do {
|
|
21
|
-
const
|
|
21
|
+
const command = new CommandType({
|
|
22
22
|
...params,
|
|
23
23
|
...(lastEvaluatedKey && { ExclusiveStartKey: lastEvaluatedKey })
|
|
24
24
|
})
|
|
25
|
+
const response = await docClient.send(command)
|
|
25
26
|
lastEvaluatedKey = response.LastEvaluatedKey
|
|
26
27
|
response.Items && items.push(...response.Items)
|
|
27
28
|
} while (lastEvaluatedKey)
|
|
28
29
|
return items
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
|
-
docClient.queryAllPromise = wrapPagedDocumentClientOperation(
|
|
32
|
-
docClient.scanAllPromise = wrapPagedDocumentClientOperation(
|
|
32
|
+
docClient.queryAllPromise = wrapPagedDocumentClientOperation(QueryCommand)
|
|
33
|
+
docClient.scanAllPromise = wrapPagedDocumentClientOperation(ScanCommand)
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
36
|
* Handles batch writes which may return UnprocessedItems. If UnprocessedItems are returned then they will be retried with exponential backoff
|
package/src/govuk-pay-api.js
CHANGED
|
@@ -87,6 +87,15 @@ export const fetchPaymentEvents = async (paymentId, recurring = false) => {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
export const isGovPayUp = async () => {
|
|
91
|
+
try {
|
|
92
|
+
return await fetch(process.env.GOV_PAY_HEALTH_CHECK_URL)
|
|
93
|
+
} catch (err) {
|
|
94
|
+
console.error('Error retrieving GovPay health status', err)
|
|
95
|
+
throw err
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
90
99
|
/**
|
|
91
100
|
* Gets payment information linked too a payment
|
|
92
101
|
* @param agreementId - agreementId set up when creating recurring payment
|
|
@@ -319,3 +319,14 @@ export const processRPResult = async (transactionId, paymentId, createdDate) =>
|
|
|
319
319
|
export const cancelRecurringPayment = async id => {
|
|
320
320
|
return exec2xxOrThrow(call(new URL(`/cancelRecurringPayment/${id}`, urlBase), 'get'))
|
|
321
321
|
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Retrieve a staged transaction
|
|
325
|
+
*
|
|
326
|
+
* @param id
|
|
327
|
+
* @returns {Promise<*>}
|
|
328
|
+
* @throws on a non-2xx response
|
|
329
|
+
*/
|
|
330
|
+
export const retrieveStagedTransaction = async id => {
|
|
331
|
+
return exec2xxOrThrow(call(new URL(`/retrieveStagedTransaction/${id}`, urlBase), 'get'))
|
|
332
|
+
}
|