cabloy 5.1.113 → 5.1.114

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 (138) hide show
  1. package/.cabloy-version +1 -1
  2. package/.claude/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +4 -0
  3. package/.github/workflows/vona-test-mysql.yml +12 -0
  4. package/.github/workflows/vona-test-pg.yml +5 -1
  5. package/CHANGELOG.md +14 -0
  6. package/CLAUDE.md +4 -0
  7. package/cabloy-docs/backend/internal-aop-guide.md +35 -0
  8. package/cabloy-docs/backend/unit-testing.md +72 -1
  9. package/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-cli/cli-set-api/toolsIsolate/test.ts +26 -13
  13. package/vona/packages-utils/compose/package.json +1 -1
  14. package/vona/packages-utils/compose/src/index.ts +19 -11
  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/beanContainer.ts +15 -3
  18. package/vona/packages-vona/vona-mock/package.json +1 -1
  19. package/vona/pnpm-lock.yaml +15 -0
  20. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/index.ts +8 -0
  21. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/bean/meta.version.ts +1 -0
  22. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/en-us.ts +3 -0
  23. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/zh-cn.ts +3 -0
  24. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/skuAttribute.tsx +17 -0
  25. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/entity/sku.tsx +10 -1
  26. package/vona/src/suite/a-commerce/modules/commerce-catalog/test/catalog.test.ts +1 -1
  27. package/vona/src/suite/a-commerce/modules/commerce-member/test/addressOwnership.test.ts +6 -1
  28. package/vona/src/suite/a-commerce/modules/commerce-payment/package.json +2 -1
  29. package/vona/src/suite/a-commerce/modules/commerce-payment/src/.metadata/index.ts +228 -0
  30. package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/meta.index.ts +15 -0
  31. package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/meta.version.ts +35 -0
  32. package/vona/src/suite/a-commerce/modules/commerce-payment/src/dto/paymentAttemptView.tsx +26 -0
  33. package/vona/src/suite/a-commerce/modules/commerce-payment/src/entity/paymentAttempt.tsx +34 -0
  34. package/vona/src/suite/a-commerce/modules/commerce-payment/src/model/paymentAttempt.ts +10 -0
  35. package/vona/src/suite/a-commerce/modules/commerce-payment/src/service/paymentAttempt.ts +41 -0
  36. package/vona/src/suite/a-commerce/modules/commerce-payment/test/paymentAttempt.test.ts +33 -0
  37. package/vona/src/suite/a-commerce/modules/commerce-promotion/package.json +4 -2
  38. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/.metadata/index.ts +533 -2
  39. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/.metadata/locales.ts +18 -0
  40. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/bean/meta.index.ts +24 -0
  41. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/bean/meta.version.ts +137 -0
  42. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/bean/ssrMenu.couponTemplate.ts +30 -0
  43. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/config/locale/en-us.ts +37 -0
  44. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/config/locale/zh-cn.ts +37 -0
  45. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/controller/coupon.ts +37 -0
  46. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/controller/couponTemplate.ts +65 -0
  47. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponIssue.tsx +22 -0
  48. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponMineItem.tsx +32 -0
  49. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponTemplateCreate.tsx +43 -0
  50. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponTemplateSelectReq.tsx +28 -0
  51. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponTemplateSelectRes.tsx +11 -0
  52. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponTemplateSelectResItem.tsx +39 -0
  53. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponTemplateUpdate.tsx +19 -0
  54. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponTemplateView.tsx +25 -0
  55. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/entity/couponAudit.tsx +73 -0
  56. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/entity/couponGrant.tsx +83 -0
  57. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/entity/couponTemplate.tsx +82 -0
  58. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/index.ts +1 -0
  59. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/model/couponAudit.ts +10 -0
  60. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/model/couponGrant.ts +10 -0
  61. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/model/couponTemplate.ts +10 -0
  62. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/service/coupon.ts +397 -0
  63. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/service/couponTemplate.ts +52 -0
  64. package/vona/src/suite/a-commerce/modules/commerce-promotion/test/couponReservation.test.ts +162 -0
  65. package/vona/src/suite/a-commerce/modules/commerce-seed/src/bean/meta.version.ts +8 -10
  66. package/vona/src/suite/a-commerce/modules/commerce-trade/package.json +4 -1
  67. package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +277 -0
  68. package/vona/src/suite/a-commerce/modules/commerce-trade/src/bean/bean.scheduleOrderExpiry.ts +26 -0
  69. package/vona/src/suite/a-commerce/modules/commerce-trade/src/bean/meta.index.ts +9 -0
  70. package/vona/src/suite/a-commerce/modules/commerce-trade/src/bean/meta.version.ts +75 -0
  71. package/vona/src/suite/a-commerce/modules/commerce-trade/src/config/locale/en-us.ts +11 -2
  72. package/vona/src/suite/a-commerce/modules/commerce-trade/src/config/locale/zh-cn.ts +11 -2
  73. package/vona/src/suite/a-commerce/modules/commerce-trade/src/controller/checkout.ts +19 -0
  74. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/checkoutCreate.tsx +19 -0
  75. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/checkoutResult.tsx +32 -0
  76. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderAddressSnapshot.tsx +33 -0
  77. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderCouponSnapshot.tsx +35 -0
  78. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderLineSkuAttributeSnapshot.tsx +15 -0
  79. package/vona/src/suite/a-commerce/modules/commerce-trade/src/entity/order.tsx +90 -0
  80. package/vona/src/suite/a-commerce/modules/commerce-trade/src/entity/orderAudit.tsx +36 -0
  81. package/vona/src/suite/a-commerce/modules/commerce-trade/src/entity/orderLine.tsx +52 -0
  82. package/vona/src/suite/a-commerce/modules/commerce-trade/src/entity/stockReservation.tsx +4 -1
  83. package/vona/src/suite/a-commerce/modules/commerce-trade/src/model/order.ts +15 -0
  84. package/vona/src/suite/a-commerce/modules/commerce-trade/src/model/orderAudit.ts +10 -0
  85. package/vona/src/suite/a-commerce/modules/commerce-trade/src/model/orderLine.ts +10 -0
  86. package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/order.ts +556 -0
  87. package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/stockBalance.ts +15 -20
  88. package/vona/src/suite/a-commerce/modules/commerce-trade/test/cartOwnership.test.ts +8 -3
  89. package/vona/src/suite/a-commerce/modules/commerce-trade/test/checkoutReservation.test.ts +158 -0
  90. package/vona/src/suite/a-commerce/modules/commerce-trade/test/orderSnapshot.test.ts +203 -0
  91. package/vona/src/suite/a-commerce/modules/commerce-trade/test/reservationExpiry.test.ts +163 -0
  92. package/vona/src/suite/a-commerce/modules/commerce-trade/test/stockBalance.test.ts +11 -6
  93. package/vona/src/suite/a-commerce/modules/commerce-trade/test/stockReservation.test.ts +11 -6
  94. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/package.json +1 -1
  95. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/service/imageCloudflare.ts +10 -7
  96. package/vona/src/suite-vendor/a-image/package.json +1 -1
  97. package/vona/src/suite-vendor/a-vona/modules/a-aspect/package.json +1 -1
  98. package/vona/src/suite-vendor/a-vona/modules/a-aspect/src/types/aopMethod.ts +16 -3
  99. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +4 -2
  100. package/vona/src/suite-vendor/a-vona/modules/a-core/src/bean/bean.core.ts +6 -0
  101. package/vona/src/suite-vendor/a-vona/modules/a-core/src/index.ts +1 -0
  102. package/vona/src/suite-vendor/a-vona/modules/a-core/src/lib/core.ts +6 -0
  103. package/vona/src/suite-vendor/a-vona/modules/a-core/src/types/fetch.ts +7 -0
  104. package/vona/src/suite-vendor/a-vona/modules/a-core/src/types/index.ts +1 -0
  105. package/vona/src/suite-vendor/a-vona/modules/a-executor/package.json +1 -1
  106. package/vona/src/suite-vendor/a-vona/modules/a-executor/src/lib/utils.ts +5 -3
  107. package/vona/src/suite-vendor/a-vona/modules/a-retryable/package.json +50 -0
  108. package/vona/src/suite-vendor/a-vona/modules/a-retryable/src/.metadata/index.ts +55 -0
  109. package/vona/src/suite-vendor/a-vona/modules/a-retryable/src/.metadata/this.ts +2 -0
  110. package/vona/src/suite-vendor/a-vona/modules/a-retryable/src/bean/aopMethod.retryable.ts +45 -0
  111. package/vona/src/suite-vendor/a-vona/modules/a-retryable/src/index.ts +1 -0
  112. package/vona/src/suite-vendor/a-vona/modules/a-retryable/tsconfig.build.json +11 -0
  113. package/vona/src/suite-vendor/a-vona/modules/a-retryable/tsconfig.json +7 -0
  114. package/vona/src/suite-vendor/a-vona/modules/a-vona/package.json +2 -1
  115. package/vona/src/suite-vendor/a-vona/package.json +2 -1
  116. package/vona/src/suite-vendor/a-vona/tsconfig.json +3 -0
  117. package/zova/packages-utils/zova-jsx/package.json +3 -3
  118. package/zova/packages-zova/zova/package.json +3 -3
  119. package/zova/packages-zova/zova-core/package.json +2 -2
  120. package/zova/src/suite/a-commerce/modules/commerce-promotion/cli/openapi.config.ts +9 -0
  121. package/zova/src/suite/a-commerce/modules/commerce-promotion/package.json +2 -1
  122. package/zova/src/suite/a-commerce/modules/commerce-promotion/src/.metadata/index.ts +115 -1
  123. package/zova/src/suite/a-commerce/modules/commerce-promotion/src/api/commercePromotionCoupon.ts +24 -0
  124. package/zova/src/suite/a-commerce/modules/commerce-promotion/src/api/openapi/baseURL.ts +5 -0
  125. package/zova/src/suite/a-commerce/modules/commerce-promotion/src/api/openapi/index.ts +3 -0
  126. package/zova/src/suite/a-commerce/modules/commerce-promotion/src/api/openapi/schemas.ts +478 -0
  127. package/zova/src/suite/a-commerce/modules/commerce-promotion/src/api/openapi/types.ts +8187 -0
  128. package/zova/src/suite/a-commerce/modules/commerce-promotion/src/apiSchema/commercePromotionCoupon.ts +13 -0
  129. package/zova/src/suite/a-commerce/modules/commerce-promotion/src/model/coupon.ts +19 -0
  130. package/zova/src/suite/a-commerce/modules/commerce-trade/cli/openapi.config.ts +1 -0
  131. package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +30 -0
  132. package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/commerceTradeCheckout.ts +27 -0
  133. package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/schemas.ts +92 -52
  134. package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/types.ts +1389 -848
  135. package/zova/src/suite/a-commerce/modules/commerce-trade/src/apiSchema/commerceTradeCheckout.ts +13 -0
  136. package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/cart.ts +13 -0
  137. package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +3 -3
  138. package/zova/src/suite-vendor/a-zova/package.json +2 -2
