cabloy 5.1.122 → 5.1.124

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 (244) hide show
  1. package/.cabloy-version +1 -1
  2. package/.claude/skills/cabloy-frontend-scaffold/SKILL.md +13 -3
  3. package/.claude/skills/cabloy-workflow/SKILL.md +6 -0
  4. package/.claude/skills/cabloy-workflow/evals/evals.json +6 -0
  5. package/CHANGELOG.md +24 -0
  6. package/CLAUDE.md +1 -1
  7. package/cabloy-docs/.vitepress/config.mjs +4 -0
  8. package/cabloy-docs/frontend/form-layout-guide.md +29 -7
  9. package/cabloy-docs/fullstack/parallel-worktree-environment.md +87 -0
  10. package/e2e/scripts/e2e.ts +13 -1
  11. package/e2e/scripts/startE2eVona.ts +4 -1
  12. package/e2e/specs/a-commerce/commerce.spec.ts +129 -32
  13. package/package.json +2 -1
  14. package/playwright-report/data/24ac790ec112339c76cfb0c8e2c2539c2f953a0b.zip +0 -0
  15. package/playwright-report/data/2fddcabcdbd0b695061a565acfff243cb3a4f5e3.zip +0 -0
  16. package/playwright-report/data/4738038d9e861d63fd25937c9a3570934786a62f.zip +0 -0
  17. package/playwright-report/data/72f99c7da67c7f6ea53cab7d8681798b70d79a10.md +222 -0
  18. package/playwright-report/data/7b9684a7c793e9798b898f8ff50177b660a7a733.md +222 -0
  19. package/playwright-report/data/8b31595f5f1cc9d23863af51a592e6b90dcbc4ce.zip +0 -0
  20. package/playwright-report/data/95b578407a68806202dda917a61c50c750bd285b.md +222 -0
  21. package/playwright-report/data/ad960b3a58f224f5b4cad04a24067600064ffb7b.md +222 -0
  22. package/playwright-report/index.html +90 -0
  23. package/playwright-report/trace/assets/codeMirrorModule-LEHpjmcn.js +32 -0
  24. package/playwright-report/trace/assets/defaultSettingsView-BNmKHKpQ.js +264 -0
  25. package/playwright-report/trace/assets/urlMatch-BYQrIQwR.js +1 -0
  26. package/playwright-report/trace/codeMirrorModule.DYBRYzYX.css +1 -0
  27. package/playwright-report/trace/codicon.DCmgc-ay.ttf +0 -0
  28. package/playwright-report/trace/defaultSettingsView.CjdS-WJx.css +1 -0
  29. package/playwright-report/trace/index.CzXZzn5A.css +1 -0
  30. package/playwright-report/trace/index.DMMX1gXU.js +2 -0
  31. package/playwright-report/trace/index.html +44 -0
  32. package/playwright-report/trace/manifest.webmanifest +16 -0
  33. package/playwright-report/trace/playwright-logo.svg +9 -0
  34. package/playwright-report/trace/snapshot.html +10 -0
  35. package/playwright-report/trace/snapshot.v8KI4P3m.js +2 -0
  36. package/playwright-report/trace/sw.bundle.js +5 -0
  37. package/playwright-report/trace/uiMode.BZQ54Kgt.css +1 -0
  38. package/playwright-report/trace/uiMode.Ut8wwJNp.js +6 -0
  39. package/playwright-report/trace/uiMode.html +18 -0
  40. package/playwright-report/trace/xtermModule.DYP7pi_n.css +32 -0
  41. package/vona/env/.env +12 -0
  42. package/vona/env/.env.test +7 -0
  43. package/vona/packages-cli/cli/package.json +1 -1
  44. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/dto/{{resourceName}}Create.tsx_ +19 -1
  45. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/dto/{{resourceName}}Update.tsx_ +19 -1
  46. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/dto/{{resourceName}}View.tsx_ +19 -1
  47. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/snippets/3-en-us.ts +6 -1
  48. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/snippets/4-zh-cn.ts +6 -1
  49. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/dto/{{resourceName}}Create.tsx_ +19 -1
  50. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/dto/{{resourceName}}Update.tsx_ +19 -1
  51. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/dto/{{resourceName}}View.tsx_ +19 -1
  52. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/snippets/3-en-us.ts +6 -1
  53. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/snippets/4-zh-cn.ts +6 -1
  54. package/vona/packages-cli/cli-set-api/package.json +1 -1
  55. package/vona/pnpm-lock.yaml +255 -12
  56. package/vona/pnpm-workspace.yaml +1 -1
  57. package/vona/src/suite/a-commerce/modules/commerce-payment/package.json +4 -1
  58. package/vona/src/suite/a-commerce/modules/commerce-payment/src/.metadata/index.ts +45 -3
  59. package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/meta.index.ts +1 -0
  60. package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/meta.version.ts +25 -1
  61. package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/payScene.commerceOrder.ts +20 -0
  62. package/vona/src/suite/a-commerce/modules/commerce-payment/src/entity/paymentAttempt.tsx +9 -0
  63. package/vona/src/suite/a-commerce/modules/commerce-payment/src/entity/paymentAudit.tsx +5 -2
  64. package/vona/src/suite/a-commerce/modules/commerce-payment/src/entity/refundAttempt.tsx +6 -0
  65. package/vona/src/suite/a-commerce/modules/commerce-payment/src/index.ts +2 -0
  66. package/vona/src/suite/a-commerce/modules/commerce-payment/src/lib/index.ts +1 -0
  67. package/vona/src/suite/a-commerce/modules/commerce-payment/src/service/commercePayScene.ts +54 -0
  68. package/vona/src/suite/a-commerce/modules/commerce-payment/src/service/paymentAttempt.ts +7 -1
  69. package/vona/src/suite/a-commerce/modules/commerce-payment/src/types/index.ts +1 -0
  70. package/vona/src/suite/a-commerce/modules/commerce-payment/test/paymentAttempt.test.ts +21 -0
  71. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/.metadata/index.ts +30 -0
  72. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/config/locale/en-us.ts +4 -0
  73. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/config/locale/zh-cn.ts +4 -0
  74. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponTemplateCreate.tsx +88 -1
  75. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponTemplateUpdate.tsx +44 -1
  76. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponTemplateView.tsx +72 -1
  77. package/vona/src/suite/a-commerce/modules/commerce-promotion/test/couponTemplateFormLayout.test.ts +121 -0
  78. package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/bean/ssrMenu.home.ts +4 -1
  79. package/vona/src/suite/a-commerce/modules/commerce-trade/package.json +4 -1
  80. package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +0 -21
  81. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/checkoutResult.tsx +3 -0
  82. package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/order.ts +79 -0
  83. package/vona/src/suite/a-commerce/modules/commerce-trade/test/orderSnapshot.test.ts +227 -0
  84. package/vona/src/suite/a-commerce/modules/commerce-trade/test/paymentOutcome.test.ts +121 -13
  85. package/vona/src/suite/a-commerce/modules/commerce-trade/test/refundLifecycle.test.ts +312 -0
  86. package/vona/src/suite-vendor/a-pay/modules/a-pay/package.json +76 -0
  87. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/.metadata/index.ts +842 -0
  88. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/.metadata/this.ts +2 -0
  89. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/bean.payProvider.ts +92 -0
  90. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/bean.payScene.ts +105 -0
  91. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/meta.index.ts +28 -0
  92. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/meta.redlock.ts +11 -0
  93. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/meta.version.ts +113 -0
  94. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/queue.outboxDispatch.ts +31 -0
  95. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/schedule.outboxDispatch.ts +11 -0
  96. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/controller/paymentSession.ts +55 -0
  97. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/controller/webhook.ts +47 -0
  98. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/dto/paymentSessionStart.tsx +8 -0
  99. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/dto/paymentSessionView.tsx +69 -0
  100. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/dto/webhookReceipt.tsx +12 -0
  101. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/outboxEvent.tsx +46 -0
  102. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/paymentAudit.tsx +36 -0
  103. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/paymentSession.tsx +72 -0
  104. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/providerOperation.tsx +54 -0
  105. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/refundOperation.tsx +37 -0
  106. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/webhookInbox.tsx +59 -0
  107. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/index.ts +3 -0
  108. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/lib/index.ts +2 -0
  109. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/lib/payProvider.ts +7 -0
  110. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/lib/payScene.ts +7 -0
  111. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/model/outboxEvent.ts +10 -0
  112. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/model/paymentAudit.ts +10 -0
  113. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/model/paymentSession.ts +10 -0
  114. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/model/providerOperation.ts +10 -0
  115. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/model/refundOperation.ts +10 -0
  116. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/model/webhookInbox.ts +10 -0
  117. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/outbox.ts +156 -0
  118. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/paymentSession.ts +278 -0
  119. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/providerOperation.ts +5 -0
  120. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/webhook.ts +127 -0
  121. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/types/index.ts +3 -0
  122. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/types/payProvider.ts +74 -0
  123. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/types/payScene.ts +63 -0
  124. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/types/payment.ts +151 -0
  125. package/vona/src/suite-vendor/a-pay/modules/a-pay/test/outbox.test.ts +209 -0
  126. package/vona/src/suite-vendor/a-pay/modules/a-pay/test/paymentSession.test.ts +153 -0
  127. package/vona/src/suite-vendor/a-pay/modules/a-pay/test/webhook.test.ts +278 -0
  128. package/vona/src/suite-vendor/a-pay/modules/a-pay/tsconfig.build.json +11 -0
  129. package/vona/src/suite-vendor/a-pay/modules/a-pay/tsconfig.json +7 -0
  130. package/vona/src/suite-vendor/a-pay/modules/pay-mock/package.json +55 -0
  131. package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/.metadata/index.ts +182 -0
  132. package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/.metadata/this.ts +2 -0
  133. package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/bean/payProvider.mock.ts +149 -0
  134. package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/controller/mockPayment.ts +25 -0
  135. package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/dto/mockPaymentComplete.tsx +13 -0
  136. package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/dto/mockPaymentReceipt.tsx +16 -0
  137. package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/index.ts +3 -0
  138. package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/lib/index.ts +1 -0
  139. package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/service/payMock.ts +78 -0
  140. package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/types/index.ts +10 -0
  141. package/vona/src/suite-vendor/a-pay/modules/pay-mock/test/payMock.test.ts +141 -0
  142. package/vona/src/suite-vendor/a-pay/modules/pay-mock/tsconfig.build.json +11 -0
  143. package/vona/src/suite-vendor/a-pay/modules/pay-mock/tsconfig.json +7 -0
  144. package/vona/src/suite-vendor/a-pay/modules/pay-paypal/package.json +55 -0
  145. package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/.metadata/index.ts +62 -0
  146. package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/.metadata/this.ts +2 -0
  147. package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/bean/payProvider.paypal.ts +91 -0
  148. package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/index.ts +3 -0
  149. package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/lib/index.ts +1 -0
  150. package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/types/index.ts +9 -0
  151. package/vona/src/suite-vendor/a-pay/modules/pay-paypal/tsconfig.build.json +11 -0
  152. package/vona/src/suite-vendor/a-pay/modules/pay-paypal/tsconfig.json +7 -0
  153. package/vona/src/suite-vendor/a-pay/modules/pay-stripe/package.json +55 -0
  154. package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/.metadata/index.ts +62 -0
  155. package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/.metadata/this.ts +2 -0
  156. package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/bean/payProvider.stripe.ts +88 -0
  157. package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/index.ts +2 -0
  158. package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/types/index.ts +8 -0
  159. package/vona/src/suite-vendor/a-pay/modules/pay-stripe/tsconfig.build.json +11 -0
  160. package/vona/src/suite-vendor/a-pay/modules/pay-stripe/tsconfig.json +7 -0
  161. package/vona/src/suite-vendor/a-pay/package.json +15 -0
  162. package/vona/src/suite-vendor/a-pay/tsconfig.base.json +4 -0
  163. package/vona/src/suite-vendor/a-pay/tsconfig.json +19 -0
  164. package/zova/env/.env +1 -0
  165. package/zova/openapi.config.ts +5 -1
  166. package/zova/package.original.json +1 -1
  167. package/zova/packages-cli/cli/package.json +2 -2
  168. package/zova/packages-cli/cli-set-front/cli/templates/openapi/config/boilerplate/project/openapi.config.ts +5 -1
  169. package/zova/packages-cli/cli-set-front/package.json +1 -1
  170. package/zova/packages-cli/cli-set-front/src/lib/bean/cli.openapi.generate.ts +8 -0
  171. package/zova/packages-cli/cli-set-front/src/lib/common/cliCreatePage.ts +7 -24
  172. package/zova/packages-cli/cli-set-front/src/lib/common/utils.ts +10 -1
  173. package/zova/packages-utils/zova-jsx/package.json +2 -2
  174. package/zova/packages-zova/zova/package.json +3 -3
  175. package/zova/packages-zova/zova-core/package.json +1 -1
  176. package/zova/packages-zova/zova-core/src/types/interface/component.ts +5 -0
  177. package/zova/pnpm-lock.yaml +96 -26
  178. package/zova/src/front/config/config/config.cabloyCommerce.ts +1 -1
  179. package/zova/src/suite/a-commerce/modules/commerce-promotion/src/api/openapi/schemas.ts +110 -12
  180. package/zova/src/suite/a-commerce/modules/commerce-promotion/src/api/openapi/types.ts +1292 -160
  181. package/zova/src/suite/a-commerce/modules/commerce-trade/cli/openapi.config.ts +0 -1
  182. package/zova/src/suite/a-commerce/modules/commerce-trade/package.json +4 -1
  183. package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +1 -47
  184. package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/schemas.ts +23 -8
  185. package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/types.ts +249 -62
  186. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/checkout/controller.tsx +5 -1
  187. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx +152 -39
  188. package/zova/src/suite/a-commerce/modules/commerce-trade/src/routes.ts +1 -1
  189. package/zova/src/suite-vendor/a-pay/modules/a-pay/cli/openapi.config.ts +9 -0
  190. package/zova/src/suite-vendor/a-pay/modules/a-pay/package.json +53 -0
  191. package/zova/src/suite-vendor/a-pay/modules/a-pay/src/.metadata/component/paymentNextAction.ts +31 -0
  192. package/zova/src/suite-vendor/a-pay/modules/a-pay/src/.metadata/index.ts +217 -0
  193. package/zova/src/suite-vendor/a-pay/modules/a-pay/src/.metadata/this.ts +2 -0
  194. package/zova/src/suite-vendor/a-pay/modules/a-pay/src/api/openapi/baseURL.ts +5 -0
  195. package/zova/src/suite-vendor/a-pay/modules/a-pay/src/api/openapi/index.ts +3 -0
  196. package/zova/src/suite-vendor/a-pay/modules/a-pay/src/api/openapi/schemas.ts +584 -0
  197. package/zova/src/suite-vendor/a-pay/modules/a-pay/src/api/openapi/types.ts +9324 -0
  198. package/zova/src/suite-vendor/a-pay/modules/a-pay/src/api/paymentSession.ts +52 -0
  199. package/zova/src/suite-vendor/a-pay/modules/a-pay/src/apiSchema/paymentSession.ts +20 -0
  200. package/zova/src/suite-vendor/a-pay/modules/a-pay/src/component/paymentNextAction/controller.tsx +76 -0
  201. package/zova/src/suite-vendor/a-pay/modules/a-pay/src/index.ts +3 -0
  202. package/zova/src/suite-vendor/a-pay/modules/a-pay/src/lib/index.ts +1 -0
  203. package/zova/src/suite-vendor/a-pay/modules/a-pay/src/model/paymentSession.ts +32 -0
  204. package/zova/src/suite-vendor/a-pay/modules/a-pay/src/service/paymentCoordinator.ts +13 -0
  205. package/zova/src/suite-vendor/a-pay/modules/a-pay/src/types/index.ts +1 -0
  206. package/zova/src/suite-vendor/a-pay/modules/a-pay/src/types/payment.ts +25 -0
  207. package/zova/src/suite-vendor/a-pay/modules/a-pay/tsconfig.build.json +13 -0
  208. package/zova/src/suite-vendor/a-pay/modules/a-pay/tsconfig.json +5 -0
  209. package/zova/src/suite-vendor/a-pay/modules/pay-mock/cli/openapi.config.ts +9 -0
  210. package/zova/src/suite-vendor/a-pay/modules/pay-mock/package.json +53 -0
  211. package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/.metadata/index.ts +140 -0
  212. package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/.metadata/this.ts +2 -0
  213. package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/api/openapi/baseURL.ts +5 -0
  214. package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/api/openapi/index.ts +3 -0
  215. package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/api/openapi/schemas.ts +584 -0
  216. package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/api/openapi/types.ts +9324 -0
  217. package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/api/payMockPayment.ts +34 -0
  218. package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/apiSchema/payMockPayment.ts +13 -0
  219. package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/index.ts +1 -0
  220. package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/model/payMockPayment.ts +20 -0
  221. package/zova/src/suite-vendor/a-pay/modules/pay-mock/tsconfig.build.json +13 -0
  222. package/zova/src/suite-vendor/a-pay/modules/pay-mock/tsconfig.json +5 -0
  223. package/zova/src/suite-vendor/a-pay/modules/pay-paypal/package.json +52 -0
  224. package/zova/src/suite-vendor/a-pay/modules/pay-paypal/src/.metadata/index.ts +26 -0
  225. package/zova/src/suite-vendor/a-pay/modules/pay-paypal/src/.metadata/this.ts +2 -0
  226. package/zova/src/suite-vendor/a-pay/modules/pay-paypal/src/index.ts +1 -0
  227. package/zova/src/suite-vendor/a-pay/modules/pay-paypal/tsconfig.build.json +13 -0
  228. package/zova/src/suite-vendor/a-pay/modules/pay-paypal/tsconfig.json +5 -0
  229. package/zova/src/suite-vendor/a-pay/modules/pay-stripe/package.json +52 -0
  230. package/zova/src/suite-vendor/a-pay/modules/pay-stripe/src/.metadata/index.ts +26 -0
  231. package/zova/src/suite-vendor/a-pay/modules/pay-stripe/src/.metadata/this.ts +2 -0
  232. package/zova/src/suite-vendor/a-pay/modules/pay-stripe/src/index.ts +1 -0
  233. package/zova/src/suite-vendor/a-pay/modules/pay-stripe/tsconfig.build.json +13 -0
  234. package/zova/src/suite-vendor/a-pay/modules/pay-stripe/tsconfig.json +5 -0
  235. package/zova/src/suite-vendor/a-pay/package.json +15 -0
  236. package/zova/src/suite-vendor/a-pay/tsconfig.base.json +4 -0
  237. package/zova/src/suite-vendor/a-pay/tsconfig.json +4 -0
  238. package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
  239. package/zova/src/suite-vendor/a-zova/package.json +2 -2
  240. package/vona/src/suite/a-commerce/modules/commerce-trade/src/controller/payment.ts +0 -23
  241. package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/commerceTradePayment.ts +0 -36
  242. package/zova/src/suite/a-commerce/modules/commerce-trade/src/apiSchema/commerceTradePayment.ts +0 -13
  243. package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/payment.ts +0 -25
  244. /package/vona/patches/{zova-core@5.1.79.patch → zova-core@5.1.81.patch} +0 -0
