@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
package/README.md CHANGED
@@ -1,87 +1,125 @@
1
1
  # @owlmeans/payment
2
2
 
3
- Payment service abstraction — product catalog, subscription management, and Stripe checkout integration.
4
-
5
- ## Overview
6
-
7
- - `makePaymentService(alias?)` — creates a payment service for context registration
8
- - `appendPaymentService(context, alias?)` — registers the payment service in the context
9
- - `PaymentService` — interface for products, plans, subscriptions, and checkout session creation
10
- - `CreateCheckoutBody` / `CreateCheckoutResponse` — request/response types for checkout
3
+ Payment contracts shared by a server and a browser: the product catalogue, amount- or
4
+ quantity-priced checkout, and the entitlement model — plans that grant capabilities and counted
5
+ limits, promos, and the entitlement view both a gate and a UI read. It talks to no paygate; a
6
+ server integration (`@owlmeans/server-payment`) implements against these contracts.
11
7
 
12
8
  ## Installation
13
9
 
14
10
  ```bash
15
- bun add @owlmeans/payment
11
+ bun add @owlmeans/payment@^0.1.18
16
12
  ```
17
13
 
14
+ ## Concepts
15
+
16
+ - **Plan** — a catalogue record with a `rank` (higher is an upgrade) and optionally `free: true`
17
+ (price 0, no paygate): every entity holds some plan, even without paying.
18
+ - **Capability** — a granted permission, written `[scope:]permission[>=n]`, asserted under
19
+ `ENTITLEMENT_GATE`.
20
+ - **Limit** — a counted allowance, written `limit:<key>[>=n]`, asserted under `LIMIT_GATE`. Three
21
+ kinds: `window` (calendar UTC day/month), `lifetime` (never renews), `occupancy` (a held count).
22
+ - **Promo** — `{ until, grandfather? }` on a capability set or a limit: in force before `until`,
23
+ or for a subscription created before it when grandfathered.
24
+ - **Entitlement view** — the effective plan, every capability and every limit with its usage, at
25
+ one instant; built by pure helpers the server and the browser share.
26
+
18
27
  ## Usage
19
28
 
20
- Create a checkout session:
29
+ Declare a plan:
21
30
 
22
31
  ```typescript
23
- import type { CreateCheckoutBody, CreateCheckoutResponse } from '@owlmeans/payment'
24
-
25
- const [result] = await ctx.module<ClientModule<CreateCheckoutResponse>>(
26
- paymentApi.service.checkout.session.external.create
27
- ).call({
28
- body: {
29
- productSku: 'vib-tokens',
30
- entityId,
31
- service: VIB_ALIAS,
32
- successUrl: helper.makeUrl(service)
33
- } satisfies CreateCheckoutBody
34
- })
35
- window.open(result.url, '_blank')
32
+ import { LimitKind, LimitWindow, PlanDuration, PlanStatus } from '@owlmeans/payment'
33
+ import type { ProductPlan } from '@owlmeans/payment'
34
+
35
+ const proMonthly: ProductPlan = {
36
+ productSku: 'pro', sku: 'pro-monthly', status: PlanStatus.Active, duration: PlanDuration.Monthly,
37
+ price: 20, title: 'Pro', rank: 10, gateways: ['stripe'],
38
+ capabilities: [
39
+ { scope: 'feature', permissions: { whitelabel: true } },
40
+ { scope: 'feature', permissions: { beta: true }, promo: { until: new Date('2027-01-01'), grandfather: true } },
41
+ ],
42
+ limits: {
43
+ seats: { kind: LimitKind.Occupancy, limit: 5 },
44
+ exports: { kind: LimitKind.Window, window: LimitWindow.Month, limit: 100, unit: 'file' },
45
+ },
46
+ }
36
47
  ```
37
48
 
38
- Handle subscription propagation:
49
+ Gate a route on a capability or on a limit:
39
50
 
