@owlmeans/payment 0.1.18-rc.9 → 0.1.18

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 (366) hide show
  1. package/README.md +95 -57
  2. package/agent-meta/manifest.json +10 -3
  3. package/agent-meta/skills/entitlements/SKILL.md +159 -0
  4. package/agent-meta/skills/payment/SKILL.md +491 -28
  5. package/build/advertise.d.ts +2 -0
  6. package/build/advertise.d.ts.map +1 -0
  7. package/build/advertise.js +10 -0
  8. package/build/advertise.js.map +1 -0
  9. package/build/checkout/consts.d.ts +2 -0
  10. package/build/checkout/consts.d.ts.map +1 -0
  11. package/build/checkout/consts.js +2 -0
  12. package/build/checkout/consts.js.map +1 -0
  13. package/build/checkout/index.d.ts +6 -0
  14. package/build/checkout/index.d.ts.map +1 -0
  15. package/build/checkout/index.js +4 -0
  16. package/build/checkout/index.js.map +1 -0
  17. package/build/checkout/narrow/types.d.ts +25 -0
  18. package/build/checkout/narrow/types.d.ts.map +1 -0
  19. package/build/checkout/narrow/types.js +2 -0
  20. package/build/checkout/narrow/types.js.map +1 -0
  21. package/build/checkout/narrow.d.ts +8 -0
  22. package/build/checkout/narrow.d.ts.map +1 -0
  23. package/build/checkout/narrow.js +47 -0
  24. package/build/checkout/narrow.js.map +1 -0
  25. package/build/checkout/protocols.d.ts +8 -0
  26. package/build/checkout/protocols.d.ts.map +1 -0
  27. package/build/checkout/protocols.js +28 -0
  28. package/build/checkout/protocols.js.map +1 -0
  29. package/build/checkout/types.d.ts +29 -0
  30. package/build/checkout/types.d.ts.map +1 -0
  31. package/build/checkout/types.js +2 -0
  32. package/build/checkout/types.js.map +1 -0
  33. package/build/consts.d.ts +242 -25
  34. package/build/consts.d.ts.map +1 -1
  35. package/build/consts.js +377 -24
  36. package/build/consts.js.map +1 -1
  37. package/build/consts.local.d.ts +20 -0
  38. package/build/consts.local.d.ts.map +1 -0
  39. package/build/consts.local.js +23 -0
  40. package/build/consts.local.js.map +1 -0
  41. package/build/consumer/cancel.d.ts +11 -0
  42. package/build/consumer/cancel.d.ts.map +1 -0
  43. package/build/consumer/cancel.js +33 -0
  44. package/build/consumer/cancel.js.map +1 -0
  45. package/build/consumer/consts.d.ts +20 -0
  46. package/build/consumer/consts.d.ts.map +1 -0
  47. package/build/consumer/consts.js +39 -0
  48. package/build/consumer/consts.js.map +1 -0
  49. package/build/consumer/consts.local.d.ts +7 -0
  50. package/build/consumer/consts.local.d.ts.map +1 -0
  51. package/build/consumer/consts.local.js +13 -0
  52. package/build/consumer/consts.local.js.map +1 -0
  53. package/build/consumer/copy/types.d.ts +56 -0
  54. package/build/consumer/copy/types.d.ts.map +1 -0
  55. package/build/consumer/copy/types.js +2 -0
  56. package/build/consumer/copy/types.js.map +1 -0
  57. package/build/consumer/copy.d.ts +22 -0
  58. package/build/consumer/copy.d.ts.map +1 -0
  59. package/build/consumer/copy.js +87 -0
  60. package/build/consumer/copy.js.map +1 -0
  61. package/build/consumer/deadline/types.d.ts +30 -0
  62. package/build/consumer/deadline/types.d.ts.map +1 -0
  63. package/build/consumer/deadline/types.js +2 -0
  64. package/build/consumer/deadline/types.js.map +1 -0
  65. package/build/consumer/deadline.d.ts +12 -0
  66. package/build/consumer/deadline.d.ts.map +1 -0
  67. package/build/consumer/deadline.js +44 -0
  68. package/build/consumer/deadline.js.map +1 -0
  69. package/build/consumer/fifo/types.d.ts +17 -0
  70. package/build/consumer/fifo/types.d.ts.map +1 -0
  71. package/build/consumer/fifo/types.js +2 -0
  72. package/build/consumer/fifo/types.js.map +1 -0
  73. package/build/consumer/fifo.d.ts +4 -0
  74. package/build/consumer/fifo.d.ts.map +1 -0
  75. package/build/consumer/fifo.js +34 -0
  76. package/build/consumer/fifo.js.map +1 -0
  77. package/build/consumer/index.d.ts +18 -0
  78. package/build/consumer/index.d.ts.map +1 -0
  79. package/build/consumer/index.js +11 -0
  80. package/build/consumer/index.js.map +1 -0
  81. package/build/consumer/policy/types.d.ts +20 -0
  82. package/build/consumer/policy/types.d.ts.map +1 -0
  83. package/build/consumer/policy/types.js +2 -0
  84. package/build/consumer/policy/types.js.map +1 -0
  85. package/build/consumer/policy.d.ts +17 -0
  86. package/build/consumer/policy.d.ts.map +1 -0
  87. package/build/consumer/policy.js +91 -0
  88. package/build/consumer/policy.js.map +1 -0
  89. package/build/consumer/protocols.d.ts +20 -0
  90. package/build/consumer/protocols.d.ts.map +1 -0
  91. package/build/consumer/protocols.js +70 -0
  92. package/build/consumer/protocols.js.map +1 -0
  93. package/build/consumer/refund/types.d.ts +27 -0
  94. package/build/consumer/refund/types.d.ts.map +1 -0
  95. package/build/consumer/refund/types.js +2 -0
  96. package/build/consumer/refund/types.js.map +1 -0
  97. package/build/consumer/refund.d.ts +9 -0
  98. package/build/consumer/refund.d.ts.map +1 -0
  99. package/build/consumer/refund.js +112 -0
  100. package/build/consumer/refund.js.map +1 -0
  101. package/build/consumer/refusal.d.ts +10 -0
  102. package/build/consumer/refusal.d.ts.map +1 -0
  103. package/build/consumer/refusal.js +35 -0
  104. package/build/consumer/refusal.js.map +1 -0
  105. package/build/consumer/revive/types.d.ts +19 -0
  106. package/build/consumer/revive/types.d.ts.map +1 -0
  107. package/build/consumer/revive/types.js +2 -0
  108. package/build/consumer/revive/types.js.map +1 -0
  109. package/build/consumer/revive.d.ts +13 -0
  110. package/build/consumer/revive.d.ts.map +1 -0
  111. package/build/consumer/revive.js +43 -0
  112. package/build/consumer/revive.js.map +1 -0
  113. package/build/consumer/types.d.ts +196 -0
  114. package/build/consumer/types.d.ts.map +1 -0
  115. package/build/consumer/types.js +2 -0
  116. package/build/consumer/types.js.map +1 -0
  117. package/build/consumer/types.local.d.ts +11 -0
  118. package/build/consumer/types.local.d.ts.map +1 -0
  119. package/build/consumer/types.local.js +2 -0
  120. package/build/consumer/types.local.js.map +1 -0
  121. package/build/countries.d.ts +3 -0
  122. package/build/countries.d.ts.map +1 -0
  123. package/build/countries.js +4 -0
  124. package/build/countries.js.map +1 -0
  125. package/build/entitled.d.ts +14 -0
  126. package/build/entitled.d.ts.map +1 -0
  127. package/build/entitled.js +17 -0
  128. package/build/entitled.js.map +1 -0
  129. package/build/entitlement/types.d.ts +29 -0
  130. package/build/entitlement/types.d.ts.map +1 -0
  131. package/build/entitlement/types.js +2 -0
  132. package/build/entitlement/types.js.map +1 -0
  133. package/build/entitlement.d.ts +7 -55
  134. package/build/entitlement.d.ts.map +1 -1
  135. package/build/entitlement.js +55 -86
  136. package/build/entitlement.js.map +1 -1
  137. package/build/errors.d.ts +189 -0
  138. package/build/errors.d.ts.map +1 -1
  139. package/build/errors.js +361 -0
  140. package/build/errors.js.map +1 -1
  141. package/build/estimate/types.d.ts +34 -0
  142. package/build/estimate/types.d.ts.map +1 -0
  143. package/build/estimate/types.js +2 -0
  144. package/build/estimate/types.js.map +1 -0
  145. package/build/estimate.d.ts +11 -0
  146. package/build/estimate.d.ts.map +1 -0
  147. package/build/estimate.js +76 -0
  148. package/build/estimate.js.map +1 -0
  149. package/build/i18n/consumer-rights/be.json +131 -0
  150. package/build/i18n/consumer-rights/de.json +131 -0
  151. package/build/i18n/consumer-rights/en.json +131 -0
  152. package/build/i18n/consumer-rights/es.json +131 -0
  153. package/build/i18n/consumer-rights/fr.json +131 -0
  154. package/build/i18n/consumer-rights/pl.json +131 -0
  155. package/build/i18n/consumer-rights/ru.json +131 -0
  156. package/build/i18n/consumer-rights/uk.json +131 -0
  157. package/build/i18n/errors/be.json +30 -0
  158. package/build/i18n/errors/de.json +30 -0
  159. package/build/i18n/errors/en.json +30 -0
  160. package/build/i18n/errors/es.json +30 -0
  161. package/build/i18n/errors/fr.json +30 -0
  162. package/build/i18n/errors/pl.json +30 -0
  163. package/build/i18n/errors/ru.json +30 -0
  164. package/build/i18n/errors/uk.json +30 -0
  165. package/build/i18n/fr.json +39 -0
  166. package/build/i18n.js +53 -8
  167. package/build/i18n.js.map +1 -1
  168. package/build/index.d.ts +18 -1
  169. package/build/index.d.ts.map +1 -1
  170. package/build/index.js +11 -1
  171. package/build/index.js.map +1 -1
  172. package/build/limit/types.d.ts +44 -0
  173. package/build/limit/types.d.ts.map +1 -0
  174. package/build/limit/types.js +2 -0
  175. package/build/limit/types.js.map +1 -0
  176. package/build/limit.d.ts +23 -0
  177. package/build/limit.d.ts.map +1 -0
  178. package/build/limit.js +97 -0
  179. package/build/limit.js.map +1 -0
  180. package/build/model/checkout.d.ts +2 -3
  181. package/build/model/checkout.d.ts.map +1 -1
  182. package/build/model/checkout.js +12 -7
  183. package/build/model/checkout.js.map +1 -1
  184. package/build/model/consts.d.ts +15 -0
  185. package/build/model/consts.d.ts.map +1 -0
  186. package/build/model/consts.js +193 -0
  187. package/build/model/consts.js.map +1 -0
  188. package/build/model/consts.local.d.ts +35 -0
  189. package/build/model/consts.local.d.ts.map +1 -0
  190. package/build/model/consts.local.js +65 -0
  191. package/build/model/consts.local.js.map +1 -0
  192. package/build/model/consumer.d.ts +23 -0
  193. package/build/model/consumer.d.ts.map +1 -0
  194. package/build/model/consumer.js +272 -0
  195. package/build/model/consumer.js.map +1 -0
  196. package/build/model/estimate.d.ts +5 -0
  197. package/build/model/estimate.d.ts.map +1 -0
  198. package/build/model/estimate.js +69 -0
  199. package/build/model/estimate.js.map +1 -0
  200. package/build/model/index.d.ts +7 -2
  201. package/build/model/index.d.ts.map +1 -1
  202. package/build/model/index.js +7 -2
  203. package/build/model/index.js.map +1 -1
  204. package/build/model/limit.d.ts +7 -0
  205. package/build/model/limit.d.ts.map +1 -0
  206. package/build/model/limit.js +32 -0
  207. package/build/model/limit.js.map +1 -0
  208. package/build/model/plan.d.ts.map +1 -1
  209. package/build/model/plan.js +24 -11
  210. package/build/model/plan.js.map +1 -1
  211. package/build/model/portal.d.ts +4 -0
  212. package/build/model/portal.d.ts.map +1 -0
  213. package/build/model/portal.js +22 -0
  214. package/build/model/portal.js.map +1 -0
  215. package/build/model/pricing.d.ts +4 -0
  216. package/build/model/pricing.d.ts.map +1 -0
  217. package/build/model/pricing.js +31 -0
  218. package/build/model/pricing.js.map +1 -0
  219. package/build/model/view.d.ts +3 -0
  220. package/build/model/view.d.ts.map +1 -0
  221. package/build/model/view.js +15 -0
  222. package/build/model/view.js.map +1 -0
  223. package/build/pricing/types.d.ts +13 -0
  224. package/build/pricing/types.d.ts.map +1 -0
  225. package/build/pricing/types.js +2 -0
  226. package/build/pricing/types.js.map +1 -0
  227. package/build/pricing.d.ts +13 -0
  228. package/build/pricing.d.ts.map +1 -0
  229. package/build/pricing.js +74 -0
  230. package/build/pricing.js.map +1 -0
  231. package/build/promo/types.d.ts +14 -0
  232. package/build/promo/types.d.ts.map +1 -0
  233. package/build/promo/types.js +2 -0
  234. package/build/promo/types.js.map +1 -0
  235. package/build/promo.d.ts +7 -0
  236. package/build/promo.d.ts.map +1 -0
  237. package/build/promo.js +25 -0
  238. package/build/promo.js.map +1 -0
  239. package/build/regions/types.d.ts +32 -0
  240. package/build/regions/types.d.ts.map +1 -0
  241. package/build/regions/types.js +2 -0
  242. package/build/regions/types.js.map +1 -0
  243. package/build/regions.d.ts +15 -0
  244. package/build/regions.d.ts.map +1 -0
  245. package/build/regions.js +43 -0
  246. package/build/regions.js.map +1 -0
  247. package/build/service.d.ts.map +1 -1
  248. package/build/service.js +24 -9
  249. package/build/service.js.map +1 -1
  250. package/build/types.d.ts +619 -52
  251. package/build/types.d.ts.map +1 -1
  252. package/build/types.local.d.ts +4 -0
  253. package/build/types.local.d.ts.map +1 -0
  254. package/build/types.local.js +2 -0
  255. package/build/types.local.js.map +1 -0
  256. package/build/view/types.d.ts +33 -0
  257. package/build/view/types.d.ts.map +1 -0
  258. package/build/view/types.js +2 -0
  259. package/build/view/types.js.map +1 -0
  260. package/build/view.d.ts +13 -0
  261. package/build/view.d.ts.map +1 -0
  262. package/build/view.js +78 -0
  263. package/build/view.js.map +1 -0
  264. package/package.json +14 -12
  265. package/src/advertise.ts +13 -0
  266. package/src/checkout/consts.ts +1 -0
  267. package/src/checkout/index.ts +5 -0
  268. package/src/checkout/narrow/types.ts +27 -0
  269. package/src/checkout/narrow.ts +63 -0
  270. package/src/checkout/protocols.ts +39 -0
  271. package/src/checkout/types.ts +27 -0
  272. package/src/consts.local.ts +34 -0
  273. package/src/consts.ts +417 -24
  274. package/src/consumer/cancel.ts +40 -0
  275. package/src/consumer/consts.local.ts +16 -0
  276. package/src/consumer/consts.ts +46 -0
  277. package/src/consumer/copy/types.ts +54 -0
  278. package/src/consumer/copy.ts +121 -0
  279. package/src/consumer/deadline/types.ts +30 -0
  280. package/src/consumer/deadline.ts +61 -0
  281. package/src/consumer/fifo/types.ts +17 -0
  282. package/src/consumer/fifo.ts +42 -0
  283. package/src/consumer/index.ts +17 -0
  284. package/src/consumer/policy/types.ts +20 -0
  285. package/src/consumer/policy.ts +101 -0
  286. package/src/consumer/protocols.ts +116 -0
  287. package/src/consumer/refund/types.ts +27 -0
  288. package/src/consumer/refund.ts +136 -0
  289. package/src/consumer/refusal.ts +35 -0
  290. package/src/consumer/revive/types.ts +23 -0
  291. package/src/consumer/revive.ts +82 -0
  292. package/src/consumer/types.local.ts +10 -0
  293. package/src/consumer/types.ts +189 -0
  294. package/src/countries.ts +5 -0
  295. package/src/entitled.ts +20 -0
  296. package/src/entitlement/types.ts +29 -0
  297. package/src/entitlement.ts +67 -103
  298. package/src/errors.ts +445 -0
  299. package/src/estimate/types.ts +34 -0
  300. package/src/estimate.ts +90 -0
  301. package/src/i18n/consumer-rights/be.json +131 -0
  302. package/src/i18n/consumer-rights/de.json +131 -0
  303. package/src/i18n/consumer-rights/en.json +131 -0
  304. package/src/i18n/consumer-rights/es.json +131 -0
  305. package/src/i18n/consumer-rights/fr.json +131 -0
  306. package/src/i18n/consumer-rights/pl.json +131 -0
  307. package/src/i18n/consumer-rights/ru.json +131 -0
  308. package/src/i18n/consumer-rights/uk.json +131 -0
  309. package/src/i18n/errors/be.json +30 -0
  310. package/src/i18n/errors/de.json +30 -0
  311. package/src/i18n/errors/en.json +30 -0
  312. package/src/i18n/errors/es.json +30 -0
  313. package/src/i18n/errors/fr.json +30 -0
  314. package/src/i18n/errors/pl.json +30 -0
  315. package/src/i18n/errors/ru.json +30 -0
  316. package/src/i18n/errors/uk.json +30 -0
  317. package/src/i18n/fr.json +39 -0
  318. package/src/i18n.ts +58 -8
  319. package/src/index.ts +18 -2
  320. package/src/limit/types.ts +41 -0
  321. package/src/limit.ts +132 -0
  322. package/src/model/checkout.ts +12 -7
  323. package/src/model/consts.local.ts +83 -0
  324. package/src/model/consts.ts +206 -0
  325. package/src/model/consumer.ts +294 -0
  326. package/src/model/estimate.ts +73 -0
  327. package/src/model/index.ts +7 -2
  328. package/src/model/limit.ts +36 -0
  329. package/src/model/plan.ts +25 -12
  330. package/src/model/portal.ts +25 -0
  331. package/src/model/pricing.ts +34 -0
  332. package/src/model/view.ts +17 -0
  333. package/src/pricing/types.ts +13 -0
  334. package/src/pricing.ts +110 -0
  335. package/src/promo/types.ts +18 -0
  336. package/src/promo.ts +46 -0
  337. package/src/regions/types.ts +42 -0
  338. package/src/regions.ts +85 -0
  339. package/src/service.ts +32 -15
  340. package/src/types.local.ts +5 -0
  341. package/src/types.ts +669 -49
  342. package/src/view/types.ts +39 -0
  343. package/src/view.ts +119 -0
  344. package/tests/checkout-narrow.spec.ts +75 -0
  345. package/tests/consumer-copy.spec.ts +378 -0
  346. package/tests/consumer-deadline.spec.ts +85 -0
  347. package/tests/consumer-fifo.spec.ts +61 -0
  348. package/tests/consumer-protocols.spec.ts +103 -0
  349. package/tests/consumer-refund.spec.ts +134 -0
  350. package/tests/consumer-regions.spec.ts +140 -0
  351. package/tests/consumer-wire.spec.ts +170 -0
  352. package/tests/contract.spec.ts +183 -0
  353. package/tests/entitlement.spec.ts +38 -25
  354. package/tests/errors.spec.ts +168 -0
  355. package/tests/estimate.spec.ts +139 -0
  356. package/tests/limit.spec.ts +117 -0
  357. package/tests/pricing.spec.ts +30 -0
  358. package/tests/view.spec.ts +130 -0
  359. package/build/.gitkeep +0 -0
  360. package/src/model/subscription.ts +0 -63
  361. package/src/model/utils.ts +0 -32
  362. package/src/modules.ts +0 -43
  363. package/tests/entitlement.spec.d.ts +0 -2
  364. package/tests/entitlement.spec.d.ts.map +0 -1
  365. package/tests/entitlement.spec.js +0 -72
  366. package/tests/entitlement.spec.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: payment
