cabloy 5.1.126 → 5.1.128

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.
Files changed (102) hide show
  1. package/.cabloy-version +1 -1
  2. package/CHANGELOG.md +37 -0
  3. package/cabloy-docs/backend/error-guide.md +17 -0
  4. package/cabloy-docs/backend/i18n-guide.md +86 -7
  5. package/e2e/specs/a-commerce/commerce.spec.ts +222 -6
  6. package/package.json +1 -1
  7. package/vona/env/.env +2 -0
  8. package/vona/env/.env.prod +6 -2
  9. package/vona/packages-cli/cabloy-cli/package.json +1 -1
  10. package/vona/packages-cli/cli/package.json +1 -1
  11. package/vona/packages-cli/cli-set-api/package.json +1 -1
  12. package/vona/packages-utils/module-glob/package.json +1 -1
  13. package/vona/packages-utils/utils/package.json +1 -1
  14. package/vona/packages-utils/utils/src/vona.ts +8 -1
  15. package/vona/packages-vona/vona/package.json +1 -1
  16. package/vona/packages-vona/vona-core/package.json +1 -1
  17. package/vona/packages-vona/vona-core/src/lib/bean/resource/error/errorClass.ts +21 -4
  18. package/vona/packages-vona/vona-core/src/lib/bean/resource/error/errorObject.ts +1 -0
  19. package/vona/packages-vona/vona-core/src/lib/bean/type.ts +3 -1
  20. package/vona/packages-vona/vona-core/src/types/interface/module.ts +6 -1
  21. package/vona/packages-vona/vona-mock/package.json +1 -1
  22. package/vona/pnpm-lock.yaml +15 -44
  23. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/index.ts +37 -2
  24. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/en-us.ts +1 -0
  25. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/zh-cn.ts +1 -0
  26. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/categorySelectResItem.tsx +3 -1
  27. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/categoryView.tsx +3 -1
  28. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productSelectResItem.tsx +3 -1
  29. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productView.tsx +3 -1
  30. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/entity/category.tsx +21 -2
  31. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/entity/product.tsx +23 -3
  32. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/model/category.ts +11 -1
  33. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/model/product.ts +1 -0
  34. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/service/category.ts +7 -2
  35. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/service/product.ts +7 -2
  36. package/vona/src/suite/a-commerce/modules/commerce-catalog/test/categoryPresentation.test.ts +218 -0
  37. package/vona/src/suite/a-commerce/modules/commerce-catalog/test/productPresentation.test.ts +214 -0
  38. package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/payScene.commerceOrder.ts +39 -3
  39. package/vona/src/suite/a-commerce/modules/commerce-payment/src/service/commercePayScene.ts +14 -1
  40. package/vona/src/suite/a-commerce/modules/commerce-payment/src/service/paymentAttempt.ts +5 -1
  41. package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +17 -0
  42. package/vona/src/suite/a-commerce/modules/commerce-trade/src/controller/checkout.ts +18 -0
  43. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/checkoutCreate.tsx +3 -0
  44. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/checkoutPaymentMethod.tsx +19 -0
  45. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/checkoutPaymentMethods.tsx +17 -0
  46. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/refundResult.tsx +3 -0
  47. package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/order.ts +53 -1
  48. package/vona/src/suite/a-commerce/modules/commerce-trade/test/paymentOutcome.test.ts +2 -1
  49. package/vona/src/suite/a-commerce/modules/commerce-trade/test/refundLifecycle.test.ts +111 -3
  50. package/vona/src/suite-vendor/a-pay/modules/a-pay/package.json +1 -1
  51. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/.metadata/index.ts +94 -55
  52. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/bean.payScene.ts +65 -1
  53. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/meta.redlock.ts +2 -0
  54. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/controller/paymentCallback.ts +32 -0
  55. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/controller/paymentSession.ts +26 -8
  56. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/paymentCallback.ts +99 -0
  57. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/paymentSession.ts +26 -3
  58. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/providerOperation.ts +214 -82
  59. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/refundOperation.ts +27 -2
  60. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/types/payScene.ts +9 -0
  61. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/types/payment.ts +5 -0
  62. package/vona/src/suite-vendor/a-pay/modules/pay-mock/package.json +1 -1
  63. package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/bean/payProvider.mock.ts +7 -0
  64. package/vona/src/suite-vendor/a-pay/modules/pay-paypal/package.json +1 -1
  65. package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/bean/payProvider.paypal.ts +395 -84
  66. package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/lib/index.ts +1 -1
  67. package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/lib/paypalGateway.ts +129 -0
  68. package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/types/index.ts +2 -0
  69. package/vona/src/suite-vendor/a-pay/modules/pay-stripe/package.json +1 -1
  70. package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/bean/payProvider.stripe.ts +7 -0
  71. package/vona/src/suite-vendor/a-pay/package.json +5 -5
  72. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  73. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  74. package/zova/packages-cli/cli/package.json +3 -3
  75. package/zova/packages-cli/cli-set-front/package.json +4 -4
  76. package/zova/packages-utils/zova-jsx/package.json +3 -3
  77. package/zova/packages-utils/zova-vite/package.json +2 -2
  78. package/zova/packages-zova/zova/package.json +3 -3
  79. package/zova/packages-zova/zova-core/package.json +2 -2
  80. package/zova/src/suite/a-commerce/modules/commerce-member/src/api/openapi/schemas.ts +16 -4
  81. package/zova/src/suite/a-commerce/modules/commerce-member/src/api/openapi/types.ts +240 -19
  82. package/zova/src/suite/a-commerce/modules/commerce-trade/cli/openapi.config.ts +1 -0
  83. package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/commerceTradeCheckout.ts +16 -0
  84. package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/schemas.ts +8 -0
  85. package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/types.ts +184 -8
  86. package/zova/src/suite/a-commerce/modules/commerce-trade/src/apiSchema/commerceTradeCheckout.ts +12 -1
  87. package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/cart.ts +11 -0
  88. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/checkout/controller.tsx +25 -1
  89. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx +27 -4
  90. package/zova/src/suite/cabloy-basic/modules/basic-select/src/bean/tableCell.select.tsx +12 -6
  91. package/zova/src/suite/cabloy-basic/modules/basic-select/src/component/formFieldSelect/controller.tsx +6 -3
  92. package/zova/src/suite-vendor/a-pay/modules/a-pay/cli/openapi.config.ts +1 -1
  93. package/zova/src/suite-vendor/a-pay/modules/a-pay/package.json +1 -1
  94. package/zova/src/suite-vendor/a-pay/modules/a-pay/src/api/openapi/schemas.ts +20 -8
  95. package/zova/src/suite-vendor/a-pay/modules/a-pay/src/api/openapi/types.ts +292 -32
  96. package/zova/src/suite-vendor/a-pay/modules/a-pay/src/api/paymentSession.ts +26 -4
  97. package/zova/src/suite-vendor/a-pay/modules/a-pay/src/apiSchema/paymentSession.ts +5 -0
  98. package/zova/src/suite-vendor/a-pay/modules/a-pay/src/component/paymentNextAction/controller.tsx +2 -1
  99. package/zova/src/suite-vendor/a-pay/modules/a-pay/src/model/paymentSession.ts +12 -0
  100. package/zova/src/suite-vendor/a-pay/package.json +2 -2
  101. package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +4 -4
  102. package/zova/src/suite-vendor/a-zova/package.json +2 -2
