cabloy 5.1.134 → 5.1.136

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 (185) hide show
  1. package/.cabloy-version +1 -1
  2. package/.claude/skills/cabloy-backend-scaffold/SKILL.md +9 -0
  3. package/.claude/skills/cabloy-zova-source-reading/SKILL.md +24 -1
  4. package/.claude/skills/cabloy-zova-source-reading/references/analysis-modes.md +12 -0
  5. package/.claude/skills/cabloy-zova-source-reading/references/core-reading-paths.md +28 -0
  6. package/.github/workflows/playwright-e2e.yml +2 -2
  7. package/.github/workflows/vona-test-pg.yml +1 -1
  8. package/.github/workflows/vona-test-sqlite3.yml +1 -1
  9. package/CHANGELOG.md +38 -0
  10. package/CLAUDE.md +1 -0
  11. package/cabloy-docs/.vitepress/config.mjs +7 -0
  12. package/cabloy-docs/backend/markdown-guide.md +189 -0
  13. package/cabloy-docs/frontend/component-guide.md +6 -0
  14. package/cabloy-docs/frontend/css-in-js-guide.md +6 -0
  15. package/cabloy-docs/frontend/markdown-guide.md +258 -0
  16. package/cabloy-docs/frontend/page-guide.md +48 -0
  17. package/cabloy-docs/frontend/page-params-guide.md +4 -2
  18. package/cabloy-docs/frontend/page-route-guide.md +2 -2
  19. package/cabloy-docs/frontend/zova-router-under-the-hood.md +2 -0
  20. package/cabloy-docs/frontend/zova-source-reading-map.md +53 -9
  21. package/cabloy-docs/fullstack/a-pay-payment-suite.md +333 -0
  22. package/cabloy-docs/fullstack/payment-sandbox-configuration.md +285 -0
  23. package/e2e/specs/a-commerce/commerce.spec.ts +503 -47
  24. package/e2e/specs/cabloy-basic/basic.spec.ts +30 -0
  25. package/package.json +1 -1
  26. package/vona/env/.env +2 -0
  27. package/vona/packages-cli/cli/package.json +1 -1
  28. package/vona/packages-cli/cli-set-api/cli/templates/init/error/boilerplate/config/errors.ts +6 -1
  29. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/entity/{{resourceName}}.tsx_ +2 -0
  30. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/entity/{{resourceName}}.tsx_ +2 -0
  31. package/vona/packages-cli/cli-set-api/package.json +1 -1
  32. package/vona/packages-utils/localeutil/package.json +1 -1
  33. package/vona/packages-utils/localeutil/src/util.ts +1 -1
  34. package/vona/packages-vona/vona/package.json +1 -1
  35. package/vona/packages-vona/vona-core/package.json +1 -1
  36. package/vona/packages-vona/vona-mock/package.json +1 -1
  37. package/vona/patches/{zova-core@5.1.82.patch → zova-core@5.1.84.patch} +2 -2
  38. package/vona/pnpm-lock.yaml +826 -270
  39. package/vona/pnpm-workspace.yaml +1 -1
  40. package/vona/src/suite/a-commerce/modules/commerce-catalog/package.json +1 -1
  41. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/index.ts +99 -1
  42. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/bean/meta.index.ts +1 -0
  43. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/bean/meta.version.ts +19 -0
  44. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/en-us.ts +3 -0
  45. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/zh-cn.ts +3 -0
  46. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/controller/product.ts +3 -3
  47. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productContentUpdate.tsx +14 -0
  48. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productContentView.tsx +19 -0
  49. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productCreate.tsx +48 -3
  50. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productPublicDetail.tsx +15 -0
  51. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productUpdate.tsx +48 -3
  52. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productView.tsx +52 -3
  53. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/entity/productContent.tsx +36 -0
  54. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/model/product.ts +6 -0
  55. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/model/productContent.ts +22 -0
  56. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/service/product.ts +82 -6
  57. package/vona/src/suite/a-commerce/modules/commerce-catalog/test/productContent.test.ts +271 -0
  58. package/vona/src/suite/a-commerce/modules/commerce-catalog/test/productPresentation.test.ts +81 -2
  59. package/vona/src/suite/a-commerce/modules/commerce-payment/package.json +4 -1
  60. package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/payScene.commerceOrder.ts +32 -15
  61. package/vona/src/suite/a-commerce/modules/commerce-payment/test/paymentAttempt.test.ts +8 -2
  62. package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/.metadata/index.ts +8 -2
  63. package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/.metadata/locales.ts +18 -0
  64. package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/bean/ssrMenu.home.ts +3 -1
  65. package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/config/locale/en-us.ts +3 -0
  66. package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/config/locale/zh-cn.ts +3 -0
  67. package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/index.ts +1 -0
  68. package/vona/src/suite/a-commerce/modules/commerce-trade/src/controller/checkout.ts +13 -2
  69. package/vona/src/suite/a-commerce/modules/commerce-trade/src/lib/providerReference.ts +18 -0
  70. package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/order.ts +43 -8
  71. package/vona/src/suite/a-commerce/modules/commerce-trade/test/paymentOutcome.test.ts +74 -0
  72. package/vona/src/suite/a-commerce/modules/commerce-trade/test/providerReference.test.ts +33 -0
  73. package/vona/src/suite/a-commerce/modules/commerce-trade/test/refundLifecycle.test.ts +73 -0
  74. package/vona/src/suite/a-commerce/modules/commerce-trade/test/reservationExpiry.test.ts +2 -0
  75. package/vona/src/suite/a-home/modules/home-user/src/.metadata/index.ts +30 -0
  76. package/vona/src/suite/a-home/modules/home-user/src/bean/meta.version.ts +7 -2
  77. package/vona/src/suite/a-home/modules/home-user/src/config/locale/en-us.ts +4 -1
  78. package/vona/src/suite/a-home/modules/home-user/src/config/locale/zh-cn.ts +4 -1
  79. package/vona/src/suite/a-home/modules/home-user/src/config/roles.ts +4 -4
  80. package/vona/src/suite/a-home/modules/home-user/src/entity/role.ts +2 -2
  81. package/vona/src/suite/a-home/modules/home-user/src/entity/user.ts +8 -1
  82. package/vona/src/suite/a-home/modules/home-user/src/service/userAdapter.ts +6 -2
  83. package/vona/src/suite/a-home/modules/home-user/test/passportTest.test.ts +1 -0
  84. package/vona/src/suite/a-home/modules/home-user/test/role.test.ts +4 -4
  85. package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/package.json +64 -0
  86. package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/src/.metadata/index.ts +54 -0
  87. package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/src/.metadata/this.ts +2 -0
  88. package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/src/bean/bean.markdown.ts +137 -0
  89. package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/src/index.ts +1 -0
  90. package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/tsconfig.build.json +11 -0
  91. package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/tsconfig.json +7 -0
  92. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/package.json +1 -1
  93. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/bean/bean.ssr.ts +17 -0
  94. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/lib/beanSsrSiteBase.ts +11 -3
  95. package/vona/src/suite-vendor/a-cabloy/package.json +2 -1
  96. package/vona/src/suite-vendor/a-cabloy/tsconfig.json +3 -0
  97. package/vona/src/suite-vendor/a-pay/modules/a-pay/package.json +1 -1
  98. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/meta.version.ts +1 -0
  99. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/providerOperation.tsx +8 -1
  100. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/lib/index.ts +1 -0
  101. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/lib/providerOperationFailure.ts +8 -0
  102. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/providerOperation.ts +187 -29
  103. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/types/payment.ts +4 -0
  104. package/vona/src/suite-vendor/a-pay/modules/pay-mock/package.json +1 -1
  105. package/vona/src/suite-vendor/a-pay/modules/pay-paypal/package.json +1 -1
  106. package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/bean/payProvider.paypal.ts +0 -6
  107. package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/index.ts +1 -0
  108. package/vona/src/suite-vendor/a-pay/modules/pay-stripe/package.json +2 -1
  109. package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/bean/payProvider.stripe.ts +458 -22
  110. package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/index.ts +1 -0
  111. package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/lib/stripeGateway.ts +85 -0
  112. package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/types/index.ts +2 -0
  113. package/vona/src/suite-vendor/a-pay/package.json +5 -5
  114. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  115. package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
  116. package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/bean.permission.ts +1 -0
  117. package/vona/src/suite-vendor/a-vona/modules/a-user/package.json +1 -1
  118. package/vona/src/suite-vendor/a-vona/modules/a-user/src/bean/bean.passport.ts +14 -0
  119. package/vona/src/suite-vendor/a-vona/modules/a-user/src/bean/guard.passport.ts +4 -0
  120. package/vona/src/suite-vendor/a-vona/modules/a-user/src/types/role.ts +3 -2
  121. package/vona/src/suite-vendor/a-vona/modules/a-user/src/types/user.ts +4 -0
  122. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  123. package/zova/packages-utils/logger/package.json +2 -2
  124. package/zova/packages-utils/zova-jsx/package.json +2 -2
  125. package/zova/packages-zova/zova/package.json +3 -3
  126. package/zova/packages-zova/zova-core/package.json +3 -3
  127. package/zova/packages-zova/zova-core/src/core/sys/resource.ts +1 -1
  128. package/zova/packages-zova/zova-core/src/core/sys/util.ts +8 -0
  129. package/zova/pnpm-lock.yaml +570 -11
  130. package/zova/src/front/config/config/config.cabloyCommerce.ts +0 -6
  131. package/zova/src/suite/a-commerce/modules/commerce-catalog/package.json +3 -1
  132. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/api/openapi/schemas.ts +236 -36
  133. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/api/openapi/types.ts +4962 -2402
  134. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/en-us.ts +1 -0
  135. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/zh-cn.ts +1 -0
  136. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/page/product/controller.tsx +8 -3
  137. package/zova/src/suite/a-commerce/modules/commerce-member/src/.metadata/index.ts +6 -7
  138. package/zova/src/suite/a-commerce/modules/commerce-member/src/page/address/controller.tsx +1 -3
  139. package/zova/src/suite/a-commerce/modules/commerce-member/src/routes.ts +2 -3
  140. package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +13 -20
  141. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/cart/controller.tsx +3 -9
  142. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/checkout/controller.tsx +9 -11
  143. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/order/controller.tsx +0 -1
  144. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/orders/controller.tsx +6 -4
  145. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx +78 -17
  146. package/zova/src/suite/a-commerce/modules/commerce-trade/src/routes.ts +10 -13
  147. package/zova/src/suite/a-home/modules/home-api/mock/passport.fake.ts +3 -3
  148. package/zova/src/suite/a-home/modules/home-api/src/.metadata/index.ts +31 -1
  149. package/zova/src/suite/a-home/modules/home-api/src/api/homeBaseSiteCatalog.ts +24 -0
  150. package/zova/src/suite/a-home/modules/home-api/src/api/openapi/schemas.ts +329 -17
  151. package/zova/src/suite/a-home/modules/home-api/src/api/openapi/types.ts +5866 -1829
  152. package/zova/src/suite/a-home/modules/home-api/src/apiSchema/homeBaseSiteCatalog.ts +13 -0
  153. package/zova/src/suite/a-home/modules/home-base/src/component/page/controller.tsx +1 -0
  154. package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/render.header.tsx +0 -1
  155. package/zova/src/suite/cabloy-basic/modules/basic-details/src/types/detail.ts +1 -0
  156. package/zova/src/suite/cabloy-basic/modules/basic-markdown/package.json +61 -0
  157. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/.metadata/component/formFieldMarkdown.ts +36 -0
  158. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/.metadata/component/markdownHtml.ts +36 -0
  159. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/.metadata/index.ts +147 -0
  160. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/.metadata/this.ts +2 -0
  161. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/formFieldMarkdown/controller.tsx +111 -0
  162. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/formFieldMarkdown/render.tsx +40 -0
  163. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/formFieldMarkdown/style.ts +13 -0
  164. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/markdownHtml/controller.tsx +17 -0
  165. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/markdownHtml/render.tsx +9 -0
  166. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/markdownHtml/style.ts +13 -0
  167. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/index.ts +1 -0
  168. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/lib/richTextContentStyle.ts +142 -0
  169. package/zova/src/suite/cabloy-basic/modules/basic-markdown/tsconfig.build.json +13 -0
  170. package/zova/src/suite/cabloy-basic/modules/basic-markdown/tsconfig.json +5 -0
  171. package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockPage/controller.tsx +25 -3
  172. package/zova/src/suite/cabloy-basic/modules/basic-page/src/types/page.ts +1 -0
  173. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/types/pageEntry.ts +1 -0
  174. package/zova/src/suite/cabloy-basic/package.json +1 -0
  175. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
  176. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +7 -0
  177. package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
  178. package/zova/src/suite-vendor/a-zova/modules/a-logger/package.json +2 -2
  179. package/zova/src/suite-vendor/a-zova/modules/a-router/package.json +1 -1
  180. package/zova/src/suite-vendor/a-zova/modules/a-router/src/bean/sys.router.ts +3 -2
  181. package/zova/src/suite-vendor/a-zova/modules/a-ssr/package.json +1 -1
  182. package/zova/src/suite-vendor/a-zova/modules/a-ssr/test/lib/ssrProfile.test.ts +88 -0
  183. package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
  184. package/zova/src/suite-vendor/a-zova/package.json +5 -5
  185. package/.claude/scheduled_tasks.lock +0 -1