@@ -6,11 +6,16 @@ import type {
6
6
 
7
7
  import { catchError } from '@cabloy/utils';
8
8
  import assert from 'node:assert';
9
+ import { randomUUID } from 'node:crypto';
9
10
  import { describe, it } from 'node:test';
10
11
  import { app } from 'vona-mock';
11
12
 
12
13
  const actionPath = '/commerce/member/address';
13
14
 
15
+ function createTestId() {
16
+ return randomUUID().slice(0, 12);
17
+ }
18
+
14
19
  function createAddress(suffix: string): DtoAddressCreate {
15
20
  return {
16
21
  recipientName: `Recipient ${suffix}`,
@@ -60,7 +65,7 @@ describe('address.test.ts', () => {
60
65
 
61
66
  it('derives ownership from the authenticated customer and scopes every action', async () => {
62
67
  await app.bean.executor.mockCtx(async () => {
63
- const suffix = `${Date.now()}`;
68
+ const suffix = createTestId();
64
69
  const customerA = await registerAndSignin(`address-a-${suffix}`);
65
70
  const customerB = await registerAndSignin(`address-b-${suffix}`);
66
71
  const addressData = createAddress(suffix);
@@ -47,6 +47,7 @@
47
47
  "vonaModule": {
48
48
  "dependencies": {
49
49
  "a-vona": "5.0.0"
50
- }
50
+ },
51
+ "fileVersion": 1
51
52
  }
52
53
  }
@@ -1,4 +1,229 @@
1
1
  // eslint-disable
2
+ import type { TypeEntityMeta,TypeModelsClassLikeGeneral,TypeSymbolKeyFieldsMore } from 'vona-module-a-orm';
3
+ import type { TypeEntityOptionsFields } from 'vona-module-a-openapi';
4
+ import type { TableIdentity } from 'table-identity';
5
+ /** entity: begin */
6
+ export * from '../entity/paymentAttempt.tsx';
7
+ import type { IEntityOptionsPaymentAttempt } from '../entity/paymentAttempt.tsx';
8
+ import 'vona-module-a-orm';
9
+ declare module 'vona-module-a-orm' {
10
+
11
+ export interface IEntityRecord {
12
+ 'commerce-payment:paymentAttempt': IEntityOptionsPaymentAttempt;
13
+ }
14
+
15
+
16
+ }
17
+ declare module 'vona-module-commerce-payment' {
18
+
19
+ }
20
+ /** entity: end */
21
+ /** entity: begin */
22
+ import type { EntityPaymentAttempt } from '../entity/paymentAttempt.tsx';
23
+ export interface IModuleEntity {
24
+ 'paymentAttempt': EntityPaymentAttemptMeta;
25
+ }
26
+ /** entity: end */
27
+ /** entity: begin */
28
+ export type EntityPaymentAttemptTableName = 'commercePaymentAttempt';
29
+ export type EntityPaymentAttemptMeta=TypeEntityMeta<EntityPaymentAttempt,EntityPaymentAttemptTableName>;
30
+ declare module 'vona-module-a-orm' {
31
+ export interface ITableRecord {
32
+ 'commercePaymentAttempt': EntityPaymentAttemptMeta;
33
+ }
34
+ }
35
+ declare module 'vona-module-commerce-payment' {
36
+
37
+ export interface IEntityOptionsPaymentAttempt {
38
+ fields?: TypeEntityOptionsFields<EntityPaymentAttempt, IEntityOptionsPaymentAttempt[TypeSymbolKeyFieldsMore]>;
39
+ }
40
+ }
41
+ /** entity: end */
42
+ /** model: begin */
43
+ export * from '../model/paymentAttempt.ts';
44
+ import type { IModelOptionsPaymentAttempt } from '../model/paymentAttempt.ts';
45
+ import 'vona-module-a-orm';
46
+ declare module 'vona-module-a-orm' {
47
+
48
+ export interface IModelRecord {
49
+ 'commerce-payment:paymentAttempt': IModelOptionsPaymentAttempt;
50
+ }
51
+
52
+
53
+ }
54
+ declare module 'vona-module-commerce-payment' {
55
+
56
+ export interface ModelPaymentAttempt {
57
+ /** @internal */
58
+ get scope(): ScopeModuleCommercePayment;
59
+ }
60
+
61
+ export interface ModelPaymentAttempt {
62
+ get $beanFullName(): 'commerce-payment.model.paymentAttempt';
63
+ get $onionName(): 'commerce-payment:paymentAttempt';
64
+ get $onionOptions(): IModelOptionsPaymentAttempt;
65
+ }
66
+ }
67
+ /** model: end */
68
+ /** model: begin */
69
+ import type { ModelPaymentAttempt } from '../model/paymentAttempt.ts';
70
+ export interface IModuleModel {
71
+ 'paymentAttempt': ModelPaymentAttempt;
72
+ }
73
+ /** model: end */
74
+ /** model: begin */
75
+
76
+ import 'vona';
77
+ declare module 'vona' {
78
+ export interface IBeanRecordGeneral {
79
+ 'commerce-payment.model.paymentAttempt': ModelPaymentAttempt;
80
+ }
81
+ }
82
+ /** model: end */
83
+ /** model: begin */
84
+ import type { IModelGetOptions, IModelMethodOptions, IModelSelectParams, TypeModelSelectAndCount, TypeModelRelationResult, TypeModelWhere, IModelInsertOptions, TypeModelMutateRelationData, IModelDeleteOptions, IModelUpdateOptions, IModelMutateOptions, IModelSelectCountParams, IModelIncrementParams, IModelSelectAggrParams, TypeModelAggrRelationResult, IModelSelectGroupParams, TypeModelGroupRelationResult } from 'vona-module-a-orm';
85
+ import { SymbolKeyEntity, SymbolKeyEntityMeta, SymbolKeyModelOptions } from 'vona-module-a-orm';
86
+ declare module 'vona-module-commerce-payment' {
87
+
88
+ export interface ModelPaymentAttempt {
89
+ [SymbolKeyEntity]: EntityPaymentAttempt;
90
+ [SymbolKeyEntityMeta]: EntityPaymentAttemptMeta;
91
+ [SymbolKeyModelOptions]: IModelOptionsPaymentAttempt;
92
+ get<T extends IModelGetOptions<EntityPaymentAttempt,ModelPaymentAttempt>>(where: TypeModelWhere<EntityPaymentAttempt>, options?: T): Promise<TypeModelRelationResult<EntityPaymentAttempt, ModelPaymentAttempt, T> | undefined>;
93
+ getForUpdate<T extends IModelGetOptions<EntityPaymentAttempt,ModelPaymentAttempt>>(where: TypeModelWhere<EntityPaymentAttempt>, options?: T): Promise<TypeModelRelationResult<EntityPaymentAttempt, ModelPaymentAttempt, T> | undefined>;
94
+ getByIdForUpdate<T extends IModelGetOptions<EntityPaymentAttempt,ModelPaymentAttempt>>(id: TableIdentity, options?: T): Promise<TypeModelRelationResult<EntityPaymentAttempt, ModelPaymentAttempt, T> | undefined>;
95
+ mget<T extends IModelGetOptions<EntityPaymentAttempt,ModelPaymentAttempt>>(ids: TableIdentity[], options?: T): Promise<TypeModelRelationResult<EntityPaymentAttempt, ModelPaymentAttempt, T>[]>;
96
+ selectAndCount<T extends IModelSelectParams<EntityPaymentAttempt,ModelPaymentAttempt,ModelJoins>, ModelJoins extends TypeModelsClassLikeGeneral | undefined = undefined>(params?: T, options?: IModelMethodOptions, modelJoins?: ModelJoins): Promise<TypeModelSelectAndCount<EntityPaymentAttempt, ModelPaymentAttempt, T>>;
97
+ select<T extends IModelSelectParams<EntityPaymentAttempt,ModelPaymentAttempt,ModelJoins>, ModelJoins extends TypeModelsClassLikeGeneral | undefined = undefined>(params?: T, options?: IModelMethodOptions, modelJoins?: ModelJoins): Promise<TypeModelRelationResult<EntityPaymentAttempt, ModelPaymentAttempt, T>[]>;
98
+ insert<T extends IModelInsertOptions<EntityPaymentAttempt,ModelPaymentAttempt>>(data?: TypeModelMutateRelationData<EntityPaymentAttempt,ModelPaymentAttempt, T>, options?: T): Promise<TypeModelMutateRelationData<EntityPaymentAttempt,ModelPaymentAttempt, T, true>>;
99
+ insertBulk<T extends IModelInsertOptions<EntityPaymentAttempt,ModelPaymentAttempt>>(items: TypeModelMutateRelationData<EntityPaymentAttempt,ModelPaymentAttempt, T>[], options?: T): Promise<TypeModelMutateRelationData<EntityPaymentAttempt,ModelPaymentAttempt, T, true>[]>;
100
+ update<T extends IModelUpdateOptions<EntityPaymentAttempt,ModelPaymentAttempt>>(data: TypeModelMutateRelationData<EntityPaymentAttempt,ModelPaymentAttempt, T>, options?: T): Promise<TypeModelMutateRelationData<EntityPaymentAttempt,ModelPaymentAttempt, T>>;
101
+ updateBulk<T extends IModelUpdateOptions<EntityPaymentAttempt,ModelPaymentAttempt>>(items: TypeModelMutateRelationData<EntityPaymentAttempt,ModelPaymentAttempt, T>[], options?: T): Promise<TypeModelMutateRelationData<EntityPaymentAttempt,ModelPaymentAttempt, T>[]>;
102
+ delete<T extends IModelDeleteOptions<EntityPaymentAttempt,ModelPaymentAttempt>>(where?: TypeModelWhere<EntityPaymentAttempt>, options?: T): Promise<void>;
103
+ deleteBulk<T extends IModelDeleteOptions<EntityPaymentAttempt,ModelPaymentAttempt>>(ids: TableIdentity[], options?: T): Promise<void>;
104
+ mutate<T extends IModelMutateOptions<EntityPaymentAttempt,ModelPaymentAttempt>>(data?: TypeModelMutateRelationData<EntityPaymentAttempt,ModelPaymentAttempt, T>, options?: T): Promise<TypeModelMutateRelationData<EntityPaymentAttempt,ModelPaymentAttempt, T>>;
105
+ mutateBulk<T extends IModelMutateOptions<EntityPaymentAttempt,ModelPaymentAttempt>>(items: TypeModelMutateRelationData<EntityPaymentAttempt,ModelPaymentAttempt, T>[], options?: T): Promise<TypeModelMutateRelationData<EntityPaymentAttempt,ModelPaymentAttempt, T>[]>;
106
+ count<T extends IModelSelectCountParams<EntityPaymentAttempt,ModelPaymentAttempt,ModelJoins>, ModelJoins extends TypeModelsClassLikeGeneral | undefined = undefined>(params?: T, options?: IModelMethodOptions, modelJoins?: ModelJoins): Promise<string | undefined>;
107
+ increment<T extends IModelIncrementParams<EntityPaymentAttempt,ModelPaymentAttempt,ModelJoins>, ModelJoins extends TypeModelsClassLikeGeneral | undefined = undefined>(params?: T, options?: IModelMethodOptions, modelJoins?: ModelJoins): Promise<number>;
108
+ decrement<T extends IModelIncrementParams<EntityPaymentAttempt,ModelPaymentAttempt,ModelJoins>, ModelJoins extends TypeModelsClassLikeGeneral | undefined = undefined>(params?: T, options?: IModelMethodOptions, modelJoins?: ModelJoins): Promise<number>;
109
+ aggregate<T extends IModelSelectAggrParams<EntityPaymentAttempt,ModelPaymentAttempt,ModelJoins>, ModelJoins extends TypeModelsClassLikeGeneral | undefined = undefined>(params?: T, options?: IModelMethodOptions, modelJoins?: ModelJoins): Promise<TypeModelAggrRelationResult<T>>;
110
+ group<T extends IModelSelectGroupParams<EntityPaymentAttempt,ModelPaymentAttempt,ModelJoins>, ModelJoins extends TypeModelsClassLikeGeneral | undefined = undefined>(params?: T, options?: IModelMethodOptions, modelJoins?: ModelJoins): Promise<TypeModelGroupRelationResult<EntityPaymentAttempt, T>[]>;
111
+ getById<T extends IModelGetOptions<EntityPaymentAttempt,ModelPaymentAttempt>>(id: TableIdentity, options?: T): Promise<TypeModelRelationResult<EntityPaymentAttempt, ModelPaymentAttempt, T> | undefined>;
112
+ updateById<T extends IModelUpdateOptions<EntityPaymentAttempt,ModelPaymentAttempt>>(id: TableIdentity, data: TypeModelMutateRelationData<EntityPaymentAttempt,ModelPaymentAttempt, T>, options?: T): Promise<TypeModelMutateRelationData<EntityPaymentAttempt,ModelPaymentAttempt, T>>;
113
+ deleteById<T extends IModelDeleteOptions<EntityPaymentAttempt,ModelPaymentAttempt>>(id: TableIdentity, options?: T): Promise<void>;
114
+ }
115
+ }
116
+ declare module 'vona-module-a-orm' {
117
+ export interface IModelClassRecord {
118
+ 'commerce-payment:paymentAttempt': ModelPaymentAttempt;
119
+ }
120
+ }
121
+ /** model: end */
122
+ /** service: begin */
123
+ export * from '../service/paymentAttempt.ts';
124
+
125
+ import 'vona-module-a-bean';
126
+ declare module 'vona-module-a-bean' {
127
+
128
+ export interface IServiceRecord {
129
+ 'commerce-payment:paymentAttempt': never;
130
+ }
131
+
132
+
133
+ }
134
+ declare module 'vona-module-commerce-payment' {
135
+
136
+ export interface ServicePaymentAttempt {
137
+ /** @internal */
138
+ get scope(): ScopeModuleCommercePayment;
139
+ }
140
+
141
+ export interface ServicePaymentAttempt {
142
+ get $beanFullName(): 'commerce-payment.service.paymentAttempt';
143
+ get $onionName(): 'commerce-payment:paymentAttempt';
144
+
145
+ }
146
+ }
147
+ /** service: end */
148
+ /** service: begin */
149
+ import type { ServicePaymentAttempt } from '../service/paymentAttempt.ts';
150
+ export interface IModuleService {
151
+ 'paymentAttempt': ServicePaymentAttempt;
152
+ }
153
+ /** service: end */
154
+ /** service: begin */
155
+
156
+ import 'vona';
157
+ declare module 'vona' {
158
+ export interface IBeanRecordGeneral {
159
+ 'commerce-payment.service.paymentAttempt': ServicePaymentAttempt;
160
+ }
161
+ }
162
+ /** service: end */
163
+ /** meta: begin */
164
+ export * from '../bean/meta.index.ts';
165
+ export * from '../bean/meta.version.ts';
166
+ import type { IMetaOptionsIndex } from 'vona-module-a-index';
167
+ import 'vona-module-a-meta';
168
+ declare module 'vona-module-a-meta' {
169
+
170
+ export interface IMetaRecord {
171
+ 'commerce-payment:index': IMetaOptionsIndex;
172
+ 'commerce-payment:version': never;
173
+ }
174
+
175
+
176
+ }
177
+ declare module 'vona-module-commerce-payment' {
178
+
179
+ export interface MetaIndex {
180
+ /** @internal */
181
+ get scope(): ScopeModuleCommercePayment;
182
+ }
183
+
184
+ export interface MetaIndex {
185
+ get $beanFullName(): 'commerce-payment.meta.index';
186
+ get $onionName(): 'commerce-payment:index';
187
+ get $onionOptions(): IMetaOptionsIndex;
188
+ }
189
+
190
+ export interface MetaVersion {
191
+ /** @internal */
192
+ get scope(): ScopeModuleCommercePayment;
193
+ }
194
+
195
+ export interface MetaVersion {
196
+ get $beanFullName(): 'commerce-payment.meta.version';
197
+ get $onionName(): 'commerce-payment:version';
198
+
199
+ }
200
+ }
201
+ /** meta: end */
202
+ /** dto: begin */
203
+ export * from '../dto/paymentAttemptView.tsx';
204
+ import type { IDtoOptionsPaymentAttemptView } from '../dto/paymentAttemptView.tsx';
205
+ import 'vona-module-a-web';
206
+ declare module 'vona-module-a-web' {
207
+
208
+ export interface IDtoRecord {
209
+ 'commerce-payment:paymentAttemptView': IDtoOptionsPaymentAttemptView;
210
+ }
211
+
212
+
213
+ }
214
+ declare module 'vona-module-commerce-payment' {
215
+
216
+ }
217
+ /** dto: end */
218
+ /** dto: begin */
219
+ import type { DtoPaymentAttemptView } from '../dto/paymentAttemptView.tsx';
220
+ declare module 'vona-module-commerce-payment' {
221
+
222
+ export interface IDtoOptionsPaymentAttemptView {
223
+ fields?: TypeEntityOptionsFields<DtoPaymentAttemptView, IDtoOptionsPaymentAttemptView[TypeSymbolKeyFieldsMore]>;
224
+ }
225
+ }
226
+ /** dto: end */
2
227
  /** scope: begin */
3
228
  import { BeanScopeBase, type BeanScopeUtil } from 'vona';
4
229
  import { Scope } from 'vona-module-a-bean';
@@ -8,6 +233,9 @@ export class ScopeModuleCommercePayment extends BeanScopeBase {}
8
233
 
9
234
  export interface ScopeModuleCommercePayment {
10
235
  util: BeanScopeUtil;
236
+ entity: IModuleEntity;
237
+ model: IModuleModel;
238
+ service: IModuleService;
11
239
  }
12
240
 
13
241
  import 'vona';
@@ -0,0 +1,15 @@
1
+ import type { IMetaOptionsIndex } from 'vona-module-a-index';
2
+
3
+ import { BeanBase } from 'vona';
4
+ import { Meta } from 'vona-module-a-meta';
5
+ import { $tableColumns } from 'vona-module-a-ormutils';
6
+
7
+ @Meta<IMetaOptionsIndex>({
8
+ indexes: {
9
+ ...$tableColumns('commercePaymentAttempt', 'orderId'),
10
+ ...$tableColumns('commercePaymentAttempt', 'userId'),
11
+ ...$tableColumns('commercePaymentAttempt', 'state'),
12
+ ...$tableColumns('commercePaymentAttempt', 'correlationId'),
13
+ },
14
+ })
15
+ export class MetaIndex extends BeanBase {}
@@ -0,0 +1,35 @@
1
+ import type { IMetaVersionUpdate, IMetaVersionUpdateOptions } from 'vona-module-a-version';
2
+
3
+ import { BeanBase } from 'vona';
4
+ import { Meta } from 'vona-module-a-meta';
5
+
6
+ @Meta()
7
+ export class MetaVersion extends BeanBase implements IMetaVersionUpdate {
8
+ async update(options: IMetaVersionUpdateOptions) {
9
+ if (options.version !== 1) return;
10
+
11
+ const entityPaymentAttempt = this.scope.entity.paymentAttempt;
12
+ await this.bean.model.createTable(entityPaymentAttempt.$table, table => {
13
+ table.comment(entityPaymentAttempt.$comment.$table);
14
+ table.basicFields();
15
+ table
16
+ .tableIdentity(entityPaymentAttempt.orderId)
17
+ .comment(entityPaymentAttempt.$comment.orderId);
18
+ table.userId(entityPaymentAttempt.userId).comment(entityPaymentAttempt.$comment.userId);
19
+ table.string(entityPaymentAttempt.state, 20).comment(entityPaymentAttempt.$comment.state);
20
+ table
21
+ .string(entityPaymentAttempt.currency, 3)
22
+ .comment(entityPaymentAttempt.$comment.currency);
23
+ table
24
+ .integer(entityPaymentAttempt.amountCents)
25
+ .comment(entityPaymentAttempt.$comment.amountCents);
26
+ table
27
+ .string(entityPaymentAttempt.correlationId, 100)
28
+ .comment(entityPaymentAttempt.$comment.correlationId);
29
+ table
30
+ .dateTime(entityPaymentAttempt.cancelledAt)
31
+ .nullable()
32
+ .comment(entityPaymentAttempt.$comment.cancelledAt);
33
+ });
34
+ }
35
+ }
@@ -0,0 +1,26 @@
1
+ import type { TableIdentity } from 'table-identity';
2
+ import type { IDecoratorDtoOptions } from 'vona-module-a-web';
3
+
4
+ import { Api, v } from 'vona-module-a-openapiutils';
5
+ import { Dto } from 'vona-module-a-web';
6
+ import { z } from 'zod';
7
+
8
+ export interface IDtoOptionsPaymentAttemptView extends IDecoratorDtoOptions {}
9
+
10
+ @Dto<IDtoOptionsPaymentAttemptView>()
11
+ export class DtoPaymentAttemptView {
12
+ @Api.field(v.tableIdentity())
13
+ id: TableIdentity;
14
+
15
+ @Api.field(v.tableIdentity())
16
+ orderId: TableIdentity;
17
+
18
+ @Api.field(z.enum(['created', 'cancelled']))
19
+ state: 'created' | 'cancelled';
20
+
21
+ @Api.field(z.literal('USD'))
22
+ currency: 'USD';
23
+
24
+ @Api.field(z.number().int().nonnegative())
25
+ amountCents: number;
26
+ }
@@ -0,0 +1,34 @@
1
+ import type { TableIdentity } from 'table-identity';
2
+ import type { IDecoratorEntityOptions } from 'vona-module-a-orm';
3
+
4
+ import { Api, v } from 'vona-module-a-openapiutils';
5
+ import { Entity, EntityBase } from 'vona-module-a-orm';
6
+ import { z } from 'zod';
7
+
8
+ export type TypePaymentAttemptState = 'created' | 'cancelled';
9
+
10
+ export interface IEntityOptionsPaymentAttempt extends IDecoratorEntityOptions {}
11
+
12
+ @Entity<IEntityOptionsPaymentAttempt>('commercePaymentAttempt')
13
+ export class EntityPaymentAttempt extends EntityBase {
14
+ @Api.field(v.tableIdentity())
15
+ orderId: TableIdentity;
16
+
17
+ @Api.field(v.tableIdentity())
18
+ userId: TableIdentity;
19
+
20
+ @Api.field(z.enum(['created', 'cancelled']))
21
+ state: TypePaymentAttemptState;
22
+
23
+ @Api.field(z.literal('USD'))
24
+ currency: 'USD';
25
+
26
+ @Api.field(z.number().int().nonnegative())
27
+ amountCents: number;
28
+
29
+ @Api.field(v.required(), v.min(1), v.max(100))
30
+ correlationId: string;
31
+
32
+ @Api.field(v.optional())
33
+ cancelledAt?: Date;
34
+ }
@@ -0,0 +1,10 @@
1
+ import type { IDecoratorModelOptions } from 'vona-module-a-orm';
2
+
3
+ import { BeanModelBase, Model } from 'vona-module-a-orm';
4
+
5
+ import { EntityPaymentAttempt } from '../entity/paymentAttempt.tsx';
6
+
7
+ export interface IModelOptionsPaymentAttempt extends IDecoratorModelOptions<EntityPaymentAttempt> {}
8
+
9
+ @Model<IModelOptionsPaymentAttempt>({ entity: EntityPaymentAttempt })
10
+ export class ModelPaymentAttempt extends BeanModelBase<EntityPaymentAttempt> {}
@@ -0,0 +1,41 @@
1
+ import type { TableIdentity } from 'table-identity';
2
+
3
+ import { BeanBase } from 'vona';
4
+ import { Service } from 'vona-module-a-bean';
5
+ import { Core } from 'vona-module-a-core';
6
+
7
+ import type { EntityPaymentAttempt } from '../entity/paymentAttempt.tsx';
8
+
9
+ export interface IPaymentAttemptCreateCommand {
10
+ orderId: TableIdentity;
11
+ userId: TableIdentity;
12
+ currency: 'USD';
13
+ amountCents: number;
14
+ correlationId: string;
15
+ }
16
+
17
+ @Service()
18
+ export class ServicePaymentAttempt extends BeanBase {
19
+ async create(command: IPaymentAttemptCreateCommand): Promise<EntityPaymentAttempt> {
20
+ return await this.scope.model.paymentAttempt.insert({
21
+ orderId: command.orderId,
22
+ userId: command.userId,
23
+ state: 'created',
24
+ currency: command.currency,
25
+ amountCents: command.amountCents,
26
+ correlationId: command.correlationId,
27
+ });
28
+ }
29
+
30
+ @Core.transaction({ isolationLevel: 'SERIALIZABLE' })
31
+ async cancel(orderId: TableIdentity): Promise<EntityPaymentAttempt | undefined> {
32
+ const attempt = await this.scope.model.paymentAttempt.getForUpdate({ orderId });
33
+ if (!attempt || attempt.state === 'cancelled') return attempt;
34
+ const cancelledAt = new Date();
35
+ await this.scope.model.paymentAttempt.updateById(attempt.id, {
36
+ state: 'cancelled',
37
+ cancelledAt,
38
+ });
39
+ return { ...attempt, state: 'cancelled', cancelledAt };
40
+ }
41
+ }
@@ -0,0 +1,33 @@
1
+ import assert from 'node:assert';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { describe, it } from 'node:test';
4
+ import { app } from 'vona-mock';
5
+
6
+ describe('paymentAttempt.test.ts', { concurrency: false }, () => {
7
+ it('creates and internally cancels an attempt exactly once', async () => {
8
+ await app.bean.executor.mockCtx(async () => {
9
+ const scope = app.scope('commerce-payment');
10
+ const suffix = randomUUID().slice(0, 12);
11
+ let attemptId: number | undefined;
12
+ try {
13
+ const user = await app.bean.user.register({ name: `payment-${suffix}` }, true);
14
+ const created = await scope.service.paymentAttempt.create({
15
+ orderId: 900_001,
16
+ userId: user.id,
17
+ currency: 'USD',
18
+ amountCents: 1299,
19
+ correlationId: `payment-${suffix}`,
20
+ });
21
+ attemptId = created.id as number;
22
+ assert.equal(created.state, 'created');
23
+ const cancelled = await scope.service.paymentAttempt.cancel(created.orderId);
24
+ assert.equal(cancelled?.state, 'cancelled');
25
+ const replay = await scope.service.paymentAttempt.cancel(created.orderId);
26
+ assert.equal(replay?.id, created.id);
27
+ assert.equal(replay?.state, 'cancelled');
28
+ } finally {
29
+ if (attemptId !== undefined) await scope.model.paymentAttempt.delete({ id: attemptId });
30
+ }
31
+ });
32
+ });
33
+ });
@@ -46,7 +46,9 @@
46
46
  "title": "commerce-promotion",
47
47
  "vonaModule": {
48
48
  "dependencies": {
49
- "a-vona": "5.0.0"
50
- }
49
+ "a-vona": "5.0.0",
50
+ "commerce-siteadmin": "5.0.0"
51
+ },
52
+ "fileVersion": 1
51
53
  }
52
54
  }