3
- description: How to use @owlmeans/payment — payment processing types, models, errors, paymentApi service alias for cross-service checkout calls. Auto-invoked when importing payment types or invoking checkout flows.
3
+ description: "How to use @owlmeans/payment — provider-agnostic payment contracts, immutable payment protocols, amount/quantity checkout policies, per-entity checkout narrowing and pricing, catalogue records, entitlement gates, and the EU consumer-rights contracts (billing regions, the policy record, withdrawal/cancellation views, refund calculators, 428/409 refusals, the legal copy, protocol factories). Auto-invoked when importing payment types or errors, declaring paid routes, creating checkout, or touching consent, withdrawal or cancellation."
4
4
  user-invocable: false
5
5
  ---
6
6
  <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
@@ -8,44 +8,507 @@ user-invocable: false
8
8
  # @owlmeans/payment
9
9
 
10
10
  **Layer:** Core
11
- **Install:** `"@owlmeans/payment": "^0.1.18-rc.8"` in `dependencies`
11
+ **Install:** `"@owlmeans/payment": "^0.1.18"` in `dependencies`
12
12
 
13
- ## Key Exports
13
+ The contracts half of payments: the catalogue (products, plans, localizations), amount- and
14
+ quantity-priced checkout, the entitlement model — plan capabilities, counted limits, promos,
15
+ and the entitlement view the server gate and the browser both read — and the EU consumer-rights
16
+ contracts (billing regions, the policy record, withdrawal and cancellation views, the refund
17
+ calculators, the refusals, the legal copy, the protocol factories). It talks to no paygate: a
18
+ server-side integration (`@owlmeans/server-payment`) implements against these, and
19
+ `@owlmeans/client-payment` adapts the catalogue service for a browser. The model as a whole —
20
+ admission, the usage ledger, the two gate services — is the `entitlements` skill.
21
+
22
+ ## Key exports
14
23
 