@@ -0,0 +1,333 @@
1
+ # A-Pay Payment Suite
2
+
3
+ This guide explains the current payment architecture in Cabloy Basic across Vona, Zova, and the Commerce domain.
4
+
5
+ It is a source-oriented architecture guide for:
6
+
7
+ - application authors integrating a payment scene;
8
+ - provider-module authors implementing a payment adapter;
9
+ - Zova developers consuming payment-session state and next actions;
10
+ - maintainers tracing callbacks, webhooks, settlement, and SSR return flows.
11
+
12
+ This is **not** a production rollout manual or payment-provider compliance guide. It does not include real credentials, account identifiers, domains, tunnel commands, or internal operational diagnostics. For sanitized PayPal and Stripe Sandbox setup, trusted public-origin configuration, webhook registration templates, and validation guidance, see [Payment Provider Sandbox Configuration](/fullstack/payment-sandbox-configuration).
13
+
14
+ ## The short version
15
+
16
+ A-Pay is a provider-neutral payment infrastructure layer. Commerce owns the business result; A-Pay owns the payment-session and provider-operation result; provider modules translate external provider facts into A-Pay facts; Zova presents and observes those facts.
17
+
18
+ ```text
19
+ Commerce trade
20
+ -> Commerce payment boundary
21
+ -> A-Pay payment-session boundary
22
+ <- pay-mock / pay-paypal / pay-stripe provider adapters
23
+ ```
24
+
25
+ The most important rule is:
26
+
27
+ > A browser redirect, callback, or rendered success state is not proof of payment. Verified provider facts, persisted payment state, durable outcome delivery, and the business aggregate owner determine settlement.
28
+
29
+ ## Suite and module topology
30
+
31
+ The payment feature is a vendor suite named `a-pay`, mirrored in both framework halves:
32
+
33
+ ```text
34
+ vona/src/suite-vendor/a-pay/
35
+ ├── modules/
36
+ │ ├── a-pay/
37
+ │ ├── pay-mock/
38
+ │ ├── pay-paypal/
39
+ │ └── pay-stripe/
40
+ └── package.json
41
+
42
+ zova/src/suite-vendor/a-pay/
43
+ ├── modules/
44
+ │ ├── a-pay/
45
+ │ ├── pay-mock/
46
+ │ ├── pay-paypal/
47
+ │ └── pay-stripe/
48
+ └── package.json
49
+ ```
50
+
51
+ The general suite/module organization is described in [Suites and Modules](/fullstack/suites-and-modules). The payment suite follows the vendor-suite layout rather than being a complete user-facing page package.
52
+
53
+ | Module | Vona responsibility | Zova responsibility |
54
+ | ------------ | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
55
+ | `a-pay` | Payment sessions, provider operations, callbacks, webhooks, normalized contracts, audit/outbox boundaries | Payment-session API/model, generated contracts, next-action component, redirect coordinator |
56
+ | `pay-mock` | Deterministic development/test provider and simulator webhook path | Mock completion mutation used by the Commerce test surface |
57
+ | `pay-paypal` | PayPal API, capture, query, refund, and webhook translation | Provider identity and metadata surface; no PayPal SDK execution in the browser |
58
+ | `pay-stripe` | Stripe Checkout, query, refund, and webhook translation | Provider identity and metadata surface; no Stripe SDK execution in the browser |
59
+
60
+ The actual Web payment page is owned by the Commerce consumer, not by `zova-module-a-pay`:
61
+
62
+ ```text
63
+ zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx
64
+ ```
65
+
66
+ This separation lets A-Pay stay reusable while Commerce owns order-specific presentation, polling, and navigation.
67
+
68
+ ## Ownership and authority
69
+
70
+ The ownership chain in the current implementation is:
71
+
72
+ ```text
73
+ commerce-trade -> commerce-payment -> a-pay <- pay-mock / pay-paypal / pay-stripe
74
+ ```
75
+
76
+ | Layer | Owns | Must not own |
77
+ | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
78
+ | Commerce trade | Order transitions, inventory and coupon effects, shipment eligibility, and order/refund business rules | Provider SDK calls or webhook parsing |
79
+ | Commerce payment | Commerce `PaymentAttempt`, refund records, audit rules, and the Commerce payment scene | Direct provider integration |
80
+ | A-Pay | Provider-neutral `PaymentSession`, provider/refund operations, verified webhook receipts, payment audit records, and durable outcome delivery | Commerce aggregate settlement rules |
81
+ | Provider modules | Translation between provider API/webhook facts and normalized A-Pay snapshots | Direct mutation of Commerce orders or payment attempts |
82
+ | Zova consumers | Payment-session observation, user actions, next-action presentation, and post-hydration refresh | Declaring payment success or settling an order |
83
+
84
+ A Commerce payment attempt initially has one A-Pay payment session. A future retry or provider switch can create another session without changing the Commerce aggregate identity. The payment session is the provider-execution boundary; the order remains the business boundary.
85
+
86
+ ## Payment session and state model
87
+
88
+ A `PaymentSession` records the facts needed to execute and reconcile one provider payment attempt. Among other fields, the current entity stores:
89
+
90
+ - the owning user and payment scene;
91
+ - the business reference and correlation identifiers;
92
+ - amount in minor units and currency;
93
+ - the selected provider, client, and environment snapshot;
94
+ - provider invoice/order/payment/capture identifiers as they become known;
95
+ - the current state, next action, expiration, and finalization time.
96
+
97
+ The amount and currency are established by the business domain before provider execution. Provider selection is resolved by the server from the payment scene; the browser does not choose arbitrary provider configuration.
98
+
99
+ The session state is a discriminated union:
100
+
101
+ ```text
102
+ created
103
+ -> starting
104
+ -> requires_action | processing | succeeded | failed | cancelled
105
+ -> expired
106
+ ```
107
+
108
+ | State | Meaning |
109
+ | ----------------- | --------------------------------------------------------------------------------------------------- |
110
+ | `created` | The business domain created the session, but provider execution has not started. |
111
+ | `starting` | A provider start operation is being prepared or executed. |
112
+ | `requires_action` | The customer must follow a provider-directed action, usually a redirect in the current providers. |
113
+ | `processing` | The provider has accepted or is processing the operation, but a terminal fact is not available yet. |
114
+ | `succeeded` | A verified terminal provider fact reports success. |
115
+ | `failed` | A verified terminal provider fact reports failure. |
116
+ | `cancelled` | The payment was cancelled or the provider reported cancellation. |
117
+ | `expired` | The session passed its allowed lifetime without completing. |
118
+
119
+ The payment session and Commerce order are separate state machines. A terminal payment session can become visible before the outbox consumer settles the order, so the frontend must represent **pending confirmation** rather than assuming that a terminal provider state immediately means a paid order.
120
+
121
+ ### The `nextAction` contract
122
+
123
+ The backend exposes a normalized action union. Zova consumers branch on `nextAction.kind`, not on PayPal- or Stripe-specific SDK objects.
124
+
125
+ | Action kind | Consumer behavior |
126
+ | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
127
+ | `redirect` | In the browser, send the customer to the supplied provider URL through the shared payment coordinator. |
128
+ | `embedded` | Pass the client-facing token/configuration to the selected provider UI adapter. The current shared coordinator does not execute embedded checkout. |
129
+ | `pending` | Show preparation or status messaging and offer a safe refresh/reconcile action when appropriate. |
130
+ | `completed` | Show completion messaging, while still waiting for the business aggregate to expose its settled state. |
131
+
132
+ The normalized contract is defined in the backend payment types and view DTO, then carried to Zova through generated OpenAPI types and schemas. Do not hand-edit generated API output; change the backend contract source and follow the [contract loop playbook](/fullstack/contract-loop-playbook).
133
+
134
+ ## End-to-end payment lifecycle
135
+
136
+ The following flow is source-confirmed by the current Commerce and A-Pay implementation.
137
+
138
+ 1. The authenticated customer opens Commerce checkout.
139
+ 2. Commerce asks its payment scene for the currently available provider candidates through `GET /api/commerce/trade/checkout/payment-methods`.
140
+ 3. Checkout submits order data and, at most, a scene-local `providerCandidateKey` through `POST /api/commerce/trade/checkout`.
141
+ 4. Commerce creates the order in `awaiting_payment`, creates a `PaymentAttempt`, and creates the associated A-Pay `PaymentSession` in `created` state.
142
+ 5. A-Pay re-evaluates provider availability and the submitted candidate on the server. It resolves a declared provider/client pair, snapshots its execution context and expiry, and rejects stale or ineligible choices.
143
+ 6. Commerce navigates to an authenticated Commerce payment route with the payment-session and order identifiers. Callback continuation remains server-selected and allowlisted; do not derive it from provider or browser input.
144
+ 7. The customer starts the session. Zova calls `POST /api/pay/payment-session/{id}/start`; A-Pay claims a provider operation with a stable idempotency key before making the external provider call.
145
+ 8. The provider adapter returns a normalized snapshot and optional `nextAction`. Redirect-capable providers normally return `requires_action` with a redirect action.
146
+ 9. Zova renders the generic next-action component. On the client, the redirect coordinator uses `window.location.assign(...)`; it does not call a provider SDK or decide whether the order is paid.
147
+ 10. The provider returns through the server callback route, or sends a webhook to the public webhook route. A return callback can request confirmation or reconciliation, but cannot assert success.
148
+ 11. A-Pay verifies provider webhook input using the selected server-side client and the raw request body, then correlates provider, client, environment, amount, currency, and identifiers with the persisted session.
149
+ 12. Verified facts are deduplicated and applied to payment state. The state transition, audit entry, and `payment.outcome.v1` outbox record are written durably.
150
+ 13. An outbox worker dispatches the outcome. The persisted payment scene resolves its outcome handler, which delegates to the Commerce order owner.
151
+ 14. Commerce validates the payment-session and business references, amount, currency, provider event identity, and current order/attempt state before applying the idempotent paid, cancelled, or refund transition.
152
+ 15. Zova reconciles and polls the payment session and order. It navigates to the order page only when the two durable views agree. If delivery is delayed, the page remains in a pending-confirmation state and offers refresh/reconcile controls.
153
+
154
+ The provider call is intentionally outside the short database transaction. The durable phases are committed before and after that external call, so crash recovery and duplicate delivery are expected parts of the design. Consumers must therefore be idempotent.
155
+
156
+ ## Payment scenes and provider selection
157
+
158
+ A payment scene is the business-domain policy boundary. It declares and coordinates:
159
+
160
+ - the allowlist of named provider/client candidates;
161
+ - supported currency and amount policy;
162
+ - capture, refund, and partial-refund policy;
163
+ - session expiry;
164
+ - current availability and default selection;
165
+ - the outcome handler for the owning business aggregate;
166
+ - the trusted continuation behavior for customer returns.
167
+
168
+ The checkout projection contains only safe presentation data such as a candidate key, label, description, display order, and interaction kind. The browser submits only the candidate key. It must never submit:
169
+
170
+ - an arbitrary provider or client name;
171
+ - environment or merchant account information;
172
+ - credentials or webhook secrets;
173
+ - provider callback URLs;
174
+ - raw provider SDK options.
175
+
176
+ A candidate appearing in the scene allowlist does not guarantee present-time availability. A-Pay recomputes availability when creating the session. An omitted choice uses the server-selected default; an invalid, stale, or disabled choice is rejected rather than silently replaced.
177
+
178
+ ### Current provider modules
179
+
180
+ - **`pay-mock`** is a deterministic development and test provider. Its simulator sends a signed mock webhook through the normal webhook path, so tests exercise verification, state transitions, durable outcome delivery, and Commerce settlement instead of directly mutating a session.
181
+ - **`pay-paypal`** owns the server-side PayPal hosted-approval, capture, query, refund, and webhook translation boundary. The browser receives normalized actions and never receives PayPal credentials or SDK authority.
182
+ - **`pay-stripe`** owns the server-side Stripe hosted Checkout, query, refund, and webhook translation boundary. The browser receives normalized actions and never receives Stripe credentials or SDK authority.
183
+
184
+ The frontend PayPal and Stripe modules are currently identity/metadata surfaces. Provider execution, credentials, callbacks, webhook verification, and reconciliation remain backend responsibilities.
185
+
186
+ ## Provider-module authoring contract
187
+
188
+ A provider adapter implements the neutral A-Pay execution boundary rather than writing Commerce-specific logic. Depending on its capabilities, the adapter supplies operations conceptually equivalent to:
189
+
190
+ - start payment;
191
+ - confirm payment;
192
+ - query or reconcile payment;
193
+ - create and query refunds;
194
+ - verify webhooks.
195
+
196
+ An adapter should:
197
+
198
+ 1. use only the selected, server-resolved client options;
199
+ 2. translate provider responses into A-Pay payment/refund snapshots;
200
+ 3. preserve provider identifiers needed for correlation and idempotency;
201
+ 4. verify webhook input before returning a trusted event identity and payload;
202
+ 5. validate provider facts against the persisted session context;
203
+ 6. return only safe summary and next-action data through outward-facing contracts;
204
+ 7. leave Commerce `PaymentAttempt` and `Order` mutations to the Commerce owner.
205
+
206
+ The adapter must not derive authoritative payment identity, amount, currency, environment, or continuation targets from untrusted browser or webhook fields. Webhook verification receives the raw request body because reserialized JSON is not an equivalent signature input.
207
+
208
+ ## Zova consumption model
209
+
210
+ Read the frontend in Zova’s own roles first:
211
+
212
+ | Zova role | Current A-Pay responsibility |
213
+ | -------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
214
+ | `ModelPaymentSession` | Model-owned query and mutation state for `view`, `start`, and `reconcile`, including invalidation after mutations |
215
+ | `ControllerPaymentNextAction` | Component-controller behavior for redirect, pending, completed, and embedded action rendering |
216
+ | `ServicePaymentCoordinator` | Client-only execution of a normalized redirect action |
217
+ | `ZPaymentNextAction` | Metadata-backed public component wrapper for the action controller |
218
+ | Commerce payment page controller | Route state, start/reconcile decisions, settlement polling, and order navigation |
219
+
220
+ This is not page-local Vue fetching rewritten with classes. The model bean owns remote query/mutation state; the controller owns interaction and lifecycle decisions; the service bean owns reusable browser-side behavior; the render/component wrapper exposes the controller to consumers.
221
+
222
+ The core frontend reading path is:
223
+
224
+ 1. `zova/src/suite-vendor/a-pay/modules/a-pay/src/types/payment.ts`
225
+ 2. `zova/src/suite-vendor/a-pay/modules/a-pay/src/model/paymentSession.ts`
226
+ 3. `zova/src/suite-vendor/a-pay/modules/a-pay/src/api/paymentSession.ts`
227
+ 4. `zova/src/suite-vendor/a-pay/modules/a-pay/src/apiSchema/paymentSession.ts`
228
+ 5. `zova/src/suite-vendor/a-pay/modules/a-pay/src/service/paymentCoordinator.ts`
229
+ 6. `zova/src/suite-vendor/a-pay/modules/a-pay/src/component/paymentNextAction/controller.tsx`
230
+ 7. `zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx`
231
+
232
+ When a consumer uses `controllerRef` on `ZPaymentNextAction`, it receives the Zova component controller instance. It is not a generic DOM or Vue component reference.
233
+
234
+ ### Frontend consumer rules
235
+
236
+ - Keep payment-session query/mutation state in `ModelPaymentSession`.
237
+ - Consume generated API types and schemas; do not hand-patch generated files.
238
+ - Pass the normalized `nextAction` to the generic action component instead of adding provider-specific branches to Commerce pages.
239
+ - Execute redirects only in the browser.
240
+ - Keep mock simulator controls limited to mock sessions and development/test surfaces.
241
+ - Treat payment-session terminal state and Commerce order settlement as separate observations.
242
+ - Keep customer-visible success behind authenticated, server-authorized reads.
243
+
244
+ ## Callback, SSR, and hydration
245
+
246
+ A provider return crosses two boundaries:
247
+
248
+ 1. **Settlement authority:** the server callback confirms or reconciles provider state; it does not trust a browser assertion.
249
+ 2. **Browser route recovery:** after an external provider navigation, the browser may need to restore the Passport projection before accessing customer-owned payment and order data.
250
+
251
+ The current return path is conceptually:
252
+
253
+ ```text
254
+ provider return or cancel
255
+ -> public A-Pay callback with opaque state
256
+ -> server confirm or reconcile
257
+ -> allowlisted Commerce continuation
258
+ -> SSR-compatible payment route
259
+ -> browser hydration and Passport admission
260
+ -> authenticated payment-session reconciliation
261
+ -> durable order settlement observation
262
+ ```
263
+
264
+ The Commerce payment route requires authentication and uses the session SSR profile. Its controller defers client initialization through `$ssr.handleDirectOrOnHydrated(...)`. The payment-session model avoids creating private server-side query state when cookies are unavailable or the Passport is not authenticated. After hydration and admission, the controller can reconcile, poll, and navigate using customer-authorized reads.
265
+
266
+ The invariant is more important than a particular visual shell: server HTML and the hydration-time initial render must agree about private payment/order data. Do not render customer-owned payment state on the server when the request cannot authorize it, and do not let the provider return itself settle Commerce.
267
+
268
+ For the deeper SSR return and Passport recovery rationale, maintainers can consult the internal architecture note. The public rule remains: settle on verified server facts; recover private browser state only after hydration and authorization.
269
+
270
+ ## Reliability and security invariants
271
+
272
+ Use this checklist when extending or reviewing the payment flow:
273
+
274
+ - Amount and currency are immutable, server-owned session facts.
275
+ - Provider selection is scene-allowlisted and revalidated when the session is created.
276
+ - Provider calls have durable operation records, idempotency keys, claim leases, and recovery paths.
277
+ - Webhooks are verified against the raw body and the selected server-side provider client.
278
+ - Verified webhook facts are correlated with the persisted session before trusted mutation.
279
+ - Webhook receipts and provider event IDs are deduplicated.
280
+ - Terminal session transitions, audit records, and outcome outbox entries are durable and atomic.
281
+ - Outbox delivery is at-least-once; Commerce consumers must handle duplicate and out-of-order delivery safely.
282
+ - Browser callbacks, frontend polling, and Passport recovery never directly settle Commerce.
283
+ - Callback continuations are opaque, short-lived, signed, and allowlisted by the server.
284
+ - Payment-session endpoints enforce authenticated ownership; route admission does not replace resource authorization.
285
+ - Provider secrets and private configuration never enter DTOs, OpenAPI output, frontend state, audit summaries, or public documentation.
286
+
287
+ ## Source-reading map
288
+
289
+ For backend contract and orchestration:
290
+
291
+ 1. `vona/src/suite-vendor/a-pay/modules/a-pay/src/types/payment.ts`
292
+ 2. `vona/src/suite-vendor/a-pay/modules/a-pay/src/controller/paymentSession.ts`
293
+ 3. `vona/src/suite-vendor/a-pay/modules/a-pay/src/service/paymentSession.ts`
294
+ 4. `vona/src/suite-vendor/a-pay/modules/a-pay/src/service/providerOperation.ts`
295
+ 5. `vona/src/suite-vendor/a-pay/modules/a-pay/src/controller/paymentCallback.ts`
296
+ 6. `vona/src/suite-vendor/a-pay/modules/a-pay/src/controller/webhook.ts`
297
+ 7. `vona/src/suite-vendor/a-pay/modules/pay-mock/src/bean/payProvider.mock.ts`
298
+ 8. `vona/src/suite-vendor/a-pay/modules/pay-paypal/src/bean/payProvider.paypal.ts`
299
+ 9. `vona/src/suite-vendor/a-pay/modules/pay-stripe/src/bean/payProvider.stripe.ts`
300
+ 10. `vona/src/suite/a-commerce/modules/commerce-trade/src/service/order.ts`
301
+
302
+ For frontend contracts and consumption:
303
+
304
+ 1. `zova/src/suite-vendor/a-pay/modules/a-pay/src/types/payment.ts`
305
+ 2. `zova/src/suite-vendor/a-pay/modules/a-pay/src/model/paymentSession.ts`
306
+ 3. `zova/src/suite-vendor/a-pay/modules/a-pay/src/api/paymentSession.ts`
307
+ 4. `zova/src/suite-vendor/a-pay/modules/a-pay/src/apiSchema/paymentSession.ts`
308
+ 5. `zova/src/suite-vendor/a-pay/modules/a-pay/src/service/paymentCoordinator.ts`
309
+ 6. `zova/src/suite-vendor/a-pay/modules/a-pay/src/component/paymentNextAction/controller.tsx`
310
+ 7. `zova/src/suite/a-commerce/modules/commerce-trade/src/routes.ts`
311
+ 8. `zova/src/suite/a-commerce/modules/commerce-trade/src/page/checkout/controller.tsx`
312
+ 9. `zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx`
313
+
314
+ For the generated contract handoff, follow the backend DTO/OpenAPI source to the generated Zova API/types and then to the model/page consumer. See [Vona + Zova Integration](/fullstack/vona-zova-integration) and [Contract Loop Playbook](/fullstack/contract-loop-playbook).
315
+
316
+ ## Relationship to other guides
317
+
318
+ - [Suites and Modules](/fullstack/suites-and-modules) explains the suite-first source-tree model.
319
+ - [Payment Provider Sandbox Configuration](/fullstack/payment-sandbox-configuration) explains sanitized PayPal and Stripe Sandbox setup, public-origin configuration, and end-to-end validation.
320
+ - [Vona + Zova Integration](/fullstack/vona-zova-integration) explains the cross-framework integration boundary.
321
+ - [Contract Loop Playbook](/fullstack/contract-loop-playbook) explains how backend contracts become generated frontend consumers.
322
+ - [Reading Zova for Vue Developers](/frontend/reading-zova-for-vue-developers) explains controller, model, service, and IoC roles in Zova-native terms.
323
+ - [Transaction Guide](/backend/transaction-guide) and [Queue Guide](/backend/queue-guide) provide framework-level background for the durable phases used by A-Pay.
324
+
325
+ ## Verification
326
+
327
+ For documentation changes, run:
328
+
329
+ ```bash
330
+ npm run docs:build
331
+ ```
332
+
333
+ Then confirm that **Fullstack / Architecture & Integration → A-Pay Payment Suite** opens correctly and that the linked public guides resolve. When the implementation changes, recheck this page against current source; generated contracts and legacy operational notes are not substitutes for authored source truth.
@@ -0,0 +1,285 @@
1
+ # Payment Provider Sandbox Configuration
2
+
3
+ This guide explains how to validate the current PayPal and Stripe payment providers in a Cabloy Basic Sandbox environment.
4
+
5
+ Use it after understanding the architecture in [A-Pay Payment Suite](/fullstack/a-pay-payment-suite). It covers safe, reproducible configuration and validation with placeholders. It does **not** authorize Live payment traffic, replace provider compliance requirements, or include real account values, secrets, deployment-specific paths, tunnel commands, or internal operational diagnostics.
6
+
7
+ ## What Sandbox validation proves
8
+
9
+ A complete Sandbox validation proves the server can:
10
+
11
+ 1. expose a configured provider as a Commerce payment method;
12
+ 2. create a provider-neutral payment session;
13
+ 3. redirect the customer to a provider Sandbox flow;
14
+ 4. receive a provider callback and verified webhook;
15
+ 5. persist the normalized payment result;
16
+ 6. deliver one durable outcome to Commerce; and
17
+ 7. settle the order idempotently.
18
+
19
+ It does **not** prove that Live credentials, a Live webhook endpoint, or production operations are approved. Keep Sandbox and Live provider accounts, credentials, webhook endpoints, and merchant references separate.
20
+
21
+ ## Security boundary
22
+
23
+ Provider credentials and webhook material are backend-only configuration. Store them in the Vona server environment or deployment secret store. For an uncommitted local setup, use the backend local override:
24
+
25
+ ```text
26
+ vona/env/.env.local
27
+ ```
28
+
29
+ Never put provider credentials or private merchant configuration in:
30
+
31
+ - Zova or other frontend environment files;
32
+ - generated API contracts or frontend state;
33
+ - source code, commits, screenshots, issue comments, or public logs.
34
+
35
+ A browser redirect, callback, or success screen is **not** payment settlement authority. Settlement requires verified provider facts, correlation with the persisted payment session, durable state changes, and idempotent Commerce outcome handling.
36
+
37
+ ## Shared server configuration
38
+
39
+ Both providers need a canonical, externally reachable HTTPS origin. A provider must be able to reach the webhook endpoint from the public Internet, so `localhost`, a private address, or an unreachable host is insufficient. An approved HTTPS tunnel can forward a public hostname to a local Vona process during Sandbox work.
40
+
41
+ ```dotenv
42
+ SERVER_SERVE_PROTOCOL=https
43
+ SERVER_SERVE_HOST=<public-https-host>
44
+ SERVER_SUBDOMAINOFFSET=<domain-parts-count>
45
+ ```
46
+
47
+ `SERVER_SERVE_PROTOCOL` and `SERVER_SERVE_HOST` are the canonical external origin used to generate provider callback URLs. They are distinct from the hostname and port on which the Vona process listens.
48
+
49
+ Use the same public hostname for:
50
+
51
+ - `SERVER_SERVE_HOST`;
52
+ - generated provider callback URLs;
53
+ - the provider Dashboard webhook endpoint.
54
+
55
+ Restart Vona after changing server or provider environment values.
56
+
57
+ ### Map the public host to the intended instance
58
+
59
+ `SERVER_SUBDOMAINOFFSET` controls how Vona derives an instance name from the incoming hostname. This matters for provider callbacks and webhooks because they arrive through the public host, not the browser's local development address.
60
+
61
+ With an offset of `2`, an ordinary two-label base domain works as follows:
62
+
63
+ ```text
64
+ example.com -> default instance ''
65
+ merchant.example.com -> named instance 'merchant'
66
+ eu.merchant.example.com -> named instance 'merchant.eu'
67
+ ```
68
+
69
+ The default instance is explicitly named `''`; an unknown hostname-derived instance does not silently fall back to it. Unknown, unavailable, or disabled instances can produce HTTP `423` before the provider webhook reaches A-Pay.
70
+
71
+ For a public hostname whose **complete** label sequence should resolve to the default instance, set `SERVER_SUBDOMAINOFFSET` to the number of labels in that complete hostname. For example, a generic four-label tunnel shape such as:
72
+
73
+ ```text
74
+ <id>.<region>.devtunnels.ms
75
+ ```
76
+
77
+ may need:
78
+
79
+ ```dotenv
80
+ SERVER_SUBDOMAINOFFSET=4
81
+ ```
82
+
83
+ Do not copy that value blindly. Count the actual hostname labels and recalculate the offset whenever the public hostname changes. If the remaining labels intentionally represent a named instance, use the base-domain label count instead and ensure that derived instance exists and is enabled.
84
+
85
+ Read [Multi-Instance and Instance Resolution](/backend/multi-instance-and-instance-resolution) for the full host-to-instance model and [Docker + Cloudflare Deployment](/fullstack/deploy-cloudflare-docker) for public-origin deployment context.
86
+
87
+ ## Callback and webhook URLs
88
+
89
+ For redirect-capable providers, A-Pay generates callback URLs from the trusted server origin. The current callback paths are:
90
+
91
+ ```text
92
+ GET /api/pay/payment-callback/return?state=<opaque-token>
93
+ GET /api/pay/payment-callback/cancel?state=<opaque-token>
94
+ ```
95
+
96
+ The `state` value is a short-lived signed server token. It binds the return to the payment session and a server-selected continuation. The provider and browser do not choose an arbitrary continuation URL.
97
+
98
+ The current default webhook endpoint templates are:
99
+
100
+ ```text
101
+ POST https://<public-host>/api/pay/webhook/pay-paypal:paypal/default
102
+ POST https://<public-host>/api/pay/webhook/pay-stripe:stripe/default
103
+ ```
104
+
105
+ Here, `pay-paypal` and `pay-stripe` are provider names, while `default` is the provider client name. `/api` is the repository's default global API prefix. If a deployment configures a different global prefix, substitute that prefix in both callback and webhook URLs.
106
+
107
+ Register the exact current public HTTPS endpoint with the provider. Do not let a browser-supplied `Host`, `Origin`, or return URL replace the configured server origin.
108
+
109
+ ## PayPal Sandbox
110
+
111
+ ### Backend configuration
112
+
113
+ Configure the selected PayPal Sandbox client on Vona only:
114
+
115
+ ```dotenv
116
+ PAYPAL_ENVIRONMENT=sandbox
117
+ PAYPAL_CLIENT_ID=xxxxx
118
+ PAYPAL_CLIENT_SECRET=xxxxx
119
+ PAYPAL_WEBHOOK_ID=xxxxx
120
+ PAYPAL_MERCHANT_REFERENCE=xxxxx
121
+ ```
122
+
123
+ | Variable | Purpose |
124
+ | --------------------------- | --------------------------------------------------------------------------------------------------------------------- |
125
+ | `PAYPAL_ENVIRONMENT` | Selects `sandbox` or `live`. Use `sandbox` for this guide. |
126
+ | `PAYPAL_CLIENT_ID` | Identifies the selected PayPal REST application. |
127
+ | `PAYPAL_CLIENT_SECRET` | Authenticates server-side PayPal API and webhook-verification work. |
128
+ | `PAYPAL_WEBHOOK_ID` | Identifies the registered PayPal webhook when the server asks PayPal to verify a delivery. It is not the webhook URL. |
129
+ | `PAYPAL_MERCHANT_REFERENCE` | Identifies the expected Sandbox merchant/payee for provider-fact correlation. |
130
+
131
+ The values must all belong to the same PayPal Sandbox environment. The Commerce payment scene offers PayPal only when the required PayPal values and the server origin are complete.
132
+
133
+ ### Dashboard setup
134
+
135
+ 1. Select the PayPal **Sandbox** environment.
136
+ 2. Create or select an isolated Sandbox merchant and buyer account.
137
+ 3. Create or select the REST application for that Sandbox environment.
138
+ 4. Copy the application's client ID and secret into the Vona backend secret source.
139
+ 5. Register this endpoint in the same Sandbox environment:
140
+
141
+ ```text
142
+ POST https://<public-host>/api/pay/webhook/pay-paypal:paypal/default
143
+ ```
144
+
145
+ 6. Store the resulting webhook ID as `PAYPAL_WEBHOOK_ID` and configure the expected Sandbox merchant reference.
146
+ 7. Subscribe only to capture and refund event types supported by the current adapter. Avoid broad unrelated event families.
147
+ 8. Restart Vona and confirm the provider becomes available in the authenticated Commerce payment-method projection.
148
+
149
+ ### Validate a PayPal payment
150
+
151
+ 1. Create a Sandbox Commerce order and select the PayPal candidate.
152
+ 2. Start payment and confirm the browser is redirected to PayPal Sandbox approval.
153
+ 3. Approve using the Sandbox buyer account.
154
+ 4. Let the browser return through the signed A-Pay callback; the backend captures or reconciles the persisted provider order.
155
+ 5. Confirm a verified webhook receipt is processed, the `PaymentSession` reaches a normalized terminal state, and Commerce receives exactly one payment outcome.
156
+ 6. Repeat the browser return and redeliver the provider webhook. The order, inventory, coupon, audit, and outbox effects must not duplicate.
157
+ 7. Validate full and partial refunds where the current provider capability and business policy allow them.
158
+
159
+ ## Stripe Sandbox
160
+
161
+ ### Backend configuration
162
+
163
+ Configure the selected Stripe Sandbox client on Vona only:
164
+
165
+ ```dotenv
166
+ STRIPE_ENVIRONMENT=sandbox
167
+ STRIPE_SECRET_KEY=xxxxx
168
+ STRIPE_WEBHOOK_SECRET=xxxxx
169
+ STRIPE_MERCHANT_REFERENCE=xxxxx
170
+ ```
171
+
172
+ | Variable | Purpose |
173
+ | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
174
+ | `STRIPE_ENVIRONMENT` | Selects `sandbox` or `live`. Use `sandbox` for this guide. |
175
+ | `STRIPE_SECRET_KEY` | Creates the server-side Stripe client for Checkout, query, and refund operations. Use a Sandbox secret key, not a publishable key. |
176
+ | `STRIPE_WEBHOOK_SECRET` | Verifies the raw request body and `Stripe-Signature` header for the registered endpoint. |
177
+ | `STRIPE_MERCHANT_REFERENCE` | Optional deployment/merchant label carried in the client configuration; it does not replace either Stripe secret. |
178
+
179
+ The Commerce payment scene offers Stripe only when its Sandbox secret key, endpoint signing secret, and server origin are complete.
180
+
181
+ ### Dashboard setup
182
+
183
+ 1. Select the Stripe **Sandbox** environment.
184
+ 2. Copy a Sandbox secret key into the Vona backend secret source. Do not use a publishable key as `STRIPE_SECRET_KEY`.
185
+ 3. Register this endpoint in the same Sandbox environment:
186
+
187
+ ```text
188
+ POST https://<public-host>/api/pay/webhook/pay-stripe:stripe/default
189
+ ```
190
+
191
+ 4. Store the signing secret from that exact endpoint as `STRIPE_WEBHOOK_SECRET`.
192
+ 5. Subscribe to the Checkout, PaymentIntent, and refund lifecycle events required by the current adapter and the intended validation flow. Avoid unrelated event families.
193
+ 6. Restart Vona and confirm the provider becomes available in the authenticated Commerce payment-method projection.
194
+
195
+ ### Validate a Stripe payment
196
+
197
+ 1. Create a Sandbox Commerce order and select the Stripe candidate.
198
+ 2. Start payment and confirm the browser is redirected to Stripe Hosted Checkout.
199
+ 3. Complete Checkout with a Stripe-provided Sandbox test payment method.
200
+ 4. Let the browser return through the signed A-Pay callback; the backend reconciles persisted Stripe facts.
201
+ 5. Confirm the endpoint receives a verified webhook, the `PaymentSession` reaches its normalized terminal state, and Commerce receives exactly one payment outcome.
202
+ 6. Redeliver the webhook and repeat the browser return. Settlement and business side effects must converge to one result.
203
+ 7. Validate full and partial refunds where supported, and confirm an indeterminate provider result remains recoverable rather than being treated as successful.
204
+
205
+ Use the current Stripe documentation for the appropriate Sandbox test payment methods. Do not treat any example test value as a Cabloy-specific payment requirement.
206
+
207
+ ## Verify the complete flow
208
+
209
+ For either provider, use this end-to-end checklist:
210
+
211
+ 1. Restart Vona after configuration changes.
212
+ 2. Authenticate and load the Commerce payment-method projection.
213
+ 3. Confirm the provider appears only with complete, environment-matched configuration.
214
+ 4. Create a USD test order and select the provider candidate.
215
+ 5. Start the payment and verify the expected provider Sandbox redirect.
216
+ 6. Complete or cancel the provider flow and return to the application.
217
+ 7. Confirm the server callback asks the provider to confirm or reconcile; it must not trust browser success parameters.
218
+ 8. Confirm the provider dashboard records a successful webhook delivery and Vona verifies it.
219
+ 9. Confirm the persisted `PaymentSession` reaches the expected provider-neutral terminal state.
220
+ 10. Confirm Commerce receives one durable outcome and exposes the corresponding settled order state.
221
+ 11. Redeliver the same webhook and repeat the browser return to prove idempotency and race convergence.
222
+ 12. Exercise supported full and partial refunds; confirm repeated provider events and queries converge to one refund outcome.
223
+
224
+ ## Troubleshooting
225
+
226
+ ### The provider does not appear in Checkout
227
+
228
+ Check the following:
229
+
230
+ - All required values are configured in Vona, not Zova.
231
+ - Every key, secret, webhook identifier/signing secret, merchant reference, and selected environment belongs to the same Sandbox environment.
232
+ - Stripe uses a secret key, not a publishable key.
233
+ - `SERVER_SERVE_PROTOCOL` and `SERVER_SERVE_HOST` are configured.
234
+ - Vona was restarted after changing the environment.
235
+ - The public hostname is current and the provider candidate remains available for the active payment scene.
236
+
237
+ ### The callback or webhook reaches HTTP `423`
238
+
239
+ Treat this as an instance-resolution check first:
240
+
241
+ - Count the labels in the incoming public hostname.
242
+ - Recheck `SERVER_SUBDOMAINOFFSET`.
243
+ - Verify whether the host should map to the default instance or a named instance.
244
+ - Verify that any intended named instance exists and is enabled.
245
+ - Do not expect an unknown hostname-derived instance to fall back to the default instance.
246
+
247
+ ### Webhook signature verification fails
248
+
249
+ Check the following:
250
+
251
+ - The endpoint is publicly reachable over HTTPS.
252
+ - The configured public hostname matches the registered provider endpoint.
253
+ - The provider credentials and webhook identifier/signing secret belong to the same Sandbox environment and exact endpoint.
254
+ - Vona has the current values after restart.
255
+ - The integration preserves the provider's required raw request body and signature/transmission headers.
256
+
257
+ ### The browser returns, but the order is not settled
258
+
259
+ A normal browser return is not proof of payment. Inspect the durable flow in order:
260
+
261
+ 1. payment session state;
262
+ 2. linked provider operation state;
263
+ 3. webhook receipt and signature-verification result;
264
+ 4. outbox delivery state;
265
+ 5. redacted provider error summary.
266
+
267
+ Do not manually mark the order paid, recreate inventory/coupon effects, or reuse a consumed provider approval URL to bypass a delayed callback or webhook.
268
+
269
+ ## Relationship to other guides
270
+
271
+ - [A-Pay Payment Suite](/fullstack/a-pay-payment-suite) explains session ownership, provider-neutral contracts, settlement, and Zova consumption.
272
+ - [Multi-Instance and Instance Resolution](/backend/multi-instance-and-instance-resolution) explains how hostname labels and `SERVER_SUBDOMAINOFFSET` select an instance.
273
+ - [Docker + Cloudflare Deployment](/fullstack/deploy-cloudflare-docker) explains public HTTPS origin deployment context.
274
+ - [Vona + Zova Integration](/fullstack/vona-zova-integration) explains the fullstack contract boundary.
275
+ - [Contract Loop Playbook](/fullstack/contract-loop-playbook) explains generated backend-to-frontend contracts.
276
+
277
+ ## Verification
278
+
279
+ After changing payment configuration or documentation:
280
+
281
+ ```bash
282
+ npm run docs:build
283
+ ```
284
+
285
+ For a real Sandbox validation, repeat provider callback and webhook deliveries deliberately. The expected result is one verified payment/refund outcome and one Commerce business effect, even when external delivery is repeated or arrives in a different order.