@@ -0,0 +1,278 @@
1
+ import assert from 'node:assert';
2
+ import { createHmac, randomUUID } from 'node:crypto';
3
+ import { describe, it } from 'node:test';
4
+ import { app } from 'vona-mock';
5
+
6
+ interface IFixture {
7
+ userId?: number;
8
+ paymentSessionId?: number;
9
+ }
10
+
11
+ async function createFixture(suffix: string): Promise<IFixture> {
12
+ const scope = app.scope('a-pay');
13
+ const user = await app.bean.user.register({ name: `pay-webhook-${suffix}` }, true);
14
+ const session = await scope.service.paymentSession.create({
15
+ userId: user.id,
16
+ payScene: 'commerce-payment:commerceOrder',
17
+ businessReference: `business-${suffix}`,
18
+ amountMinor: 1299,
19
+ currency: 'USD',
20
+ correlationId: `payment-${suffix}`,
21
+ });
22
+ return { userId: user.id as number, paymentSessionId: session.id as number };
23
+ }
24
+
25
+ async function cleanup(fixture: IFixture) {
26
+ const scope = app.scope('a-pay');
27
+ if (fixture.paymentSessionId !== undefined) {
28
+ await scope.model.outboxEvent.delete({ paymentSessionId: fixture.paymentSessionId });
29
+ await scope.model.paymentAudit.delete({ paymentSessionId: fixture.paymentSessionId });
30
+ await scope.model.webhookInbox.delete({ paymentSessionId: fixture.paymentSessionId });
31
+ await scope.model.paymentSession.delete({ id: fixture.paymentSessionId });
32
+ }
33
+ if (fixture.userId !== undefined) {
34
+ await app.scope('home-user').model.roleUser.delete({ userId: fixture.userId });
35
+ await app.bean.user.removeById(fixture.userId);
36
+ }
37
+ }
38
+
39
+ function createRawBody(
40
+ fixture: IFixture,
41
+ suffix: string,
42
+ state: 'succeeded' | 'failed' | 'cancelled' = 'succeeded',
43
+ options?: { eventId?: string; providerPaymentId?: string; providerCaptureId?: string },
44
+ ) {
45
+ return JSON.stringify({
46
+ state,
47
+ amountMinor: 1299,
48
+ eventType: `payment.${state}`,
49
+ paymentSessionId: String(fixture.paymentSessionId),
50
+ eventId: options?.eventId ?? `mock-${suffix}`,
51
+ providerPaymentId: options?.providerPaymentId ?? `payment-${suffix}`,
52
+ providerCaptureId: options?.providerCaptureId ?? `capture-${suffix}`,
53
+ currency: 'USD',
54
+ });
55
+ }
56
+
57
+ function createCommand(rawBody: string) {
58
+ const body = JSON.parse(rawBody);
59
+ return {
60
+ providerName: 'pay-mock:mock',
61
+ clientName: 'default',
62
+ environment: 'sandbox' as const,
63
+ rawBody,
64
+ verified: {
65
+ eventId: body.eventId,
66
+ eventType: body.eventType,
67
+ paymentSessionId: body.paymentSessionId,
68
+ payment: {
69
+ state: body.state,
70
+ providerPaymentId: body.providerPaymentId,
71
+ providerCaptureId: body.providerCaptureId,
72
+ },
73
+ summary: {
74
+ amountMinor: body.amountMinor,
75
+ currency: body.currency,
76
+ },
77
+ },
78
+ };
79
+ }
80
+
81
+ function sign(rawBody: string, clientName: 'default' | 'secondary' = 'default') {
82
+ const options = app.bean.payProvider.getOptions('pay-mock:mock', clientName);
83
+ assert.equal(typeof options.secretWebhook, 'string');
84
+ return createHmac('sha256', options.secretWebhook).update(rawBody).digest('hex');
85
+ }
86
+
87
+ function webhookUrl(providerName = 'pay-mock:mock', clientName = 'default') {
88
+ const url = app.util.getAbsoluteUrlByApiPath(`/pay/webhook/${providerName}/${clientName}`);
89
+ return url.startsWith('http') ? url : `http://127.0.0.1:${app.config.server.listen.port}${url}`;
90
+ }
91
+
92
+ async function countFacts(paymentSessionId: number) {
93
+ const scope = app.scope('a-pay');
94
+ const [inboxes, audits, outbox] = await Promise.all([
95
+ scope.model.webhookInbox.select({ where: { paymentSessionId } }),
96
+ scope.model.paymentAudit.select({ where: { paymentSessionId } }),
97
+ scope.model.outboxEvent.select({ where: { paymentSessionId } }),
98
+ ]);
99
+ return { inboxes, audits, outbox };
100
+ }
101
+
102
+ describe('webhook.test.ts', { concurrency: false, sequential: true }, () => {
103
+ it('accepts a raw-body signed HTTP webhook and rejects an invalid signature without mutation', async () => {
104
+ const fixture: IFixture = {};
105
+ try {
106
+ await app.bean.executor.mockCtx(async () => {
107
+ Object.assign(fixture, await createFixture(randomUUID().slice(0, 12)));
108
+ });
109
+ const rawBody = createRawBody(fixture, randomUUID().slice(0, 12));
110
+ const url = webhookUrl();
111
+ const invalid = await fetch(url, {
112
+ method: 'POST',
113
+ headers: {
114
+ 'Content-Type': 'application/json',
115
+ 'x-pay-mock-signature': 'invalid',
116
+ },
117
+ body: rawBody,
118
+ });
119
+ assert.equal(invalid.status, 401, await invalid.text());
120
+ await app.bean.executor.mockCtx(async () => {
121
+ assert.deepEqual(await countFacts(fixture.paymentSessionId!), {
122
+ inboxes: [],
123
+ audits: [],
124
+ outbox: [],
125
+ });
126
+ });
127
+ const valid = await fetch(url, {
128
+ method: 'POST',
129
+ headers: {
130
+ 'Content-Type': 'application/json',
131
+ 'x-pay-mock-signature': sign(rawBody),
132
+ },
133
+ body: rawBody,
134
+ });
135
+ assert.equal(valid.status, 200);
136
+ await app.bean.executor.mockCtx(async () => {
137
+ const session = await app
138
+ .scope('a-pay')
139
+ .model.paymentSession.getById(fixture.paymentSessionId!);
140
+ const facts = await countFacts(fixture.paymentSessionId!);
141
+ assert.equal(session?.state, 'succeeded');
142
+ assert.deepEqual(
143
+ [facts.inboxes.length, facts.audits.length, facts.outbox.length],
144
+ [1, 1, 1],
145
+ );
146
+ });
147
+ } finally {
148
+ await app.bean.executor.mockCtx(async () => await cleanup(fixture));
149
+ }
150
+ });
151
+
152
+ it('uses the route client webhook secret and preserves session client binding', async () => {
153
+ const fixture: IFixture = {};
154
+ try {
155
+ await app.bean.executor.mockCtx(async () => {
156
+ Object.assign(fixture, await createFixture(randomUUID().slice(0, 12)));
157
+ });
158
+ const rawBody = createRawBody(fixture, randomUUID().slice(0, 12));
159
+ const url = webhookUrl('pay-mock:mock', 'secondary');
160
+ const wrongClient = await fetch(url, {
161
+ method: 'POST',
162
+ headers: {
163
+ 'Content-Type': 'application/json',
164
+ 'x-pay-mock-signature': sign(rawBody, 'default'),
165
+ },
166
+ body: rawBody,
167
+ });
168
+ assert.equal(wrongClient.status, 401, await wrongClient.text());
169
+ const selectedClient = await fetch(url, {
170
+ method: 'POST',
171
+ headers: {
172
+ 'Content-Type': 'application/json',
173
+ 'x-pay-mock-signature': sign(rawBody, 'secondary'),
174
+ },
175
+ body: rawBody,
176
+ });
177
+ assert.equal(selectedClient.status, 409, await selectedClient.text());
178
+ await app.bean.executor.mockCtx(async () => {
179
+ assert.deepEqual(await countFacts(fixture.paymentSessionId!), {
180
+ inboxes: [],
181
+ audits: [],
182
+ outbox: [],
183
+ });
184
+ });
185
+ } finally {
186
+ await app.bean.executor.mockCtx(async () => await cleanup(fixture));
187
+ }
188
+ });
189
+
190
+ it('persists one verified payment fact and one logical outbox event across replay', async () => {
191
+ await app.bean.executor.mockCtx(async () => {
192
+ const fixture = await createFixture(randomUUID().slice(0, 12));
193
+ try {
194
+ const rawBody = createRawBody(fixture, randomUUID().slice(0, 12));
195
+ const first = await app.scope('a-pay').service.webhook.receive(createCommand(rawBody));
196
+ const replay = await app.scope('a-pay').service.webhook.receive(createCommand(rawBody));
197
+ assert.equal(replay.id, first.id);
198
+ assert.equal(replay.state, 'processed');
199
+ const session = await app
200
+ .scope('a-pay')
201
+ .model.paymentSession.getById(fixture.paymentSessionId!);
202
+ const facts = await countFacts(fixture.paymentSessionId!);
203
+ assert.equal(session?.state, 'succeeded');
204
+ assert.deepEqual(
205
+ [facts.inboxes.length, facts.audits.length, facts.outbox.length],
206
+ [1, 1, 1],
207
+ );
208
+ assert.equal(facts.outbox[0]?.eventType, 'payment.outcome.v1');
209
+ } finally {
210
+ await cleanup(fixture);
211
+ }
212
+ });
213
+ });
214
+
215
+ it('converges concurrent duplicate and out-of-order terminal webhooks on the first terminal state', async () => {
216
+ const fixture: IFixture = {};
217
+ try {
218
+ await app.bean.executor.mockCtx(async () => {
219
+ Object.assign(fixture, await createFixture(randomUUID().slice(0, 12)));
220
+ });
221
+ const rawBody = createRawBody(fixture, randomUUID().slice(0, 12));
222
+ const receive = async () => {
223
+ return await app.bean.executor.mockCtx(async () => {
224
+ return await app.scope('a-pay').service.webhook.receive(createCommand(rawBody));
225
+ });
226
+ };
227
+ const results = await Promise.allSettled([receive(), receive()]);
228
+ assert.equal(
229
+ results.filter(result => result.status === 'fulfilled').length,
230
+ 2,
231
+ JSON.stringify(results),
232
+ );
233
+ await app.bean.executor.mockCtx(async () => {
234
+ const failedRawBody = createRawBody(fixture, randomUUID().slice(0, 12), 'failed');
235
+ const ignored = await app
236
+ .scope('a-pay')
237
+ .service.webhook.receive(createCommand(failedRawBody));
238
+ const session = await app
239
+ .scope('a-pay')
240
+ .model.paymentSession.getById(fixture.paymentSessionId!);
241
+ const facts = await countFacts(fixture.paymentSessionId!);
242
+ assert.equal(session?.state, 'succeeded');
243
+ assert.equal(ignored.state, 'processed');
244
+ assert.match(ignored.errorSummary ?? '', /ignored after succeeded/);
245
+ assert.deepEqual(
246
+ [facts.inboxes.length, facts.audits.length, facts.outbox.length],
247
+ [2, 1, 1],
248
+ );
249
+ });
250
+ } finally {
251
+ await app.bean.executor.mockCtx(async () => await cleanup(fixture));
252
+ }
253
+ });
254
+
255
+ it('rejects a same-state webhook with conflicting provider facts', async () => {
256
+ await app.bean.executor.mockCtx(async () => {
257
+ const fixture = await createFixture(randomUUID().slice(0, 12));
258
+ try {
259
+ const first = createRawBody(fixture, randomUUID().slice(0, 12));
260
+ await app.scope('a-pay').service.webhook.receive(createCommand(first));
261
+ const conflict = createRawBody(fixture, randomUUID().slice(0, 12), 'succeeded', {
262
+ providerCaptureId: `other-capture-${randomUUID().slice(0, 12)}`,
263
+ });
264
+ await assert.rejects(
265
+ app.scope('a-pay').service.webhook.receive(createCommand(conflict)),
266
+ (error: any) => error.code === 409,
267
+ );
268
+ const facts = await countFacts(fixture.paymentSessionId!);
269
+ assert.deepEqual(
270
+ [facts.inboxes.length, facts.audits.length, facts.outbox.length],
271
+ [1, 1, 1],
272
+ );
273
+ } finally {
274
+ await cleanup(fixture);
275
+ }
276
+ });
277
+ });
278
+ });
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../tsconfig.base",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "declaration": true,
6
+ "declarationMap": true,
7
+ "emitDeclarationOnly": true,
8
+ "outDir": "dist"
9
+ },
10
+ "include": ["src", "src/.metadata/*.ts", "src/**/*.json"]
11
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../../tsconfig.base",
3
+ "compilerOptions": {
4
+ "noEmit": true
5
+ },
6
+ "include": ["src", "src/.metadata/*.ts", "src/**/*.json", "test"]
7
+ }
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "vona-module-pay-mock",
3
+ "version": "5.0.1",
4
+ "description": "",
5
+ "keywords": [
6
+ "Vona Module"
7
+ ],
8
+ "author": "",
9
+ "files": [
10
+ "assets",
11
+ "dist",
12
+ "src"
13
+ ],
14
+ "type": "module",
15
+ "exports": {
16
+ ".": {
17
+ "types": [
18
+ "./src/index.ts",
19
+ "./dist/index.d.ts"
20
+ ],
21
+ "import": "./src/index.ts",
22
+ "default": "./dist/index.js"
23
+ },
24
+ "./package.json": "./package.json"
25
+ },
26
+ "scripts": {
27
+ "clean": "rimraf dist tsconfig.build.tsbuildinfo",
28
+ "tsc:publish": "npm run clean && node ../../../../../packages-cli/cli/src/bin/vona.ts :bin:buildModule --sourcemap && tsc -p tsconfig.build.json",
29
+ "prepublishOnly": "npm run tsc:publish",
30
+ "prepack": "clean-package",
31
+ "postpack": "clean-package restore"
32
+ },
33
+ "dependencies": {
34
+ "vona-module-a-pay": "workspace:*"
35
+ },
36
+ "devDependencies": {
37
+ "clean-package": "^2.2.0",
38
+ "rimraf": "^6.1.3"
39
+ },
40
+ "clean-package": {
41
+ "indent": 2,
42
+ "remove": [
43
+ "clean-package",
44
+ "devDependencies",
45
+ "exports.\\..import"
46
+ ]
47
+ },
48
+ "title": "pay-mock",
49
+ "vonaModule": {
50
+ "dependencies": {
51
+ "a-pay": "5.0.0",
52
+ "a-vona": "5.0.0"
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,182 @@
1
+ // eslint-disable
2
+ import type { TypeSymbolKeyFieldsMore } from 'vona-module-a-orm';
3
+ import type { TypeEntityOptionsFields,TypeControllerOptionsActions } from 'vona-module-a-openapi';
4
+ /** service: begin */
5
+ export * from '../service/payMock.ts';
6
+
7
+ import 'vona-module-a-bean';
8
+ declare module 'vona-module-a-bean' {
9
+
10
+ export interface IServiceRecord {
11
+ 'pay-mock:payMock': never;
12
+ }
13
+
14
+
15
+ }
16
+ declare module 'vona-module-pay-mock' {
17
+
18
+ export interface ServicePayMock {
19
+ /** @internal */
20
+ get scope(): ScopeModulePayMock;
21
+ }
22
+
23
+ export interface ServicePayMock {
24
+ get $beanFullName(): 'pay-mock.service.payMock';
25
+ get $onionName(): 'pay-mock:payMock';
26
+
27
+ }
28
+ }
29
+ /** service: end */
30
+ /** service: begin */
31
+ import type { ServicePayMock } from '../service/payMock.ts';
32
+ export interface IModuleService {
33
+ 'payMock': ServicePayMock;
34
+ }
35
+ /** service: end */
36
+ /** service: begin */
37
+
38
+ import 'vona';
39
+ declare module 'vona' {
40
+ export interface IBeanRecordGeneral {
41
+ 'pay-mock.service.payMock': ServicePayMock;
42
+ }
43
+ }
44
+ /** service: end */
45
+ /** dto: begin */
46
+ export * from '../dto/mockPaymentComplete.tsx';
47
+ export * from '../dto/mockPaymentReceipt.tsx';
48
+ import type { IDtoOptionsMockPaymentComplete } from '../dto/mockPaymentComplete.tsx';
49
+ import type { IDtoOptionsMockPaymentReceipt } from '../dto/mockPaymentReceipt.tsx';
50
+ import 'vona-module-a-web';
51
+ declare module 'vona-module-a-web' {
52
+
53
+ export interface IDtoRecord {
54
+ 'pay-mock:mockPaymentComplete': IDtoOptionsMockPaymentComplete;
55
+ 'pay-mock:mockPaymentReceipt': IDtoOptionsMockPaymentReceipt;
56
+ }
57
+
58
+
59
+ }
60
+ declare module 'vona-module-pay-mock' {
61
+
62
+ }
63
+ /** dto: end */
64
+ /** dto: begin */
65
+ import type { DtoMockPaymentComplete } from '../dto/mockPaymentComplete.tsx';
66
+ import type { DtoMockPaymentReceipt } from '../dto/mockPaymentReceipt.tsx';
67
+ declare module 'vona-module-pay-mock' {
68
+
69
+ export interface IDtoOptionsMockPaymentComplete {
70
+ fields?: TypeEntityOptionsFields<DtoMockPaymentComplete, IDtoOptionsMockPaymentComplete[TypeSymbolKeyFieldsMore]>;
71
+ }
72
+
73
+ export interface IDtoOptionsMockPaymentReceipt {
74
+ fields?: TypeEntityOptionsFields<DtoMockPaymentReceipt, IDtoOptionsMockPaymentReceipt[TypeSymbolKeyFieldsMore]>;
75
+ }
76
+ }
77
+ /** dto: end */
78
+ /** controller: begin */
79
+ export * from '../controller/mockPayment.ts';
80
+ import type { IControllerOptionsMockPayment } from '../controller/mockPayment.ts';
81
+ import 'vona-module-a-web';
82
+ declare module 'vona-module-a-web' {
83
+
84
+ export interface IControllerRecord {
85
+ 'pay-mock:mockPayment': IControllerOptionsMockPayment;
86
+ }
87
+
88
+
89
+ }
90
+ declare module 'vona-module-pay-mock' {
91
+
92
+ export interface ControllerMockPayment {
93
+ /** @internal */
94
+ get scope(): ScopeModulePayMock;
95
+ }
96
+
97
+ export interface ControllerMockPayment {
98
+ get $beanFullName(): 'pay-mock.controller.mockPayment';
99
+ get $onionName(): 'pay-mock:mockPayment';
100
+ get $onionOptions(): IControllerOptionsMockPayment;
101
+ }
102
+ }
103
+ /** controller: end */
104
+ /** controller: begin */
105
+ // @ts-ignore ignore
106
+ import type { ControllerMockPayment } from '../controller/mockPayment.ts';
107
+ declare module 'vona-module-pay-mock' {
108
+
109
+ export interface IControllerOptionsMockPayment {
110
+ actions?: TypeControllerOptionsActions<ControllerMockPayment>;
111
+ }
112
+ }
113
+ declare module 'vona-module-a-web' {
114
+ export interface IApiPathPostRecord{
115
+ '/pay/mock/payment-session/:id/complete': undefined;
116
+ }
117
+
118
+ }
119
+
120
+ /** controller: end */
121
+ /** payProvider: begin */
122
+ export * from '../bean/payProvider.mock.ts';
123
+ import type { IPayProviderOptionsMock } from '../bean/payProvider.mock.ts';
124
+ import 'vona-module-a-pay';
125
+ declare module 'vona-module-a-pay' {
126
+
127
+ export interface IPayProviderRecord {
128
+ 'pay-mock:mock': IPayProviderOptionsMock;
129
+ }
130
+
131
+
132
+ }
133
+ declare module 'vona-module-pay-mock' {
134
+
135
+ export interface PayProviderMock {
136
+ /** @internal */
137
+ get scope(): ScopeModulePayMock;
138
+ }
139
+
140
+ export interface PayProviderMock {
141
+ get $beanFullName(): 'pay-mock.payProvider.mock';
142
+ get $onionName(): 'pay-mock:mock';
143
+ get $onionOptions(): IPayProviderOptionsMock;
144
+ }
145
+ }
146
+ /** payProvider: end */
147
+ /** payProvider: begin */
148
+ import type { PayProviderMock } from '../bean/payProvider.mock.ts';
149
+ export interface IModulePayProvider {
150
+ 'mock': PayProviderMock;
151
+ }
152
+ /** payProvider: end */
153
+ /** scope: begin */
154
+ import { BeanScopeBase, type BeanScopeUtil } from 'vona';
155
+ import { Scope } from 'vona-module-a-bean';
156
+
157
+ @Scope()
158
+ export class ScopeModulePayMock extends BeanScopeBase {}
159
+
160
+ export interface ScopeModulePayMock {
161
+ util: BeanScopeUtil;
162
+ service: IModuleService;
163
+ payProvider: IModulePayProvider;
164
+ }
165
+
166
+ import 'vona';
167
+ declare module 'vona' {
168
+ export interface IBeanScopeRecord {
169
+ 'pay-mock': ScopeModulePayMock;
170
+ }
171
+
172
+ export interface IBeanScopeContainer {
173
+ payMock: ScopeModulePayMock;
174
+ }
175
+
176
+
177
+
178
+
179
+
180
+
181
+ }
182
+ /** scope: end */
@@ -0,0 +1,2 @@
1
+ export const __ThisModule__ = 'pay-mock';
2
+ export { ScopeModulePayMock as ScopeModule } from './index.ts';
@@ -0,0 +1,149 @@
1
+ import type {
2
+ IDecoratorPayProviderOptions,
3
+ IPayProviderClientOptions,
4
+ IPayProviderClientRecord,
5
+ IPayProviderExecute,
6
+ IPayProviderPaymentInput,
7
+ IPayProviderPaymentSnapshot,
8
+ IPayProviderRefundInput,
9
+ IPayProviderRefundSnapshot,
10
+ IPayProviderVerifiedWebhook,
11
+ IPayProviderWebhookInput,
12
+ } from 'vona-module-a-pay';
13
+
14
+ import { createHmac, timingSafeEqual } from 'node:crypto';
15
+ import { BeanBase, useApp } from 'vona';
16
+ import { PayProvider } from 'vona-module-a-pay';
17
+ import { z } from 'zod';
18
+
19
+ const WebhookBodySchema = z.object({
20
+ eventId: z.string().min(1).max(255),
21
+ eventType: z.enum(['payment.succeeded', 'payment.failed', 'payment.cancelled']),
22
+ paymentSessionId: z.string().min(1),
23
+ state: z.enum(['succeeded', 'failed', 'cancelled']),
24
+ amountMinor: z.number().int().nonnegative(),
25
+ currency: z.string().length(3),
26
+ providerPaymentId: z.string().min(1).max(255).optional(),
27
+ providerCaptureId: z.string().min(1).max(255).optional(),
28
+ });
29
+
30
+ export interface IPayProviderMockClientRecord extends IPayProviderClientRecord {
31
+ secondary: never;
32
+ }
33
+
34
+ export interface IPayProviderMockClientOptions extends IPayProviderClientOptions {
35
+ secretCredential: string | undefined;
36
+ secretWebhook: string | undefined;
37
+ }
38
+
39
+ const app = useApp();
40
+
41
+ export interface IPayProviderOptionsMock extends IDecoratorPayProviderOptions<
42
+ IPayProviderMockClientRecord,
43
+ IPayProviderMockClientOptions
44
+ > {}
45
+
46
+ @PayProvider<IPayProviderOptionsMock>({
47
+ base: {
48
+ capabilities: {
49
+ redirectCheckout: false,
50
+ embeddedCheckout: true,
51
+ automaticCapture: true,
52
+ manualCapture: false,
53
+ refunds: true,
54
+ partialRefunds: true,
55
+ webhooks: true,
56
+ },
57
+ },
58
+ clients: {
59
+ default: {
60
+ environment: 'sandbox',
61
+ secretCredential: app.meta.env.PAY_MOCK_DEFAULT_CREDENTIAL,
62
+ secretWebhook: app.meta.env.PAY_MOCK_DEFAULT_WEBHOOK,
63
+ },
64
+ secondary: {
65
+ environment: 'sandbox',
66
+ secretCredential: app.meta.env.PAY_MOCK_SECONDARY_CREDENTIAL,
67
+ secretWebhook: app.meta.env.PAY_MOCK_SECONDARY_WEBHOOK,
68
+ },
69
+ },
70
+ })
71
+ export class PayProviderMock
72
+ extends BeanBase
73
+ implements IPayProviderExecute<IPayProviderMockClientOptions>
74
+ {
75
+ async startPayment(
76
+ input: IPayProviderPaymentInput,
77
+ _clientOptions: IPayProviderMockClientOptions,
78
+ ): Promise<IPayProviderPaymentSnapshot> {
79
+ return {
80
+ state: 'requires_action',
81
+ providerPaymentId: `mock-payment-${input.paymentSessionId}`,
82
+ nextAction: { kind: 'pending' },
83
+ };
84
+ }
85
+
86
+ async queryPayment(
87
+ input: IPayProviderPaymentInput,
88
+ _clientOptions: IPayProviderMockClientOptions,
89
+ ): Promise<IPayProviderPaymentSnapshot> {
90
+ return {
91
+ state: 'processing',
92
+ providerPaymentId: `mock-payment-${input.paymentSessionId}`,
93
+ };
94
+ }
95
+
96
+ async createRefund(
97
+ input: IPayProviderRefundInput,
98
+ _clientOptions: IPayProviderMockClientOptions,
99
+ ): Promise<IPayProviderRefundSnapshot> {
100
+ return {
101
+ state: 'pending',
102
+ providerRefundId: `mock-refund-${input.refundOperationId}`,
103
+ };
104
+ }
105
+
106
+ async verifyWebhook(
107
+ input: IPayProviderWebhookInput,
108
+ clientOptions: IPayProviderMockClientOptions,
109
+ ): Promise<IPayProviderVerifiedWebhook> {
110
+ const signature = input.headers['x-pay-mock-signature'];
111
+ const actual = Array.isArray(signature) ? signature[0] : signature;
112
+ const secret = clientOptions.secretWebhook;
113
+ const rawBody = input.rawBody;
114
+ const expected =
115
+ typeof secret === 'string' && secret && rawBody !== undefined
116
+ ? createHmac('sha256', secret).update(rawBody).digest('hex')
117
+ : undefined;
118
+ if (!expected || !actual || !safeEqual(actual, expected)) {
119
+ this.app.throw(401, 'mock webhook signature is invalid');
120
+ }
121
+ const body = WebhookBodySchema.safeParse(input.body);
122
+ if (!body.success) this.app.throw(400, 'mock webhook is invalid');
123
+ const eventType = `payment.${body.data.state}`;
124
+ if (body.data.eventType !== eventType)
125
+ this.app.throw(400, 'mock webhook event type is invalid');
126
+ return {
127
+ eventId: body.data.eventId,
128
+ eventType,
129
+ paymentSessionId: body.data.paymentSessionId,
130
+ payment: {
131
+ state: body.data.state,
132
+ providerPaymentId: body.data.providerPaymentId,
133
+ providerCaptureId: body.data.providerCaptureId,
134
+ },
135
+ summary: {
136
+ amountMinor: body.data.amountMinor,
137
+ currency: body.data.currency,
138
+ },
139
+ };
140
+ }
141
+ }
142
+
143
+ function safeEqual(actual: string, expected: string) {
144
+ const actualBuffer = Buffer.from(actual);
145
+ const expectedBuffer = Buffer.from(expected);
146
+ return (
147
+ actualBuffer.length === expectedBuffer.length && timingSafeEqual(actualBuffer, expectedBuffer)
148
+ );
149
+ }