15
24
  | Export | Description |
16
- |--------|-------------|
17
- | `paymentApi` | Service alias tree for payment endpoints (e.g. `paymentApi.service.checkout.session.external.create`) |
18
- | `Payment`, `Checkout` types | Domain model shapes |
19
- | `paymentModules` | Entrypoint declarations to spread into `appModules` |
20
- | Errors | Typed payment errors |
21
- | Constants | Payment service aliases |
22
- | `model` submodule | Reusable payment models |
23
- | i18n | Translatable error messages |
25
+ |---|---|
26
+ | `makePaymentService(alias?)` · `appendPaymentService(ctx, alias?)` | The catalogue reader, registered under `DEFAULT_ALIAS` / `PAYMENT_SERVICE` (`'payment'`). |
27
+ | `PaymentService` | `product(sku)` · `products()` · `plans(productSku, duration)` · `plan(planSku)` · `allPlans(productSku)` · `localize(lng, entity)` · `shallowAuthentication(token)` · `pricingPolicy()` · `consumerRightsPolicy()` (`null` when undeclared). |
28
+ | `Product` · `ProductPlan` · `Localization` (+ schemas) | Catalogue records. `ProductPlan` carries `rank`, `free`, `gateways`, `suspendedAt`, `capabilities: PlanCapability[]`, `limits: { [key]: LimitDeclaration }`, `withdrawal.components` (§ Consumer rights). |
29
+ | `PlanCapability` · `LimitDeclaration` · `PromoDeclaration` (+ schemas) | What a plan grants: a permission set, a counted allowance, a time box on either. |
30
+ | `SubscriptionStatus` · `ENTITLING_STATUSES` · `TERMINAL_STATUSES` · `INTERNAL_PAYGATE` | Lifecycle vocabulary. `Active`/`Trial`/`PastDue` entitle; `Canceled`/`Expired`/`Ended`/`Blocked` are terminal; `Suspended` is revoked until resumed. |
31
+ | `LimitKind` · `LimitWindow` · `PortalFlow` (+ schemas) | `window`/`lifetime`/`occupancy`; `day`/`month`; `manage`/`cancel`/`update`/`change`/`payment-method`. |
32
+ | `ENTITLEMENT_GATE` · `LIMIT_GATE` · `CAPABILITY_FEATURE_SCOPE` · `CAPABILITY_LIMIT_SCOPE` | The two gate aliases and the two reserved scopes (`feature` for flags, `limit` for limit parameters). |
33
+ | `entitled` · `entitlementParamHelper` (`parseEntitlementParam` · `formatEntitlementParam` · `hasEntitlement` · `entitlementList`) | The capability grammar and predicate. |
34
+ | `planLimitHelper` (`parseLimitParam` · `formatLimitParam` · `windowKeyOf` · `windowBoundsOf`) · `LIFETIME_WINDOW` · `OCCUPANCY_WINDOW` | The limit grammar and window algebra. |
35
+ | `promoHelper` (`promoActive` · `promoViewOf`) | Whether a promo is in force for one subscription at one instant. |
36
+ | `EntitlementView` · `EntitlementPlanView` · `CapabilityView` · `LimitView` · `PromoView` · `LimitUsage` (+ wire schemas) | The entitlement view and its rows. |
37
+ | `entitlementViewHelper` (`capabilityViewsOf` · `limitViewsOf` · `entitlementViewOf` · `reviveEntitlementView`) · `planLimitHelper` (`capabilityOf` · `limitOf` · `hasLimitRoom`) | Pure view builders and readers. |
38
+ | `CreateCheckoutBody` (`planSku`, `country`, `startRequestId`) · `CreateCheckoutResponse` · `PortalLinkBody` · `PortalLinkResponse` (+ schemas) | Wire shapes an application's own checkout and portal protocols use. |
39
+ | `CheckoutPricingMode` · `AmountCheckoutPolicy` / `QuantityCheckoutPolicy` (+ schemas) · `checkoutPricingHelper` (`assertAmountCheckoutPolicy` · `assertQuantityCheckoutPolicy` · `assertCheckoutAmount` · `chargeAmountMinor`) | Checkout pricing policies and their validation. |
40
+ | `PricingPolicy` (+ schema) · `DEFAULT_PRICING_POLICY` · `priceEstimateHelper.assertPricingPolicy` · `TaxBehavior` · `TaxEstimateStatus` · `TaxType` (+ schemas) | Declared tax/currency behaviour (see § Pricing policy and tax estimate). |
41
+ | `PriceEstimate` · `PriceEstimateBody` · `TaxEstimate` · `TaxRateEstimate` (+ schemas) · `priceEstimateHelper` (`estimateOf` · `ratePpmOf`) | The tax/local-currency estimate read model and its pure math. |
42
+ | `COUNTRY_CURRENCIES` · `currencyOfCountry` · `COUNTRY_CODES` · `CountrySchema` | ISO 3166-1 → ISO 4217 reference map for a country picker. |
43
+ | `EntitlementRefusal` · `CapabilityRequired` · `LimitExhausted` | Entitlement refusals — all `AuthForbidden` (403). |
44
+ | `PaymentError` · `PaygateError` · `UnknownPaygate` · `PaygateMappingError` · `WebhookSetupError` · `PortalUnavailable` · `ProductError` · `UnknownProduct` · `UnknownPlan` · `PlanRequired` · `PlanRankConflict` · `LimitUnknown` · `LimitMisdeclared` · `PaymentIdentificationError` · `SubscriptionError` · `UnknownSubscription` · `ConsumerRightsError` | Faults (500), except `PortalUnavailable` (409). Importing the package registers every type's message under `errors.<type>` in eight languages (the seven of `SUPPORTED_LNGS` plus `fr`). |
45
+ | `ConsumerRightsRefusal` · `PerformanceConsentRequired` · `SubscriptionStartRequired` (428) · `BillingCountryLocked` · `WithdrawalUnavailable` · `CancellationUnavailable` · `CheckoutLimitExceeded` (409) · `consentRefusalOf` | Consumer-rights and checkout-limit refusals — declared statuses, never `AuthForbidden` (§ Consumer rights). |
46
+ | `ConsumerRegion` · `PurchaseKind` · `ConsentKind` · `DeclarationKind` · `DeclarationChannel` · `CancellationKind` · `WithdrawalStatus` · `CancellationStatus` · `WithdrawalUnavailableReason` · `CancellationUnavailableReason` (+ schemas) | Consumer-rights vocabulary. |
47
+ | `EU_COUNTRIES` · `EU_CONSUMER_TERRITORIES` · `EEA_EXTRA` · `CONSUMER_RIGHTS_TERRITORIES` · `COUNTRY_LANGUAGES` · `consumerRegionHelper` (`isEuCountry` · `isEeaCountry` · `regionOf` · `inScope` · `chargeCurrencyOf` · `billingLanguageOf`) | Territories, region, scope, charge currency, legal language. |
48
+ | `ConsumerRightsPolicy` (+ `Links`, `Mechanisms`, schemas) · `DEFAULT_CONSUMER_RIGHTS` · `makeConsumerRightsPolicy` · `consumerRightsPolicyHelper` (`assertConsumerRightsPolicy` · `linksOf`) · `CONSUMER_RIGHTS_RECORD_TYPE`/`_ID` | The consumer-rights policy record. |
49
+ | `BillingProfileView` · `PurchaseView`/`List` · `PerformanceConsentView`/`Body`/`Response` · `SubscriptionStartView`/`Query`/`Body`/`Response` · `WithdrawalEstimate` · `WithdrawalCandidate`/`List` · `WithdrawalBody` · `DeclarationReceipt` · `WithdrawalReceipt` · `CancellationBody` · `CancellationReceipt` · `ConsumerRightsPublicView` (+ schemas) · `consumerReviveHelper.revive*` | Consumer-rights wire shapes (ISO dates) and their revivers. |
50
+ | `withdrawalDeadlineHelper` (`withdrawalDeadlineOf` · `lastWithdrawalDayOf` · `withdrawalOpen`) · `withdrawalRefundHelper` (`oneTimeWithdrawalRefund` · `subscriptionWithdrawalRefund` · `splitByShares`) · `fifoHelper` (`allocateFifo` · `unitsUsedAfter`) · `cancellationEffectiveAt` | Pure calculators. |
51
+ | `CONSUMER_RIGHTS_RESOURCE` · `CONSUMER_RIGHTS_COPY_VERSION` · `consumerCopyHelper` (`consumerRightsCopy` · `consumerText` · `consentStatementOf` · `startContextOf` · `legalLabelsOf` · `placeholdersOf`) | The legal copy and the start statement's variant. |
52
+ | `makeConsumerRightsProtocols` · `makeCheckoutReadProtocols` | Protocol factories. |
53
+ | `AmountNarrowing` · `amountNarrowingHelper` (`narrowAmountPolicy` · `amountAllowed`) · `CheckoutLimitView` · `AmountPolicyView`/`Query` · `PlanPriceView`/`List`/`PlanPricesQuery` (+ schemas) | Per-entity narrowing of an amount checkout, and synced plan prices. |
54
+
55
+ Subpath: `./utils` — the `Config` / `Context` aliases to type your own context against.
56
+
57
+ ## The catalogue is config, not a table
58
+
59
+ `PaymentService` reads everything through the context's config resource, so a deployment ships its
60
+ catalogue the same way it ships any other configuration:
61
+
62
+ | Record | Id | Type |
63
+ |---|---|---|
64
+ | product | `product:<sku>` | `PRODUCT_RECORD_TYPE` |
65
+ | plan | `plan:<planSku>` | `PLAN_RECORD_TYPE` |
66
+ | localization | `l10nToId(l10n, true)` → `l10n:<entityType>:<sku>:<lng>` | `L10N_RECORD_TYPE` |
67
+
68
+ `product` and `plan` read the record with the config resource's `get`, which **throws
69
+ `UnknownRecordError`** (`@owlmeans/resource`) when the id is absent rather than returning null — so
70
+ that, not `UnknownProduct` / `UnknownPlan`, is what a checkout for a sku outside the catalogue
71
+ raises. `localize` is the opposite: it reads with `load`, tries the asked-for language, falls back
72
+ to the default one, and returns `null` when neither exists, because a missing translation is a
73
+ display problem and never a reason to fail a call.
74
+
75
+ ## Declaring plans
76
+
77
+ - **Every plan of a product has a `rank`; a higher rank is an upgrade.** An absent rank reads as
78
+ `0`. Plan changes are classified by rank, and the effective plan of an entity is its
79
+ highest-ranked entitling subscription — two plans of one product at one rank make both
80
+ questions unanswerable.
81
+ - **The free tier is a real plan**: `free: true`, `price: 0`, `gateways: []`, usually `rank: 0`.
82
+ Every entity holds some plan, so a gate never has to special-case "no subscription".
83
+ - **`gateways`** names the paygates a plan is sold through; a free plan has none.
84
+ - **`status` is a `PlanStatus`** (active, hidden, archived …) — the catalogue state of the plan,
85
+ never a subscription's lifecycle.
86
+ - Plan records use the record date convention (`DateSchema` from `@owlmeans/auth`, a `Date`
87
+ object); wire shapes do not (below).
88
+
89
+ ## Capabilities and limits are different things
90
+
91
+ A **capability** is granted: a permission in a `PlanCapability` set, asked for as
92
+
93
+ [<scope>:]<permission>[>=<n>]
94
+
95
+ `feature:branding--whitelabel` · `renewable:credits>=100` · `production--standalone`
96
+
97
+ A **limit** is counted: a `LimitDeclaration` under a key in `plan.limits`, asked for as
98
+
99
+ limit:<key>[>=<n>]
100
+
101
+ - **`limit` is a reserved scope.** A plan never declares a capability set under it,
102
+ `entitlementParamHelper.hasEntitlement` answers `false` for any `limit:` parameter, and the view
103
+ builders skip such a set. A limit requirement can therefore never be satisfied by a capability
104
+ grant.
105
+ - **One grammar, two gate services.** Capability parameters go to `ENTITLEMENT_GATE`
106
+ (`entitled(params)` is sugar for it), limit parameters to `LIMIT_GATE`
107
+ (`{ gate: { alias: LIMIT_GATE, params: [planLimitHelper.formatLimitParam(key)] } }`). The aliases
108
+ differ because the framework collects an entrypoint's gates per gate service — one alias would let
109
+ one requirement hide the other.
110
+ - **`@` is deliberately not part of the grammar.** That is `@owlmeans/iam`'s resource-selector
111
+ syntax; reusing it would make two different things look identical in a route declaration.
112
+ - **Keep flags, magnitudes and counts apart.** `CAPABILITY_FEATURE_SCOPE` carries booleans; a
113
+ granted magnitude nothing spends (`renewable:credits>=100`) lives under its own scope; anything
114
+ spent against a ceiling is a limit. Merged, the purchase that spends a quota takes the feature
115
+ with it.
116
+ - **A floor is a numeric question.** `>=n` passes only for a number at least that big — a boolean
117
+ flag, however true, does not answer it. For a limit, `>=n` asks for `n` units of room.
118
+ - **A malformed parameter answers `false` / `null`, it never throws.**
119
+ `entitlementParamHelper.hasEntitlement` refuses, `planLimitHelper.parseLimitParam` returns `null`
120
+ for a capability parameter, a bare key, an empty key or a floor that is not positive. A gate that
121
+ crashed on a typo would take down the endpoint it guards.
122
+ - **Declare the requirement on the protocol, not in the handler.** The framework asserts gates
123
+ before the handler is entered, so the route table states what a feature costs. Several
124
+ parameters of one gate are OR'd. The gate services themselves live in the server integration.
125
+
126
+ ## The three limit kinds
24
127
 
