@owlmeans/server-payment 0.1.18-rc.3 → 0.1.18-rc.30

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 (276) hide show
  1. package/README.md +146 -25
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/server-payment/SKILL.md +593 -53
  4. package/build/actions/index.d.ts +1 -0
  5. package/build/actions/index.d.ts.map +1 -1
  6. package/build/actions/index.js +1 -0
  7. package/build/actions/index.js.map +1 -1
  8. package/build/actions/resync-subscriptions.d.ts +3 -0
  9. package/build/actions/resync-subscriptions.d.ts.map +1 -0
  10. package/build/actions/resync-subscriptions.js +6 -0
  11. package/build/actions/resync-subscriptions.js.map +1 -0
  12. package/build/actions/resync.d.ts +6 -1
  13. package/build/actions/resync.d.ts.map +1 -1
  14. package/build/actions/resync.js +12 -4
  15. package/build/actions/resync.js.map +1 -1
  16. package/build/actions/webhook.d.ts +2 -1
  17. package/build/actions/webhook.d.ts.map +1 -1
  18. package/build/actions/webhook.js +9 -4
  19. package/build/actions/webhook.js.map +1 -1
  20. package/build/config.d.ts +60 -5
  21. package/build/config.d.ts.map +1 -1
  22. package/build/config.js +248 -5
  23. package/build/config.js.map +1 -1
  24. package/build/consts.d.ts +74 -16
  25. package/build/consts.d.ts.map +1 -1
  26. package/build/consts.js +86 -23
  27. package/build/consts.js.map +1 -1
  28. package/build/consts.local.d.ts +94 -0
  29. package/build/consts.local.d.ts.map +1 -0
  30. package/build/consts.local.js +44 -0
  31. package/build/consts.local.js.map +1 -0
  32. package/build/consumer/capture.d.ts +61 -0
  33. package/build/consumer/capture.d.ts.map +1 -0
  34. package/build/consumer/capture.js +300 -0
  35. package/build/consumer/capture.js.map +1 -0
  36. package/build/consumer/consts.local.d.ts +10 -0
  37. package/build/consumer/consts.local.d.ts.map +1 -0
  38. package/build/consumer/consts.local.js +11 -0
  39. package/build/consumer/consts.local.js.map +1 -0
  40. package/build/consumer/format.d.ts +27 -0
  41. package/build/consumer/format.d.ts.map +1 -0
  42. package/build/consumer/format.js +81 -0
  43. package/build/consumer/format.js.map +1 -0
  44. package/build/consumer/handlers.d.ts +21 -0
  45. package/build/consumer/handlers.d.ts.map +1 -0
  46. package/build/consumer/handlers.js +173 -0
  47. package/build/consumer/handlers.js.map +1 -0
  48. package/build/consumer/index.d.ts +7 -0
  49. package/build/consumer/index.d.ts.map +1 -0
  50. package/build/consumer/index.js +6 -0
  51. package/build/consumer/index.js.map +1 -0
  52. package/build/consumer/mail.d.ts +27 -0
  53. package/build/consumer/mail.d.ts.map +1 -0
  54. package/build/consumer/mail.js +314 -0
  55. package/build/consumer/mail.js.map +1 -0
  56. package/build/consumer/origin.d.ts +14 -0
  57. package/build/consumer/origin.d.ts.map +1 -0
  58. package/build/consumer/origin.js +46 -0
  59. package/build/consumer/origin.js.map +1 -0
  60. package/build/consumer/reconcile.d.ts +12 -0
  61. package/build/consumer/reconcile.d.ts.map +1 -0
  62. package/build/consumer/reconcile.js +316 -0
  63. package/build/consumer/reconcile.js.map +1 -0
  64. package/build/consumer/records.d.ts +108 -0
  65. package/build/consumer/records.d.ts.map +1 -0
  66. package/build/consumer/records.js +299 -0
  67. package/build/consumer/records.js.map +1 -0
  68. package/build/consumer/service.d.ts +44 -0
  69. package/build/consumer/service.d.ts.map +1 -0
  70. package/build/consumer/service.js +771 -0
  71. package/build/consumer/service.js.map +1 -0
  72. package/build/consumer/types.d.ts +87 -0
  73. package/build/consumer/types.d.ts.map +1 -0
  74. package/build/consumer/types.js +2 -0
  75. package/build/consumer/types.js.map +1 -0
  76. package/build/consumer/types.local.d.ts +25 -0
  77. package/build/consumer/types.local.d.ts.map +1 -0
  78. package/build/consumer/types.local.js +2 -0
  79. package/build/consumer/types.local.js.map +1 -0
  80. package/build/consumer/withdrawal.d.ts +34 -0
  81. package/build/consumer/withdrawal.d.ts.map +1 -0
  82. package/build/consumer/withdrawal.js +247 -0
  83. package/build/consumer/withdrawal.js.map +1 -0
  84. package/build/entitlement.d.ts +10 -0
  85. package/build/entitlement.d.ts.map +1 -0
  86. package/build/entitlement.js +69 -0
  87. package/build/entitlement.js.map +1 -0
  88. package/build/entrypoints.d.ts +19 -2
  89. package/build/entrypoints.d.ts.map +1 -1
  90. package/build/entrypoints.js +16 -4
  91. package/build/entrypoints.js.map +1 -1
  92. package/build/gate.d.ts +20 -6
  93. package/build/gate.d.ts.map +1 -1
  94. package/build/gate.js +59 -19
  95. package/build/gate.js.map +1 -1
  96. package/build/index.d.ts +20 -3
  97. package/build/index.d.ts.map +1 -1
  98. package/build/index.js +17 -3
  99. package/build/index.js.map +1 -1
  100. package/build/limit.d.ts +11 -0
  101. package/build/limit.d.ts.map +1 -0
  102. package/build/limit.js +48 -0
  103. package/build/limit.js.map +1 -0
  104. package/build/log.d.ts +3 -0
  105. package/build/log.d.ts.map +1 -0
  106. package/build/log.js +4 -0
  107. package/build/log.js.map +1 -0
  108. package/build/observer.d.ts +9 -0
  109. package/build/observer.d.ts.map +1 -1
  110. package/build/observer.js +38 -12
  111. package/build/observer.js.map +1 -1
  112. package/build/plan.d.ts +33 -0
  113. package/build/plan.d.ts.map +1 -0
  114. package/build/plan.js +101 -0
  115. package/build/plan.js.map +1 -0
  116. package/build/plugins/checkout-plugins.d.ts +41 -0
  117. package/build/plugins/checkout-plugins.d.ts.map +1 -0
  118. package/build/plugins/checkout-plugins.js +125 -0
  119. package/build/plugins/checkout-plugins.js.map +1 -0
  120. package/build/plugins/consts.local.d.ts +13 -0
  121. package/build/plugins/consts.local.d.ts.map +1 -0
  122. package/build/plugins/consts.local.js +22 -0
  123. package/build/plugins/consts.local.js.map +1 -0
  124. package/build/plugins/estimate.d.ts +18 -0
  125. package/build/plugins/estimate.d.ts.map +1 -0
  126. package/build/plugins/estimate.js +278 -0
  127. package/build/plugins/estimate.js.map +1 -0
  128. package/build/plugins/events.d.ts +31 -10
  129. package/build/plugins/events.d.ts.map +1 -1
  130. package/build/plugins/events.js +625 -136
  131. package/build/plugins/events.js.map +1 -1
  132. package/build/plugins/fx.d.ts +28 -0
  133. package/build/plugins/fx.d.ts.map +1 -0
  134. package/build/plugins/fx.js +83 -0
  135. package/build/plugins/fx.js.map +1 -0
  136. package/build/plugins/portal.d.ts +40 -0
  137. package/build/plugins/portal.d.ts.map +1 -0
  138. package/build/plugins/portal.js +278 -0
  139. package/build/plugins/portal.js.map +1 -0
  140. package/build/plugins/refunds.d.ts +13 -0
  141. package/build/plugins/refunds.d.ts.map +1 -0
  142. package/build/plugins/refunds.js +80 -0
  143. package/build/plugins/refunds.js.map +1 -0
  144. package/build/plugins/stripe.d.ts +44 -4
  145. package/build/plugins/stripe.d.ts.map +1 -1
  146. package/build/plugins/stripe.js +559 -97
  147. package/build/plugins/stripe.js.map +1 -1
  148. package/build/plugins/types.d.ts +82 -0
  149. package/build/plugins/types.d.ts.map +1 -0
  150. package/build/plugins/types.js +2 -0
  151. package/build/plugins/types.js.map +1 -0
  152. package/build/plugins/types.local.d.ts +76 -0
  153. package/build/plugins/types.local.d.ts.map +1 -0
  154. package/build/plugins/types.local.js +2 -0
  155. package/build/plugins/types.local.js.map +1 -0
  156. package/build/plugins/webhook-manager.d.ts +54 -0
  157. package/build/plugins/webhook-manager.d.ts.map +1 -0
  158. package/build/plugins/webhook-manager.js +242 -0
  159. package/build/plugins/webhook-manager.js.map +1 -0
  160. package/build/reconcile.d.ts +15 -0
  161. package/build/reconcile.d.ts.map +1 -0
  162. package/build/reconcile.js +89 -0
  163. package/build/reconcile.js.map +1 -0
  164. package/build/resource.d.ts +12 -1
  165. package/build/resource.d.ts.map +1 -1
  166. package/build/resource.js +98 -6
  167. package/build/resource.js.map +1 -1
  168. package/build/schemas.d.ts +15 -0
  169. package/build/schemas.d.ts.map +1 -0
  170. package/build/schemas.js +228 -0
  171. package/build/schemas.js.map +1 -0
  172. package/build/service.d.ts +42 -3
  173. package/build/service.d.ts.map +1 -1
  174. package/build/service.js +193 -19
  175. package/build/service.js.map +1 -1
  176. package/build/subscription.d.ts +27 -0
  177. package/build/subscription.d.ts.map +1 -0
  178. package/build/subscription.js +179 -0
  179. package/build/subscription.js.map +1 -0
  180. package/build/sync.d.ts +28 -1
  181. package/build/sync.d.ts.map +1 -1
  182. package/build/sync.js +208 -32
  183. package/build/sync.js.map +1 -1
  184. package/build/types.d.ts +1258 -36
  185. package/build/types.d.ts.map +1 -1
  186. package/build/types.local.d.ts +27 -0
  187. package/build/types.local.d.ts.map +1 -0
  188. package/build/types.local.js +2 -0
  189. package/build/types.local.js.map +1 -0
  190. package/build/usage.d.ts +52 -0
  191. package/build/usage.d.ts.map +1 -0
  192. package/build/usage.js +373 -0
  193. package/build/usage.js.map +1 -0
  194. package/build/utils.d.ts +70 -2
  195. package/build/utils.d.ts.map +1 -1
  196. package/build/utils.js +105 -7
  197. package/build/utils.js.map +1 -1
  198. package/package.json +21 -16
  199. package/src/actions/index.ts +1 -0
  200. package/src/actions/resync-subscriptions.ts +9 -0
  201. package/src/actions/resync.ts +11 -4
  202. package/src/actions/webhook.ts +8 -4
  203. package/src/config.ts +268 -9
  204. package/src/consts.local.ts +62 -0
  205. package/src/consts.ts +100 -36
  206. package/src/consumer/capture.ts +335 -0
  207. package/src/consumer/consts.local.ts +18 -0
  208. package/src/consumer/format.ts +90 -0
  209. package/src/consumer/handlers.ts +200 -0
  210. package/src/consumer/index.ts +6 -0
  211. package/src/consumer/mail.ts +354 -0
  212. package/src/consumer/origin.ts +61 -0
  213. package/src/consumer/reconcile.ts +326 -0
  214. package/src/consumer/records.ts +357 -0
  215. package/src/consumer/service.ts +855 -0
  216. package/src/consumer/types.local.ts +22 -0
  217. package/src/consumer/types.ts +98 -0
  218. package/src/consumer/withdrawal.ts +273 -0
  219. package/src/entitlement.ts +84 -0
  220. package/src/entrypoints.ts +17 -4
  221. package/src/gate.ts +72 -26
  222. package/src/index.ts +27 -3
  223. package/src/limit.ts +54 -0
  224. package/src/log.ts +4 -0
  225. package/src/observer.ts +44 -11
  226. package/src/plan.ts +124 -0
  227. package/src/plugins/checkout-plugins.ts +145 -0
  228. package/src/plugins/consts.local.ts +30 -0
  229. package/src/plugins/estimate.ts +319 -0
  230. package/src/plugins/events.ts +674 -121
  231. package/src/plugins/fx.ts +101 -0
  232. package/src/plugins/portal.ts +313 -0
  233. package/src/plugins/refunds.ts +93 -0
  234. package/src/plugins/stripe.ts +613 -98
  235. package/src/plugins/types.local.ts +62 -0
  236. package/src/plugins/types.ts +87 -0
  237. package/src/plugins/webhook-manager.ts +278 -0
  238. package/src/reconcile.ts +102 -0
  239. package/src/resource.ts +145 -7
  240. package/src/schemas.ts +247 -0
  241. package/src/service.ts +217 -20
  242. package/src/subscription.ts +208 -0
  243. package/src/sync.ts +240 -30
  244. package/src/types.local.ts +20 -0
  245. package/src/types.ts +1367 -32
  246. package/src/usage.ts +443 -0
  247. package/src/utils.ts +175 -14
  248. package/tests/checkout-consumer.spec.ts +367 -0
  249. package/tests/checkout-locks.spec.ts +160 -0
  250. package/tests/checkout-plugins.spec.ts +164 -0
  251. package/tests/checkout.spec.ts +204 -91
  252. package/tests/consumer-events.spec.ts +217 -0
  253. package/tests/consumer-fixtures.ts +132 -0
  254. package/tests/consumer-ops.spec.ts +351 -0
  255. package/tests/consumer-rights.integration.spec.ts +149 -0
  256. package/tests/consumer-rights.spec.ts +577 -0
  257. package/tests/context.ts +109 -0
  258. package/tests/entitlement.spec.ts +139 -0
  259. package/tests/estimate.spec.ts +274 -0
  260. package/tests/events.spec.ts +356 -0
  261. package/tests/fake-stripe.ts +975 -0
  262. package/tests/gate-handlers.spec.ts +120 -0
  263. package/tests/gate.spec.ts +62 -72
  264. package/tests/gateway-options.spec.ts +181 -0
  265. package/tests/limit-gate.spec.ts +68 -0
  266. package/tests/portal.spec.ts +200 -0
  267. package/tests/protocol.spec.ts +17 -3
  268. package/tests/sync.spec.ts +114 -0
  269. package/tests/usage.integration.spec.ts +101 -0
  270. package/tests/usage.spec.ts +171 -0
  271. package/tests/webhook-manager.spec.ts +152 -0
  272. package/build/model.d.ts +0 -6
  273. package/build/model.d.ts.map +0 -1
  274. package/build/model.js +0 -33
  275. package/build/model.js.map +0 -1
  276. package/src/model.ts +0 -37