40
51
  ```typescript
41
- import { SubscriptionPropagateBody, PlanDuration, SubscriptionStatus } from '@owlmeans/payment'
42
-
43
- const handler = handleBody<SubscriptionPropagateBody>(async (body, context) => {
44
- if (body.status === SubscriptionStatus.Consumable) {
45
- const tokens = body.capabilities?.find(
46
- c => c.scope === PlanDuration.Consumable
47
- )?.permissions.units ?? 0
48
- await ctx.agentToken().topUpTokens(entityId, tokens * 1000)
49
- }
50
- })
51
- ```
52
+ import { ENTITLEMENT_GATE, LIMIT_GATE, entitled, planLimitHelper } from '@owlmeans/payment'
52
53
 
53
- ## API
54
+ protocol(route(WHITELABEL, '/whitelabel', backend(BASE, RouteMethod.POST)), contract(typed()),
55
+ entitled('feature:whitelabel'))
56
+ protocol(route(INVITE, '/invite', backend(BASE, RouteMethod.POST)), contract(typed()),
57
+ { gate: { alias: LIMIT_GATE, params: [planLimitHelper.formatLimitParam('seats')] } })
58
+ ```
54
59
 
55
- ### `makePaymentService(alias?): PaymentService`
60
+ Build and read an entitlement view:
56
61
 
57
- Creates the payment service.
62
+ ```typescript
63
+ import { entitlementViewHelper, planLimitHelper } from '@owlmeans/payment'
58
64
 
59
- ### `PaymentService`
65
+ const view = entitlementViewHelper.entitlementViewOf(plan, planView, usage) // server
66
+ const fromWire = entitlementViewHelper.reviveEntitlementView(await response.json()) // browser: ISO strings → Dates
67
+ planLimitHelper.capabilityOf(fromWire, 'feature:whitelabel')
68
+ planLimitHelper.hasLimitRoom(planLimitHelper.limitOf(fromWire, 'seats'))
69
+ ```
60
70
 
61
- - `product(sku): Promise<Product>` — get a product by SKU
62
- - `products(): Promise<Product[]>` — list all products
63
- - `plans(productSku, duration): Promise<ProductPlan[]>` — list plans for a product
64
- - `plan(planSku): Promise<ProductPlan>` — get a plan by SKU
65
- - `shallowAuthentication(token): Promise<string>` — shallow auth for payment flows
66
- - `localize(lng, entity): Promise<Localization | null>` — get localized payment content
71
+ Refuse and recover the fields on the other side of a service hop:
67
72
 
68
- ### Types
73
+ ```typescript
74
+ import { LimitExhausted } from '@owlmeans/payment'
75
+ import { ResilientError } from '@owlmeans/error'
69
76
 