25
- ## Subpath Exports
128
+ | Kind | Window key (`planLimitHelper.windowKeyOf`) | Renews | Typical use |
129
+ |---|---|---|---|
130
+ | `window` + `day` | `YYYY-MM-DD` | at UTC midnight | per-day actions |
131
+ | `window` + `month` | `YYYY-MM` | on the 1st, UTC | per-month actions |
132
+ | `lifetime` | `lifetime` | never — the count belongs to the entity and survives plan changes | "N ever" |
133
+ | `occupancy` | `occupancy` | never — held `+1` / released `-1`, reconciled against reality | "N at once" |
26
134
 
27
- - `./utils` — payment helpers
135
+ - Windows are **calendar UTC**, never rolling and never local: two servers in two zones must agree
136
+ on which counter a unit lands in.
137
+ - `planLimitHelper.windowBoundsOf(window, at)` returns `start` inclusive and `resetsAt`
138
+ **exclusive** — `resetsAt` is the first instant of the next window and already belongs to it.
139
+ - A `window` limit without a known `window` (or an unknown kind) throws `LimitMisdeclared`: a
140
+ misdeclared plan is loud, not a silently unlimited one.
141
+ - `limit: 0` means "not included" — a declared key with no room, which a UI renders as such.
28
142
 
29
- ## Usage
143
+ ## Promos
30
144
 