package/README.md CHANGED
@@ -1,42 +1,163 @@
1
1
  # @owlmeans/server-payment
2
2
 
3
- Public server-side payment primitives for OwlMeans applications. The package registers payment
4
- resources, the entitlement gate, protocol-bound webhook/resync entrypoints, and a Stripe gateway
5
- supporting amount-priced consumables, quantity-priced consumables, and subscriptions.
3
+ Server-side payments for OwlMeans applications: a Stripe gateway embedded in the backend
4
+ (amount and quantity checkout, subscriptions, the customer portal), a subscription store that
5
+ understands the whole Stripe lifecycle, a usage ledger for counted plan limits, the entitlement
6
+ service that resolves what an organization entity may do, and the two gate services that refuse a
7
+ request before its handler runs. The contracts — plans, limits, promos, the entitlement view, the
8
+ refusal errors — live in `@owlmeans/payment`.
6
9
 
7
- Applications declare products and plans during configuration, mount `paymentGateEntrypoints`, and
8
- register completion callbacks on the payment observer. Amount policies are validated at declaration
9
- time. Stripe Checkout is always authoritative: completion is idempotent by session id and an
10
- amount checkout is fulfilled only after its paid currency and subtotal match its signed-in metadata.
10
+ ## Declare the catalogue
11
11
 