@@ -0,0 +1,17 @@
1
+ import type { IDecoratorDtoOptions } from 'vona-module-a-web';
2
+
3
+ import { Api, v } from 'vona-module-a-openapiutils';
4
+ import { Dto } from 'vona-module-a-web';
5
+
6
+ import { DtoCheckoutPaymentMethod } from './checkoutPaymentMethod.tsx';
7
+
8
+ export interface IDtoOptionsCheckoutPaymentMethods extends IDecoratorDtoOptions {}
9
+
10
+ @Dto<IDtoOptionsCheckoutPaymentMethods>()
11
+ export class DtoCheckoutPaymentMethods {
12
+ @Api.field(v.array(DtoCheckoutPaymentMethod))
13
+ items: DtoCheckoutPaymentMethod[];
14
+
15
+ @Api.field(v.required(), v.min(1), v.max(100))
16
+ defaultKey: string;
17
+ }
@@ -18,6 +18,9 @@ export class DtoRefundResult {
18
18
  @Api.field(v.optional(), v.tableIdentity())
19
19
  refundAttemptId?: TableIdentity;
20
20
 
21
+ @Api.field(v.optional(), v.tableIdentity())
22
+ refundOperationId?: TableIdentity;
23
+
21
24
  @Api.field(z.enum(['paid', 'refund_requested', 'refund_approved', 'refund_rejected', 'refunded']))
22
25
  orderState: 'paid' | 'refund_requested' | 'refund_approved' | 'refund_rejected' | 'refunded';
23
26
 
@@ -322,6 +322,7 @@ export class ServiceOrder extends BeanBase {
322
322
  currency: order.currency,
323
323
  amountCents: order.payableTotalCents,
324
324
  correlationId: `${command.correlationId}:payment`,
325
+ providerCandidateKey: command.providerCandidateKey,
325
326
  });
