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
@@ -11,14 +11,20 @@ import type {
11
11
  IPayProviderWebhookInput,
12
12
  } from 'vona-module-a-pay';
13
13
 
14
+ import Stripe from 'stripe';
14
15
  import { BeanBase, useApp } from 'vona';
15
- import { PayProvider } from 'vona-module-a-pay';
16
+ import { PayProvider, ProviderOperationFailure } from 'vona-module-a-pay';
17
+
18
+ import type { IStripeGateway, IStripeGatewayOptions } from '../lib/stripeGateway.ts';
19
+
20
+ import { createStripeGateway, StripeGatewayError } from '../lib/stripeGateway.ts';
16
21
 
17
22
  export interface IPayProviderStripeClientRecord extends IPayProviderClientRecord {}
18
23
 
19
24
  export interface IPayProviderStripeClientOptions extends IPayProviderClientOptions {
20
25
  secretCredential: string | undefined;
21
26
  secretWebhook: string | undefined;
27
+ gateway?: IStripeGateway;
22
28
  }
23
29
 
24
30
  const app = useApp();
@@ -31,8 +37,8 @@ export interface IPayProviderOptionsStripe extends IDecoratorPayProviderOptions<
31
37
  @PayProvider<IPayProviderOptionsStripe>({
32
38
  base: {
33
39
  capabilities: {
34
- redirectCheckout: false,
35
- embeddedCheckout: true,
40
+ redirectCheckout: true,
41
+ embeddedCheckout: false,
36
42
  automaticCapture: true,
37
43
  manualCapture: false,
38
44
  refunds: true,
@@ -42,9 +48,10 @@ export interface IPayProviderOptionsStripe extends IDecoratorPayProviderOptions<
42
48
  },
43
49
  clients: {
44
50
  default: {
45
- environment: 'sandbox',
51
+ environment: app.meta.env.STRIPE_ENVIRONMENT as 'sandbox' | 'live',
46
52
  secretCredential: app.meta.env.STRIPE_SECRET_KEY,
47
53
  secretWebhook: app.meta.env.STRIPE_WEBHOOK_SECRET,
54
+ merchantReference: app.meta.env.STRIPE_MERCHANT_REFERENCE,
48
55
  },
49
56
  },
50
57
  })
@@ -53,43 +60,472 @@ export class PayProviderStripe
53
60
  implements IPayProviderExecute<IPayProviderStripeClientOptions>
54
61
  {
55
62
  async startPayment(
56
- _input: IPayProviderPaymentInput,
57
- _clientOptions: IPayProviderStripeClientOptions,
63
+ input: IPayProviderPaymentInput,
64
+ clientOptions: IPayProviderStripeClientOptions,
58
65
  ): Promise<IPayProviderPaymentSnapshot> {
59
- this.app.throw(501, 'Stripe provider is not implemented');
66
+ if (input.providerOrderId) return await this.queryPayment(input, clientOptions);
67
+ if (!input.returnUrl || !input.cancelUrl) {
68
+ this.app.throw(500, 'Stripe payment requires return and cancel URLs');
69
+ }
70
+ const gatewayOptions = this._gatewayOptions(clientOptions);
71
+ assertStripeMoney(input.amountMinor, input.currency);
72
+ const body = {
73
+ mode: 'payment',
74
+ success_url: input.returnUrl,
75
+ cancel_url: input.cancelUrl,
76
+ client_reference_id: input.providerCorrelationReference,
77
+ metadata: this._paymentMetadata(input),
78
+ payment_intent_data: { metadata: this._paymentMetadata(input) },
79
+ managed_payments: { enabled: false },
80
+ line_items: [
81
+ {
82
+ quantity: 1,
83
+ price_data: {
84
+ currency: input.currency.toLowerCase(),
85
+ unit_amount: input.amountMinor,
86
+ product_data: { name: `Payment ${input.providerInvoiceReference}` },
87
+ },
88
+ },
89
+ ],
90
+ } satisfies Stripe.Checkout.SessionCreateParams;
91
+ let checkoutSession: unknown;
92
+ try {
93
+ checkoutSession = await this._gateway(clientOptions).createCheckoutSession(gatewayOptions, {
94
+ idempotencyKey: input.idempotencyKey,
95
+ body,
96
+ });
97
+ } catch (error) {
98
+ this._throwTerminalPaymentFailure(error);
99
+ }
100
+ return this._mapCheckoutSession(checkoutSession, input, clientOptions, true);
60
101
  }
61
102
 
62
103
  async confirmPayment(
63
- _input: IPayProviderPaymentInput,
64
- _clientOptions: IPayProviderStripeClientOptions,
104
+ input: IPayProviderPaymentInput,
105
+ clientOptions: IPayProviderStripeClientOptions,
65
106
  ): Promise<IPayProviderPaymentSnapshot> {
66
- this.app.throw(501, 'Stripe provider is not implemented');
107
+ return await this.queryPayment(input, clientOptions);
67
108
  }
68
109
 
69
110
  async queryPayment(
70
- _input: IPayProviderPaymentInput,
71
- _clientOptions: IPayProviderStripeClientOptions,
111
+ input: IPayProviderPaymentInput,
112
+ clientOptions: IPayProviderStripeClientOptions,
72
113
  ): Promise<IPayProviderPaymentSnapshot> {
73
- this.app.throw(501, 'Stripe provider is not implemented');
114
+ const gatewayOptions = this._gatewayOptions(clientOptions);
115
+ if (!input.providerOrderId) this.app.throw(409, 'Stripe payment has no Checkout Session');
116
+ const checkoutSession = await this._gateway(clientOptions).retrieveCheckoutSession(
117
+ gatewayOptions,
118
+ { id: input.providerOrderId },
119
+ );
120
+ return this._mapCheckoutSession(checkoutSession, input, clientOptions);
74
121
  }
75
122
 
76
123
  async createRefund(
77
- _input: IPayProviderRefundInput,
78
- _clientOptions: IPayProviderStripeClientOptions,
124
+ input: IPayProviderRefundInput,
125
+ clientOptions: IPayProviderStripeClientOptions,
126
+ ): Promise<IPayProviderRefundSnapshot> {
127
+ const gatewayOptions = this._gatewayOptions(clientOptions);
128
+ assertStripeMoney(input.amountMinor, input.currency);
129
+ try {
130
+ const refund = await this._gateway(clientOptions).createRefund(gatewayOptions, {
131
+ idempotencyKey: input.idempotencyKey,
132
+ body: {
133
+ payment_intent: input.providerCaptureId,
134
+ amount: input.amountMinor,
135
+ metadata: this._refundMetadata(input),
136
+ },
137
+ });
138
+ return this._mapRefund(refund, input);
139
+ } catch (error) {
140
+ if (this._isDefinitiveRefundRejection(error)) return { state: 'failed' };
141
+ throw error;
142
+ }
143
+ }
144
+
145
+ async queryRefund(
146
+ input: IPayProviderRefundInput,
147
+ clientOptions: IPayProviderStripeClientOptions,
79
148
  ): Promise<IPayProviderRefundSnapshot> {
80
- this.app.throw(501, 'Stripe provider is not implemented');
149
+ const gatewayOptions = this._gatewayOptions(clientOptions);
150
+ if (!input.providerRefundId)
151
+ this.app.throw(409, 'Stripe refund has no provider refund identifier');
152
+ const refund = await this._gateway(clientOptions).retrieveRefund(gatewayOptions, {
153
+ id: input.providerRefundId,
154
+ });
155
+ return this._mapRefund(refund, input);
81
156
  }
82
157
 
83
158
  async verifyWebhook(
84
- _input: IPayProviderWebhookInput,
85
- _clientOptions: IPayProviderStripeClientOptions,
159
+ input: IPayProviderWebhookInput,
160
+ clientOptions: IPayProviderStripeClientOptions,
161
+ ): Promise<IPayProviderVerifiedWebhook> {
162
+ const gatewayOptions = this._gatewayOptions(clientOptions);
163
+ let event: Record<string, unknown>;
164
+ try {
165
+ event = asRecord(
166
+ await this._gateway(clientOptions).constructWebhookEvent(gatewayOptions, input),
167
+ );
168
+ } catch (error) {
169
+ this._throwGatewayError(error);
170
+ }
171
+ const eventId = readString(event.id);
172
+ const eventType = readString(event.type);
173
+ const resource = asRecord(asRecord(event.data).object);
174
+ if (!eventId || !eventType || !Object.keys(resource).length) {
175
+ this.app.throw(400, 'Stripe webhook is invalid');
176
+ }
177
+ if (eventType.startsWith('checkout.session.')) {
178
+ return await this._mapCheckoutWebhook(eventId, eventType, resource, clientOptions);
179
+ }
180
+ if (eventType.startsWith('payment_intent.')) {
181
+ return await this._mapPaymentIntentWebhook(eventId, eventType, resource);
182
+ }
183
+ if (eventType.startsWith('refund.')) {
184
+ return await this._mapRefundWebhook(eventId, eventType, resource);
185
+ }
186
+ return { eventId, eventType, ignored: true, summary: { amountMinor: 0, currency: 'USD' } };
187
+ }
188
+
189
+ private async _mapCheckoutWebhook(
190
+ eventId: string,
191
+ eventType: string,
192
+ checkoutSession: Record<string, unknown>,
193
+ clientOptions: IPayProviderStripeClientOptions,
194
+ ): Promise<IPayProviderVerifiedWebhook> {
195
+ const input = await this._inputFromCheckoutSession(checkoutSession);
196
+ const payment = await this._mapCheckoutSession(checkoutSession, input, clientOptions);
197
+ if (eventType === 'checkout.session.async_payment_failed') {
198
+ return {
199
+ eventId,
200
+ eventType,
201
+ paymentSessionId: input.paymentSessionId,
202
+ payment: { ...payment, state: 'failed' },
203
+ summary: { amountMinor: input.amountMinor, currency: input.currency },
204
+ };
205
+ }
206
+ if (!['succeeded', 'failed', 'cancelled'].includes(payment.state)) {
207
+ return {
208
+ eventId,
209
+ eventType,
210
+ ignored: true,
211
+ summary: { amountMinor: input.amountMinor, currency: input.currency },
212
+ };
213
+ }
214
+ return {
215
+ eventId,
216
+ eventType,
217
+ paymentSessionId: input.paymentSessionId,
218
+ payment,
219
+ summary: { amountMinor: input.amountMinor, currency: input.currency },
220
+ };
221
+ }
222
+
223
+ private async _mapPaymentIntentWebhook(
224
+ eventId: string,
225
+ eventType: string,
226
+ paymentIntent: Record<string, unknown>,
86
227
  ): Promise<IPayProviderVerifiedWebhook> {
87
- this.app.throw(501, 'Stripe provider is not implemented');
228
+ const metadata = asRecord(paymentIntent.metadata);
229
+ const providerCorrelationReference = readString(metadata.providerCorrelationReference);
230
+ const providerInvoiceReference = readString(metadata.providerInvoiceReference);
231
+ if (!providerCorrelationReference || !providerInvoiceReference) {
232
+ this.app.throw(400, 'Stripe PaymentIntent webhook is not correlated');
233
+ }
234
+ const session = await this.$scope.pay.model.paymentSession.get({
235
+ providerCorrelationReference,
236
+ });
237
+ if (!session || session.providerInvoiceReference !== providerInvoiceReference) {
238
+ this.app.throw(400, 'Stripe PaymentIntent webhook is not correlated');
239
+ }
240
+ const payment = this._mapPaymentIntent(paymentIntent, {
241
+ paymentSessionId: session.id,
242
+ businessReference: session.businessReference,
243
+ providerInvoiceReference: session.providerInvoiceReference,
244
+ providerCorrelationReference: session.providerCorrelationReference,
245
+ idempotencyKey: '',
246
+ amountMinor: session.amountMinor,
247
+ currency: session.currency,
248
+ providerOrderId: session.providerOrderId,
249
+ });
250
+ const terminalPayment =
251
+ eventType === 'payment_intent.payment_failed'
252
+ ? { ...payment, state: 'failed' as const }
253
+ : payment;
254
+ if (!['succeeded', 'failed', 'cancelled'].includes(terminalPayment.state)) {
255
+ return {
256
+ eventId,
257
+ eventType,
258
+ ignored: true,
259
+ summary: { amountMinor: session.amountMinor, currency: session.currency },
260
+ };
261
+ }
262
+ return {
263
+ eventId,
264
+ eventType,
265
+ paymentSessionId: session.id,
266
+ payment: terminalPayment,
267
+ summary: { amountMinor: session.amountMinor, currency: session.currency },
268
+ };
269
+ }
270
+
271
+ private async _mapRefundWebhook(
272
+ eventId: string,
273
+ eventType: string,
274
+ refundResource: Record<string, unknown>,
275
+ ): Promise<IPayProviderVerifiedWebhook> {
276
+ const refundId = readString(refundResource.id);
277
+ const metadata = asRecord(refundResource.metadata);
278
+ const providerCorrelationReference = readString(metadata.providerCorrelationReference);
279
+ if (!refundId || !providerCorrelationReference) {
280
+ this.app.throw(400, 'Stripe refund webhook is not correlated');
281
+ }
282
+ const refund = await this.$scope.pay.model.refundOperation.get({
283
+ providerCorrelationReference,
284
+ });
285
+ if (!refund) this.app.throw(400, 'Stripe refund webhook is not correlated');
286
+ const session = await this.$scope.pay.model.paymentSession.getById(refund.paymentSessionId);
287
+ if (!session?.providerCaptureId) this.app.throw(409, 'Stripe refund webhook payment conflicts');
288
+ const snapshot = this._mapRefund(refundResource, {
289
+ paymentSessionId: session.id,
290
+ refundOperationId: refund.id,
291
+ businessReference: refund.businessReference,
292
+ providerInvoiceReference: refund.providerInvoiceReference,
293
+ providerCorrelationReference: refund.providerCorrelationReference,
294
+ idempotencyKey: '',
295
+ amountMinor: refund.amountMinor,
296
+ currency: refund.currency,
297
+ providerCaptureId: session.providerCaptureId,
298
+ providerRefundId: refundId,
299
+ });
300
+ return {
301
+ eventId,
302
+ eventType,
303
+ refundOperationId: refund.id,
304
+ refund: snapshot,
305
+ summary: { amountMinor: refund.amountMinor, currency: refund.currency },
306
+ };
307
+ }
308
+
309
+ private async _inputFromCheckoutSession(
310
+ checkoutSession: Record<string, unknown>,
311
+ ): Promise<IPayProviderPaymentInput> {
312
+ const metadata = asRecord(checkoutSession.metadata);
313
+ const providerCorrelationReference = readString(metadata.providerCorrelationReference);
314
+ const providerInvoiceReference = readString(metadata.providerInvoiceReference);
315
+ const providerOrderId = readString(checkoutSession.id);
316
+ if (!providerCorrelationReference || !providerInvoiceReference || !providerOrderId) {
317
+ this.app.throw(400, 'Stripe Checkout Session is not correlated');
318
+ }
319
+ const session = await this.$scope.pay.model.paymentSession.get({
320
+ providerCorrelationReference,
321
+ });
322
+ if (!session || session.providerInvoiceReference !== providerInvoiceReference) {
323
+ this.app.throw(400, 'Stripe Checkout Session is not correlated');
324
+ }
325
+ return {
326
+ paymentSessionId: session.id,
327
+ businessReference: session.businessReference,
328
+ providerInvoiceReference: session.providerInvoiceReference,
329
+ providerCorrelationReference: session.providerCorrelationReference,
330
+ idempotencyKey: '',
331
+ amountMinor: session.amountMinor,
332
+ currency: session.currency,
333
+ providerOrderId,
334
+ };
335
+ }
336
+
337
+ private _mapCheckoutSession(
338
+ checkoutSession: unknown,
339
+ input: IPayProviderPaymentInput,
340
+ clientOptions: IPayProviderStripeClientOptions,
341
+ requireRedirect = false,
342
+ ): IPayProviderPaymentSnapshot {
343
+ const record = asRecord(checkoutSession);
344
+ const providerOrderId = readString(record.id);
345
+ const amountMinor = readInteger(record.amount_total);
346
+ const currency = readString(record.currency)?.toUpperCase();
347
+ const metadata = asRecord(record.metadata);
348
+ if (
349
+ !providerOrderId ||
350
+ (input.providerOrderId && input.providerOrderId !== providerOrderId) ||
351
+ amountMinor !== input.amountMinor ||
352
+ currency !== input.currency ||
353
+ readString(record.client_reference_id) !== input.providerCorrelationReference ||
354
+ readString(metadata.providerCorrelationReference) !== input.providerCorrelationReference ||
355
+ readString(metadata.providerInvoiceReference) !== input.providerInvoiceReference ||
356
+ !this._matchesEnvironment(record, clientOptions)
357
+ ) {
358
+ this.app.throw(409, 'Stripe Checkout Session facts conflict with the payment session');
359
+ }
360
+ const paymentIntent = asRecord(record.payment_intent);
361
+ const paymentIntentId =
362
+ readString(record.payment_intent) ?? readString(paymentIntent.id) ?? undefined;
363
+ if (paymentIntentId && Object.keys(paymentIntent).length) {
364
+ return this._mapPaymentIntent(paymentIntent, { ...input, providerOrderId }, providerOrderId);
365
+ }
366
+ if (record.status === 'expired') {
367
+ return { state: 'cancelled', providerPaymentId: providerOrderId, providerOrderId };
368
+ }
369
+ if (record.payment_status === 'paid' && paymentIntentId) {
370
+ return {
371
+ state: 'succeeded',
372
+ providerPaymentId: paymentIntentId,
373
+ providerOrderId,
374
+ providerCaptureId: paymentIntentId,
375
+ nextAction: { kind: 'completed' },
376
+ };
377
+ }
378
+ const url = readString(record.url);
379
+ if (requireRedirect && !url) this.app.throw(502, 'Stripe did not return a Checkout URL');
380
+ return {
381
+ state: 'requires_action',
382
+ providerPaymentId: providerOrderId,
383
+ providerOrderId,
384
+ ...(url && { nextAction: { kind: 'redirect' as const, url } }),
385
+ };
386
+ }
387
+
388
+ private _mapPaymentIntent(
389
+ paymentIntent: Record<string, unknown>,
390
+ input: IPayProviderPaymentInput,
391
+ providerOrderId = input.providerOrderId,
392
+ ): IPayProviderPaymentSnapshot {
393
+ const providerPaymentId = readString(paymentIntent.id);
394
+ const amountMinor = readInteger(paymentIntent.amount);
395
+ const currency = readString(paymentIntent.currency)?.toUpperCase();
396
+ const metadata = asRecord(paymentIntent.metadata);
397
+ if (
398
+ !providerPaymentId ||
399
+ amountMinor !== input.amountMinor ||
400
+ currency !== input.currency ||
401
+ readString(metadata.providerCorrelationReference) !== input.providerCorrelationReference ||
402
+ readString(metadata.providerInvoiceReference) !== input.providerInvoiceReference
403
+ ) {
404
+ this.app.throw(409, 'Stripe PaymentIntent facts conflict with the payment session');
405
+ }
406
+ const status = readString(paymentIntent.status);
407
+ if (status === 'succeeded') {
408
+ return {
409
+ state: 'succeeded',
410
+ providerPaymentId,
411
+ providerOrderId,
412
+ providerCaptureId: providerPaymentId,
413
+ nextAction: { kind: 'completed' },
414
+ };
415
+ }
416
+ if (status === 'processing') return { state: 'processing', providerPaymentId, providerOrderId };
417
+ if (status === 'canceled') return { state: 'cancelled', providerPaymentId, providerOrderId };
418
+ if (status === 'requires_payment_method')
419
+ return { state: 'failed', providerPaymentId, providerOrderId };
420
+ return { state: 'requires_action', providerPaymentId, providerOrderId };
421
+ }
422
+
423
+ private _mapRefund(refund: unknown, input: IPayProviderRefundInput): IPayProviderRefundSnapshot {
424
+ const record = asRecord(refund);
425
+ const providerRefundId = readString(record.id);
426
+ const amountMinor = readInteger(record.amount);
427
+ const currency = readString(record.currency)?.toUpperCase();
428
+ const metadata = asRecord(record.metadata);
429
+ const paymentIntent = readString(record.payment_intent);
430
+ if (
431
+ !providerRefundId ||
432
+ (input.providerRefundId && input.providerRefundId !== providerRefundId) ||
433
+ amountMinor !== input.amountMinor ||
434
+ currency !== input.currency ||
435
+ paymentIntent !== input.providerCaptureId ||
436
+ readString(metadata.providerCorrelationReference) !== input.providerCorrelationReference ||
437
+ readString(metadata.providerInvoiceReference) !== input.providerInvoiceReference
438
+ ) {
439
+ this.app.throw(409, 'Stripe refund facts conflict with the refund operation');
440
+ }
441
+ const status = readString(record.status);
442
+ if (status === 'succeeded') return { state: 'succeeded', providerRefundId };
443
+ if (status === 'failed') return { state: 'failed', providerRefundId };
444
+ if (status === 'canceled') return { state: 'cancelled', providerRefundId };
445
+ return { state: 'pending', providerRefundId };
446
+ }
447
+
448
+ private _paymentMetadata(input: IPayProviderPaymentInput): Record<string, string> {
449
+ return {
450
+ paymentSessionId: String(input.paymentSessionId),
451
+ providerCorrelationReference: input.providerCorrelationReference,
452
+ providerInvoiceReference: input.providerInvoiceReference,
453
+ };
454
+ }
455
+
456
+ private _refundMetadata(input: IPayProviderRefundInput): Record<string, string> {
457
+ return {
458
+ paymentSessionId: String(input.paymentSessionId),
459
+ refundOperationId: String(input.refundOperationId),
460
+ providerCorrelationReference: input.providerCorrelationReference,
461
+ providerInvoiceReference: input.providerInvoiceReference,
462
+ };
463
+ }
464
+
465
+ private _gateway(clientOptions: IPayProviderStripeClientOptions): IStripeGateway {
466
+ return clientOptions.gateway ?? createStripeGateway();
467
+ }
468
+
469
+ private _gatewayOptions(clientOptions: IPayProviderStripeClientOptions): IStripeGatewayOptions {
470
+ const secretKey = clientOptions.secretCredential;
471
+ const webhookSecret = clientOptions.secretWebhook;
472
+ if (!secretKey || !webhookSecret) this.app.throw(503, 'Stripe is not fully configured');
473
+ return { secretKey, webhookSecret };
474
+ }
475
+
476
+ private _matchesEnvironment(
477
+ value: Record<string, unknown>,
478
+ clientOptions: IPayProviderStripeClientOptions,
479
+ ): boolean {
480
+ const livemode = value.livemode;
481
+ return typeof livemode !== 'boolean' || livemode === (clientOptions.environment === 'live');
482
+ }
483
+
484
+ private _isDefinitiveRefundRejection(error: unknown): boolean {
485
+ return isStripeDefinitiveRejection(error);
486
+ }
487
+
488
+ private _throwTerminalPaymentFailure(error: unknown): never {
489
+ if (isStripeDefinitiveRejection(error)) {
490
+ throw new ProviderOperationFailure(
491
+ 'stripe_request_rejected',
492
+ 'Stripe rejected the payment request',
493
+ );
494
+ }
495
+ throw error;
496
+ }
497
+
498
+ private _throwGatewayError(error: unknown): never {
499
+ if (error instanceof StripeGatewayError) this.app.throw(error.status, error.message);
500
+ throw error;
88
501
  }
89
502
  }
90
503
 
91
- declare module 'vona-module-a-pay' {
92
- export interface IPayProviderRecord {
93
- 'pay-stripe:stripe': IPayProviderOptionsStripe;
504
+ function isStripeDefinitiveRejection(error: unknown): boolean {
505
+ if (error instanceof StripeGatewayError) return error.status >= 400 && error.status < 500;
506
+ const type = (error as Record<string, unknown> | undefined)?.type;
507
+ return [
508
+ 'StripeInvalidRequestError',
509
+ 'StripeIdempotencyError',
510
+ 'StripeAuthenticationError',
511
+ 'StripePermissionError',
512
+ ].includes(type as string);
513
+ }
514
+
515
+ function assertStripeMoney(amountMinor: number, currency: string) {
516
+ if (!Number.isSafeInteger(amountMinor) || amountMinor <= 0 || currency !== 'USD') {
517
+ throw new Error('Stripe supports only positive USD minor-unit amounts');
94
518
  }
95
519
  }
520
+
521
+ function asRecord(value: unknown): Record<string, unknown> {
522
+ return value && typeof value === 'object' ? (value as Record<string, unknown>) : {};
523
+ }
524
+
525
+ function readString(value: unknown): string | undefined {
526
+ return typeof value === 'string' && value ? value : undefined;
527
+ }
528
+
529
+ function readInteger(value: unknown): number | undefined {
530
+ return typeof value === 'number' && Number.isSafeInteger(value) ? value : undefined;
531
+ }
@@ -1,2 +1,3 @@
1
+ export type { IPayProviderStripeClientOptions } from './bean/payProvider.stripe.ts';
1
2
  export * from './types/index.ts';
2
3
  export * from './.metadata/index.ts';
@@ -0,0 +1,85 @@
1
+ import Stripe from 'stripe';
2
+
3
+ export interface IStripeGatewayOptions {
4
+ secretKey: string;
5
+ webhookSecret: string;
6
+ }
7
+
8
+ export const StripeRequestTimeoutMilliseconds = 15_000;
9
+
10
+ export interface IStripeGateway {
11
+ createCheckoutSession(options: IStripeGatewayOptions, input: unknown): Promise<unknown>;
12
+ retrieveCheckoutSession(options: IStripeGatewayOptions, input: unknown): Promise<unknown>;
13
+ createRefund(options: IStripeGatewayOptions, input: unknown): Promise<unknown>;
14
+ retrieveRefund(options: IStripeGatewayOptions, input: unknown): Promise<unknown>;
15
+ constructWebhookEvent(
16
+ options: IStripeGatewayOptions,
17
+ input: { rawBody?: string; headers: Record<string, string | string[] | undefined> },
18
+ ): Promise<unknown>;
19
+ }
20
+
21
+ export class StripeGatewayError extends Error {
22
+ constructor(
23
+ readonly status: number,
24
+ message: string,
25
+ ) {
26
+ super(message);
27
+ }
28
+ }
29
+
30
+ export function createStripeGateway(): IStripeGateway {
31
+ return {
32
+ async createCheckoutSession(options, input) {
33
+ const request = input as {
34
+ body: Stripe.Checkout.SessionCreateParams;
35
+ idempotencyKey: string;
36
+ };
37
+ return await createClient(options).checkout.sessions.create(request.body, {
38
+ idempotencyKey: request.idempotencyKey,
39
+ });
40
+ },
41
+ async retrieveCheckoutSession(options, input) {
42
+ const request = input as { id: string };
43
+ return await createClient(options).checkout.sessions.retrieve(request.id, {
44
+ expand: ['payment_intent'],
45
+ });
46
+ },
47
+ async createRefund(options, input) {
48
+ const request = input as { body: Stripe.RefundCreateParams; idempotencyKey: string };
49
+ return await createClient(options).refunds.create(request.body, {
50
+ idempotencyKey: request.idempotencyKey,
51
+ });
52
+ },
53
+ async retrieveRefund(options, input) {
54
+ const request = input as { id: string };
55
+ return await createClient(options).refunds.retrieve(request.id);
56
+ },
57
+ async constructWebhookEvent(options, input) {
58
+ const signature = getHeader(input.headers, 'stripe-signature');
59
+ if (!input.rawBody || !signature) {
60
+ throw new StripeGatewayError(401, 'Stripe webhook signature is invalid');
61
+ }
62
+ try {
63
+ return createClient(options).webhooks.constructEvent(
64
+ input.rawBody,
65
+ signature,
66
+ options.webhookSecret,
67
+ );
68
+ } catch {
69
+ throw new StripeGatewayError(401, 'Stripe webhook signature is invalid');
70
+ }
71
+ },
72
+ };
73
+ }
74
+
75
+ function createClient(options: IStripeGatewayOptions) {
76
+ return new Stripe(options.secretKey, {
77
+ maxNetworkRetries: 0,
78
+ timeout: StripeRequestTimeoutMilliseconds,
79
+ });
80
+ }
81
+
82
+ function getHeader(headers: Record<string, string | string[] | undefined>, key: string) {
83
+ const value = headers[key];
84
+ return Array.isArray(value) ? value[0] : value;
85
+ }
@@ -1,7 +1,9 @@
1
1
  declare module 'vona' {
2
2
  export interface VonaConfigEnv {
3
+ STRIPE_ENVIRONMENT: 'sandbox' | 'live' | undefined;
3
4
  STRIPE_SECRET_KEY: string | undefined;
4
5
  STRIPE_WEBHOOK_SECRET: string | undefined;
6
+ STRIPE_MERCHANT_REFERENCE: string | undefined;
5
7
  }
6
8
  }
7
9
 
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "vona-suite-a-pay",
3
- "version": "5.0.5",
3
+ "version": "5.0.6",
4
4
  "description": "",
5
5
  "author": "",
6
6
  "files": [],
7
7
  "type": "module",
8
8
  "dependencies": {
9
- "vona-module-a-pay": "^5.0.5",
10
- "vona-module-pay-mock": "^5.0.5",
11
- "vona-module-pay-paypal": "^5.0.5",
12
- "vona-module-pay-stripe": "^5.0.5"
9
+ "vona-module-a-pay": "^5.0.6",
10
+ "vona-module-pay-mock": "^5.0.6",
11
+ "vona-module-pay-paypal": "^5.0.6",
12
+ "vona-module-pay-stripe": "^5.0.6"
13
13
  },
14
14
  "title": "a-pay"
15
15
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-core",
3
- "version": "5.1.49",
3
+ "version": "5.1.50",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-permission",
3
- "version": "5.1.15",
3
+ "version": "5.1.16",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "keywords": [
@@ -153,6 +153,7 @@ export class BeanPermission extends BeanBase {
153
153
  const isAuthenticated = this.bean.passport.isAuthenticated;
154
154
  if (!options.public && !isAuthenticated) return false;
155
155
  if (!isAuthenticated) return true;
156
+ if (!this.bean.passport.isAccountActive) return false;
156
157
  if (options.activated === true && !this.bean.passport.isActivated) return false;
157
158
  if (options.activated === false && this.bean.passport.isActivated) return false;
158
159
  return true;