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.
- package/.cabloy-version +1 -1
- package/.claude/skills/cabloy-frontend-scaffold/SKILL.md +13 -3
- package/.claude/skills/cabloy-workflow/SKILL.md +6 -0
- package/.claude/skills/cabloy-workflow/evals/evals.json +6 -0
- package/CHANGELOG.md +24 -0
- package/CLAUDE.md +1 -1
- package/cabloy-docs/.vitepress/config.mjs +4 -0
- package/cabloy-docs/frontend/form-layout-guide.md +29 -7
- package/cabloy-docs/fullstack/parallel-worktree-environment.md +87 -0
- package/e2e/scripts/e2e.ts +13 -1
- package/e2e/scripts/startE2eVona.ts +4 -1
- package/e2e/specs/a-commerce/commerce.spec.ts +129 -32
- package/package.json +2 -1
- package/playwright-report/data/24ac790ec112339c76cfb0c8e2c2539c2f953a0b.zip +0 -0
- package/playwright-report/data/2fddcabcdbd0b695061a565acfff243cb3a4f5e3.zip +0 -0
- package/playwright-report/data/4738038d9e861d63fd25937c9a3570934786a62f.zip +0 -0
- package/playwright-report/data/72f99c7da67c7f6ea53cab7d8681798b70d79a10.md +222 -0
- package/playwright-report/data/7b9684a7c793e9798b898f8ff50177b660a7a733.md +222 -0
- package/playwright-report/data/8b31595f5f1cc9d23863af51a592e6b90dcbc4ce.zip +0 -0
- package/playwright-report/data/95b578407a68806202dda917a61c50c750bd285b.md +222 -0
- package/playwright-report/data/ad960b3a58f224f5b4cad04a24067600064ffb7b.md +222 -0
- package/playwright-report/index.html +90 -0
- package/playwright-report/trace/assets/codeMirrorModule-LEHpjmcn.js +32 -0
- package/playwright-report/trace/assets/defaultSettingsView-BNmKHKpQ.js +264 -0
- package/playwright-report/trace/assets/urlMatch-BYQrIQwR.js +1 -0
- package/playwright-report/trace/codeMirrorModule.DYBRYzYX.css +1 -0
- package/playwright-report/trace/codicon.DCmgc-ay.ttf +0 -0
- package/playwright-report/trace/defaultSettingsView.CjdS-WJx.css +1 -0
- package/playwright-report/trace/index.CzXZzn5A.css +1 -0
- package/playwright-report/trace/index.DMMX1gXU.js +2 -0
- package/playwright-report/trace/index.html +44 -0
- package/playwright-report/trace/manifest.webmanifest +16 -0
- package/playwright-report/trace/playwright-logo.svg +9 -0
- package/playwright-report/trace/snapshot.html +10 -0
- package/playwright-report/trace/snapshot.v8KI4P3m.js +2 -0
- package/playwright-report/trace/sw.bundle.js +5 -0
- package/playwright-report/trace/uiMode.BZQ54Kgt.css +1 -0
- package/playwright-report/trace/uiMode.Ut8wwJNp.js +6 -0
- package/playwright-report/trace/uiMode.html +18 -0
- package/playwright-report/trace/xtermModule.DYP7pi_n.css +32 -0
- package/vona/env/.env +12 -0
- package/vona/env/.env.test +7 -0
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/dto/{{resourceName}}Create.tsx_ +19 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/dto/{{resourceName}}Update.tsx_ +19 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/dto/{{resourceName}}View.tsx_ +19 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/snippets/3-en-us.ts +6 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/snippets/4-zh-cn.ts +6 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/dto/{{resourceName}}Create.tsx_ +19 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/dto/{{resourceName}}Update.tsx_ +19 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/dto/{{resourceName}}View.tsx_ +19 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/snippets/3-en-us.ts +6 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/snippets/4-zh-cn.ts +6 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/pnpm-lock.yaml +255 -12
- package/vona/pnpm-workspace.yaml +1 -1
- package/vona/src/suite/a-commerce/modules/commerce-payment/package.json +4 -1
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/.metadata/index.ts +45 -3
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/meta.index.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/meta.version.ts +25 -1
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/payScene.commerceOrder.ts +20 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/entity/paymentAttempt.tsx +9 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/entity/paymentAudit.tsx +5 -2
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/entity/refundAttempt.tsx +6 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/index.ts +2 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/lib/index.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/service/commercePayScene.ts +54 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/service/paymentAttempt.ts +7 -1
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/types/index.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/test/paymentAttempt.test.ts +21 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/.metadata/index.ts +30 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/config/locale/en-us.ts +4 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/config/locale/zh-cn.ts +4 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponTemplateCreate.tsx +88 -1
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponTemplateUpdate.tsx +44 -1
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponTemplateView.tsx +72 -1
- package/vona/src/suite/a-commerce/modules/commerce-promotion/test/couponTemplateFormLayout.test.ts +121 -0
- package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/bean/ssrMenu.home.ts +4 -1
- package/vona/src/suite/a-commerce/modules/commerce-trade/package.json +4 -1
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +0 -21
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/checkoutResult.tsx +3 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/order.ts +79 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/orderSnapshot.test.ts +227 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/paymentOutcome.test.ts +121 -13
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/refundLifecycle.test.ts +312 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/package.json +76 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/.metadata/index.ts +842 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/.metadata/this.ts +2 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/bean.payProvider.ts +92 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/bean.payScene.ts +105 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/meta.index.ts +28 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/meta.redlock.ts +11 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/meta.version.ts +113 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/queue.outboxDispatch.ts +31 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/schedule.outboxDispatch.ts +11 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/controller/paymentSession.ts +55 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/controller/webhook.ts +47 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/dto/paymentSessionStart.tsx +8 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/dto/paymentSessionView.tsx +69 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/dto/webhookReceipt.tsx +12 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/outboxEvent.tsx +46 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/paymentAudit.tsx +36 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/paymentSession.tsx +72 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/providerOperation.tsx +54 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/refundOperation.tsx +37 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/webhookInbox.tsx +59 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/index.ts +3 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/lib/index.ts +2 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/lib/payProvider.ts +7 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/lib/payScene.ts +7 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/model/outboxEvent.ts +10 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/model/paymentAudit.ts +10 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/model/paymentSession.ts +10 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/model/providerOperation.ts +10 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/model/refundOperation.ts +10 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/model/webhookInbox.ts +10 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/outbox.ts +156 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/paymentSession.ts +278 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/providerOperation.ts +5 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/webhook.ts +127 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/types/index.ts +3 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/types/payProvider.ts +74 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/types/payScene.ts +63 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/types/payment.ts +151 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/test/outbox.test.ts +209 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/test/paymentSession.test.ts +153 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/test/webhook.test.ts +278 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/tsconfig.build.json +11 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/tsconfig.json +7 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/package.json +55 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/.metadata/index.ts +182 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/.metadata/this.ts +2 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/bean/payProvider.mock.ts +149 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/controller/mockPayment.ts +25 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/dto/mockPaymentComplete.tsx +13 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/dto/mockPaymentReceipt.tsx +16 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/index.ts +3 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/lib/index.ts +1 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/service/payMock.ts +78 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/types/index.ts +10 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/test/payMock.test.ts +141 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/tsconfig.build.json +11 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/tsconfig.json +7 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/package.json +55 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/.metadata/index.ts +62 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/.metadata/this.ts +2 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/bean/payProvider.paypal.ts +91 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/index.ts +3 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/lib/index.ts +1 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/types/index.ts +9 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/tsconfig.build.json +11 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/tsconfig.json +7 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/package.json +55 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/.metadata/index.ts +62 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/.metadata/this.ts +2 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/bean/payProvider.stripe.ts +88 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/index.ts +2 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/types/index.ts +8 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/tsconfig.build.json +11 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/tsconfig.json +7 -0
- package/vona/src/suite-vendor/a-pay/package.json +15 -0
- package/vona/src/suite-vendor/a-pay/tsconfig.base.json +4 -0
- package/vona/src/suite-vendor/a-pay/tsconfig.json +19 -0
- package/zova/env/.env +1 -0
- package/zova/openapi.config.ts +5 -1
- package/zova/package.original.json +1 -1
- package/zova/packages-cli/cli/package.json +2 -2
- package/zova/packages-cli/cli-set-front/cli/templates/openapi/config/boilerplate/project/openapi.config.ts +5 -1
- package/zova/packages-cli/cli-set-front/package.json +1 -1
- package/zova/packages-cli/cli-set-front/src/lib/bean/cli.openapi.generate.ts +8 -0
- package/zova/packages-cli/cli-set-front/src/lib/common/cliCreatePage.ts +7 -24
- package/zova/packages-cli/cli-set-front/src/lib/common/utils.ts +10 -1
- package/zova/packages-utils/zova-jsx/package.json +2 -2
- package/zova/packages-zova/zova/package.json +3 -3
- package/zova/packages-zova/zova-core/package.json +1 -1
- package/zova/packages-zova/zova-core/src/types/interface/component.ts +5 -0
- package/zova/pnpm-lock.yaml +96 -26
- package/zova/src/front/config/config/config.cabloyCommerce.ts +1 -1
- package/zova/src/suite/a-commerce/modules/commerce-promotion/src/api/openapi/schemas.ts +110 -12
- package/zova/src/suite/a-commerce/modules/commerce-promotion/src/api/openapi/types.ts +1292 -160
- package/zova/src/suite/a-commerce/modules/commerce-trade/cli/openapi.config.ts +0 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/package.json +4 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +1 -47
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/schemas.ts +23 -8
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/types.ts +249 -62
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/checkout/controller.tsx +5 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx +152 -39
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/routes.ts +1 -1
- package/zova/src/suite-vendor/a-pay/modules/a-pay/cli/openapi.config.ts +9 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/package.json +53 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/.metadata/component/paymentNextAction.ts +31 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/.metadata/index.ts +217 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/.metadata/this.ts +2 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/api/openapi/baseURL.ts +5 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/api/openapi/index.ts +3 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/api/openapi/schemas.ts +584 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/api/openapi/types.ts +9324 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/api/paymentSession.ts +52 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/apiSchema/paymentSession.ts +20 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/component/paymentNextAction/controller.tsx +76 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/index.ts +3 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/lib/index.ts +1 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/model/paymentSession.ts +32 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/service/paymentCoordinator.ts +13 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/types/index.ts +1 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/types/payment.ts +25 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/tsconfig.build.json +13 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/tsconfig.json +5 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/cli/openapi.config.ts +9 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/package.json +53 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/.metadata/index.ts +140 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/.metadata/this.ts +2 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/api/openapi/baseURL.ts +5 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/api/openapi/index.ts +3 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/api/openapi/schemas.ts +584 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/api/openapi/types.ts +9324 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/api/payMockPayment.ts +34 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/apiSchema/payMockPayment.ts +13 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/index.ts +1 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/model/payMockPayment.ts +20 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/tsconfig.build.json +13 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/tsconfig.json +5 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-paypal/package.json +52 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-paypal/src/.metadata/index.ts +26 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-paypal/src/.metadata/this.ts +2 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-paypal/src/index.ts +1 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-paypal/tsconfig.build.json +13 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-paypal/tsconfig.json +5 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-stripe/package.json +52 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-stripe/src/.metadata/index.ts +26 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-stripe/src/.metadata/this.ts +2 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-stripe/src/index.ts +1 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-stripe/tsconfig.build.json +13 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-stripe/tsconfig.json +5 -0
- package/zova/src/suite-vendor/a-pay/package.json +15 -0
- package/zova/src/suite-vendor/a-pay/tsconfig.base.json +4 -0
- package/zova/src/suite-vendor/a-pay/tsconfig.json +4 -0
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/package.json +2 -2
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/controller/payment.ts +0 -23
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/commerceTradePayment.ts +0 -36
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/apiSchema/commerceTradePayment.ts +0 -13
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/payment.ts +0 -25
- /package/vona/patches/{zova-core@5.1.79.patch → zova-core@5.1.81.patch} +0 -0
|
@@ -1153,13 +1153,11 @@ declare module 'vona-module-commerce-trade' {
|
|
|
1153
1153
|
export * from '../controller/cart.ts';
|
|
1154
1154
|
export * from '../controller/checkout.ts';
|
|
1155
1155
|
export * from '../controller/order.ts';
|
|
1156
|
-
export * from '../controller/payment.ts';
|
|
1157
1156
|
export * from '../controller/stockAudit.ts';
|
|
1158
1157
|
export * from '../controller/stockBalance.ts';
|
|
1159
1158
|
import type { IControllerOptionsCart } from '../controller/cart.ts';
|
|
1160
1159
|
import type { IControllerOptionsCheckout } from '../controller/checkout.ts';
|
|
1161
1160
|
import type { IControllerOptionsOrder } from '../controller/order.ts';
|
|
1162
|
-
import type { IControllerOptionsPayment } from '../controller/payment.ts';
|
|
1163
1161
|
import type { IControllerOptionsStockAudit } from '../controller/stockAudit.ts';
|
|
1164
1162
|
import type { IControllerOptionsStockBalance } from '../controller/stockBalance.ts';
|
|
1165
1163
|
import 'vona-module-a-web';
|
|
@@ -1169,7 +1167,6 @@ declare module 'vona-module-a-web' {
|
|
|
1169
1167
|
'commerce-trade:cart': IControllerOptionsCart;
|
|
1170
1168
|
'commerce-trade:checkout': IControllerOptionsCheckout;
|
|
1171
1169
|
'commerce-trade:order': IControllerOptionsOrder;
|
|
1172
|
-
'commerce-trade:payment': IControllerOptionsPayment;
|
|
1173
1170
|
'commerce-trade:stockAudit': IControllerOptionsStockAudit;
|
|
1174
1171
|
'commerce-trade:stockBalance': IControllerOptionsStockBalance;
|
|
1175
1172
|
}
|
|
@@ -1211,17 +1208,6 @@ declare module 'vona-module-commerce-trade' {
|
|
|
1211
1208
|
get $onionOptions(): IControllerOptionsOrder;
|
|
1212
1209
|
}
|
|
1213
1210
|
|
|
1214
|
-
export interface ControllerPayment {
|
|
1215
|
-
/** @internal */
|
|
1216
|
-
get scope(): ScopeModuleCommerceTrade;
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
export interface ControllerPayment {
|
|
1220
|
-
get $beanFullName(): 'commerce-trade.controller.payment';
|
|
1221
|
-
get $onionName(): 'commerce-trade:payment';
|
|
1222
|
-
get $onionOptions(): IControllerOptionsPayment;
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
1211
|
export interface ControllerStockAudit {
|
|
1226
1212
|
/** @internal */
|
|
1227
1213
|
get scope(): ScopeModuleCommerceTrade;
|
|
@@ -1253,8 +1239,6 @@ import type { ControllerCheckout } from '../controller/checkout.ts';
|
|
|
1253
1239
|
// @ts-ignore ignore
|
|
1254
1240
|
import type { ControllerOrder } from '../controller/order.ts';
|
|
1255
1241
|
// @ts-ignore ignore
|
|
1256
|
-
import type { ControllerPayment } from '../controller/payment.ts';
|
|
1257
|
-
// @ts-ignore ignore
|
|
1258
1242
|
import type { ControllerStockAudit } from '../controller/stockAudit.ts';
|
|
1259
1243
|
// @ts-ignore ignore
|
|
1260
1244
|
import type { ControllerStockBalance } from '../controller/stockBalance.ts';
|
|
@@ -1272,10 +1256,6 @@ declare module 'vona-module-commerce-trade' {
|
|
|
1272
1256
|
actions?: TypeControllerOptionsActions<ControllerOrder>;
|
|
1273
1257
|
}
|
|
1274
1258
|
|
|
1275
|
-
export interface IControllerOptionsPayment {
|
|
1276
|
-
actions?: TypeControllerOptionsActions<ControllerPayment>;
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
1259
|
export interface IControllerOptionsStockAudit {
|
|
1280
1260
|
actions?: TypeControllerOptionsActions<ControllerStockAudit>;
|
|
1281
1261
|
}
|
|
@@ -1304,7 +1284,6 @@ export interface IApiPathPostRecord{
|
|
|
1304
1284
|
'/commerce/trade/order/:id/rejectRefund': undefined;
|
|
1305
1285
|
'/commerce/trade/order/:id/refundOutcome': undefined;
|
|
1306
1286
|
'/commerce/trade/order/:id/ship': undefined;
|
|
1307
|
-
'/commerce/trade/payment/:attemptId/outcome': undefined;
|
|
1308
1287
|
'/commerce/trade/stockBalance/adjustStock': undefined;
|
|
1309
1288
|
}
|
|
1310
1289
|
export interface IApiPathPatchRecord{
|
|
@@ -15,6 +15,9 @@ export class DtoCheckoutResult {
|
|
|
15
15
|
@Api.field(v.tableIdentity())
|
|
16
16
|
paymentAttemptId: TableIdentity;
|
|
17
17
|
|
|
18
|
+
@Api.field(v.tableIdentity())
|
|
19
|
+
paymentSessionId: TableIdentity;
|
|
20
|
+
|
|
18
21
|
@Api.field(z.enum(['awaiting_payment', 'paid', 'cancelled', 'expired']))
|
|
19
22
|
state: 'awaiting_payment' | 'paid' | 'cancelled' | 'expired';
|
|
20
23
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { TableIdentity } from 'table-identity';
|
|
2
2
|
import type { IQueryParams } from 'vona-module-a-orm';
|
|
3
|
+
import type { IPaymentOutcomeEvent } from 'vona-module-a-pay';
|
|
3
4
|
import type { EntityPaymentAttempt } from 'vona-module-commerce-payment';
|
|
4
5
|
import type {
|
|
5
6
|
EntityRefundAttempt,
|
|
@@ -106,6 +107,7 @@ export type ShipmentFailureStage =
|
|
|
106
107
|
type ShipmentStageCallback = (stage: ShipmentFailureStage) => void | Promise<void>;
|
|
107
108
|
|
|
108
109
|
export type RefundFailureStage =
|
|
110
|
+
| 'beforeRefundOutcomeOrderLock'
|
|
109
111
|
| 'afterRefundRequestInsert'
|
|
110
112
|
| 'afterRefundRequestOrderState'
|
|
111
113
|
| 'afterRefundRequestAudit'
|
|
@@ -336,6 +338,7 @@ export class ServiceOrder extends BeanBase {
|
|
|
336
338
|
return {
|
|
337
339
|
orderId: order.id,
|
|
338
340
|
paymentAttemptId: paymentAttempt.id,
|
|
341
|
+
paymentSessionId: paymentAttempt.paymentSessionId!,
|
|
339
342
|
state: 'awaiting_payment',
|
|
340
343
|
paymentAttemptState: 'created',
|
|
341
344
|
currency: order.currency,
|
|
@@ -344,6 +347,80 @@ export class ServiceOrder extends BeanBase {
|
|
|
344
347
|
};
|
|
345
348
|
}
|
|
346
349
|
|
|
350
|
+
@Core.transaction({ isolationLevel: 'SERIALIZABLE' })
|
|
351
|
+
@Core.retryable(serializationRetryOptions)
|
|
352
|
+
async settlePaymentFromProvider(event: IPaymentOutcomeEvent): Promise<DtoPaymentOutcomeResult> {
|
|
353
|
+
const attempt = await this.$scope.commercePayment.model.paymentAttempt.get({
|
|
354
|
+
paymentSessionId: event.paymentSessionId,
|
|
355
|
+
});
|
|
356
|
+
if (!attempt || String(attempt.id) !== event.businessReference) {
|
|
357
|
+
this.app.throw(404, 'commerce payment attempt not found');
|
|
358
|
+
}
|
|
359
|
+
const order = await this.scope.model.order.getByIdForUpdate(attempt.orderId);
|
|
360
|
+
if (!order) this.app.throw(404, 'order not found');
|
|
361
|
+
const lockedAttempt = await this.$scope.commercePayment.model.paymentAttempt.getByIdForUpdate(
|
|
362
|
+
attempt.id,
|
|
363
|
+
);
|
|
364
|
+
if (!lockedAttempt) this.app.throw(404, 'commerce payment attempt not found');
|
|
365
|
+
if (
|
|
366
|
+
lockedAttempt.amountCents !== event.amountMinor ||
|
|
367
|
+
lockedAttempt.currency !== event.currency ||
|
|
368
|
+
String(lockedAttempt.paymentSessionId) !== String(event.paymentSessionId) ||
|
|
369
|
+
lockedAttempt.providerName !== event.providerName
|
|
370
|
+
) {
|
|
371
|
+
this.app.throw(409, 'provider event conflicts with the commerce payment attempt');
|
|
372
|
+
}
|
|
373
|
+
const existingAudit = await this.$scope.commercePayment.model.paymentAudit.get({
|
|
374
|
+
paymentAttemptId: lockedAttempt.id,
|
|
375
|
+
providerEventId: event.eventId,
|
|
376
|
+
});
|
|
377
|
+
if (existingAudit) return this._paymentOutcomeResult(order, lockedAttempt);
|
|
378
|
+
if (order.state !== 'awaiting_payment' || lockedAttempt.state !== 'created') {
|
|
379
|
+
this.app.throw(409, 'payment attempt is no longer available');
|
|
380
|
+
}
|
|
381
|
+
const correlationId = `${order.correlationId}:provider:${event.eventId}`;
|
|
382
|
+
const orderState = event.state === 'succeeded' ? 'paid' : 'cancelled';
|
|
383
|
+
const reason = `provider payment ${event.state}`;
|
|
384
|
+
await this.scope.model.order.updateById(order.id, { state: orderState });
|
|
385
|
+
const finalizedAttempt = await this.$scope.commercePayment.service.paymentAttempt.finalize(
|
|
386
|
+
order.id,
|
|
387
|
+
event.state,
|
|
388
|
+
);
|
|
389
|
+
if (!finalizedAttempt) this.app.throw(404, 'commerce payment attempt not found');
|
|
390
|
+
await this.$scope.commercePayment.model.paymentAttempt.updateById(finalizedAttempt.id, {
|
|
391
|
+
providerName: event.providerName,
|
|
392
|
+
providerCaptureId: event.providerCaptureId,
|
|
393
|
+
});
|
|
394
|
+
if (event.state === 'succeeded') {
|
|
395
|
+
await this._consumeReservedOrderResources(order, correlationId, reason);
|
|
396
|
+
} else {
|
|
397
|
+
await this._releaseReservedOrderResources(order, correlationId, reason);
|
|
398
|
+
}
|
|
399
|
+
await this.$scope.commercePayment.model.paymentAudit.insert({
|
|
400
|
+
paymentAttemptId: finalizedAttempt.id,
|
|
401
|
+
orderId: order.id,
|
|
402
|
+
userId: order.userId,
|
|
403
|
+
provider: event.providerName,
|
|
404
|
+
providerEventId: event.eventId,
|
|
405
|
+
outcome: event.state,
|
|
406
|
+
fromAttemptState: 'created',
|
|
407
|
+
toOrderState: orderState,
|
|
408
|
+
idempotencyKey: event.eventId.slice(0, 100),
|
|
409
|
+
correlationId,
|
|
410
|
+
reason,
|
|
411
|
+
processedAt: new Date(),
|
|
412
|
+
});
|
|
413
|
+
await this._appendAudit({
|
|
414
|
+
orderId: order.id,
|
|
415
|
+
operation: orderState === 'paid' ? 'paid' : 'cancelled',
|
|
416
|
+
fromState: 'awaiting_payment',
|
|
417
|
+
toState: orderState,
|
|
418
|
+
correlationId,
|
|
419
|
+
reason,
|
|
420
|
+
});
|
|
421
|
+
return this._paymentOutcomeResult({ ...order, state: orderState }, finalizedAttempt);
|
|
422
|
+
}
|
|
423
|
+
|
|
347
424
|
@Core.transaction({ isolationLevel: 'SERIALIZABLE' })
|
|
348
425
|
@Core.retryable(serializationRetryOptions)
|
|
349
426
|
async applyPaymentOutcome(
|
|
@@ -585,6 +662,7 @@ export class ServiceOrder extends BeanBase {
|
|
|
585
662
|
): Promise<DtoRefundResult> {
|
|
586
663
|
const idempotencyKey = command.idempotencyKey.trim();
|
|
587
664
|
if (!idempotencyKey) this.app.throw(400, 'refund idempotencyKey is required');
|
|
665
|
+
await onStage?.('beforeRefundOutcomeOrderLock');
|
|
588
666
|
const order = await this.scope.model.order.getByIdForUpdate(orderId);
|
|
589
667
|
if (!order) this.app.throw(404, 'order not found');
|
|
590
668
|
const existingAudit = await this.$scope.commercePayment.model.refundAudit.get({
|
|
@@ -1132,6 +1210,7 @@ export class ServiceOrder extends BeanBase {
|
|
|
1132
1210
|
return {
|
|
1133
1211
|
orderId: order.id,
|
|
1134
1212
|
paymentAttemptId: paymentAttempt.id,
|
|
1213
|
+
paymentSessionId: paymentAttempt.paymentSessionId!,
|
|
1135
1214
|
state: order.state,
|
|
1136
1215
|
paymentAttemptState: paymentAttempt.state,
|
|
1137
1216
|
currency: order.currency,
|
|
@@ -9,7 +9,9 @@ interface IFixture {
|
|
|
9
9
|
productId?: number;
|
|
10
10
|
skuId?: number;
|
|
11
11
|
addressId?: number;
|
|
12
|
+
cartId?: number;
|
|
12
13
|
orderId?: number;
|
|
14
|
+
paymentAttemptId?: number;
|
|
13
15
|
couponTemplateId?: number;
|
|
14
16
|
couponGrantId?: number;
|
|
15
17
|
userId?: number;
|
|
@@ -17,10 +19,27 @@ interface IFixture {
|
|
|
17
19
|
|
|
18
20
|
async function cleanup(fixture: IFixture) {
|
|
19
21
|
const promotion = app.scope('commerce-promotion');
|
|
22
|
+
const payment = app.scope('commerce-payment');
|
|
20
23
|
const trade = app.scope('commerce-trade');
|
|
21
24
|
const catalog = app.scope('commerce-catalog');
|
|
22
25
|
const member = app.scope('commerce-member');
|
|
23
26
|
if (fixture.orderId !== undefined) {
|
|
27
|
+
const requests = await payment.model.refundRequest.select({
|
|
28
|
+
where: { orderId: fixture.orderId },
|
|
29
|
+
});
|
|
30
|
+
for (const request of requests) {
|
|
31
|
+
await payment.model.refundAudit.delete({ refundRequestId: request.id });
|
|
32
|
+
await payment.model.refundAttempt.delete({ refundRequestId: request.id });
|
|
33
|
+
}
|
|
34
|
+
await payment.model.refundRequest.delete({ orderId: fixture.orderId });
|
|
35
|
+
await trade.model.shipment.delete({ orderId: fixture.orderId });
|
|
36
|
+
}
|
|
37
|
+
if (fixture.paymentAttemptId !== undefined) {
|
|
38
|
+
await payment.model.paymentAudit.delete({ paymentAttemptId: fixture.paymentAttemptId });
|
|
39
|
+
await payment.model.paymentAttempt.delete({ id: fixture.paymentAttemptId });
|
|
40
|
+
}
|
|
41
|
+
if (fixture.orderId !== undefined) {
|
|
42
|
+
await trade.model.orderAudit.delete({ orderId: fixture.orderId });
|
|
24
43
|
const lines = await trade.model.orderLine.select({ where: { orderId: fixture.orderId } });
|
|
25
44
|
for (const line of lines) {
|
|
26
45
|
await trade.model.stockReservation.delete({ orderLineId: line.id });
|
|
@@ -28,6 +47,10 @@ async function cleanup(fixture: IFixture) {
|
|
|
28
47
|
await trade.model.orderLine.delete({ orderId: fixture.orderId });
|
|
29
48
|
await trade.model.order.delete({ id: fixture.orderId });
|
|
30
49
|
}
|
|
50
|
+
if (fixture.cartId !== undefined) {
|
|
51
|
+
await trade.model.cartItem.delete({ cartId: fixture.cartId });
|
|
52
|
+
await trade.model.cart.delete({ id: fixture.cartId });
|
|
53
|
+
}
|
|
31
54
|
if (fixture.couponGrantId !== undefined) {
|
|
32
55
|
await promotion.model.couponAudit.delete({ couponGrantId: fixture.couponGrantId });
|
|
33
56
|
await promotion.model.couponGrant.delete({ id: fixture.couponGrantId });
|
|
@@ -222,4 +245,208 @@ describe('orderSnapshot.test.ts', { concurrency: false }, () => {
|
|
|
222
245
|
}
|
|
223
246
|
});
|
|
224
247
|
});
|
|
248
|
+
|
|
249
|
+
it('keeps immutable purchase facts in the refunded customer order view', async () => {
|
|
250
|
+
await app.bean.executor.mockCtx(async () => {
|
|
251
|
+
const fixture: IFixture = {};
|
|
252
|
+
const suffix = randomUUID().slice(0, 12);
|
|
253
|
+
const original = {
|
|
254
|
+
addressCity: 'San Francisco',
|
|
255
|
+
addressLine1: '1 Market Street',
|
|
256
|
+
couponName: `refund snapshot coupon ${suffix}`,
|
|
257
|
+
productTitle: `refund snapshot product ${suffix}`,
|
|
258
|
+
skuCode: `refund-snapshot-sku-${suffix}`,
|
|
259
|
+
attributes: [{ name: 'Color', value: 'Black' }],
|
|
260
|
+
};
|
|
261
|
+
try {
|
|
262
|
+
await app.bean.passport.signinMock();
|
|
263
|
+
fixture.categoryId = await app.bean.executor.performAction(
|
|
264
|
+
'post',
|
|
265
|
+
'/commerce/catalog/category',
|
|
266
|
+
{ body: { name: `refund snapshot category ${suffix}`, published: true } },
|
|
267
|
+
);
|
|
268
|
+
fixture.productId = await app.bean.executor.performAction(
|
|
269
|
+
'post',
|
|
270
|
+
'/commerce/catalog/product',
|
|
271
|
+
{
|
|
272
|
+
body: { categoryId: fixture.categoryId, title: original.productTitle, published: true },
|
|
273
|
+
},
|
|
274
|
+
);
|
|
275
|
+
fixture.skuId = await app.bean.executor.performAction('post', '/commerce/catalog/sku', {
|
|
276
|
+
body: {
|
|
277
|
+
productId: fixture.productId,
|
|
278
|
+
code: original.skuCode,
|
|
279
|
+
priceCents: 1299,
|
|
280
|
+
attributes: original.attributes,
|
|
281
|
+
lifecycle: 'active',
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
await app.scope('commerce-trade').service.stockBalance.adjustStock({
|
|
285
|
+
skuId: fixture.skuId,
|
|
286
|
+
delta: 1,
|
|
287
|
+
reason: 'refund snapshot fixture',
|
|
288
|
+
correlationId: `refund-snapshot-stock-${suffix}`,
|
|
289
|
+
});
|
|
290
|
+
await app.bean.passport.signout();
|
|
291
|
+
|
|
292
|
+
const customerName = `refund-snapshot-customer-${suffix}`;
|
|
293
|
+
const customer = await app.bean.user.register({ name: customerName }, true);
|
|
294
|
+
fixture.userId = customer.id as number;
|
|
295
|
+
await app.bean.passport.signinMock(customerName as any);
|
|
296
|
+
const userId = app.bean.passport.currentUser!.id;
|
|
297
|
+
fixture.couponTemplateId = (
|
|
298
|
+
await app.scope('commerce-promotion').model.couponTemplate.insert({
|
|
299
|
+
name: original.couponName,
|
|
300
|
+
state: 'active',
|
|
301
|
+
currency: 'USD',
|
|
302
|
+
discountCents: 500,
|
|
303
|
+
minSpendCents: 1_000,
|
|
304
|
+
validFrom: new Date(Date.now() - 1_000),
|
|
305
|
+
validUntil: new Date(Date.now() + 60_000),
|
|
306
|
+
issuedCount: 0,
|
|
307
|
+
redeemedCount: 0,
|
|
308
|
+
})
|
|
309
|
+
).id as number;
|
|
310
|
+
fixture.couponGrantId = (
|
|
311
|
+
await app.scope('commerce-promotion').service.coupon.issue({
|
|
312
|
+
templateId: fixture.couponTemplateId,
|
|
313
|
+
userId,
|
|
314
|
+
correlationId: `refund-snapshot-coupon-${suffix}`,
|
|
315
|
+
reason: 'refund snapshot fixture',
|
|
316
|
+
})
|
|
317
|
+
).id as number;
|
|
318
|
+
fixture.addressId = await app.bean.executor.performAction(
|
|
319
|
+
'post',
|
|
320
|
+
'/commerce/member/address/createMine',
|
|
321
|
+
{
|
|
322
|
+
body: {
|
|
323
|
+
recipientName: 'Refund Snapshot Recipient',
|
|
324
|
+
phone: '15555550123',
|
|
325
|
+
countryCode: 'US',
|
|
326
|
+
region: 'California',
|
|
327
|
+
city: original.addressCity,
|
|
328
|
+
postalCode: '94105',
|
|
329
|
+
addressLine1: original.addressLine1,
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
);
|
|
333
|
+
const cart = await app.scope('commerce-trade').model.cart.insert({ userId });
|
|
334
|
+
fixture.cartId = cart.id as number;
|
|
335
|
+
await app.scope('commerce-trade').model.cartItem.insert({
|
|
336
|
+
cartId: cart.id,
|
|
337
|
+
skuId: fixture.skuId,
|
|
338
|
+
quantity: 1,
|
|
339
|
+
});
|
|
340
|
+
const checkout = await app.scope('commerce-trade').service.order.checkout({
|
|
341
|
+
addressId: fixture.addressId,
|
|
342
|
+
couponGrantId: fixture.couponGrantId,
|
|
343
|
+
correlationId: `refund-snapshot-checkout-${suffix}`,
|
|
344
|
+
});
|
|
345
|
+
fixture.orderId = checkout.orderId as number;
|
|
346
|
+
fixture.paymentAttemptId = checkout.paymentAttemptId as number;
|
|
347
|
+
await app
|
|
348
|
+
.scope('commerce-trade')
|
|
349
|
+
.service.order.applyPaymentOutcome(fixture.paymentAttemptId, {
|
|
350
|
+
outcome: 'succeeded',
|
|
351
|
+
idempotencyKey: `refund-snapshot-payment-${suffix}`,
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
await app.scope('commerce-catalog').model.product.updateById(fixture.productId, {
|
|
355
|
+
title: 'Changed refund product title',
|
|
356
|
+
published: false,
|
|
357
|
+
});
|
|
358
|
+
await app.scope('commerce-catalog').model.sku.updateById(fixture.skuId, {
|
|
359
|
+
code: 'changed-refund-sku-code',
|
|
360
|
+
priceCents: 9999,
|
|
361
|
+
attributes: [{ name: 'Color', value: 'Blue' }],
|
|
362
|
+
lifecycle: 'inactive',
|
|
363
|
+
});
|
|
364
|
+
await app.scope('commerce-member').model.address.updateById(fixture.addressId, {
|
|
365
|
+
city: 'Oakland',
|
|
366
|
+
addressLine1: 'Changed refund address',
|
|
367
|
+
});
|
|
368
|
+
await app
|
|
369
|
+
.scope('commerce-promotion')
|
|
370
|
+
.model.couponTemplate.updateById(fixture.couponTemplateId, {
|
|
371
|
+
name: 'Changed refund coupon',
|
|
372
|
+
discountCents: 999,
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
await app.scope('commerce-trade').service.order.requestRefund(fixture.orderId, {
|
|
376
|
+
reason: 'snapshot refund request',
|
|
377
|
+
idempotencyKey: `refund-snapshot-request-${suffix}`,
|
|
378
|
+
});
|
|
379
|
+
await app.bean.passport.signout();
|
|
380
|
+
await app.bean.passport.signinMock();
|
|
381
|
+
await app.scope('commerce-trade').service.order.approveRefund(fixture.orderId, {
|
|
382
|
+
reason: 'snapshot refund approval',
|
|
383
|
+
idempotencyKey: `refund-snapshot-approval-${suffix}`,
|
|
384
|
+
});
|
|
385
|
+
await app.scope('commerce-trade').service.order.applyRefundOutcome(fixture.orderId, {
|
|
386
|
+
outcome: 'succeeded',
|
|
387
|
+
idempotencyKey: `refund-snapshot-outcome-${suffix}`,
|
|
388
|
+
});
|
|
389
|
+
await app.bean.passport.signout();
|
|
390
|
+
await app.bean.passport.signinMock(customerName as any);
|
|
391
|
+
|
|
392
|
+
const detail = await app.scope('commerce-trade').service.order.viewMine(fixture.orderId);
|
|
393
|
+
assert.equal(detail?.state, 'refunded');
|
|
394
|
+
assert.equal(detail?.shipment, undefined);
|
|
395
|
+
assert.equal(detail?.addressSnapshot.city, original.addressCity);
|
|
396
|
+
assert.equal(detail?.addressSnapshot.addressLine1, original.addressLine1);
|
|
397
|
+
assert.equal(detail?.couponSnapshot?.templateName, original.couponName);
|
|
398
|
+
assert.equal(detail?.couponSnapshot?.fixedDiscountCents, 500);
|
|
399
|
+
assert.equal(detail?.discountCents, 500);
|
|
400
|
+
assert.equal(detail?.payableTotalCents, 799);
|
|
401
|
+
assert.equal(detail?.lines.length, 1);
|
|
402
|
+
assert.deepEqual(
|
|
403
|
+
detail?.lines[0] && {
|
|
404
|
+
skuCodeSnapshot: detail.lines[0].skuCodeSnapshot,
|
|
405
|
+
titleSnapshot: detail.lines[0].titleSnapshot,
|
|
406
|
+
skuAttributesSnapshot: detail.lines[0].skuAttributesSnapshot,
|
|
407
|
+
unitPriceCents: detail.lines[0].unitPriceCents,
|
|
408
|
+
quantity: detail.lines[0].quantity,
|
|
409
|
+
lineTotalCents: detail.lines[0].lineTotalCents,
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
skuCodeSnapshot: original.skuCode,
|
|
413
|
+
titleSnapshot: original.productTitle,
|
|
414
|
+
skuAttributesSnapshot: original.attributes,
|
|
415
|
+
unitPriceCents: 1299,
|
|
416
|
+
quantity: 1,
|
|
417
|
+
lineTotalCents: 1299,
|
|
418
|
+
},
|
|
419
|
+
);
|
|
420
|
+
const liveProduct = await app
|
|
421
|
+
.scope('commerce-catalog')
|
|
422
|
+
.model.product.getById(fixture.productId);
|
|
423
|
+
const liveSku = await app.scope('commerce-catalog').model.sku.getById(fixture.skuId);
|
|
424
|
+
const liveAddress = await app
|
|
425
|
+
.scope('commerce-member')
|
|
426
|
+
.model.address.getById(fixture.addressId);
|
|
427
|
+
const liveCoupon = await app
|
|
428
|
+
.scope('commerce-promotion')
|
|
429
|
+
.model.couponTemplate.getById(fixture.couponTemplateId);
|
|
430
|
+
assert.deepEqual(
|
|
431
|
+
[
|
|
432
|
+
liveProduct?.title,
|
|
433
|
+
liveSku?.code,
|
|
434
|
+
liveSku?.priceCents,
|
|
435
|
+
liveAddress?.city,
|
|
436
|
+
liveCoupon?.name,
|
|
437
|
+
],
|
|
438
|
+
[
|
|
439
|
+
'Changed refund product title',
|
|
440
|
+
'changed-refund-sku-code',
|
|
441
|
+
9999,
|
|
442
|
+
'Oakland',
|
|
443
|
+
'Changed refund coupon',
|
|
444
|
+
],
|
|
445
|
+
);
|
|
446
|
+
} finally {
|
|
447
|
+
await cleanup(fixture);
|
|
448
|
+
await app.bean.passport.signout();
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
});
|
|
225
452
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
|
-
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { createHmac, randomUUID } from 'node:crypto';
|
|
3
|
+
import { describe, it } from 'node:test';
|
|
4
|
+
import { app } from 'vona-mock';
|
|
5
5
|
|
|
6
6
|
interface IFixture {
|
|
7
7
|
categoryId?: number;
|
|
@@ -17,6 +17,7 @@ interface IFixture {
|
|
|
17
17
|
checkoutCorrelationId?: string;
|
|
18
18
|
orderId?: number;
|
|
19
19
|
paymentAttemptId?: number;
|
|
20
|
+
paymentSessionId?: number;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
async function cleanup(fixture: IFixture) {
|
|
@@ -25,10 +26,18 @@ async function cleanup(fixture: IFixture) {
|
|
|
25
26
|
const promotion = app.scope('commerce-promotion');
|
|
26
27
|
const catalog = app.scope('commerce-catalog');
|
|
27
28
|
const member = app.scope('commerce-member');
|
|
29
|
+
const pay = app.scope('a-pay');
|
|
30
|
+
if (fixture.paymentSessionId !== undefined) {
|
|
31
|
+
await pay.model.outboxEvent.delete({ paymentSessionId: fixture.paymentSessionId });
|
|
32
|
+
await pay.model.paymentAudit.delete({ paymentSessionId: fixture.paymentSessionId });
|
|
33
|
+
await pay.model.webhookInbox.delete({ paymentSessionId: fixture.paymentSessionId });
|
|
34
|
+
}
|
|
28
35
|
if (fixture.paymentAttemptId !== undefined)
|
|
29
36
|
await payment.model.paymentAudit.delete({ paymentAttemptId: fixture.paymentAttemptId });
|
|
30
37
|
if (fixture.paymentAttemptId !== undefined)
|
|
31
38
|
await payment.model.paymentAttempt.delete({ id: fixture.paymentAttemptId });
|
|
39
|
+
if (fixture.paymentSessionId !== undefined)
|
|
40
|
+
await pay.model.paymentSession.delete({ id: fixture.paymentSessionId });
|
|
32
41
|
if (fixture.orderId !== undefined)
|
|
33
42
|
await trade.model.orderAudit.delete({ orderId: fixture.orderId });
|
|
34
43
|
if (fixture.skuId !== undefined) await trade.model.stockAudit.delete({ skuId: fixture.skuId });
|
|
@@ -152,18 +161,81 @@ async function createCheckoutFixture(suffix: string, withCoupon = true): Promise
|
|
|
152
161
|
});
|
|
153
162
|
fixture.orderId = checkout.orderId as number;
|
|
154
163
|
fixture.paymentAttemptId = checkout.paymentAttemptId as number;
|
|
164
|
+
fixture.paymentSessionId = checkout.paymentSessionId as number;
|
|
155
165
|
return fixture;
|
|
156
166
|
}
|
|
157
167
|
|
|
158
168
|
describe('paymentOutcome.test.ts', { concurrency: false, sequential: true }, () => {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
169
|
+
it('delivers a signed webhook through the durable outbox to Commerce exactly once', async () => {
|
|
170
|
+
await app.bean.executor.mockCtx(async () => {
|
|
171
|
+
const fixture: IFixture = {};
|
|
172
|
+
try {
|
|
173
|
+
Object.assign(fixture, await createCheckoutFixture(randomUUID().slice(0, 12)));
|
|
174
|
+
const eventId = `provider-${randomUUID().slice(0, 12)}`;
|
|
175
|
+
const rawBody = JSON.stringify({
|
|
176
|
+
paymentSessionId: String(fixture.paymentSessionId),
|
|
177
|
+
currency: 'USD',
|
|
178
|
+
amountMinor: 799,
|
|
179
|
+
eventType: 'payment.succeeded',
|
|
180
|
+
eventId,
|
|
181
|
+
state: 'succeeded',
|
|
182
|
+
providerPaymentId: `payment-${eventId}`,
|
|
183
|
+
providerCaptureId: `capture-${eventId}`,
|
|
184
|
+
});
|
|
185
|
+
const options = app.bean.payProvider.getOptions('pay-mock:mock', 'default');
|
|
186
|
+
assert.equal(typeof options.secretWebhook, 'string');
|
|
187
|
+
const signature = createHmac('sha256', options.secretWebhook).update(rawBody).digest('hex');
|
|
188
|
+
const webhookPath = app.util.getAbsoluteUrlByApiPath('/pay/webhook/pay-mock:mock/default');
|
|
189
|
+
const webhookUrl = webhookPath.startsWith('http')
|
|
190
|
+
? webhookPath
|
|
191
|
+
: `http://127.0.0.1:${app.config.server.listen.port}${webhookPath}`;
|
|
192
|
+
const response = await fetch(webhookUrl, {
|
|
193
|
+
method: 'POST',
|
|
194
|
+
headers: {
|
|
195
|
+
'Content-Type': 'application/json',
|
|
196
|
+
'x-pay-mock-signature': signature,
|
|
197
|
+
},
|
|
198
|
+
body: rawBody,
|
|
199
|
+
});
|
|
200
|
+
assert.equal(response.status, 200);
|
|
201
|
+
const pay = app.scope('a-pay');
|
|
202
|
+
const outbox = await pay.model.outboxEvent.get({
|
|
203
|
+
paymentSessionId: fixture.paymentSessionId,
|
|
204
|
+
});
|
|
205
|
+
assert.ok(outbox);
|
|
206
|
+
await pay.queue.outboxDispatch.pushAsync({ outboxEventId: outbox.id });
|
|
207
|
+
await pay.queue.outboxDispatch.pushAsync({ outboxEventId: outbox.id });
|
|
208
|
+
const [dispatched, order, attempt, line, balance, grant, paymentAudits] = await Promise.all(
|
|
209
|
+
[
|
|
210
|
+
pay.model.outboxEvent.getById(outbox.id),
|
|
211
|
+
app.scope('commerce-trade').model.order.getById(fixture.orderId!),
|
|
212
|
+
app.scope('commerce-payment').model.paymentAttempt.getById(fixture.paymentAttemptId!),
|
|
213
|
+
app.scope('commerce-trade').model.orderLine.get({ orderId: fixture.orderId }),
|
|
214
|
+
app.scope('commerce-trade').model.stockBalance.getById(fixture.balanceId!),
|
|
215
|
+
app.scope('commerce-promotion').model.couponGrant.getById(fixture.grantId!),
|
|
216
|
+
app.scope('commerce-payment').model.paymentAudit.select({
|
|
217
|
+
where: { paymentAttemptId: fixture.paymentAttemptId },
|
|
218
|
+
}),
|
|
219
|
+
],
|
|
220
|
+
);
|
|
221
|
+
const reservation = await app.scope('commerce-trade').model.stockReservation.get({
|
|
222
|
+
orderLineId: line?.id,
|
|
223
|
+
});
|
|
224
|
+
assert.deepEqual([dispatched?.state, dispatched?.attemptCount], ['dispatched', 1]);
|
|
225
|
+
assert.equal(dispatched?.claimToken, undefined);
|
|
226
|
+
assert.deepEqual(
|
|
227
|
+
[order?.state, attempt?.state, reservation?.state],
|
|
228
|
+
['paid', 'succeeded', 'consumed'],
|
|
229
|
+
);
|
|
230
|
+
assert.deepEqual([balance?.onHand, balance?.reserved, balance?.available], [0, 0, 0]);
|
|
231
|
+
assert.equal(grant?.state, 'redeemed');
|
|
232
|
+
assert.equal(paymentAudits.length, 1);
|
|
233
|
+
assert.equal(paymentAudits[0]?.providerEventId, eventId);
|
|
234
|
+
} finally {
|
|
235
|
+
await cleanup(fixture);
|
|
236
|
+
await app.bean.passport.signout();
|
|
237
|
+
}
|
|
238
|
+
});
|
|
167
239
|
});
|
|
168
240
|
|
|
169
241
|
it('settles a successful payment exactly once with stock and coupon consumption', async () => {
|
|
@@ -214,6 +286,43 @@ describe('paymentOutcome.test.ts', { concurrency: false, sequential: true }, ()
|
|
|
214
286
|
});
|
|
215
287
|
});
|
|
216
288
|
|
|
289
|
+
it('settles a provider event without customer Passport authority', async () => {
|
|
290
|
+
await app.bean.executor.mockCtx(async () => {
|
|
291
|
+
const fixture: IFixture = {};
|
|
292
|
+
try {
|
|
293
|
+
Object.assign(fixture, await createCheckoutFixture(randomUUID().slice(0, 12)));
|
|
294
|
+
await app.bean.passport.signout();
|
|
295
|
+
const event = {
|
|
296
|
+
eventId: `provider-${randomUUID().slice(0, 12)}`,
|
|
297
|
+
paymentSessionId: fixture.paymentSessionId!,
|
|
298
|
+
businessReference: String(fixture.paymentAttemptId),
|
|
299
|
+
providerName: 'pay-mock:mock',
|
|
300
|
+
state: 'succeeded' as const,
|
|
301
|
+
providerCaptureId: `capture-${randomUUID().slice(0, 12)}`,
|
|
302
|
+
amountMinor: 799,
|
|
303
|
+
currency: 'USD',
|
|
304
|
+
};
|
|
305
|
+
const first = await app
|
|
306
|
+
.scope('commerce-trade')
|
|
307
|
+
.service.order.settlePaymentFromProvider(event);
|
|
308
|
+
const replay = await app
|
|
309
|
+
.scope('commerce-trade')
|
|
310
|
+
.service.order.settlePaymentFromProvider(event);
|
|
311
|
+
assert.deepEqual(replay, first);
|
|
312
|
+
assert.deepEqual([first.orderState, first.paymentAttemptState], ['paid', 'succeeded']);
|
|
313
|
+
const audits = await app.scope('commerce-payment').model.paymentAudit.select({
|
|
314
|
+
where: { paymentAttemptId: fixture.paymentAttemptId },
|
|
315
|
+
});
|
|
316
|
+
assert.equal(audits.length, 1);
|
|
317
|
+
assert.equal(audits[0]?.providerEventId, event.eventId);
|
|
318
|
+
assert.equal(audits[0]?.actorId, undefined);
|
|
319
|
+
} finally {
|
|
320
|
+
await cleanup(fixture);
|
|
321
|
+
await app.bean.passport.signout();
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
217
326
|
it('accepts one concurrent PostgreSQL success event without duplicate effects', async t => {
|
|
218
327
|
if (process.env.DATABASE_DEFAULT_CLIENT !== 'pg') {
|
|
219
328
|
t.skip('requires PostgreSQL row-lock contention');
|
|
@@ -438,9 +547,8 @@ describe('paymentOutcome.test.ts', { concurrency: false, sequential: true }, ()
|
|
|
438
547
|
innerAccess: false,
|
|
439
548
|
}),
|
|
440
549
|
() =>
|
|
441
|
-
app.bean.executor.performAction('
|
|
550
|
+
app.bean.executor.performAction('get', '/payment-session/1', {
|
|
442
551
|
innerAccess: false,
|
|
443
|
-
body: { outcome: 'succeeded', idempotencyKey: 'anonymous-payment-1' },
|
|
444
552
|
}),
|
|
445
553
|
];
|
|
446
554
|
for (const request of requests) {
|