70
- - `CreateCheckoutBody` — `{ productSku, entityId, service, successUrl?, cancelUrl? }`
71
- - `CreateCheckoutResponse` — `{ url: string }`
72
- - `SubscriptionPropagateBody` — extends `PlanSubscription`
73
- - `PlanSubscription` — `{ sku, entityId, createdAt, status, capabilities? }`
77
+ throw new LimitExhausted({ key: 'seats', used: 5, limit: 5 })
78
+ // …after marshal/unmarshal:
79
+ const error = ResilientError.ensure(caught)
80
+ if (error instanceof LimitExhausted) { error.limitKey; error.used; error.limit; error.resetsAt }
81
+ ```
74
82
 
75
- ### Enums
83
+ ## API
76
84
 
77
- - `PlanDuration` — `Monthly`, `Yearly`, `Consumable`, etc.
78
- - `SubscriptionStatus` — `Active`, `Consumable`, `Canceled`, etc.
79
- - `ProductType` — product category enum
80
- - `PaymentEntityType` — entity classification enum
85
+ - Catalogue: `makePaymentService(alias?)`, `appendPaymentService(ctx, alias?)`, `PaymentService`
86
+ (`product`, `products`, `plans`, `plan`, `allPlans`, `localize`, `shallowAuthentication`),
87
+ `l10nToId`, record types/prefixes.
88
+ - Checkout: `CheckoutPricingMode`, `AmountCheckoutPolicy`/`QuantityCheckoutPolicy` (+ schemas),
89
+ `checkoutPricingHelper` (`assertAmountCheckoutPolicy`, `assertQuantityCheckoutPolicy`,
90
+ `assertCheckoutAmount`, `chargeAmountMinor`), `CreateCheckoutBody` (`planSku`) / `CreateCheckoutResponse` (+ schemas),
91
+ `PortalFlow`, `PortalLinkBody` / `PortalLinkResponse` (+ schemas).
92
+ - Entitlement grammar: `ENTITLEMENT_GATE`, `LIMIT_GATE`, `CAPABILITY_FEATURE_SCOPE`,
93
+ `CAPABILITY_LIMIT_SCOPE`, `entitled`, `entitlementParamHelper` (`parseEntitlementParam`,
94
+ `formatEntitlementParam`, `hasEntitlement`, `entitlementList`), `planLimitHelper`
95
+ (`parseLimitParam`, `formatLimitParam`).
96
+ - Statuses: `SubscriptionStatus` (incl. `PastDue`), `ENTITLING_STATUSES`, `TERMINAL_STATUSES`,
97
+ `INTERNAL_PAYGATE`.
98
+ - Limits and promos: `LimitKind`, `LimitWindow`, `LimitDeclaration`, `PlanCapability`,
99
+ `PromoDeclaration` (+ schemas), `planLimitHelper` (`windowKeyOf`, `windowBoundsOf`),
100
+ `LIFETIME_WINDOW`, `OCCUPANCY_WINDOW`, `promoHelper` (`promoActive`, `promoViewOf`).
101
+ - Views: `EntitlementView`, `EntitlementPlanView`, `CapabilityView`, `LimitView`, `PromoView`,
102
+ `LimitUsage` (+ wire schemas), `entitlementViewHelper` (`capabilityViewsOf`, `limitViewsOf`,
103
+ `entitlementViewOf`, `reviveEntitlementView`), `planLimitHelper` (`capabilityOf`, `limitOf`,
104
+ `hasLimitRoom`).
105
+ - Errors: `EntitlementRefusal` → `CapabilityRequired`, `LimitExhausted` (all `AuthForbidden`);
106
+ `PaymentError` family incl. `LimitUnknown`, `LimitMisdeclared`, `PlanRequired`,
107
+ `PlanRankConflict`, `WebhookSetupError`, `PortalUnavailable` (declares `httpStatus = 409`, so an
108
+ HTTP boundary answers 409; the other faults answer 500). Messages are registered under
109
+ `errors.<type>` in seven languages.
110
+
111
+ ## Common pitfalls
112
+
113
+ - A limit parameter is never a capability: `entitlementParamHelper.hasEntitlement(sets, 'limit:x')` is always `false`.
114
+ - A view's dates are ISO strings on the wire — revive before calling date methods.
115
+ - A promo ends AT `until`; grandfathering needs the subscription's creation date on the plan view.
116
+ - A refusal's fields survive a hop only through its message; catch the class, not the text.
81
117
 
82
118
  ## Related Packages
83
119
 
84
- - [`@owlmeans/client-payment`](../client-payment) — client-side wrapper for browser contexts
120
+ - `@owlmeans/server-payment` — Stripe gateway, subscription store, usage ledger and the two gates
121
+ - `@owlmeans/client-payment` — browser-side catalogue service
122
+ - `@owlmeans/web-payment` — React hooks and pieces over the entitlement view
85
123
 
86
124
  <!-- owlmeans:agent-guidance:start -->
87
125
  ## Agent guidance
@@ -91,7 +129,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
91
129
  your project's skill store (`.agents/skills/`):
92
130
 
93
131
  ```sh