31
- Spread the entrypoints into your app, then call them via `ctx.entrypoint(paymentApi....)`:
145
+ `promo: { until: Date, grandfather?: boolean }` on a capability set or a limit.
32
146
 
33
- ```typescript
34
- import { paymentApi, paymentModules } from '@owlmeans/payment'
35
- import type { CreateCheckoutResponse } from '@owlmeans/payment'
36
- import type { ClientEntrypoint } from '@owlmeans/entrypoint'
147
+ - In force while `at < until`. **`at === until` is over.**
148
+ - With `grandfather: true`, also in force for a subscription created before `until`, forever.
149
+ - A lapsed promo makes a capability `granted: false` and a limit's `limit: 0` — the row stays in
150
+ the view with `promo.active: false`, so a UI can say the promotion ended rather than pretend the
151
+ feature never existed.
152
+ - `promoHelper.promoViewOf` → `{ until, grandfathered, active }` is what a UI inscribes: active and
153
+ not grandfathered ⇒ "free until"; grandfathered ⇒ "kept for your plan"; inactive ⇒ "ended".
154
+ - An unparseable `until` is never in force.
37
155
 
38
- export const appModules = [...modules, ...paymentModules]
156
+ ## The entitlement view
157
+
158
+ `entitlementViewHelper.entitlementViewOf(plan, planView, usage, at?)` is the one read of what an
159
+ entity may do:
160
+
161
+ - `plan` — the `EntitlementPlanView` the caller resolved (sku, rank, free, status, paygate,
162
+ period, trial, cancel-at-period-end, past-due, fallback). **Promos are measured against
163
+ `planView.subscribedAt`** — set it to the subscription's creation date, or nothing is
164
+ grandfathered.
165
+ - `capabilities` — its `capabilityViewsOf`: one row per non-null, non-false permission, `param`
166
+ exactly as the capability gate takes it.
167
+ - `limits` — its `limitViewsOf`: one row per declared key; `used` from the `LimitUsage` row of the
168
+ CURRENT window (none ⇒ `0`); `remaining = max(0, limit - used)`; `windowStart`/`resetsAt` only
169
+ for a window limit.
170
+ - Read it with `planLimitHelper.capabilityOf(view, param)` (the same predicate as the gate, over
171
+ granted rows), `planLimitHelper.limitOf(view, key)` and
172
+ `planLimitHelper.hasLimitRoom(limitView, atLeast)`. The server gate and the browser call the same
173
+ functions, so the UI's "disabled" and the server's refusal cannot disagree.
174
+ - **The wire carries dates as ISO strings.** The view schemas describe that
175
+ (`{ type: 'string', format: 'date-time' }`): a response serializer writes a `Date` through it as
176
+ ISO, while the record convention (`DateSchema`, an object) would serialize it as `{}`. A browser
177
+ calls `entitlementViewHelper.reviveEntitlementView` before touching a date.
178
+
179
+ ## Refusals and faults
180
+
181
+ - **A refusal extends `AuthForbidden`** (through `EntitlementRefusal`), so an HTTP boundary answers
182
+ 403.
183
+ - `CapabilityRequired(params)` — message marker `capability-required:<a|b>`, field `params`.
184
+ - `LimitExhausted({ key, used, limit, resetsAt? })` — marker
185
+ `limit-exhausted:<key>:<used>/<limit>[:<ISO>]`, fields `limitKey`, `used`, `limit`,
186
+ `resetsAt`.
187
+ - **Only `type` and `message` survive a marshal.** Both refusals pack their fields into the
188
+ message and rebuild them in `finalizeUnmarshal()`; catch the class after
189
+ `ResilientError.ensure`, never parse the text yourself.
190
+ - **Consumer-rights and checkout-limit refusals are not entitlement refusals**: they declare
191
+ 428/409 and never extend `AuthForbidden` (§ Consumer rights).
192
+ - **Faults are not refusals**: `LimitUnknown` (a key no plan declares), `LimitMisdeclared`,
193
+ `PlanRequired` (no plan resolvable, not even a free one), `PlanRankConflict`,
194
+ `WebhookSetupError` are `PaymentError`s — a configuration or setup problem, not the user's plan
195
+ saying no — and declare no status, so an HTTP boundary answers 500.
196
+ - **`PortalUnavailable` is the entity's state, not a fault**: no paygate customer, no entitling
197
+ subscription, no plan or item for the flow. It is a `PaymentError` declaring
198
+ `static httpStatus = 409`, which `@owlmeans/server-api` answers as 409 Conflict. A new payment
199
+ error that is the caller's condition declares its 4xx the same way; one that is a fault declares
200
+ nothing.
201
+ - A UI phrases any of them from `errors.<type>`; the package registers those messages.
202
+
203
+ ## Checkout
204
+
205
+ `CreateCheckoutBody` is the wire body of a checkout: `productSku`, `planSku` for a subscription,
206
+ `entitySlug` (the only organization value on a public body — a server resolves the stable
207
+ `entityId` before persisting anything), `service`, `amountMinor` for an amount checkout, return
208
+ URLs, `country` (the declared billing country; a locked profile overrides it) and
209
+ `startRequestId` (the subscription start request recorded just before). The package declares no
210
+ fixed checkout protocols: an application declares its own checkout and portal protocols over
211
+ `CreateCheckoutBodySchema` / `PortalLinkBodySchema`; `PortalLinkBody.flow` picks the portal flow
212
+ and `planSku` names the target of a `change`. The read side and the consumer-rights surface come
213
+ as factories (§ Protocol factories).
214
+
215
+ ### Amount checkout
216
+
217
+ `amountMinor` is the net value credited to the customer, in integer currency minor units. The
218
+ policy bounds that value; fees and tax do not enlarge the credit. Gross up the pre-tax charge with:
39
219
 
40
- // In a handler:
41
- const [result] = await ctx.entrypoint<ClientEntrypoint<CreateCheckoutResponse>>(
42
- paymentApi.service.checkout.session.external.create
43
- ).call({
44
- body: { productSku: 'tokens', entityId, service: VIB_ALIAS, successUrl: helper.makeUrl(service) }
45
- })
46
220
  ```