326
327
  await onStage?.('afterPaymentAttempt');
327
328
  await this._appendAudit({
@@ -376,6 +377,13 @@ export class ServiceOrder extends BeanBase {
376
377
  providerEventId: event.eventId,
377
378
  });
378
379
  if (existingAudit) return this._paymentOutcomeResult(order, lockedAttempt);
380
+ if (
381
+ order.state === 'expired' &&
382
+ lockedAttempt.state === 'cancelled' &&
383
+ event.state === 'succeeded'
384
+ ) {
385
+ return await this._scheduleLateCaptureCompensation(order, lockedAttempt, event);
386
+ }
379
387
  if (order.state !== 'awaiting_payment' || lockedAttempt.state !== 'created') {
380
388
  this.app.throw(409, 'payment attempt is no longer available');
381
389
  }
@@ -1157,7 +1165,7 @@ export class ServiceOrder extends BeanBase {
1157
1165
  private _refundResult(
1158
1166
  order: Pick<EntityOrder, 'id' | 'state' | 'currency'>,
1159
1167
  refundRequest: Pick<EntityRefundRequest, 'id' | 'state' | 'amountCents'>,
1160
- refundAttempt?: Pick<EntityRefundAttempt, 'id' | 'state'>,
1168
+ refundAttempt?: Pick<EntityRefundAttempt, 'id' | 'state' | 'refundOperationId'>,
1161
1169
  ): DtoRefundResult {
1162
1170
  if (
1163
1171
  order.state !== 'paid' &&
@@ -1172,6 +1180,7 @@ export class ServiceOrder extends BeanBase {
1172
1180
  orderId: order.id,
1173
1181
  refundRequestId: refundRequest.id,
1174
1182
  refundAttemptId: refundAttempt?.id,
1183
+ refundOperationId: refundAttempt?.refundOperationId,
1175
1184
  orderState: order.state,
1176
1185
  refundState: refundRequest.state,
1177
1186
  refundAttemptState: refundAttempt?.state,
@@ -1216,6 +1225,49 @@ export class ServiceOrder extends BeanBase {
1216
1225
  });
1217
1226
  }
1218
1227
 
1228
+ private async _scheduleLateCaptureCompensation(
1229
+ order: EntityOrder,
1230
+ attempt: EntityPaymentAttempt,
1231
+ event: IPaymentOutcomeEvent,
1232
+ ) {
1233
+ if (!event.providerCaptureId)
1234
+ this.app.throw(409, 'late payment outcome has no provider capture');
1235
+ await this.$scope.commercePayment.model.paymentAttempt.updateById(attempt.id, {
1236
+ providerCaptureId: event.providerCaptureId,
1237
+ });
1238
+ const correlationId = `${order.correlationId}:late-capture:${event.eventId}`;
1239
+ const existingAudit = await this.$scope.commercePayment.model.paymentAudit.get({
1240
+ paymentAttemptId: attempt.id,
1241
+ providerEventId: event.eventId,
1242
+ });
1243
+ if (existingAudit) return this._paymentOutcomeResult(order, attempt);
1244
+ await this.$scope.commercePayment.model.paymentAudit.insert({
1245
+ paymentAttemptId: attempt.id,
1246
+ orderId: order.id,
1247
+ userId: order.userId,
1248
+ provider: event.providerName,
1249
+ providerEventId: event.eventId,
1250
+ outcome: 'succeeded',
1251
+ fromAttemptState: attempt.state,
1252
+ toOrderState: 'expired',
1253
+ idempotencyKey: event.eventId.slice(0, 100),
1254
+ correlationId,
1255
+ reason: 'late provider capture requires automatic compensation',
1256
+ processedAt: new Date(),
1257
+ });
1258
+ const session = await this.$scope.pay.model.paymentSession.getById(event.paymentSessionId);
1259
+ if (!session) this.app.throw(404, 'payment session not found');
1260
+ await this.$scope.pay.service.refundOperation.create({
1261
+ paymentSessionId: session.id,
1262
+ businessReference: `late-capture:${attempt.id}`,
1263
+ amountMinor: event.amountMinor,
1264
+ currency: event.currency,
1265
+ idempotencyKey: `${correlationId}:refund`,
1266
+ correlationId,
1267
+ });
1268
+ return this._paymentOutcomeResult(order, attempt);
1269
+ }
1270
+
1219
1271
  private async _expireLockedOrder(order: EntityOrder) {
1220
1272
  const correlationId = `${order.correlationId}:expiry`;
1221
1273
  const reason = 'unpaid order expired';
@@ -550,6 +550,7 @@ describe('paymentOutcome.test.ts', { concurrency: false, sequential: true }, ()
550
550
 
551
551
  it('denies anonymous customer payment and order actions', async () => {
552
552
  await app.bean.executor.mockCtx(async () => {
553
+ await app.bean.passport.signout();
553
554
  const requests = [
554
555
  () =>
555
556
  app.bean.executor.performAction('get', '/commerce/trade/order/mine', {
@@ -564,7 +565,7 @@ describe('paymentOutcome.test.ts', { concurrency: false, sequential: true }, ()
564
565
  innerAccess: false,
565
566
  }),
566
567
  () =>
567
- app.bean.executor.performAction('get', '/payment-session/1', {
568
+ app.bean.executor.performAction('get', '/pay/payment-session/1', {
568
569
  innerAccess: false,
569
570
  }),
570
571
  ];
@@ -16,6 +16,7 @@ interface IFixture {
16
16
  customerName?: string;
17
17
  orderId?: number;
18
18
  paymentAttemptId?: number;
19
+ paymentSessionId?: number;
19
20
  }
20
21
 
21
22
  async function cleanup(fixture: IFixture) {
@@ -24,6 +25,21 @@ async function cleanup(fixture: IFixture) {
24
25
  const promotion = app.scope('commerce-promotion');
25
26
  const catalog = app.scope('commerce-catalog');
26
27
  const member = app.scope('commerce-member');
28
+ const pay = app.scope('a-pay');
29
+ if (fixture.paymentSessionId !== undefined) {
30
+ const refundOperations = await pay.model.refundOperation.select({
31
+ where: { paymentSessionId: fixture.paymentSessionId },
32
+ });
33
+ for (const refundOperation of refundOperations) {
34
+ await pay.model.providerOperation.delete({ refundOperationId: refundOperation.id });
35
+ await pay.model.outboxEvent.delete({ refundOperationId: refundOperation.id });
36
+ await pay.model.webhookInbox.delete({ refundOperationId: refundOperation.id });
37
+ await pay.model.refundOperation.delete({ id: refundOperation.id });
38
+ }
39
+ await pay.model.outboxEvent.delete({ paymentSessionId: fixture.paymentSessionId });
40
+ await pay.model.webhookInbox.delete({ paymentSessionId: fixture.paymentSessionId });
41
+ await pay.model.providerOperation.delete({ paymentSessionId: fixture.paymentSessionId });
42
+ }
27
43
  if (fixture.orderId !== undefined) {
28
44
  const requests = await payment.model.refundRequest.select({
29
45
  where: { orderId: fixture.orderId },
@@ -39,6 +55,8 @@ async function cleanup(fixture: IFixture) {
39
55
  await payment.model.paymentAudit.delete({ paymentAttemptId: fixture.paymentAttemptId });
40
56
  await payment.model.paymentAttempt.delete({ id: fixture.paymentAttemptId });
41
57
  }
58
+ if (fixture.paymentSessionId !== undefined)
59
+ await pay.model.paymentSession.delete({ id: fixture.paymentSessionId });
42
60
  if (fixture.orderId !== undefined)
43
61
  await trade.model.orderAudit.delete({ orderId: fixture.orderId });
44
62
  if (fixture.skuId !== undefined) await trade.model.stockAudit.delete({ skuId: fixture.skuId });
@@ -152,10 +170,17 @@ async function createPaidOrder(suffix: string): Promise<IFixture> {
152
170
  });
153
171
  fixture.orderId = checkout.orderId as number;
154
172
  fixture.paymentAttemptId = checkout.paymentAttemptId as number;
155
- await app.scope('commerce-trade').service.order.applyPaymentOutcome(fixture.paymentAttemptId, {
156
- outcome: 'succeeded',
157
- idempotencyKey: `refund-payment-${suffix}`,
173
+ fixture.paymentSessionId = checkout.paymentSessionId as number;
174
+ const pay = app.scope('a-pay');
175
+ await pay.service.paymentSession.start(fixture.paymentSessionId);
176
+ await app
177
+ .scope('pay-mock')
178
+ .service.payMock.completePaymentSession(fixture.paymentSessionId, 'succeeded');
179
+ const outbox = await pay.model.outboxEvent.get({
180
+ paymentSessionId: fixture.paymentSessionId,
181
+ eventType: 'payment.outcome.v1',
158
182
  });
183
+ await pay.queue.outboxDispatch.pushAsync({ outboxEventId: outbox!.id });
159
184
  return fixture;
160
185
  }
161
186
 
@@ -310,6 +335,89 @@ describe('refundLifecycle.test.ts', { concurrency: false, sequential: true }, ()
310
335
  });
311
336
  });
312
337
 
338
+ it('settles an executed mock-provider refund through the durable outbox', async () => {
339
+ await app.bean.executor.mockCtx(async () => {
340
+ const fixture: IFixture = {};
341
+ try {
342
+ Object.assign(fixture, await createPaidOrder(randomUUID().slice(0, 12)));
343
+ const order = app.scope('commerce-trade').service.order;
344
+ await order.requestRefund(fixture.orderId!, {
345
+ reason: 'provider-backed refund',
346
+ idempotencyKey: 'provider-backed-request-1',
347
+ });
348
+ await app.bean.passport.signout();
349
+ await app.bean.passport.signinMock();
350
+ await order.approveRefund(fixture.orderId!, {
351
+ reason: 'provider-backed approval',
352
+ idempotencyKey: 'provider-backed-approve-1',
353
+ });
354
+ const executed = await order.executeRefund(fixture.orderId!);
355
+ assert.ok(executed.refundAttemptId);
356
+ assert.ok(executed.refundOperationId);
357
+ assert.deepEqual(
358
+ [executed.orderState, executed.refundState, executed.refundAttemptState],
359
+ ['refund_approved', 'approved', 'created'],
360
+ );
361
+ const pay = app.scope('a-pay');
362
+ const refundOperation = await pay.model.refundOperation.getById(executed.refundOperationId);
363
+ const refundAttempt = await app
364
+ .scope('commerce-payment')
365
+ .model.refundAttempt.getById(executed.refundAttemptId);
366
+ assert.equal(String(refundAttempt?.refundOperationId), String(executed.refundOperationId));
367
+ assert.equal(refundOperation?.state, 'pending');
368
+ const receipt = await app
369
+ .scope('pay-mock')
370
+ .service.payMock.completeRefundOperation(executed.refundOperationId, 'succeeded');
371
+ assert.deepEqual(receipt, {
372
+ refundOperationId: executed.refundOperationId,
373
+ accepted: true,
374
+ });
375
+ const outbox = await pay.model.outboxEvent.get({
376
+ refundOperationId: executed.refundOperationId,
377
+ eventType: 'refund.outcome.v1',
378
+ });
379
+ assert.ok(outbox);
380
+ await pay.queue.outboxDispatch.pushAsync({ outboxEventId: outbox.id });
381
+ await pay.queue.outboxDispatch.pushAsync({ outboxEventId: outbox.id });
382
+ const [
383
+ dispatched,
384
+ settledOrder,
385
+ settledRequest,
386
+ settledAttempt,
387
+ line,
388
+ balance,
389
+ grant,
390
+ audits,
391
+ ] = await Promise.all([
392
+ pay.model.outboxEvent.getById(outbox.id),
393
+ app.scope('commerce-trade').model.order.getById(fixture.orderId!),
394
+ app.scope('commerce-payment').model.refundRequest.getById(executed.refundRequestId),
395
+ app.scope('commerce-payment').model.refundAttempt.getById(executed.refundAttemptId),
396
+ app.scope('commerce-trade').model.orderLine.get({ orderId: fixture.orderId }),
397
+ app.scope('commerce-trade').model.stockBalance.getById(fixture.balanceId!),
398
+ app.scope('commerce-promotion').model.couponGrant.getById(fixture.grantId!),
399
+ app
400
+ .scope('commerce-payment')
401
+ .model.refundAudit.select({ where: { orderId: fixture.orderId } }),
402
+ ]);
403
+ const reservation = await app
404
+ .scope('commerce-trade')
405
+ .model.stockReservation.get({ orderLineId: line?.id });
406
+ assert.deepEqual([dispatched?.state, dispatched?.attemptCount], ['dispatched', 1]);
407
+ assert.deepEqual(
408
+ [settledOrder?.state, settledRequest?.state, settledAttempt?.state, reservation?.state],
409
+ ['refunded', 'refunded', 'succeeded', 'restored'],
410
+ );
411
+ assert.deepEqual([balance?.onHand, balance?.reserved, balance?.available], [1, 0, 1]);
412
+ assert.equal(grant?.state, 'redeemed');
413
+ assert.equal(audits.filter(audit => audit.toRefundState === 'refunded').length, 1);
414
+ } finally {
415
+ await app.bean.passport.signout();
416
+ await cleanup(fixture);
417
+ }
418
+ });
419
+ });
420
+
313
421
  it('preserves rejected and failed cycles while permitting a fresh request', async () => {
314
422
  await app.bean.executor.mockCtx(async () => {
315
423
  const fixture: IFixture = {};
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-pay",
3
- "version": "5.0.3",
3
+ "version": "5.0.4",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "Vona Module"