12
12
  ```typescript
13
- import { CheckoutPricingMode, PlanDuration, ProductType } from '@owlmeans/payment'
13
+ import { CheckoutPricingMode, LimitKind, LimitWindow, PlanDuration, ProductType } from '@owlmeans/payment'
14
14
  import {
15
- appendPaymentGatewayService, declarePaymentPlan, declarePaymentProduct, observer,
15
+ declarePaymentPlan, declarePaymentProduct, portalBranding, stripeSecrets,
16
16
  } from '@owlmeans/server-payment'
17
17
 
18
- declarePaymentProduct(cfg, {
19
- sku: 'app-credits', type: ProductType.Consumable, services: ['app'], gateways: ['stripe'],
20
- name: 'Credits',
18
+ stripeSecrets(cfg, { api: '/secrets/stripe-key' }) // `webhook` is an optional override
19
+ portalBranding(cfg, { returnUrl: 'https://app.example.com/billing', headline: 'Example' })
20
+
21
+ declarePaymentProduct(cfg, { sku: 'app-plans', type: ProductType.Service, services: ['app'], name: 'Plans' })
22
+ declarePaymentPlan(cfg, {
23
+ productSku: 'app-plans', sku: 'free', duration: PlanDuration.Monthly, rank: 0, free: true, price: 0,
24
+ capabilities: [{ scope: 'feature', permissions: { basic: true } }],
25
+ limits: { seats: { kind: LimitKind.Occupancy, limit: 1 }, exports: { kind: LimitKind.Window, window: LimitWindow.Month, limit: 3 } },
21
26
  })
22
27
  declarePaymentPlan(cfg, {
23
- productSku: 'app-credits', sku: 'app-credit-unit', duration: PlanDuration.Consumable,
24
- price: 0.02, pricingMode: CheckoutPricingMode.Amount,
25
- amountPolicy: {
26
- currency: 'usd', minimumMinor: 500, maximumMinor: 50_000, defaultMinor: 1_000,
27
- presetsMinor: [1_000, 2_000, 5_000, 10_000], fixedMinor: 0, rateBps: 200,
28
- },
28
+ productSku: 'app-plans', sku: 'pro-monthly', duration: PlanDuration.Monthly, rank: 10, price: 20,
29
+ recurring: { interval: 'month' },
30
+ capabilities: [{ scope: 'feature', permissions: { basic: true, whitelabel: true } }],
31
+ limits: { seats: { kind: LimitKind.Occupancy, limit: 5 }, exports: { kind: LimitKind.Window, window: LimitWindow.Day, limit: 20 } },
29
32
  })
30
33
 
31
- appendPaymentGatewayService(context)
32
- observer(context).onTopUp(async completion => {
33
- if (completion.mode === 'amount') await credit(completion.entityId, completion.amountMinor)
34
+ declarePaymentProduct(cfg, { sku: 'app-credits', type: ProductType.Consumable, services: ['app'], name: 'Credits' })
35
+ declarePaymentPlan(cfg, {
36
+ productSku: 'app-credits', sku: 'app-credit-unit', duration: PlanDuration.Consumable, price: 0.02,
37
+ pricingMode: CheckoutPricingMode.Amount,
38
+ amountPolicy: { currency: 'usd', minimumMinor: 500, maximumMinor: 50_000, defaultMinor: 1_000,
39
+ presetsMinor: [1_000, 5_000], fixedMinor: 0, rateBps: 200 },
34
40
  })
35
41
  ```