221
+ ceil((amountMinor + fixedMinor) * 10_000 / (10_000 - rateBps))
222
+ ```
223
+
224
+ Validate the whole policy when configuration is declared, then validate every selected amount at
225
+ the backend boundary. Minimum, default, maximum, preset entries, fixed adjustment and basis-point
226
+ rate are safe integers; presets are unique/in-range and the rate is below 100%. The maximum applies
227
+ to `amountMinor`, not the adjusted checkout subtotal or tax-inclusive total.
228
+
229
+ Quantity checkout remains supported. It uses its reusable unit price and quantity policy; do not
230
+ infer a pricing mode from the presence of `amountMinor`.
231
+
232
+ ### Narrowing an amount checkout per entity
233
+
234
+ The plan's `amountPolicy` is the same for everyone; what one entity may buy NOW is narrower when a
235
+ checkout plugin (a spending tier, a rolling cap, a fraud hold) answers an `AmountNarrowing`
236
+ `{ maximumMinor, reason, resetsAt?, remainingMinor? }`.
237
+ `amountNarrowingHelper.narrowAmountPolicy(base, narrowings, { productSku?, planSku? })` is the one
238
+ computation the server's refusal and the dialog's control share, so they cannot disagree:
239
+
240
+ - the maximum is the smallest of the base maximum and every narrowing's (negative or fractional
241
+ values floor at 0); `reason`/`resetsAt`/`remainingMinor` come from the narrowing that set it,
242
+ the first on a tie;
243
+ - presets above the maximum are dropped, the default is clamped into `[minimum, maximum]`;
244
+ - `limit` is `null` when nothing lowered the base maximum — no note to show;
245
+ - **`limit.blocked`** (`limit.maximumMinor < base.minimumMinor`) means no amount may be bought now.
246
+ The returned `policy` is then still a VALID policy
247
+ (`checkoutPricingHelper.assertAmountCheckoutPolicy` accepts it), pinned to the minimum with no
248
+ presets, so a validator never throws — but it is NOT an offer of the minimum: a UI disables the
249
+ input and the confirm button on `blocked`, and a server refuses every amount with
250
+ `CheckoutLimitExceeded`. `amountNarrowingHelper.amountAllowed(view, amount)` answers exactly that.
251
+
252
+ `CheckoutLimitExceeded` (409) packs `checkout-limit-exceeded:<encodeURIComponent(reason)>:
253
+ <maximumMinor>:<currency>[:<resetsAt ISO>]` and rebuilds `reason`, `maximumMinor`, `currency`,
254
+ `resetsAt`. It is a payment refusal, not an entitlement one.
255
+
256
+ ## Pricing policy and tax estimate
257
+
258
+ `PricingPolicy` (a `PRICING_POLICY_RECORD_ID` singleton config record, `declarePaymentPricing` in
259
+ `@owlmeans/server-payment`) is what a checkout-owning application declares once for tax and
260
+ currency behaviour: `tax.automatic` (Stripe Tax on the session), `tax.behavior` (a synced price's
261
+ `TaxBehavior.Exclusive`/`Inclusive`, absent = leave it `unspecified`), `tax.collectTaxId`,
262
+ `tax.estimate` / `currency.estimate` (serve a tax/local-currency estimate endpoint — the estimate
263
+ requires `tax.automatic`, the currency one requires `currency.adaptive`), `currency.adaptive`
264
+ (Stripe Adaptive Pricing on the session), `currency.adaptiveFeeRate` (the conversion fee Adaptive
265
+ Pricing adds to Stripe's base rate, a fraction in `[0, 1)` — Stripe says 2–4% and picks it per
266
+ currency; it needs `currency.adaptive`; absent, an estimate keeps the FX Quotes' own 2% fee, which
267
+ is NOT what Checkout charges). `PaymentService.pricingPolicy()` reads the declared record
268
+ or `DEFAULT_PRICING_POLICY` — the fixed behaviour every checkout had before this policy existed
269
+ (automatic tax and tax-id collection on, no forced behavior, no Adaptive Pricing, no estimate), so
270
+ an application that declares nothing sees no change.
271
+
272
+ `PriceEstimate` (built by `@owlmeans/server-payment`'s gateway, `estimatePrice`) is the read model:
273
+ `country`/`source` (`'request'`/`'customer'`, or `'profile'` with `locked: true` when the entity's
274
+ billing country is locked and overrides the request — a picker then shows it and does not change
275
+ it), `region`, `currency`, `behavior`, `tax: TaxEstimate` (`status`
276
+ one of `TaxEstimateStatus` — `taxed`/`reverse-charge`/`none`/`at-checkout`/`location-required` —
277
+ plus `subtotalMinor`/`taxMinor`/`totalMinor`, `scalable`, and `rates: TaxRateEstimate[]` with
278
+ `ratePpm` parsed by `priceEstimateHelper.ratePpmOf` from Stripe's `percentage_decimal`, never
279
+ `Number(x) * 10_000`), and an optional `local` (currency + `exchangeRate`, from Stripe's FX Quotes:
280
+ the quote's `base_rate / (1 + currency.adaptiveFeeRate)` when the fee is declared, else its
281
+ `exchange_rate`; `fxFeeRate` is the fee that rate includes).
282
+ The pure helper `priceEstimateHelper.estimateOf(amountMinor, estimate)` re-derives tax and a total
283
+ for a DIFFERENT amount than the estimate's own reference one — exact only when `scalable` and the
284
+ behavior is `Exclusive`; both `taxMinor`/`totalMinor` come back `null` otherwise, meaning "computed
285
+ at checkout", never a wrong number. When the source estimate carries `local`, the result's own
286
+ `local` gives the subtotal, tax and total in THAT currency
287
+ (`subtotalAmount`/`taxAmount`/`totalAmount`, major units) alongside the integration-currency ones —
288
+ a UI shows one or the other, marking the local figures `≈`, never both side by side.
289
+ `COUNTRY_CURRENCIES` / `currencyOfCountry` / `COUNTRY_CODES` is a reference ISO 3166-1 → ISO 4217
290
+ map for a country picker and the local-currency lookup — not a Stripe list, and a country absent
291
+ from it still gets a tax estimate, just no local-currency line.
292
+
293
+ ## Consumer rights (EU withdrawal, cancellation, country lock)
294
+
295
+ The contracts behind the right of withdrawal (CRD Art. 9–16, the Art. 11a withdrawal function),
296
+ the cancellation function (§ 312k BGB / L215-1-1) and a billing country fixed at the first
297
+ purchase. `@owlmeans/server-payment` records, enforces and mails; `@owlmeans/web-payment` renders.
298
+ Nothing here is product copy — the trader, plan and product names are placeholders.
299
+
300
+ ### Territories, region, scope
301
+
302
+ - `EU_COUNTRIES` (27, Greece is `GR`), `EU_CONSUMER_TERRITORIES` (plus AX, GF, GP, MQ, RE, YT, MF —
303
+ consumer law applies there even where EU VAT does not), `EEA_EXTRA` (IS, LI, NO) and their union
304
+ `CONSUMER_RIGHTS_TERRITORIES`, the default `policy.countries`.
305
+ - `consumerRegionHelper.regionOf(country, policy?)`: `Eu` inside the policy's territories, `Other`
306
+ outside, `null` for no country. `consumerRegionHelper.inScope(region, country, policy)`: a known
307
+ country decides by the territories, else a known region, else `unknownCountry` — `'protect'` by
308
+ default, so an unknown buyer is protected.
309
+ - `consumerRegionHelper.chargeCurrencyOf(region, policy, fallback)`: the policy's currency for the
310
+ region; an unknown region reads as `Eu`. The display currency is the charge currency, everywhere.
311
+ - `consumerRegionHelper.billingLanguageOf(country, policy?, fallback?)`: `policy.languages`, then
312
+ `COUNTRY_LANGUAGES` (unambiguous countries only — BE, LU, CH are absent), then `fallback`, then
313
+ `policy.defaultLanguage`. Legal copy is shown in this language, with a toggle to the UI language.
314
+
315
+ ### The policy record
316
+
317
+ `ConsumerRightsPolicy` is a singleton config record (`CONSUMER_RIGHTS_RECORD_ID`), declared by the
318
+ server integration through `makeConsumerRightsPolicy(def)` — `DEFAULT_CONSUMER_RIGHTS` filled in
319
+ (14 days, weekend rollover, margin 5 — a period ending on a public holiday runs to the next working
320
+ day and holiday clusters need up to five, no per-country calendar is kept —, every mechanism OFF,
321
+ `defaultLanguage: 'en'`, start requests
322
+ usable 3600 s) — and `consumerRightsPolicyHelper.assertConsumerRightsPolicy` (non-empty
323
+ `textVersion`, `^[A-Z]{2}$` countries, `withdrawalDays >= 14`, margin 0..7, lowercase currencies,
324
+ https links, the default language present, `withdrawalInformation` required while `withdrawal` or
325
+ `performanceConsent` is on, `consentContext` a lowercase key `^[a-z][a-z0-9-]*$`; throws
326
+ `ConsumerRightsError('policy:<field>')`). It carries only public links, territories, switches and
327
+ the consent context — mail options live in a backend-only plugin config — so it is ADVERTISED to the
328
+ browser like the pricing policy. **`consentContext`** names the copy variant of the performance
329
+ consent (`'included'`: the credit limits included in a plan are used first, so the consent covers
330
+ topped-up credits once those limits have run out): the server records and mails, and the dialog
331
+ shows, the `_<context>` texts (§ The legal copy); absent, the base texts.
332
+ `PaymentService.consumerRightsPolicy()` answers `null` when none is declared: no consumer-rights
333
+ behaviour at all. `consumerRightsPolicyHelper.linksOf(policy, lng)` merges a language's links field
334
+ by field over the default language's (`de-AT` reads `de`).
335
+
336
+ `ProductPlan.withdrawal.components: PlanWithdrawalComponent[]` (`{ key, basis: 'time' | 'units',
337
+ shareMinor }`) states the separately priced parts of a subscription (CJEU C-641/19 PE Digital:
338
+ without them the whole price is pro rata by time). The same declaration picks the subscription start
339
+ statement: a `units` part makes it the `_units` variant (`consumerCopyHelper.startContextOf`, § The
340
+ legal copy).
341
+
342
+ ### Wire views
343
+
344
+ Every view schema carries dates as ISO strings (as `model/view.ts` does) and is revived with its
345
+ `consumerReviveHelper.revive*` member (`reviveConsentView`, `revivePurchase`/`List`,
346
+ `reviveBillingProfile`, `reviveConsentResponse`, `reviveStartResponse`, `reviveWithdrawalList`,
347
+ `reviveReceipt`, `reviveCheckoutLimit`, `reviveAmountPolicyView`) — idempotent. A nullable enum on
348
+ the wire lists `null` in its enum (ajv rejects `null` otherwise). The consent and start views carry
349
+ `trader`, the name the statement is rendered with, and `context`, the variant it is rendered with —
350
+ the consent view the policy's `consentContext`, the start view
351
+ `consumerCopyHelper.startContextOf(plan)` (`'units'`), each absent for the base — so the server's
352
+ record and the dialog's text are identical. `WithdrawalBody` and `CancellationBody` ask only for
353
+ name, contract and e-mail (plus the cancellation kind, reason and date) and accept an optional
354
+ `honeypot` a person never fills. A public declaration answers `DeclarationReceipt` — what was
355
+ declared and when, never whether a contract matched.
356
+
357
+ ### Calculators — always in the consumer's favour
358
+
359
+ All amounts in BigInt; a deduction rounds DOWN, a refund rounds UP, a refund never exceeds what is
360
+ still unrefunded.
361
+
362
+ - `withdrawalDeadlineHelper.withdrawalDeadlineOf(purchasedAt, rule | policy)` → the EXCLUSIVE end:
363
+ the purchase's UTC day + `days`, a Saturday/Sunday last day moved to Monday, + `marginDays`, start
364
+ of the next UTC day. Wed 2026-09-23 → 2026-10-13T00:00Z; Sat 2026-09-26 → 2026-10-18T00:00Z; Sun
365
+ 2026-12-20 → 2027-01-10T00:00Z (margin 5). `withdrawalDeadlineHelper.withdrawalOpen(window, at)` —
366
+ before the deadline, not withdrawn, not refunded. **A person is shown the last included day**,
367
+ `withdrawalDeadlineHelper.lastWithdrawalDayOf(deadline)` (the UTC day of `deadline − 1 ms`),
368
+ phrased "until the end of <date>" — never the exclusive instant.
369
+ - `withdrawalRefundHelper.oneTimeWithdrawalRefund({ paidMinor, refundedMinor?, unitsGranted, unitsUsed })`
370
+ → `min(paid − refunded, ceil(paid × (granted − used) / granted))`; `unitsUsed` is the deduction —
371
+ units used AFTER consent (none before it) plus debt settled from the lot plus units already
372
+ clawed back (`@owlmeans/server-payment` passes the meter's `usedAfter + settled + clawed`);
373
+ returns `unitsReturned`. 1256 paid, 125k of 500k used → 942.
374
+ - `withdrawalRefundHelper.subscriptionWithdrawalRefund(...)` splits `netMinor` by the components
375
+ (its `splitByShares`, largest remainder); a `time` part loses
376
+ `floor(c × elapsedDays / periodDays)` counted from `max(periodStart, servicesRequestedAt)` —
377
+ NOTHING without a start request; a `units` part loses `floor(c × used / granted)`; gross =
378
+ `ceil(paid × refundNet / net)`. Net 2000 / paid 2460, 3 of 30 days and 100k of 500k used → 2091.
379
+ - `fifoHelper.allocateFifo(lots, spends)` — a spend takes the oldest lot granted at or before it;
380
+ overflow is `unallocated`. `fifoHelper.unitsUsedAfter(lot, consentedAt)` counts slices strictly
381
+ after the consent, `0` without one.
382
+ - `cancellationEffectiveAt(periodEnd, 'month' | 'year', requested?)` — the period end, or the first
383
+ boundary on or after a later requested date (end-of-month clamped from the anchor).
384
+
385
+ ### Refusals
386
+
387
+ `ConsumerRightsRefusal` extends `ConsumerRightsError` (a `PaymentError`), NEVER `AuthForbidden`:
388
+ an HTTP boundary tests that family first, and a 403 would hide the status a client acts on. Each
389
+ refusal declares `static httpStatus`, packs its fields into the message and rebuilds them in
390
+ `finalizeUnmarshal()` (the marker is read after its LAST occurrence, so a registry rebuild of a
391
+ doubled prefix still parses):
392
+
393
+ | Class | Status | Marker (after `payment:consumer-rights:`) | Fields |
394
+ |---|---|---|---|
395
+ | `PerformanceConsentRequired` | 428 | `performance-consent-required:<pending>[:<deadline ISO>]` | `pending`, `deadline` |
396
+ | `SubscriptionStartRequired` | 428 | `subscription-start-required:<encodeURIComponent(planSku)>` | `planSku` (pass it raw) |
397
+ | `BillingCountryLocked` | 409 | `billing-country-locked:<country>[:<requested>]` | `country`, `requested` |
398
+ | `WithdrawalUnavailable` | 409 | `withdrawal-unavailable:<WithdrawalUnavailableReason>` | `reason` |
399
+ | `CancellationUnavailable` | 409 | `cancellation-unavailable:<CancellationUnavailableReason>` | `reason` |
400
+
401
+ `consentRefusalOf(e)` → `'performance' | 'subscription-start' | null`: the class after
402
+ `ResilientError.ensure`, else the marker or type name in `message`/`type`. A production body
403
+ carries only an incident id — a bare 428 is the caller's to read with `@owlmeans/api/status`.
404
+
405
+ ### The legal copy
406
+
407
+ The i18n resource `payment-consumer-rights` (`CONSUMER_RIGHTS_RESOURCE`, library tier, `lib`
408
+ namespace), in en pl ru be uk es de fr, versioned by `CONSUMER_RIGHTS_COPY_VERSION` (bump it on ANY
409
+ change to a bundle). Branches: `performance-consent`, `subscription-start` (`title`, `intro`,
410
+ `request`, `acknowledgement`, `checkbox` = request + space + acknowledgement, `confirm`,
411
+ `decline`; the consent branch also `purchase`; the start branch's `request`/`acknowledgement`/
412
+ `checkbox` also as `_units`), `withdrawal`, `cancellation` (form labels and the
413
+ statutory buttons `function` / `confirm`), `links`, `checkout` (`terms-acceptance.{in-scope,
414
+ other}` markdown, `renewal.{month, year, after-submit}`, `price.{exclusive, inclusive, exclusive-tax}` (VAT wording for the territories, "applicable tax" for a buyer outside them), `top-up`,
415
+ `top-up-note`), `email.{common, consent, start (`rule` + `rule_units`), purchase, withdrawal, cancellation}` (the purchase
416
+ mail carries the CRD Annex I(A) withdrawal information with the function's address and the Annex
417
+ I(B) model form, per language from the national models; there `{{trader}}` is the trader's whole
418
+ identity — legal name, address, e-mail; a `review` withdrawal receipt promises the reimbursement
419
+ within the statutory 14 days; `email.cancellation.review` answers an extraordinary cancellation,
420
+ whose status is `CancellationStatus.Review`), `credit-note.memo`. In the consent and start
421
+ STATEMENTS `{{trader}}` is the trader's short name.
422
+
423
+ - Read it with `consumerCopyHelper.consumerRightsCopy(lng)` (any language, merged key by key over
424
+ English, no i18next instance, never drains a bundle — `i18nHelper.resolveI18nResource`),
425
+ `consumerCopyHelper.consumerText(lng, path, vars, context?)` (fills `{{name}}`; throws
426
+ `ConsumerRightsError('copy:<path>[:<name>]')` on a missing text or value — a legal text never goes
427
+ out with a hole), `consumerCopyHelper.consentStatementOf(lng, kind, { trader, plan?, context? })`
428
+ (the exact statement the dialog shows, the server records and the mail repeats) and
429
+ `consumerCopyHelper.legalLabelsOf(lng)`.
430
+ - **Context variants** (the i18next context suffix): with a `context`,
431
+ `consumerCopyHelper.consumerText` reads `<path>_<context>` first and the base `path` only where no
432
+ such variant exists (a missing value then names the variant: `copy:<path>_<context>:<name>`); an
433
+ empty context is none. `consumerCopyHelper.consentStatementOf` passes its `context` to all three
434
+ texts. Shipped variants, all eight languages, same placeholders as the base keys, each request
435
+ keeping the base request's statutory opening verbatim; a new variant lands in every language at
436
+ once, with its base key's placeholders:
437
+ - `performance-consent.{title,intro,request,checkbox}_included` — the consuming platform's
438
+ vocabulary: "topped-up credits" (pl doładowane, ru пополненные, be папоўненыя, uk поповнені, es
439
+ recargados, de aufgeladene, fr rechargés) are spent once "the credit limits included in my plan"
440
+ (a plan's non-monetary credits) have run out; `checkbox_included` = `request_included` + space +
441
+ the BASE `acknowledgement` (generic "credits", so `acknowledgement` has no variant).
442
+ - **The subscription start statement follows the plan's withdrawal arithmetic.** The BASE
443
+ `subscription-start.{request,acknowledgement,checkbox}` and `email.start.rule` describe the
444
+ common case — the plan withdrawn pro rata by the DAYS ELAPSED only ("starts the {{plan}}
445
+ services — including the AI work within the usage the plan includes — now …; … I pay for the
446
+ services provided until then, pro rata by the days elapsed, and everything else is
447
+ reimbursed"); `…_units` (`checkbox_units` = `request_units` + space + `acknowledgement_units`)
448
+ describe the split — services pro rata by time, the right of withdrawal expiring for the
449
+ included units used. `consumerCopyHelper.startContextOf(plan)` is the ONE decision: `'units'`
450
+ exactly when the plan's `withdrawal.components` has a `basis: 'units'` part, else `undefined`.
451
+ Every renderer takes the context from it — the server's start view, the recorded statement and
452
+ the start mail, and the dialog (from the view's `context`) — so the statement on screen is the
453
+ one recorded. `title`, `intro`, `confirm`, `decline` have no `_units` variant. Generic statutory
454
+ text (the purchase mail's withdrawal information) stays generic.
455
+ - Statutory labels are pinned by a test: en "Withdraw from contract here" / "Confirm withdrawal",
456
+ "Cancel contracts here" / "Cancel now"; de "Vertrag widerrufen" / "Widerruf bestätigen",
457
+ "Verträge hier kündigen" / "Jetzt kündigen"; fr "Renoncer au contrat ici" / "Confirmer la
458
+ rétractation", "Résilier votre contrat" / "Notification de la résiliation"; pl "Odstąp od umowy
459
+ tutaj" / "Potwierdź odstąpienie od umowy", "Wypowiedz umowę tutaj" / "Wypowiedz teraz"; es
460
+ "Desistir del contrato aquí" / "Confirmar el desistimiento", "Cancelar contratos aquí" /
461
+ "Cancelar ahora"; uk/ru/be are courtesy translations.
462
+ - The express request uses the statutory verbs (PL "Żądam i wyrażam wyraźną zgodę … Przyjmuję do
463
+ wiadomości …", DE "Ich verlange ausdrücklich und stimme ausdrücklich zu … Mir ist bekannt …", FR
464
+ "Je demande expressément et j’accepte expressément … Je reconnais perdre …"). Every start
465
+ request — base and `_units`, every language — opens with the same three words as that language's
466
+ performance request (pinned by the copy spec).
467
+ - Wording rule, enforced by a scan of every language: say "the right of withdrawal expires" and
468
+ "only unused credits are reimbursed" — never non-refundable / nicht erstattungsfähig / non
469
+ remboursable / bezzwrotny / no reembolsable / невозвратный / неповоротний / незваротны.
470
+ - Paygate texts (`checkout.*`) stay within 1200 characters after interpolating long URLs.
471
+ - An application overrides a text with `i18nHelper.addI18nApp(lng, CONSUMER_RIGHTS_RESOURCE, data,
472
+ { ns: LIB_NAMESPACE })`; every placeholder of a text must match its English master.
473
+
474
+ ### Protocol factories
475
+
476
+ `makeConsumerRightsProtocols({ prefix, parent | guards (+ gate), path = '/consumer-rights',
477
+ public?: false | { path = '/public/consumer-rights', parent?, screens?: { withdrawal?,
478
+ cancellation?, parent? } } })`, the `makeMarketingConsentProtocols` way:
479
+
480
+ - account subtree under the app's GUARDED parent (its guards and gate inherited): `base`,
481
+ `profile` GET `/profile`, `purchases` GET `/purchases`, `consent` GET / `giveConsent` POST
482
+ `/consent`, `start` GET `/start?planSku` / `requestStart` POST `/start`, `withdrawals` GET /
483
+ `withdraw` POST `/withdrawal`, `cancel` POST `/cancellation`; aliases `<prefix>:<name>`
484
+ (`consent:give`, `start:request`);
485
+ - `public` (only when declared): `base`, `policy` GET `/policy`, `withdraw` POST `/withdrawal`,
486
+ `cancel` POST `/cancellation` — NO guard and NO gate (like `paymentGate.webhook`; a public
487
+ `parent` must be unguarded), receipts typed `DeclarationReceipt`; `screens` declares BOTH sticky
488
+ frontend routes (`/legal/withdraw`, `/legal/cancel` by default); aliases `<prefix>:public:<name>`;
489
+ - neither `parent` nor `guards` is a `SyntaxError`; absent parts are left OUT of the tree (never
490
+ `undefined`), so `protocols(tree)` and `mapProtocols` walk it as is.
491
+
492
+ `makeCheckoutReadProtocols({ prefix, parent | guards })` → `base` (`/checkout`), `amountPolicy`
493
+ GET `/amount-policy?productSku&planSku` (`AmountPolicyView`) and `planPrices` GET
494
+ `/plan-prices?productSku` (`PlanPriceList`).
495
+
496
+ ## `shallowAuthentication` identifies, it does not authorize
497
+
498
+ It reads the `profileId` out of an envelope token WITHOUT verifying the signature, and throws
499
+ `PaymentIdentificationError` when there is no token or no profile in it. Use it to correlate a
500
+ checkout return with a visitor; never as the basis of an access decision — that is a gate's job.
47
501
 
48
502
  ## Depends On
49
503
 
50
- - `@owlmeans/entrypoint`, `@owlmeans/route`
51
- - `@owlmeans/auth`, `@owlmeans/error`, `@owlmeans/i18n`
504
+ - `@owlmeans/entrypoint` · `@owlmeans/route` · `@owlmeans/config` · `@owlmeans/resource`
505
+ - `@owlmeans/auth` (`PermissionSet`, `AuthForbidden`) · `@owlmeans/basic-envelope` · `@owlmeans/context`
506
+ - `@owlmeans/error` · `@owlmeans/i18n` · `@owlmeans/api-config` · peer `ajv`
507
+
508
+ ## Related
509
+
510
+ - [[entitlements]] — the model across packages: admission, the usage ledger, the gates
511
+ - [[server-payment]] — the Stripe gateway, subscription store, usage ledger and gate services
512
+ - [[web-payment]] — hooks and pieces over the entitlement view
513
+ - [[client-payment]] — the browser-side adaptation of the catalogue service
514
+ - [[iam]] — `PermissionSet` and the resource-selector syntax entitlements deliberately avoid
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=advertise.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"advertise.d.ts","sourceRoot":"","sources":["../src/advertise.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ import { CONFIG_RECORD } from '@owlmeans/context';
2
+ import { apiConfigPlugin, every } from '@owlmeans/api-config';
3
+ import { advertisedRecordTypes } from './consts.local.js';
4
+ apiConfigPlugin({
5
+ allow: {
6
+ [CONFIG_RECORD]: every(true, value => value != null && typeof value === 'object'
7
+ && advertisedRecordTypes.has(value.recordType ?? '')),
8
+ },
9
+ });
10
+ //# sourceMappingURL=advertise.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"advertise.js","sourceRoot":"","sources":["../src/advertise.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAA;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAA;AAEzD,eAAe,CAAC;IACd,KAAK,EAAE;QACL,CAAC,aAAa,CAAC,EAAE,KAAK,CACpB,IAAI,EACJ,KAAK,CAAC,EAAE,CAAC,KAAK,IAAI,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;eAC9C,qBAAqB,CAAC,GAAG,CAAE,KAAiC,CAAC,UAAU,IAAI,EAAE,CAAC,CACpF;KACF;CACF,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ export declare const CHECKOUT_READ_API_PATH = "/checkout";
2
+ //# sourceMappingURL=consts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../../src/checkout/consts.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,cAAc,CAAA"}
@@ -0,0 +1,2 @@
1
+ export const CHECKOUT_READ_API_PATH = '/checkout';
2
+ //# sourceMappingURL=consts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consts.js","sourceRoot":"","sources":["../../src/checkout/consts.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,sBAAsB,GAAG,WAAW,CAAA"}
@@ -0,0 +1,6 @@
1
+ export * from './narrow.js';
2
+ export type * from './types.js';
3
+ export type * from './narrow/types.js';
4
+ export * from './protocols.js';
5
+ export * from './consts.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/checkout/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,mBAAmB,YAAY,CAAA;AAC/B,mBAAmB,mBAAmB,CAAA;AACtC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA"}
@@ -0,0 +1,4 @@
1
+ export * from './narrow.js';
2
+ export * from './protocols.js';
3
+ export * from './consts.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/checkout/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAG3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA"}