94
- npx @owlmeans/agent-skills
132
+ npx @owlmeans/agent-skills@^0.1.18
95
133
  ```
96
134
 
97
135
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,10 +1,17 @@
1
1
  {
2
- "schemaVersion": 2,
2
+ "schemaVersion": 3,
3
3
  "package": "@owlmeans/payment",
4
- "version": "0.1.18-rc.8",
5
- "generatedAt": "2026-08-27T16:32:18.507Z",
4
+ "version": "0.1.18",
5
+ "generatedAt": "2026-10-28T00:31:34.748Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
+ {
9
+ "kind": "skill",
10
+ "name": "entitlements",
11
+ "category": "multi-package",
12
+ "file": "skills/entitlements/SKILL.md",
13
+ "canonicalPath": ".agents/skills/entitlements/SKILL.md"
14
+ },
8
15
  {
9
16
  "kind": "skill",
10
17
  "name": "payment",
@@ -0,0 +1,159 @@
1
+ ---
2
+ name: entitlements
3
+ description: "The OwlMeans entitlement model across @owlmeans/payment, @owlmeans/server-payment and @owlmeans/web-payment — ranked plans with a real free plan, capabilities versus counted limits, the three limit kinds, promos and grandfathering, the admission-first usage ledger and its may-over-count-never-over-admit invariant, the entitlement view the server gate and the browser both read, the two gate services, and the refusal errors. Use when deciding what a plan grants, gating a route on a feature or a quota, consuming or releasing a limit, or rendering plan state in a UI."
4
+ user-invocable: false
5
+ ---
6
+ <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
7
+
8
+ # Entitlements — what a plan lets an entity do
9
+
10
+ An entity (the organization) always holds exactly one **effective plan**. The plan grants
11
+ **capabilities** (may it do X?) and **limits** (how many X may it do?). A route declares what it
12
+ needs; a gate refuses before the handler runs; the handler consumes. The browser renders the same
13
+ answer from the same pure functions, so a disabled button and a 403 cannot disagree.
14
+
15
+ ## Where each half lives
16
+
17
+ | Layer | Package | Owns |
18
+ |---|---|---|
19
+ | Contracts | `@owlmeans/payment` | Plan declarations, `LimitKind`/`LimitWindow`, promos, the param grammars, the view builders, the refusal errors, `ENTITLEMENT_GATE`/`LIMIT_GATE` |
20
+ | Server | `@owlmeans/server-payment` | The subscription store, plan resolution, the usage ledger and its counters, the two gate services, reconciliation, the paygate lifecycle |
21
+ | Browser | `@owlmeans/web-payment` | Hooks over a served entitlement view (`useEntitlementView`, `useCapability`, `useLimit`, `usePortal`) and presentational pieces |
22
+
23
+ ## Plans
24
+
25
+ - **Rank orders a product's plans.** A higher `rank` is an upgrade; a plan change is classified
26
+ as upgrade/downgrade by rank. No two paid plans of one product share a rank.
27
+ - **The free plan is a real plan** — `free: true`, `price: 0`, `gateways: []`, lowest rank. The
28
+ application grants it as an internal subscription row (`paygate: INTERNAL_PAYGATE`) when an
29
+ entity is created, and plan resolution falls back to it when no entitling row exists. With no
30
+ free plan declared and no entitling row, resolution throws `PlanRequired` — a setup fault, not a
31
+ refusal.
32
+ - **The effective plan** is the highest-ranked subscription in `ENTITLING_STATUSES`
33
+ (`Active`, `Trial`, `PastDue`). `PastDue` still entitles and is flagged; `Suspended` revokes
34
+ until resumed; terminal statuses fall back to the free plan.
35
+ - **One subscription may hold other ceilings than its plan declares**: the row's `overrides.limits`
36
+ replace the plan's ceilings for that subscription only, promo or not (the `server-payment` skill,
37
+ § The subscription store). Read a plan's limits from the effective plan or the entitlement view,
38
+ never from the catalogue by sku, or an override is missed.
39
+ - A plan names its keys, never a product's copy: every limit key an application gates on appears
40
+ on every plan (with `limit: 0` where not included), so "not included" is an answer, not a
41
+ missing row.
42
+
43
+ ## Capabilities versus limits
44
+
45
+ | | Capability | Limit |
46
+ |---|---|---|
47
+ | Declared as | `PlanCapability` (a permission set) | `LimitDeclaration` under a key in `plan.limits` |
48
+ | Asked as | `[scope:]permission[>=n]` | `limit:<key>[>=n]` |
49
+ | Gate alias | `ENTITLEMENT_GATE` | `LIMIT_GATE` |
50
+ | Answer | granted or not | room left in the current window |
51
+ | Changes when | the plan changes | something is consumed or released |
52
+
53
+ - `limit` is a reserved capability scope: no capability can answer a limit parameter.
54
+ - The two gates are different aliases because an entrypoint's gates are collected per gate
55
+ service; one alias would let one requirement hide the other.
56
+ - Several parameters on one gate are OR'd.
57
+
58
+ ## The three limit kinds
59
+
60
+ | Kind | Counter window | Renews |
61
+ |---|---|---|
62
+ | `window` (`day` / `month`) | `YYYY-MM-DD` / `YYYY-MM`, calendar UTC | at the UTC boundary; `resetsAt` is the exclusive start of the next window |
63
+ | `lifetime` | `lifetime` | never — the count belongs to the entity and survives plan changes, so an upgrade reads "1 of 4 used" |
64
+ | `occupancy` | `occupancy` | never — a held count, `+1` on acquire and `-1` on release, reconciled against what actually exists |
65
+
66
+ Windows are calendar UTC, never rolling and never local.
67
+
68
+ ## Promos and grandfathering
69
+
70
+ `promo: { until, grandfather? }` on a capability set or a limit is plan metadata resolved per
71
+ subscription:
72
+
73
+ - in force while `now < until` (`now === until` is over);
74
+ - with `grandfather`, also for a subscription created before `until`, for as long as it lasts;
75
+ - lapsed: the capability is listed `granted: false`, the limit reads `limit: 0`, and the promo
76
+ view says `active: false` — the UI says the promotion ended.
77
+
78
+ The server must put the subscription's creation date on the plan view (`subscribedAt`); without it
79
+ nothing is grandfathered.
80
+
81
+ ## The usage ledger — admission first
82
+
83
+ Limits are event-sourced with synchronous admission:
84
+
85
+ - **The ledger of usage events is the source of truth; the counter per (entity, key, window) is a
86
+ projection.**
87
+ - **Consume** increments the counter FIRST with an atomic conditional update (only while
88
+ `used <= limit - amount`), then appends the event keyed by an idempotency `eventKey`. No update
89
+ ⇒ `LimitExhausted`. A duplicate `eventKey` undoes the increment and replays the earlier outcome.
90
+ - **Release** appends first, then decrements (never below zero), and is idempotent.
91
+ - **Invariant: the counter may over-count, never over-admit.** A crash between the increment and
92
+ the event leaves a unit counted that nothing spent — the periodic reconciliation recomputes
93
+ counters from the ledger (and occupancy from reality). The opposite order would let two
94
+ concurrent requests both pass a read-then-write check.
95
+ - **Key a consumption by the record it pays for** (`eventKey = <purpose>:<recordId>`,
96
+ `ref = recordId`). A retry, a resumed run or a second attempt on the same record replays the same
97
+ event instead of spending a second unit, and "was a unit already spent on this record?" is a
98
+ ledger read rather than a marker written onto the record.
99
+ - Occupancy is reconciled against the live count; an entity over its ceiling is flagged
100
+ (`overSince`) and the application decides what to do after a grace period — a cron never
101
+ silently deletes customer work.
102
+
103
+ ## The gates — the gate refuses, the handler consumes
104
+
105
+ - The **capability gate** passes when the effective plan grants any parameter. The subscription
106
+ is the authority: a token's explicit `false` for a permission denies, a token's own grant never
107
+ allows, and requiring the token permission as well is an opt-in gate option. Store errors
108
+ refuse.
109
+ - The **limit gate** passes when any declared parameter has `remaining >= atLeast`. It **never
110
+ consumes** — checking room and spending it are different moments, and only the handler knows
111
+ whether the work actually started. Unparseable parameters are skipped; none left ⇒ refuse.
112
+ - The **handler consumes** with a record-keyed `eventKey` right before the paid work starts, and
113
+ releases (or relies on the key's replay) when the work is undone.
114
+ - Declare the requirement on the protocol, never inside a handler, so the route table states what
115
+ a feature costs.
116
+
117
+ ## The entitlement view
118
+
119
+ One read of an entity's position, built by
120
+ `entitlementViewHelper.entitlementViewOf(plan, planView, usage, at)`:
121
+
122
+ - `plan` — sku, rank, free, status, paygate, period, trial, cancel-at-period-end, past-due,
123
+ fallback plan;
124
+ - `capabilities` — one row per granted permission, `param` exactly as the gate takes it;
125
+ - `limits` — one row per declared key with `limit`, `used`, `remaining` (floored at 0), and
126
+ `windowStart`/`resetsAt` for window limits;
127
+ - `at` — when it was computed.
128
+
129
+ The server serves it; the browser reads it with the same `planLimitHelper.capabilityOf` /
130
+ `limitOf` / `hasLimitRoom` the gate logic uses. On the wire every date is an ISO string — revive
131
+ (`entitlementViewHelper.reviveEntitlementView`) before using a date. In the browser `null` means
132
+ "not known yet": render paid controls disabled rather than enabled-then-refused, and an application
133
+ with its own store reads the view through the pure selectors rather than polling it a second time.
134
+
135
+ ## Refusals
136
+
137
+ | Error | Means | HTTP | Fields (rebuilt after a hop) |
138
+ |---|---|---|---|
139
+ | `CapabilityRequired` | none of the capability parameters is granted | 403 | `params` |
140
+ | `LimitExhausted` | no room in the current window | 403 | `limitKey`, `used`, `limit`, `resetsAt?` |
141
+ | `LimitUnknown` · `LimitMisdeclared` · `PlanRequired` · `PlanRankConflict` | configuration faults | 500 | — |
142
+
143
+ - Both refusals extend `AuthForbidden` through `EntitlementRefusal` — that is what turns them into
144
+ a 403 at the HTTP boundary.
145
+ - Only `type` and `message` cross a service hop; the refusals pack their fields into the message
146
+ and rebuild them on unmarshal. Catch the class after `ResilientError.ensure`.
147
+ - A UI phrases a refusal from `errors.<type>` and its fields (the reset date of an exhausted
148
+ window), never from the message text.
149
+ - Not entitlement refusals: the consumer-rights refusals (`PerformanceConsentRequired`,
150
+ `SubscriptionStartRequired` 428; `BillingCountryLocked`, `WithdrawalUnavailable`,
151
+ `CancellationUnavailable` 409) and `CheckoutLimitExceeded` (409) declare their status and never
152
+ extend `AuthForbidden` — the `payment` skill, § Consumer rights.
153
+
154
+ ## Related
155
+
156
+ - [[payment]] — the contracts in detail: grammars, window algebra, schemas
157
+ - [[server-payment]] — the Stripe lifecycle, the subscription store, the ledger and gates
158
+ - [[web-payment]] — the hooks and pieces
159
+ - [[error]] — marshalling and the error registry