36
42
 
37
- Mount `paymentGateEntrypoints` in the server entrypoint list. `gateway(context).createLink(...)`
38
- accepts the stable organization `entityId` only after the application has resolved it at its
39
- authenticated boundary; public protocol bodies use `entitySlug`.
43
+ `declarePaymentPlan` validates each declaration before recording it; `assertPlanDeclarations` checks
44
+ the catalogue as a whole (one free plan per rank, no two paid plans of a product at one rank) and runs
45
+ when the gateway initializes. A limit key may use a different kind on different plans — each kind keeps
46
+ its own counter window, so a lifetime count still sticks to the entity when it moves to a monthly plan.
47
+
48
+ ## Wire the services
49
+
50
+ ```typescript
51
+ import {
52
+ appendPaymentGatewayService, entitlements, gateway, observer, paymentGateEntrypoints,
53
+ } from '@owlmeans/server-payment'
54
+
55
+ appendPaymentGatewayService(context) // the process that owns Stripe
56
+ appendPaymentGatewayService(context, { manage: false }) // a process that only reads entitlements
57
+ // several managed processes on one database — the same `owner` and `webhookService` in each:
58
+ appendPaymentGatewayService(context, { owner: 'app-api', webhookService: 'app-hooks' }) // receives the webhook
59
+ appendPaymentGatewayService(context, { owner: 'app-api', webhookService: 'app-hooks', bootstrap: false }) // checkout and portal
60
+ export const serverBindings = [...paymentGateEntrypoints]
61
+
62
+ observer(context).onTopUp(async completion => { /* credit, idempotent by completion.externalId */ })
63
+ observer(context).onSubscription(async event => { /* idempotent by event.eventKey */ })
64
+ observer(context).onRefund(async event => { /* claw back, idempotent by event.eventKey */ })
65
+ observer(context).onDispute(async event => { /* … */ })
66
+ observer(context).onPaymentFailed(async event => { /* notify */ })
67
+ ```
68
+
69
+ A managed gateway brings Stripe to the declared state at boot — products and prices, one
70
+ customer-portal configuration, one webhook endpoint at this deployment's public URL (its signing
71
+ secret stored in `payment-webhook`) — and each step is fingerprinted, so an unchanged deployment
72
+ makes no Stripe call.
73
+
74
+ Three gateway options place that state when a deployment runs several processes:
75
+
76
+ | Option | Default | Decides |
77
+ |---|---|---|
78
+ | `webhookService` | `cfg.service` | the `cfg.services` alias whose host and base form the webhook URL — also the portal's deployment key |
79
+ | `owner` | `cfg.service` | the key of the `payment-webhook` rows, the signing-secret lookup, the `portal:<owner>` fingerprint and the Stripe labels |
80
+ | `bootstrap` | `manage` | whether this process runs the bootstrap at boot; a forced one (`resync`, `bootstrapStripe(ctx, stripe, { force: true })`) runs in any managed process |
81
+
82
+ Give every process of the database the same `owner` and `webhookService` (the process that receives
83
+ the webhook), and keep `bootstrap` on that receiver only. Changing `webhookService` moves the
84
+ endpoint on the next bootstrap: created at the new URL, the endpoint of the owner's former URL
85
+ deleted, a new portal configuration for the new deployment key.
86
+
87
+ Several deployments of one application may share a Stripe account (typically every test-mode
88
+ deployment), each with its own database and URL — and a deployment's identity is its webhook URL,
89
+ even an undeliverable local one.
90
+
91
+ - **Webhook endpoint.** A deployment owns exactly the endpoint at its own URL: it deletes an
92
+ endpoint only when its own `payment-webhook` rows name it — the endpoint of a URL it has moved away
93
+ from — and never one it merely finds at another URL, whatever its metadata says. The endpoint of a
94
+ retired deployment is removed by hand in the Stripe dashboard; one deleted from outside comes back
95
+ on the next forced `resync`.
96
+ - **Portal configuration.** Each deployment has its own, tagged
97
+ `{ owlmeans: 'payment', service: <owner>, deployment: <webhook URL> }`. It updates the configuration its
98
+ `portal:<owner>` fingerprint row names, unless that one is tagged for another deployment, and
99
+ without a row adopts only a configuration carrying exactly its own tag. Stripe cannot delete a
100
+ portal configuration, so one a deployment can no longer identify stays in the account and a new
101
+ one is created.
102
+
103
+ ## Use it
104
+
105
+ ```typescript
106
+ // Checkout and the portal (in-process; resolve the stable entityId at your authenticated boundary)
107
+ const url = await gateway(ctx).createLink(ctx, { productSku: 'app-plans', planSku: 'pro-monthly', entityId, service: 'app', successUrl })
108
+ const portal = await gateway(ctx).portalLink(ctx, entityId, { flow: PortalFlow.Change, planSku: 'pro-monthly', returnUrl })
109
+ await gateway(ctx).grantInternalPlan(ctx, entityId, 'free') // when the organization is created
110
+
111
+ // What the entity may do
112
+ const view = await entitlements(ctx).entitlements(entityId) // the EntitlementView a UI renders
113
+ await entitlements(ctx).hasCapability(entityId, 'feature:whitelabel')
114
+
115
+ // Spend a counted allowance — key it by the record it pays for
116
+ await entitlements(ctx).consume({ entityId, limitKey: 'exports', eventKey: `export:${reportId}`, ref: reportId })
117
+ await entitlements(ctx).release({ entityId, limitKey: 'exports', eventKey: `export:${reportId}` })
118
+
119
+ // Periodic repair
120
+ await reconcileAll(ctx, { freePlanSku: 'free' })
121
+ await entitlements(ctx).reconcileOccupancy(entityId, 'seats', liveSeatCount)
122
+ ```
123
+
124
+ Routes declare what they need; the gates refuse before the handler:
125
+
126
+ ```typescript
127
+ protocol(route(...), contract(...), { gate: { alias: ENTITLEMENT_GATE, params: ['feature:whitelabel'] } })
128
+ protocol(route(...), contract(...), { gate: { alias: LIMIT_GATE, params: [formatLimitParam('seats')] } })
129
+ ```
130
+
131
+ `CapabilityRequired` and `LimitExhausted` extend `AuthForbidden`, so an HTTP boundary answers 403.
132
+ The limit gate never consumes; the handler does.
133
+
134
+ ## Routes
135
+
136
+ `paymentGateEntrypoints` binds the immutable `paymentGate` tree: the public Stripe webhook
137
+ (`POST /payment-gate/webhook/:paygate`, verified by signature over the raw body; a missing or
138
+ unverifiable signature is a `PaygateSignatureError`, answered 400), and two
139
+ Ed25519-guarded repairs — `resync` (products, portal, webhook endpoint) and `resyncSubscriptions`
140
+ (re-reads every live Stripe subscription, answering `{ scanned, updated }`).
141
+
142
+ An application that declares the gate itself — its own aliases, pinned to the service that receives
143
+ the webhook — binds its declarations to the same actions instead, and registers nothing of
144
+ `paymentGate`:
145
+
146
+ ```typescript
147
+ import { bind } from '@owlmeans/server-app'
148
+ import { paymentGateHandlers } from '@owlmeans/server-payment'
149
+
150
+ export const hookBindings = [
151
+ bind(own.base),
152
+ bind(own.webhook, paymentGateHandlers.webhook),
153
+ bind(own.resync, paymentGateHandlers.resync),
154
+ bind(own.resyncSubscriptions, paymentGateHandlers.resyncSubscriptions),
155
+ ]
156
+ ```
157
+
158
+ Each handler runs in the context of the entrypoint it is bound to. The declarations keep
159
+ `paymentGate`'s paths (the webhook URL registered with Stripe is formed from them), contracts and
160
+ guards; bind each with `bind` — `bindAll` pairs a handler only with the declaration it was made for.
40
161
 
41
162
  <!-- owlmeans:agent-guidance:start -->
42
163
  ## Agent guidance
@@ -46,7 +167,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
46
167
  your project's skill store (`.agents/skills/`):
47
168
 
48
169
  ```sh
49
- npx @owlmeans/agent-skills@^0.1.18-rc.18
170
+ npx @owlmeans/agent-skills@^0.1.18-rc.48
50
171
  ```
51
172
 
52
173
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/server-payment",
4
- "version": "0.1.18-rc.3",
5
- "generatedAt": "2026-09-12T13:35:38.915Z",
4
+ "version": "0.1.18-rc.30",
5
+ "generatedAt": "2026-10-05T08:32:19.125Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {