vona-module-test-pay 5.0.1 → 5.0.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 +4 -4
- package/test/outbox.test.ts +14 -4
- package/test/payMock.test.ts +13 -3
- package/test/paymentSession.test.ts +128 -8
- package/test/paypalProvider.test.ts +118 -0
- package/test/webhook.test.ts +12 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vona-module-test-pay",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Vona Module"
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"postpack": "clean-package restore"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"vona-module-a-pay": "5.0.
|
|
34
|
-
"vona-module-pay-mock": "5.0.
|
|
33
|
+
"vona-module-a-pay": "5.0.4",
|
|
34
|
+
"vona-module-pay-mock": "5.0.4"
|
|
35
35
|
},
|
|
36
36
|
"title": "test-pay",
|
|
37
37
|
"vonaModule": {
|
|
@@ -51,5 +51,5 @@
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
|
-
"gitHead": "
|
|
54
|
+
"gitHead": "c408492ce774644ac8a2530702db916986f52151"
|
|
55
55
|
}
|
package/test/outbox.test.ts
CHANGED
|
@@ -2,8 +2,8 @@ import type { IPaymentOutcomeEvent } from 'vona-module-a-pay';
|
|
|
2
2
|
|
|
3
3
|
import assert from 'node:assert';
|
|
4
4
|
import { randomUUID } from 'node:crypto';
|
|
5
|
-
import { describe, it } from 'node:test';
|
|
6
|
-
import { app } from 'vona-mock';
|
|
5
|
+
import { after, before, describe, it } from 'node:test';
|
|
6
|
+
import { acquireTestLock, app } from 'vona-mock';
|
|
7
7
|
|
|
8
8
|
interface IFixture {
|
|
9
9
|
userId?: number;
|
|
@@ -79,6 +79,16 @@ async function cleanup(fixture: IFixture) {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
describe('outbox.test.ts', { concurrency: false }, () => {
|
|
82
|
+
let releaseTestLock: (() => void) | undefined;
|
|
83
|
+
|
|
84
|
+
before(async () => {
|
|
85
|
+
releaseTestLock = await acquireTestLock('a-pay');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
after(() => {
|
|
89
|
+
releaseTestLock?.();
|
|
90
|
+
});
|
|
91
|
+
|
|
82
92
|
it('claims, backs off, and exhausts durable delivery attempts', async () => {
|
|
83
93
|
await app.bean.executor.mockCtx(async () => {
|
|
84
94
|
const fixture = await createFixture(randomUUID().slice(0, 12));
|
|
@@ -100,7 +110,7 @@ describe('outbox.test.ts', { concurrency: false }, () => {
|
|
|
100
110
|
assert.ok((persisted?.nextAttemptAt?.getTime() ?? 0) >= releaseStart + 900);
|
|
101
111
|
assert.equal(await scope.service.outbox.claim(event.id), undefined);
|
|
102
112
|
await scope.model.outboxEvent.updateById(event.id, {
|
|
103
|
-
nextAttemptAt: new Date(Date.now() -
|
|
113
|
+
nextAttemptAt: new Date(Date.now() - 1_000),
|
|
104
114
|
});
|
|
105
115
|
const second = await scope.service.outbox.claim(event.id);
|
|
106
116
|
assert.equal(second?.attemptCount, 2);
|
|
@@ -150,7 +160,7 @@ describe('outbox.test.ts', { concurrency: false }, () => {
|
|
|
150
160
|
const scope = app.scope('a-pay');
|
|
151
161
|
const first = await scope.service.outbox.claim(event.id);
|
|
152
162
|
await scope.model.outboxEvent.updateById(event.id, {
|
|
153
|
-
claimExpiresAt: new Date(Date.now() -
|
|
163
|
+
claimExpiresAt: new Date(Date.now() - 1_000),
|
|
154
164
|
});
|
|
155
165
|
const second = await scope.service.outbox.claim(event.id);
|
|
156
166
|
assert.ok(second?.claimToken);
|
package/test/payMock.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import { describe, it } from 'node:test';
|
|
4
|
-
import { app } from 'vona-mock';
|
|
3
|
+
import { after, before, describe, it } from 'node:test';
|
|
4
|
+
import { acquireTestLock, app } from 'vona-mock';
|
|
5
5
|
|
|
6
6
|
interface IFixture {
|
|
7
7
|
userId?: number;
|
|
@@ -38,7 +38,17 @@ async function cleanup(fixture: IFixture) {
|
|
|
38
38
|
await app.bean.passport.signout();
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
describe('payMock.test.ts', { concurrency: false }, () => {
|
|
41
|
+
describe('payMock.test.ts', { concurrency: false, sequential: true }, () => {
|
|
42
|
+
let releaseTestLock: (() => void) | undefined;
|
|
43
|
+
|
|
44
|
+
before(async () => {
|
|
45
|
+
releaseTestLock = await acquireTestLock('a-pay');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
after(() => {
|
|
49
|
+
releaseTestLock?.();
|
|
50
|
+
});
|
|
51
|
+
|
|
42
52
|
it('captures Vona env secrets in the mock Provider options', () => {
|
|
43
53
|
const options = app.bean.payProvider.getOptions('pay-mock:mock', 'default');
|
|
44
54
|
assert.equal(options.secretCredential, app.meta.env.PAY_MOCK_DEFAULT_CREDENTIAL);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import { describe, it } from 'node:test';
|
|
4
|
-
import { app } from 'vona-mock';
|
|
3
|
+
import { after, before, describe, it } from 'node:test';
|
|
4
|
+
import { acquireTestLock, app } from 'vona-mock';
|
|
5
5
|
|
|
6
6
|
interface IFixture {
|
|
7
7
|
userId?: number;
|
|
@@ -27,6 +27,10 @@ async function createFixture(expiresAt: Date): Promise<IFixture> {
|
|
|
27
27
|
async function cleanup(fixture: IFixture) {
|
|
28
28
|
const scope = app.scope('a-pay');
|
|
29
29
|
if (fixture.paymentSessionId !== undefined) {
|
|
30
|
+
await scope.model.outboxEvent.delete({ paymentSessionId: fixture.paymentSessionId });
|
|
31
|
+
await scope.model.webhookInbox.delete({ paymentSessionId: fixture.paymentSessionId });
|
|
32
|
+
await scope.model.providerOperation.delete({ paymentSessionId: fixture.paymentSessionId });
|
|
33
|
+
await scope.model.refundOperation.delete({ paymentSessionId: fixture.paymentSessionId });
|
|
30
34
|
await scope.model.paymentAudit.delete({ paymentSessionId: fixture.paymentSessionId });
|
|
31
35
|
await scope.model.paymentSession.delete({ id: fixture.paymentSessionId });
|
|
32
36
|
}
|
|
@@ -37,6 +41,16 @@ async function cleanup(fixture: IFixture) {
|
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
describe('paymentSession.test.ts', { concurrency: false }, () => {
|
|
44
|
+
let releaseTestLock: (() => void) | undefined;
|
|
45
|
+
|
|
46
|
+
before(async () => {
|
|
47
|
+
releaseTestLock = await acquireTestLock('a-pay');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
after(() => {
|
|
51
|
+
releaseTestLock?.();
|
|
52
|
+
});
|
|
53
|
+
|
|
40
54
|
it('derives provider facts and expiry from the payment scene', async () => {
|
|
41
55
|
await app.bean.executor.mockCtx(async () => {
|
|
42
56
|
const scope = app.scope('a-pay');
|
|
@@ -65,6 +79,31 @@ describe('paymentSession.test.ts', { concurrency: false }, () => {
|
|
|
65
79
|
});
|
|
66
80
|
});
|
|
67
81
|
|
|
82
|
+
it('rejects an unavailable provider candidate instead of falling back', async () => {
|
|
83
|
+
await app.bean.executor.mockCtx(async () => {
|
|
84
|
+
const scope = app.scope('a-pay');
|
|
85
|
+
const suffix = randomUUID().slice(0, 12);
|
|
86
|
+
const user = await app.bean.user.register({ name: `payment-candidate-${suffix}` }, true);
|
|
87
|
+
const fixture: IFixture = { userId: user.id as number };
|
|
88
|
+
try {
|
|
89
|
+
await assert.rejects(
|
|
90
|
+
scope.service.paymentSession.create({
|
|
91
|
+
userId: user.id,
|
|
92
|
+
payScene: 'commerce-payment:commerceOrder',
|
|
93
|
+
businessReference: `business-${suffix}`,
|
|
94
|
+
amountMinor: 1299,
|
|
95
|
+
currency: 'USD',
|
|
96
|
+
correlationId: `payment-${suffix}`,
|
|
97
|
+
providerCandidateKey: 'stripe',
|
|
98
|
+
}),
|
|
99
|
+
{ status: 422 },
|
|
100
|
+
);
|
|
101
|
+
} finally {
|
|
102
|
+
await cleanup(fixture);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
68
107
|
it('rejects a currency outside the payment scene policy', async () => {
|
|
69
108
|
await app.bean.executor.mockCtx(async () => {
|
|
70
109
|
const scope = app.scope('a-pay');
|
|
@@ -102,9 +141,8 @@ describe('paymentSession.test.ts', { concurrency: false }, () => {
|
|
|
102
141
|
const scope = app.scope('a-pay');
|
|
103
142
|
const session = await scope.service.paymentSession.start(fixture.paymentSessionId!);
|
|
104
143
|
assert.equal(session.state, 'requires_action');
|
|
105
|
-
await
|
|
106
|
-
|
|
107
|
-
});
|
|
144
|
+
const replayed = await scope.service.paymentSession.start(fixture.paymentSessionId!);
|
|
145
|
+
assert.equal(replayed.state, 'requires_action');
|
|
108
146
|
} finally {
|
|
109
147
|
await cleanup(fixture);
|
|
110
148
|
}
|
|
@@ -113,7 +151,7 @@ describe('paymentSession.test.ts', { concurrency: false }, () => {
|
|
|
113
151
|
|
|
114
152
|
it('does not start an expired payment session', async () => {
|
|
115
153
|
await app.bean.executor.mockCtx(async () => {
|
|
116
|
-
const fixture = await createFixture(new Date(Date.now() -
|
|
154
|
+
const fixture = await createFixture(new Date(Date.now() - 1_000));
|
|
117
155
|
try {
|
|
118
156
|
const scope = app.scope('a-pay');
|
|
119
157
|
await assert.rejects(scope.service.paymentSession.start(fixture.paymentSessionId!), {
|
|
@@ -129,6 +167,83 @@ describe('paymentSession.test.ts', { concurrency: false }, () => {
|
|
|
129
167
|
});
|
|
130
168
|
});
|
|
131
169
|
|
|
170
|
+
it('returns a terminal payment session when confirmation is replayed', async () => {
|
|
171
|
+
await app.bean.executor.mockCtx(async () => {
|
|
172
|
+
const fixture = await createFixture(new Date(Date.now() + 60_000));
|
|
173
|
+
try {
|
|
174
|
+
const scope = app.scope('a-pay');
|
|
175
|
+
await scope.model.paymentSession.updateById(fixture.paymentSessionId!, {
|
|
176
|
+
state: 'succeeded',
|
|
177
|
+
finalizedAt: new Date(),
|
|
178
|
+
});
|
|
179
|
+
const session = await scope.service.providerOperation.confirm(fixture.paymentSessionId!);
|
|
180
|
+
assert.equal(session.state, 'succeeded');
|
|
181
|
+
assert.deepEqual(
|
|
182
|
+
await scope.model.providerOperation.select({
|
|
183
|
+
where: { paymentSessionId: fixture.paymentSessionId!, kind: 'confirm' },
|
|
184
|
+
}),
|
|
185
|
+
[],
|
|
186
|
+
);
|
|
187
|
+
} finally {
|
|
188
|
+
await cleanup(fixture);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('schedules a pending refund for durable reconciliation', async () => {
|
|
194
|
+
await app.bean.executor.mockCtx(async () => {
|
|
195
|
+
const fixture = await createFixture(new Date(Date.now() + 60_000));
|
|
196
|
+
try {
|
|
197
|
+
const scope = app.scope('a-pay');
|
|
198
|
+
await scope.model.paymentSession.updateById(fixture.paymentSessionId!, {
|
|
199
|
+
state: 'succeeded',
|
|
200
|
+
providerCaptureId: `capture-${randomUUID().slice(0, 12)}`,
|
|
201
|
+
finalizedAt: new Date(),
|
|
202
|
+
});
|
|
203
|
+
const refund = await scope.service.refundOperation.create({
|
|
204
|
+
paymentSessionId: fixture.paymentSessionId!,
|
|
205
|
+
businessReference: `refund-${randomUUID().slice(0, 12)}`,
|
|
206
|
+
amountMinor: 1299,
|
|
207
|
+
currency: 'USD',
|
|
208
|
+
idempotencyKey: `refund-${randomUUID()}`,
|
|
209
|
+
correlationId: `refund-${randomUUID()}`,
|
|
210
|
+
});
|
|
211
|
+
const providerOperation = await scope.model.providerOperation.get({
|
|
212
|
+
refundOperationId: refund.id,
|
|
213
|
+
kind: 'refund',
|
|
214
|
+
});
|
|
215
|
+
assert.ok(providerOperation);
|
|
216
|
+
const claimed = await scope.service.providerOperation.claim(providerOperation.id);
|
|
217
|
+
assert.ok(claimed?.claimToken);
|
|
218
|
+
await scope.service.providerOperation.markSubmitted(
|
|
219
|
+
providerOperation.id,
|
|
220
|
+
claimed.claimToken,
|
|
221
|
+
);
|
|
222
|
+
await scope.service.refundOperation.settleProviderSnapshot(
|
|
223
|
+
providerOperation.id,
|
|
224
|
+
claimed.claimToken,
|
|
225
|
+
{ state: 'pending', providerRefundId: `refund-${randomUUID().slice(0, 12)}` },
|
|
226
|
+
);
|
|
227
|
+
const [storedRefund, storedOperation, outbox] = await Promise.all([
|
|
228
|
+
scope.model.refundOperation.getById(refund.id),
|
|
229
|
+
scope.model.providerOperation.getById(providerOperation.id),
|
|
230
|
+
scope.model.outboxEvent.select({
|
|
231
|
+
where: { paymentSessionId: fixture.paymentSessionId! },
|
|
232
|
+
}),
|
|
233
|
+
]);
|
|
234
|
+
assert.equal(storedRefund?.state, 'pending');
|
|
235
|
+
assert.equal(storedOperation?.state, 'reconciliation_required');
|
|
236
|
+
assert.equal(storedOperation?.providerResourceId, storedRefund?.providerRefundId);
|
|
237
|
+
assert.equal(storedOperation?.claimToken, undefined);
|
|
238
|
+
assert.equal(storedOperation?.claimExpiresAt, undefined);
|
|
239
|
+
assert.ok(storedOperation?.nextAttemptAt && storedOperation.nextAttemptAt > new Date());
|
|
240
|
+
assert.deepEqual(outbox, []);
|
|
241
|
+
} finally {
|
|
242
|
+
await cleanup(fixture);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
132
247
|
it('allows only one competing start to call the provider', async () => {
|
|
133
248
|
const fixture = await app.bean.executor.mockCtx(async () => {
|
|
134
249
|
return await createFixture(new Date(Date.now() + 60_000));
|
|
@@ -142,8 +257,13 @@ describe('paymentSession.test.ts', { concurrency: false }, () => {
|
|
|
142
257
|
return await app.scope('a-pay').service.paymentSession.start(fixture.paymentSessionId!);
|
|
143
258
|
}),
|
|
144
259
|
]);
|
|
145
|
-
assert.equal(results.filter(result => result.status === 'fulfilled').length,
|
|
146
|
-
|
|
260
|
+
assert.equal(results.filter(result => result.status === 'fulfilled').length, 2);
|
|
261
|
+
const operations = await app.bean.executor.mockCtx(async () => {
|
|
262
|
+
return await app.scope('a-pay').model.providerOperation.select({
|
|
263
|
+
where: { paymentSessionId: fixture.paymentSessionId, kind: 'start' },
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
assert.equal(operations.length, 1);
|
|
147
267
|
} finally {
|
|
148
268
|
await app.bean.executor.mockCtx(async () => {
|
|
149
269
|
await cleanup(fixture);
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
import { app } from 'vona-mock';
|
|
4
|
+
|
|
5
|
+
describe('paypalProvider.test.ts', { concurrency: false }, () => {
|
|
6
|
+
it('maps SDK camelCase payment and refund facts through the PayPal gateway', async () => {
|
|
7
|
+
await app.bean.executor.mockCtx(async () => {
|
|
8
|
+
const calls: Array<{ kind: string; input: unknown }> = [];
|
|
9
|
+
const gateway = {
|
|
10
|
+
async createOrder(_options: unknown, input: unknown) {
|
|
11
|
+
calls.push({ kind: 'createOrder', input });
|
|
12
|
+
return {
|
|
13
|
+
id: 'order-1',
|
|
14
|
+
links: [
|
|
15
|
+
{ rel: 'approve', href: 'https://www.sandbox.paypal.com/checkoutnow?token=order-1' },
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
},
|
|
19
|
+
async captureOrder() {
|
|
20
|
+
throw new Error('unexpected captureOrder');
|
|
21
|
+
},
|
|
22
|
+
async getOrder(_options: unknown, input: unknown) {
|
|
23
|
+
calls.push({ kind: 'getOrder', input });
|
|
24
|
+
return {
|
|
25
|
+
id: 'order-1',
|
|
26
|
+
purchaseUnits: [
|
|
27
|
+
{
|
|
28
|
+
customId: '101',
|
|
29
|
+
invoiceId: 'business-1',
|
|
30
|
+
amount: { currencyCode: 'USD', value: '12.99' },
|
|
31
|
+
payee: { merchantId: 'merchant-1' },
|
|
32
|
+
payments: {
|
|
33
|
+
captures: [
|
|
34
|
+
{
|
|
35
|
+
id: 'capture-1',
|
|
36
|
+
status: 'COMPLETED',
|
|
37
|
+
amount: { currencyCode: 'USD', value: '12.99' },
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
async refundCapturedPayment(_options: unknown, input: unknown) {
|
|
46
|
+
calls.push({ kind: 'refundCapturedPayment', input });
|
|
47
|
+
return {
|
|
48
|
+
id: 'refund-1',
|
|
49
|
+
customId: '201',
|
|
50
|
+
invoiceId: 'refund-business-1',
|
|
51
|
+
amount: { currencyCode: 'USD', value: '5.00' },
|
|
52
|
+
status: 'COMPLETED',
|
|
53
|
+
payee: { merchantId: 'merchant-1' },
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
async getRefund() {
|
|
57
|
+
throw new Error('unexpected getRefund');
|
|
58
|
+
},
|
|
59
|
+
async verifyWebhookSignature() {
|
|
60
|
+
throw new Error('unexpected verifyWebhookSignature');
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
const { provider, clientOptions } = app.bean.payProvider.resolveByName(
|
|
64
|
+
'pay-paypal:paypal',
|
|
65
|
+
'default',
|
|
66
|
+
);
|
|
67
|
+
const options = {
|
|
68
|
+
...clientOptions,
|
|
69
|
+
secretCredential: { clientId: 'client-1', clientSecret: 'secret-1' },
|
|
70
|
+
webhookId: 'webhook-1',
|
|
71
|
+
merchantReference: 'merchant-1',
|
|
72
|
+
gateway,
|
|
73
|
+
};
|
|
74
|
+
const paymentInput = {
|
|
75
|
+
paymentSessionId: 101,
|
|
76
|
+
businessReference: 'business-1',
|
|
77
|
+
idempotencyKey: 'payment-key',
|
|
78
|
+
amountMinor: 1299,
|
|
79
|
+
currency: 'USD',
|
|
80
|
+
providerOrderId: 'order-1',
|
|
81
|
+
};
|
|
82
|
+
const payment = await provider.queryPayment(paymentInput, options as never);
|
|
83
|
+
assert.deepEqual(payment, {
|
|
84
|
+
state: 'succeeded',
|
|
85
|
+
providerPaymentId: 'order-1',
|
|
86
|
+
providerOrderId: 'order-1',
|
|
87
|
+
providerCaptureId: 'capture-1',
|
|
88
|
+
nextAction: { kind: 'completed' },
|
|
89
|
+
});
|
|
90
|
+
const refund = await provider.createRefund(
|
|
91
|
+
{
|
|
92
|
+
paymentSessionId: 101,
|
|
93
|
+
refundOperationId: 201,
|
|
94
|
+
businessReference: 'refund-business-1',
|
|
95
|
+
idempotencyKey: 'refund-key',
|
|
96
|
+
amountMinor: 500,
|
|
97
|
+
currency: 'USD',
|
|
98
|
+
providerCaptureId: 'capture-1',
|
|
99
|
+
},
|
|
100
|
+
options as never,
|
|
101
|
+
);
|
|
102
|
+
assert.deepEqual(refund, { state: 'succeeded', providerRefundId: 'refund-1' });
|
|
103
|
+
assert.deepEqual(
|
|
104
|
+
calls.map(item => item.kind),
|
|
105
|
+
['getOrder', 'refundCapturedPayment'],
|
|
106
|
+
);
|
|
107
|
+
assert.deepEqual(calls[1]?.input, {
|
|
108
|
+
captureId: 'capture-1',
|
|
109
|
+
paypalRequestId: 'refund-key',
|
|
110
|
+
body: {
|
|
111
|
+
amount: { currencyCode: 'USD', value: '5.00' },
|
|
112
|
+
customId: '201',
|
|
113
|
+
invoiceId: 'refund-business-1',
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|
package/test/webhook.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
import { createHmac, randomUUID } from 'node:crypto';
|
|
3
|
-
import { describe, it } from 'node:test';
|
|
4
|
-
import { app } from 'vona-mock';
|
|
3
|
+
import { after, before, describe, it } from 'node:test';
|
|
4
|
+
import { acquireTestLock, app } from 'vona-mock';
|
|
5
5
|
|
|
6
6
|
interface IFixture {
|
|
7
7
|
userId?: number;
|
|
@@ -100,6 +100,16 @@ async function countFacts(paymentSessionId: number) {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
describe('webhook.test.ts', { concurrency: false, sequential: true }, () => {
|
|
103
|
+
let releaseTestLock: (() => void) | undefined;
|
|
104
|
+
|
|
105
|
+
before(async () => {
|
|
106
|
+
releaseTestLock = await acquireTestLock('a-pay');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
after(() => {
|
|
110
|
+
releaseTestLock?.();
|
|
111
|
+
});
|
|
112
|
+
|
|
103
113
|
it('accepts a raw-body signed HTTP webhook and rejects an invalid signature without mutation', async () => {
|
|
104
114
|
const fixture: IFixture = {};
|
|
105
115
|
try {
|