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

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 (536) hide show
  1. package/README.md +157 -25
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/server-payment/SKILL.md +593 -53
  4. package/build/access/types.d.ts +46 -0
  5. package/build/access/types.d.ts.map +1 -0
  6. package/build/access/types.js +2 -0
  7. package/build/access/types.js.map +1 -0
  8. package/build/access.d.ts +6 -0
  9. package/build/access.d.ts.map +1 -0
  10. package/build/access.js +45 -0
  11. package/build/access.js.map +1 -0
  12. package/build/actions/index.d.ts +1 -0
  13. package/build/actions/index.d.ts.map +1 -1
  14. package/build/actions/index.js +1 -0
  15. package/build/actions/index.js.map +1 -1
  16. package/build/actions/resync-subscriptions.d.ts +3 -0
  17. package/build/actions/resync-subscriptions.d.ts.map +1 -0
  18. package/build/actions/resync-subscriptions.js +7 -0
  19. package/build/actions/resync-subscriptions.js.map +1 -0
  20. package/build/actions/resync.d.ts +6 -1
  21. package/build/actions/resync.d.ts.map +1 -1
  22. package/build/actions/resync.js +15 -5
  23. package/build/actions/resync.js.map +1 -1
  24. package/build/actions/webhook.d.ts +2 -1
  25. package/build/actions/webhook.d.ts.map +1 -1
  26. package/build/actions/webhook.js +10 -4
  27. package/build/actions/webhook.js.map +1 -1
  28. package/build/bootstrap/types.d.ts +16 -0
  29. package/build/bootstrap/types.d.ts.map +1 -0
  30. package/build/bootstrap/types.js +2 -0
  31. package/build/bootstrap/types.js.map +1 -0
  32. package/build/bootstrap.d.ts +6 -0
  33. package/build/bootstrap.d.ts.map +1 -0
  34. package/build/bootstrap.js +26 -0
  35. package/build/bootstrap.js.map +1 -0
  36. package/build/catalogue/types.d.ts +38 -0
  37. package/build/catalogue/types.d.ts.map +1 -0
  38. package/build/catalogue/types.js +2 -0
  39. package/build/catalogue/types.js.map +1 -0
  40. package/build/catalogue.d.ts +6 -0
  41. package/build/catalogue.d.ts.map +1 -0
  42. package/build/catalogue.js +110 -0
  43. package/build/catalogue.js.map +1 -0
  44. package/build/commit/types.d.ts +22 -0
  45. package/build/commit/types.d.ts.map +1 -0
  46. package/build/commit/types.js +2 -0
  47. package/build/commit/types.js.map +1 -0
  48. package/build/commit.d.ts +6 -0
  49. package/build/commit.d.ts.map +1 -0
  50. package/build/commit.js +125 -0
  51. package/build/commit.js.map +1 -0
  52. package/build/config.d.ts +44 -5
  53. package/build/config.d.ts.map +1 -1
  54. package/build/config.js +216 -10
  55. package/build/config.js.map +1 -1
  56. package/build/consts.d.ts +74 -16
  57. package/build/consts.d.ts.map +1 -1
  58. package/build/consts.js +86 -23
  59. package/build/consts.js.map +1 -1
  60. package/build/consts.local.d.ts +94 -0
  61. package/build/consts.local.d.ts.map +1 -0
  62. package/build/consts.local.js +44 -0
  63. package/build/consts.local.js.map +1 -0
  64. package/build/consumer/cancellation/types.d.ts +11 -0
  65. package/build/consumer/cancellation/types.d.ts.map +1 -0
  66. package/build/consumer/cancellation/types.js +2 -0
  67. package/build/consumer/cancellation/types.js.map +1 -0
  68. package/build/consumer/cancellation.d.ts +6 -0
  69. package/build/consumer/cancellation.d.ts.map +1 -0
  70. package/build/consumer/cancellation.js +33 -0
  71. package/build/consumer/cancellation.js.map +1 -0
  72. package/build/consumer/capture/types.d.ts +50 -0
  73. package/build/consumer/capture/types.d.ts.map +1 -0
  74. package/build/consumer/capture/types.js +2 -0
  75. package/build/consumer/capture/types.js.map +1 -0
  76. package/build/consumer/capture.d.ts +6 -0
  77. package/build/consumer/capture.d.ts.map +1 -0
  78. package/build/consumer/capture.js +284 -0
  79. package/build/consumer/capture.js.map +1 -0
  80. package/build/consumer/consts.local.d.ts +10 -0
  81. package/build/consumer/consts.local.d.ts.map +1 -0
  82. package/build/consumer/consts.local.js +11 -0
  83. package/build/consumer/consts.local.js.map +1 -0
  84. package/build/consumer/format/types.d.ts +30 -0
  85. package/build/consumer/format/types.d.ts.map +1 -0
  86. package/build/consumer/format/types.js +2 -0
  87. package/build/consumer/format/types.js.map +1 -0
  88. package/build/consumer/format.d.ts +4 -0
  89. package/build/consumer/format.d.ts.map +1 -0
  90. package/build/consumer/format.js +73 -0
  91. package/build/consumer/format.js.map +1 -0
  92. package/build/consumer/handlers.d.ts +21 -0
  93. package/build/consumer/handlers.d.ts.map +1 -0
  94. package/build/consumer/handlers.js +174 -0
  95. package/build/consumer/handlers.js.map +1 -0
  96. package/build/consumer/index.d.ts +10 -0
  97. package/build/consumer/index.d.ts.map +1 -0
  98. package/build/consumer/index.js +6 -0
  99. package/build/consumer/index.js.map +1 -0
  100. package/build/consumer/mail/types.d.ts +29 -0
  101. package/build/consumer/mail/types.d.ts.map +1 -0
  102. package/build/consumer/mail/types.js +2 -0
  103. package/build/consumer/mail/types.js.map +1 -0
  104. package/build/consumer/mail.d.ts +6 -0
  105. package/build/consumer/mail.d.ts.map +1 -0
  106. package/build/consumer/mail.js +313 -0
  107. package/build/consumer/mail.js.map +1 -0
  108. package/build/consumer/observers/types.d.ts +15 -0
  109. package/build/consumer/observers/types.d.ts.map +1 -0
  110. package/build/consumer/observers/types.js +2 -0
  111. package/build/consumer/observers/types.js.map +1 -0
  112. package/build/consumer/observers.d.ts +6 -0
  113. package/build/consumer/observers.d.ts.map +1 -0
  114. package/build/consumer/observers.js +74 -0
  115. package/build/consumer/observers.js.map +1 -0
  116. package/build/consumer/origin/types.d.ts +17 -0
  117. package/build/consumer/origin/types.d.ts.map +1 -0
  118. package/build/consumer/origin/types.js +2 -0
  119. package/build/consumer/origin/types.js.map +1 -0
  120. package/build/consumer/origin.d.ts +8 -0
  121. package/build/consumer/origin.d.ts.map +1 -0
  122. package/build/consumer/origin.js +43 -0
  123. package/build/consumer/origin.js.map +1 -0
  124. package/build/consumer/reconcile/types.d.ts +13 -0
  125. package/build/consumer/reconcile/types.d.ts.map +1 -0
  126. package/build/consumer/reconcile/types.js +2 -0
  127. package/build/consumer/reconcile/types.js.map +1 -0
  128. package/build/consumer/reconcile.d.ts +5 -0
  129. package/build/consumer/reconcile.d.ts.map +1 -0
  130. package/build/consumer/reconcile.js +320 -0
  131. package/build/consumer/reconcile.js.map +1 -0
  132. package/build/consumer/records/types.d.ts +66 -0
  133. package/build/consumer/records/types.d.ts.map +1 -0
  134. package/build/consumer/records/types.js +2 -0
  135. package/build/consumer/records/types.js.map +1 -0
  136. package/build/consumer/records.d.ts +6 -0
  137. package/build/consumer/records.d.ts.map +1 -0
  138. package/build/consumer/records.js +243 -0
  139. package/build/consumer/records.js.map +1 -0
  140. package/build/consumer/service.d.ts +28 -0
  141. package/build/consumer/service.d.ts.map +1 -0
  142. package/build/consumer/service.js +706 -0
  143. package/build/consumer/service.js.map +1 -0
  144. package/build/consumer/types.d.ts +84 -0
  145. package/build/consumer/types.d.ts.map +1 -0
  146. package/build/consumer/types.js +2 -0
  147. package/build/consumer/types.js.map +1 -0
  148. package/build/consumer/types.local.d.ts +26 -0
  149. package/build/consumer/types.local.d.ts.map +1 -0
  150. package/build/consumer/types.local.js +2 -0
  151. package/build/consumer/types.local.js.map +1 -0
  152. package/build/consumer/withdrawal/types.d.ts +36 -0
  153. package/build/consumer/withdrawal/types.d.ts.map +1 -0
  154. package/build/consumer/withdrawal/types.js +2 -0
  155. package/build/consumer/withdrawal/types.js.map +1 -0
  156. package/build/consumer/withdrawal.d.ts +6 -0
  157. package/build/consumer/withdrawal.d.ts.map +1 -0
  158. package/build/consumer/withdrawal.js +237 -0
  159. package/build/consumer/withdrawal.js.map +1 -0
  160. package/build/entitlement.d.ts +4 -0
  161. package/build/entitlement.d.ts.map +1 -0
  162. package/build/entitlement.js +72 -0
  163. package/build/entitlement.js.map +1 -0
  164. package/build/entrypoints.d.ts +19 -2
  165. package/build/entrypoints.d.ts.map +1 -1
  166. package/build/entrypoints.js +16 -4
  167. package/build/entrypoints.js.map +1 -1
  168. package/build/gate/request.d.ts +4 -0
  169. package/build/gate/request.d.ts.map +1 -0
  170. package/build/gate/request.js +17 -0
  171. package/build/gate/request.js.map +1 -0
  172. package/build/gate/types.d.ts +15 -0
  173. package/build/gate/types.d.ts.map +1 -0
  174. package/build/gate/types.js +2 -0
  175. package/build/gate/types.js.map +1 -0
  176. package/build/gate.d.ts +13 -9
  177. package/build/gate.d.ts.map +1 -1
  178. package/build/gate.js +36 -21
  179. package/build/gate.js.map +1 -1
  180. package/build/index.d.ts +50 -3
  181. package/build/index.d.ts.map +1 -1
  182. package/build/index.js +29 -3
  183. package/build/index.js.map +1 -1
  184. package/build/limit.d.ts +11 -0
  185. package/build/limit.d.ts.map +1 -0
  186. package/build/limit.js +48 -0
  187. package/build/limit.js.map +1 -0
  188. package/build/log.d.ts +3 -0
  189. package/build/log.d.ts.map +1 -0
  190. package/build/log.js +4 -0
  191. package/build/log.js.map +1 -0
  192. package/build/models/declarations/types.d.ts +22 -0
  193. package/build/models/declarations/types.d.ts.map +1 -0
  194. package/build/models/declarations/types.js +2 -0
  195. package/build/models/declarations/types.js.map +1 -0
  196. package/build/models/declarations.d.ts +4 -0
  197. package/build/models/declarations.d.ts.map +1 -0
  198. package/build/models/declarations.js +26 -0
  199. package/build/models/declarations.js.map +1 -0
  200. package/build/models/purchase/types.d.ts +13 -0
  201. package/build/models/purchase/types.d.ts.map +1 -0
  202. package/build/models/purchase/types.js +2 -0
  203. package/build/models/purchase/types.js.map +1 -0
  204. package/build/models/purchase.d.ts +4 -0
  205. package/build/models/purchase.d.ts.map +1 -0
  206. package/build/models/purchase.js +37 -0
  207. package/build/models/purchase.js.map +1 -0
  208. package/build/observer.d.ts +9 -0
  209. package/build/observer.d.ts.map +1 -1
  210. package/build/observer.js +38 -12
  211. package/build/observer.js.map +1 -1
  212. package/build/plan/types.d.ts +21 -0
  213. package/build/plan/types.d.ts.map +1 -0
  214. package/build/plan/types.js +2 -0
  215. package/build/plan/types.js.map +1 -0
  216. package/build/plan.d.ts +7 -0
  217. package/build/plan.d.ts.map +1 -0
  218. package/build/plan.js +29 -0
  219. package/build/plan.js.map +1 -0
  220. package/build/plugins/checkout-plugins/types.d.ts +25 -0
  221. package/build/plugins/checkout-plugins/types.d.ts.map +1 -0
  222. package/build/plugins/checkout-plugins/types.js +2 -0
  223. package/build/plugins/checkout-plugins/types.js.map +1 -0
  224. package/build/plugins/checkout-plugins.d.ts +12 -0
  225. package/build/plugins/checkout-plugins.d.ts.map +1 -0
  226. package/build/plugins/checkout-plugins.js +72 -0
  227. package/build/plugins/checkout-plugins.js.map +1 -0
  228. package/build/plugins/checkout-policy/types.d.ts +18 -0
  229. package/build/plugins/checkout-policy/types.d.ts.map +1 -0
  230. package/build/plugins/checkout-policy/types.js +2 -0
  231. package/build/plugins/checkout-policy/types.js.map +1 -0
  232. package/build/plugins/checkout-policy.d.ts +4 -0
  233. package/build/plugins/checkout-policy.d.ts.map +1 -0
  234. package/build/plugins/checkout-policy.js +26 -0
  235. package/build/plugins/checkout-policy.js.map +1 -0
  236. package/build/plugins/checkout-registry/types.d.ts +7 -0
  237. package/build/plugins/checkout-registry/types.d.ts.map +1 -0
  238. package/build/plugins/checkout-registry/types.js +2 -0
  239. package/build/plugins/checkout-registry/types.js.map +1 -0
  240. package/build/plugins/checkout-registry.d.ts +8 -0
  241. package/build/plugins/checkout-registry.d.ts.map +1 -0
  242. package/build/plugins/checkout-registry.js +21 -0
  243. package/build/plugins/checkout-registry.js.map +1 -0
  244. package/build/plugins/consts.local.d.ts +13 -0
  245. package/build/plugins/consts.local.d.ts.map +1 -0
  246. package/build/plugins/consts.local.js +22 -0
  247. package/build/plugins/consts.local.js.map +1 -0
  248. package/build/plugins/customer-lock/types.d.ts +6 -0
  249. package/build/plugins/customer-lock/types.d.ts.map +1 -0
  250. package/build/plugins/customer-lock/types.js +2 -0
  251. package/build/plugins/customer-lock/types.js.map +1 -0
  252. package/build/plugins/customer-lock.d.ts +5 -0
  253. package/build/plugins/customer-lock.d.ts.map +1 -0
  254. package/build/plugins/customer-lock.js +22 -0
  255. package/build/plugins/customer-lock.js.map +1 -0
  256. package/build/plugins/estimate/cache.d.ts +4 -0
  257. package/build/plugins/estimate/cache.d.ts.map +1 -0
  258. package/build/plugins/estimate/cache.js +7 -0
  259. package/build/plugins/estimate/cache.js.map +1 -0
  260. package/build/plugins/estimate/types.d.ts +27 -0
  261. package/build/plugins/estimate/types.d.ts.map +1 -0
  262. package/build/plugins/estimate/types.js +2 -0
  263. package/build/plugins/estimate/types.js.map +1 -0
  264. package/build/plugins/estimate.d.ts +6 -0
  265. package/build/plugins/estimate.d.ts.map +1 -0
  266. package/build/plugins/estimate.js +264 -0
  267. package/build/plugins/estimate.js.map +1 -0
  268. package/build/plugins/events/types.d.ts +12 -0
  269. package/build/plugins/events/types.d.ts.map +1 -0
  270. package/build/plugins/events/types.js +2 -0
  271. package/build/plugins/events/types.js.map +1 -0
  272. package/build/plugins/events.d.ts +6 -13
  273. package/build/plugins/events.d.ts.map +1 -1
  274. package/build/plugins/events.js +441 -144
  275. package/build/plugins/events.js.map +1 -1
  276. package/build/plugins/fx/types.d.ts +31 -0
  277. package/build/plugins/fx/types.d.ts.map +1 -0
  278. package/build/plugins/fx/types.js +2 -0
  279. package/build/plugins/fx/types.js.map +1 -0
  280. package/build/plugins/fx.d.ts +6 -0
  281. package/build/plugins/fx.d.ts.map +1 -0
  282. package/build/plugins/fx.js +82 -0
  283. package/build/plugins/fx.js.map +1 -0
  284. package/build/plugins/portal/types.d.ts +44 -0
  285. package/build/plugins/portal/types.d.ts.map +1 -0
  286. package/build/plugins/portal/types.js +2 -0
  287. package/build/plugins/portal/types.js.map +1 -0
  288. package/build/plugins/portal.d.ts +6 -0
  289. package/build/plugins/portal.d.ts.map +1 -0
  290. package/build/plugins/portal.js +255 -0
  291. package/build/plugins/portal.js.map +1 -0
  292. package/build/plugins/refunds/types.d.ts +35 -0
  293. package/build/plugins/refunds/types.d.ts.map +1 -0
  294. package/build/plugins/refunds/types.js +2 -0
  295. package/build/plugins/refunds/types.js.map +1 -0
  296. package/build/plugins/refunds.d.ts +6 -0
  297. package/build/plugins/refunds.d.ts.map +1 -0
  298. package/build/plugins/refunds.js +81 -0
  299. package/build/plugins/refunds.js.map +1 -0
  300. package/build/plugins/session/types.d.ts +32 -0
  301. package/build/plugins/session/types.d.ts.map +1 -0
  302. package/build/plugins/session/types.js +2 -0
  303. package/build/plugins/session/types.js.map +1 -0
  304. package/build/plugins/session.d.ts +4 -0
  305. package/build/plugins/session.d.ts.map +1 -0
  306. package/build/plugins/session.js +58 -0
  307. package/build/plugins/session.js.map +1 -0
  308. package/build/plugins/status.d.ts +8 -0
  309. package/build/plugins/status.d.ts.map +1 -0
  310. package/build/plugins/status.js +21 -0
  311. package/build/plugins/status.js.map +1 -0
  312. package/build/plugins/stripe/types.d.ts +20 -0
  313. package/build/plugins/stripe/types.d.ts.map +1 -0
  314. package/build/plugins/stripe/types.js +2 -0
  315. package/build/plugins/stripe/types.js.map +1 -0
  316. package/build/plugins/stripe.d.ts +5 -13
  317. package/build/plugins/stripe.d.ts.map +1 -1
  318. package/build/plugins/stripe.js +481 -129
  319. package/build/plugins/stripe.js.map +1 -1
  320. package/build/plugins/subscriptions/types.d.ts +41 -0
  321. package/build/plugins/subscriptions/types.d.ts.map +1 -0
  322. package/build/plugins/subscriptions/types.js +2 -0
  323. package/build/plugins/subscriptions/types.js.map +1 -0
  324. package/build/plugins/subscriptions.d.ts +6 -0
  325. package/build/plugins/subscriptions.d.ts.map +1 -0
  326. package/build/plugins/subscriptions.js +169 -0
  327. package/build/plugins/subscriptions.js.map +1 -0
  328. package/build/plugins/types.d.ts +7 -0
  329. package/build/plugins/types.d.ts.map +1 -0
  330. package/build/plugins/types.js +2 -0
  331. package/build/plugins/types.js.map +1 -0
  332. package/build/plugins/types.local.d.ts +75 -0
  333. package/build/plugins/types.local.d.ts.map +1 -0
  334. package/build/plugins/types.local.js +2 -0
  335. package/build/plugins/types.local.js.map +1 -0
  336. package/build/plugins/webhook-manager/types.d.ts +62 -0
  337. package/build/plugins/webhook-manager/types.d.ts.map +1 -0
  338. package/build/plugins/webhook-manager/types.js +2 -0
  339. package/build/plugins/webhook-manager/types.js.map +1 -0
  340. package/build/plugins/webhook-manager.d.ts +8 -0
  341. package/build/plugins/webhook-manager.d.ts.map +1 -0
  342. package/build/plugins/webhook-manager.js +245 -0
  343. package/build/plugins/webhook-manager.js.map +1 -0
  344. package/build/protocol.d.ts +13 -0
  345. package/build/protocol.d.ts.map +1 -0
  346. package/build/protocol.js +17 -0
  347. package/build/protocol.js.map +1 -0
  348. package/build/reconcile/types.d.ts +17 -0
  349. package/build/reconcile/types.d.ts.map +1 -0
  350. package/build/reconcile/types.js +2 -0
  351. package/build/reconcile/types.js.map +1 -0
  352. package/build/reconcile.d.ts +12 -0
  353. package/build/reconcile.d.ts.map +1 -0
  354. package/build/reconcile.js +91 -0
  355. package/build/reconcile.js.map +1 -0
  356. package/build/resource.d.ts +12 -1
  357. package/build/resource.d.ts.map +1 -1
  358. package/build/resource.js +97 -6
  359. package/build/resource.js.map +1 -1
  360. package/build/schemas.d.ts +19 -0
  361. package/build/schemas.d.ts.map +1 -0
  362. package/build/schemas.js +244 -0
  363. package/build/schemas.js.map +1 -0
  364. package/build/service.d.ts +28 -3
  365. package/build/service.d.ts.map +1 -1
  366. package/build/service.js +140 -19
  367. package/build/service.js.map +1 -1
  368. package/build/subscription/types.d.ts +21 -0
  369. package/build/subscription/types.d.ts.map +1 -0
  370. package/build/subscription/types.js +2 -0
  371. package/build/subscription/types.js.map +1 -0
  372. package/build/subscription.d.ts +4 -0
  373. package/build/subscription.d.ts.map +1 -0
  374. package/build/subscription.js +84 -0
  375. package/build/subscription.js.map +1 -0
  376. package/build/sync/types.d.ts +25 -0
  377. package/build/sync/types.d.ts.map +1 -0
  378. package/build/sync/types.js +2 -0
  379. package/build/sync/types.js.map +1 -0
  380. package/build/sync.d.ts +4 -3
  381. package/build/sync.d.ts.map +1 -1
  382. package/build/sync.js +231 -79
  383. package/build/sync.js.map +1 -1
  384. package/build/types.d.ts +1258 -36
  385. package/build/types.d.ts.map +1 -1
  386. package/build/types.local.d.ts +22 -0
  387. package/build/types.local.d.ts.map +1 -0
  388. package/build/types.local.js +2 -0
  389. package/build/types.local.js.map +1 -0
  390. package/build/usage/types.d.ts +56 -0
  391. package/build/usage/types.d.ts.map +1 -0
  392. package/build/usage/types.js +2 -0
  393. package/build/usage/types.js.map +1 -0
  394. package/build/usage.d.ts +6 -0
  395. package/build/usage.d.ts.map +1 -0
  396. package/build/usage.js +357 -0
  397. package/build/usage.js.map +1 -0
  398. package/build/utils/types.d.ts +31 -0
  399. package/build/utils/types.d.ts.map +1 -0
  400. package/build/utils/types.js +2 -0
  401. package/build/utils/types.js.map +1 -0
  402. package/build/utils.d.ts +21 -7
  403. package/build/utils.d.ts.map +1 -1
  404. package/build/utils.js +56 -17
  405. package/build/utils.js.map +1 -1
  406. package/package.json +21 -16
  407. package/src/access/types.ts +52 -0
  408. package/src/access.ts +86 -0
  409. package/src/actions/index.ts +1 -0
  410. package/src/actions/resync-subscriptions.ts +10 -0
  411. package/src/actions/resync.ts +14 -5
  412. package/src/actions/webhook.ts +9 -4
  413. package/src/bootstrap/types.ts +16 -0
  414. package/src/bootstrap.ts +31 -0
  415. package/src/catalogue/types.ts +32 -0
  416. package/src/catalogue.ts +135 -0
  417. package/src/commit/types.ts +27 -0
  418. package/src/commit.ts +152 -0
  419. package/src/config.ts +234 -14
  420. package/src/consts.local.ts +62 -0
  421. package/src/consts.ts +100 -36
  422. package/src/consumer/cancellation/types.ts +13 -0
  423. package/src/consumer/cancellation.ts +43 -0
  424. package/src/consumer/capture/types.ts +53 -0
  425. package/src/consumer/capture.ts +321 -0
  426. package/src/consumer/consts.local.ts +18 -0
  427. package/src/consumer/format/types.ts +29 -0
  428. package/src/consumer/format.ts +85 -0
  429. package/src/consumer/handlers.ts +204 -0
  430. package/src/consumer/index.ts +9 -0
  431. package/src/consumer/mail/types.ts +31 -0
  432. package/src/consumer/mail.ts +353 -0
  433. package/src/consumer/observers/types.ts +22 -0
  434. package/src/consumer/observers.ts +97 -0
  435. package/src/consumer/origin/types.ts +17 -0
  436. package/src/consumer/origin.ts +61 -0
  437. package/src/consumer/reconcile/types.ts +13 -0
  438. package/src/consumer/reconcile.ts +330 -0
  439. package/src/consumer/records/types.ts +64 -0
  440. package/src/consumer/records.ts +288 -0
  441. package/src/consumer/service.ts +768 -0
  442. package/src/consumer/types.local.ts +22 -0
  443. package/src/consumer/types.ts +96 -0
  444. package/src/consumer/withdrawal/types.ts +37 -0
  445. package/src/consumer/withdrawal.ts +268 -0
  446. package/src/entitlement.ts +85 -0
  447. package/src/entrypoints.ts +17 -4
  448. package/src/gate/request.ts +24 -0
  449. package/src/gate/types.ts +13 -0
  450. package/src/gate.ts +47 -30
  451. package/src/index.ts +55 -3
  452. package/src/limit.ts +54 -0
  453. package/src/log.ts +4 -0
  454. package/src/models/declarations/types.ts +22 -0
  455. package/src/models/declarations.ts +30 -0
  456. package/src/models/purchase/types.ts +13 -0
  457. package/src/models/purchase.ts +43 -0
  458. package/src/observer.ts +44 -11
  459. package/src/plan/types.ts +21 -0
  460. package/src/plan.ts +46 -0
  461. package/src/plugins/checkout-plugins/types.ts +25 -0
  462. package/src/plugins/checkout-plugins.ts +90 -0
  463. package/src/plugins/checkout-policy/types.ts +18 -0
  464. package/src/plugins/checkout-policy.ts +34 -0
  465. package/src/plugins/checkout-registry/types.ts +7 -0
  466. package/src/plugins/checkout-registry.ts +23 -0
  467. package/src/plugins/consts.local.ts +30 -0
  468. package/src/plugins/customer-lock/types.ts +5 -0
  469. package/src/plugins/customer-lock.ts +25 -0
  470. package/src/plugins/estimate/cache.ts +12 -0
  471. package/src/plugins/estimate/types.ts +28 -0
  472. package/src/plugins/estimate.ts +298 -0
  473. package/src/plugins/events/types.ts +11 -0
  474. package/src/plugins/events.ts +466 -132
  475. package/src/plugins/fx/types.ts +38 -0
  476. package/src/plugins/fx.ts +100 -0
  477. package/src/plugins/portal/types.ts +45 -0
  478. package/src/plugins/portal.ts +284 -0
  479. package/src/plugins/refunds/types.ts +31 -0
  480. package/src/plugins/refunds.ts +95 -0
  481. package/src/plugins/session/types.ts +28 -0
  482. package/src/plugins/session.ts +73 -0
  483. package/src/plugins/status.ts +22 -0
  484. package/src/plugins/stripe/types.ts +20 -0
  485. package/src/plugins/stripe.ts +523 -135
  486. package/src/plugins/subscriptions/types.ts +39 -0
  487. package/src/plugins/subscriptions.ts +195 -0
  488. package/src/plugins/types.local.ts +61 -0
  489. package/src/plugins/types.ts +7 -0
  490. package/src/plugins/webhook-manager/types.ts +63 -0
  491. package/src/plugins/webhook-manager.ts +288 -0
  492. package/src/protocol.ts +29 -0
  493. package/src/reconcile/types.ts +17 -0
  494. package/src/reconcile.ts +110 -0
  495. package/src/resource.ts +158 -11
  496. package/src/schemas.ts +271 -0
  497. package/src/service.ts +161 -19
  498. package/src/subscription/types.ts +23 -0
  499. package/src/subscription.ts +104 -0
  500. package/src/sync/types.ts +25 -0
  501. package/src/sync.ts +255 -72
  502. package/src/types.local.ts +18 -0
  503. package/src/types.ts +1365 -32
  504. package/src/usage/types.ts +51 -0
  505. package/src/usage.ts +424 -0
  506. package/src/utils/types.ts +30 -0
  507. package/src/utils.ts +94 -29
  508. package/tests/checkout-consumer.spec.ts +366 -0
  509. package/tests/checkout-locks.spec.ts +161 -0
  510. package/tests/checkout-plugins.spec.ts +164 -0
  511. package/tests/checkout.spec.ts +209 -93
  512. package/tests/consumer-events.spec.ts +215 -0
  513. package/tests/consumer-fixtures.ts +134 -0
  514. package/tests/consumer-ops.spec.ts +350 -0
  515. package/tests/consumer-rights.integration.spec.ts +147 -0
  516. package/tests/consumer-rights.spec.ts +572 -0
  517. package/tests/context.ts +109 -0
  518. package/tests/entitlement.spec.ts +139 -0
  519. package/tests/estimate.spec.ts +275 -0
  520. package/tests/events.spec.ts +357 -0
  521. package/tests/fake-stripe.ts +981 -0
  522. package/tests/gate-handlers.spec.ts +120 -0
  523. package/tests/gate.spec.ts +63 -72
  524. package/tests/gateway-options.spec.ts +180 -0
  525. package/tests/limit-gate.spec.ts +66 -0
  526. package/tests/portal.spec.ts +202 -0
  527. package/tests/protocol.spec.ts +17 -3
  528. package/tests/sync.spec.ts +114 -0
  529. package/tests/usage.integration.spec.ts +99 -0
  530. package/tests/usage.spec.ts +169 -0
  531. package/tests/webhook-manager.spec.ts +149 -0
  532. package/build/model.d.ts +0 -6
  533. package/build/model.d.ts.map +0 -1
  534. package/build/model.js +0 -33
  535. package/build/model.js.map +0 -1
  536. package/src/model.ts +0 -37
@@ -1,81 +1,621 @@
1
1
  ---
2
2
  name: server-payment
3
- description: Public in-process Stripe gateway for OwlMeans backends — amount and quantity checkout, subscriptions, protocol-bound webhook routes, product sync, fulfillment observers and entitlement gates. Use when wiring @owlmeans/server-payment or changing Stripe payment behavior.
3
+ description: Public in-process Stripe gateway for OwlMeans backends — amount and quantity checkout, subscriptions, the checkout plugin seam (per-entity narrowing, admission, holds), protocol-bound webhook routes, product sync with per-currency prices, fulfillment observers, entitlement gates, and the EU consumer-rights service (country lock, purchases and withdrawal windows, spend consent, subscription start requests, the withdrawal and cancellation functions with automatic refunds and credit notes, durable-medium mails, reconcile). Use when wiring @owlmeans/server-payment or changing Stripe payment behavior.
4
4
  user-invocable: false
5
5
  ---
6
6
  <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
7
7
 
8
8
  # @owlmeans/server-payment
9
9
 
10
- Public MIT package in the common monorepo. It embeds Stripe into an application backend; the
11
- consumer owns products, credit conversion and entitlement side effects. It persists customers,
12
- subscriptions/fulfilled sessions and sync fingerprints in Mongo.
10
+ **Install:** `bun add @owlmeans/server-payment@^0.1.18-rc.31`
11
+
12
+ Public MIT package. It embeds Stripe into an application backend and owns everything between
13
+ Stripe and an entity's entitlements: the subscription store, one-time fulfillments, the usage
14
+ ledger of counted limits, plan resolution, the two gate services, Stripe's own configuration
15
+ (products, prices, the portal, the webhook endpoint) and the EU consumer-rights records and
16
+ functions. The application owns its catalogue, what a purchase is worth to it (credits,
17
+ provisioning), its usage meter and its side effects. Contracts — plans, limits, promos, the
18
+ entitlement view, the consumer-rights views, calculators, copy and refusals — are
19
+ `@owlmeans/payment`; the model across packages is the `entitlements` skill. Field lists, the
20
+ service contract and the step-by-step algorithms are in `reference.md` in this skill folder.
13
21
 
14
22
  ## Wiring
15
23
 
16
24
  ```typescript
17
- stripeSecrets(cfg, { api: '/secrets/stripe-key', webhook: '/secrets/stripe-webhook' })
18
- declarePaymentProduct(cfg, { sku: 'credits', type: ProductType.Consumable,
19
- services: ['app'], name: 'Credits', taxCode: 'txcd_10103000' })
20
- declarePaymentPlan(cfg, { productSku: 'credits', sku: 'credits-unit',
21
- duration: PlanDuration.Consumable, price: 0.02,
22
- pricingMode: CheckoutPricingMode.Amount, amountPolicy })
23
-
24
- appendPaymentGatewayService(context)
25
- export const appEntrypoints = [...paymentGateEntrypoints]
26
- observer(context).onTopUp(async completion => { /* append an idempotent ledger event */ })
25
+ stripeSecrets(cfg, { api: '/secrets/stripe-key' }) // webhook secret: optional override
26
+ portalBranding(cfg, { returnUrl: 'https://app.example.com/billing', headline: 'Example' })
27
+ declarePaymentPricing(cfg, { // absent entirely: today's fixed behaviour, unchanged
28
+ tax: { automatic: true, behavior: TaxBehavior.Exclusive, collectTaxId: true, estimate: true },
29
+ currency: { adaptive: true, estimate: true },
30
+ stripe: { settlementCurrency: 'eur', subscriptionPaymentMethodTypes: ['card', 'link', 'klarna'],
31
+ subscriptionPaymentMethodTypesByCurrency: { usd: ['card', 'link'] },
32
+ lockCustomerEmail: true, lockCustomerCountry: true }, // optional: see "Customer locks"
33
+ })
34
+ declareConsumerRights(cfg, { // absent: no consumer-rights behaviour at all
35
+ textVersion: 'terms-2026-09', links: { en: { billingTerms, withdrawalInformation, withdrawalFunction, cancellation } },
36
+ currencies: { eu: 'eur', other: 'usd' },
37
+ mechanisms: { countryLock: true, checkoutTerms: true, performanceConsent: true, subscriptionStart: true,
38
+ withdrawal: true, automaticRefunds: true, cancellation: true, purchaseConfirmation: true },
39
+ trader: { name: 'Example', legalName: 'Example Ltd', address: '…', email: 'support@example.com' },
40
+ mail: { from: 'billing@example.com', bcc: ['archive@example.com'] }, // alias: default MAILER_SERVICE
41
+ consentContext: 'included', // optional: the `_included` consent copy variant
42
+ })
43
+ declarePaymentProduct(cfg, { sku: 'app-plans', type: ProductType.Service, services: ['app'], name: 'Plans' })
44
+ declarePaymentPlan(cfg, { productSku: 'app-plans', sku: 'free', rank: 0, free: true, price: 0, … })
45
+ declarePaymentPlan(cfg, { productSku: 'app-plans', sku: 'pro-monthly', rank: 10, price: 20,
46
+ recurring: { interval: 'month' }, currencyPrices: { usd: 20 },
47
+ withdrawal: { components: [{ key: 'services', basis: 'time', shareMinor: 1000 },
48
+ { key: 'credits', basis: 'units', shareMinor: 1000 }] }, capabilities: […] })
49
+
50
+ appendPaymentGatewayService(context) // the process that talks to Stripe
51
+ appendPaymentGatewayService(context, { manage: false }) // a worker that only reads entitlements and asserts consent
52
+ // One deployment, several managed processes on one database: the same owner and webhookService everywhere
53
+ appendPaymentGatewayService(context, { owner: 'app-api', webhookService: 'app-hooks' }) // receives the webhook, bootstraps at boot
54
+ appendPaymentGatewayService(context, { owner: 'app-api', webhookService: 'app-hooks', bootstrap: false }) // checkout and portal only
55
+ appendConsumerRights(context, { manage, usage: myUsageMeter }) // before or after the gateway; idempotent
56
+ paymentAccessOf(context).consumerRights().useMailRenderer(myRenderer) // lazy service: works while wiring
57
+ paymentAccessOf(context).gateway().use(myCheckoutPlugin) // a tier / cap / hold plugin — once the context is initialized
58
+ export const serverBindings = [
59
+ ...paymentGateEntrypoints, // the library's own, unpinned `paymentGate`
60
+ ...consumerRightsEntrypoints(consumerProtocols, { guardMoney, throttle, subjectOf, planNameOf }),
61
+ ...checkoutReadEntrypoints(checkoutProtocols),
62
+ ]
63
+ // …or the gate re-declared by the application (its aliases, pinned to its service, the library's paths):
64
+ export const hookBindings = [
65
+ bind(own.base),
66
+ bind(own.webhook, paymentGateHandlers.webhook),
67
+ bind(own.resync, paymentGateHandlers.resync),
68
+ bind(own.resyncSubscriptions, paymentGateHandlers.resyncSubscriptions),
69
+ ]
70
+ paymentAccessOf(context).observer().onSubscription(async event => { /* keyed by event.eventKey */ })
27
71
  ```
28
72
 
29
- `declarePaymentPlan` validates amount and quantity policies immediately. An amount plan requires
30
- `amountPolicy`; a quantity/legacy plan may declare `quantityPolicy` or the legacy min/default/max
31
- fields. `gateway(ctx).createLink(...)` takes the stable internal `entityId`; an authenticated public
32
- handler must resolve that id from its request entity before calling the in-process service.
73
+ - `appendPaymentGatewayService` registers twelve resources, the catalogue service
74
+ (`PAYMENT_SERVICE`), the completion observer, the gateway (`GATEWAY_SERVICE`), the capability
75
+ gate (`ENTITLEMENT_GATE`), the limit gate (`LIMIT_GATE`), the entitlement service
76
+ (`ENTITLEMENT_SERVICE`) and the consumer-rights service (`CONSUMER_RIGHTS_SERVICE`), each only
77
+ when not registered already. `appendConsumerRights` does the consumer-rights part alone.
78
+ - **Registration order is free.** Whichever of `appendConsumerRights` and the gateway comes first,
79
+ `usage` and `stripe` are installed on the one service, and `manage` resolves as the application's
80
+ explicit value, else the gateway's, else managed. The consumer-rights resources keep the aliases
81
+ of the first registration.
82
+ - **The consumer-rights service is lazy** (like the completion observer): `paymentAccessOf(ctx).consumerRights()`,
83
+ `useMeter` and `useMailRenderer` work while the application is wired; the gateway's initialization
84
+ initializes it, so its boot checks still run at boot. The gateway is NOT lazy: `paymentAccessOf(ctx).gateway()` (and
85
+ `.use(plugin)`) needs the initialized context.
86
+ - **`manage: false`** registers the same surface with no Stripe client: no bootstrap at init, and
87
+ `createLink`, `portalLink`, `resyncSubscription`, `resyncAll`, the webhook route, `withdraw` and
88
+ `cancel` throw `PaygateError('unmanaged')`. `grantInternalPlan`, the entitlement service,
89
+ `amountPolicy`, `planPrices` and the consumer-rights reads, `recordConsent`,
90
+ `recordStartRequest` and `assertConsent` work, because they are Mongo only.
91
+ - **Three keys of a deployment's Stripe state**, each a `PaymentGatewayOptions` field, each read
92
+ back on the gateway (`paymentAccessOf(ctx).gateway().webhookService / owner / bootstrap`):
93
+ - `webhookService` (default `cfg.service`) — the `cfg.services` alias whose host and base form
94
+ `webhookOf(ctx).webhookUrlOf`, the portal's deployment key too. Name the process that RECEIVES the webhook
95
+ (the one serving `paymentGate`) in every process, so every bootstrap computes one URL.
96
+ - `owner` (default `cfg.service`) — the `payment-webhook` rows' `service`, the signing-secret
97
+ lookup, the endpoint's `owlmeans:<owner>` description and `service` metadata, the
98
+ `portal:<owner>` fingerprint and the portal's `service` metadata. One value in every process of
99
+ the database, so each finds and replaces the others' rows.
100
+ - `bootstrap` (default: `manage`) — run `stripeBootstrapOf(ctx).bootstrapStripe` at boot. Exactly one kind of process
101
+ keeps it (the receiver). A managed process with `bootstrap: false` still serves checkout, the
102
+ portal, estimates, the resyncs and the webhook route, and a FORCED bootstrap (`resync`, an
103
+ application's maintenance `stripeBootstrapOf(ctx).bootstrapStripe(stripe, { force: true })`) still runs there —
104
+ same URL, same rows, fingerprinted, so idempotent from any process.
105
+ - Refused at construction: `bootstrap: true` with `manage: false`, an empty `owner` or
106
+ `webhookService` (`PaygateError`). A managed, bootstrapping gateway fails its initialization when
107
+ `webhookService` is not declared in `cfg.services` (`WebhookSetupError('service:<alias>')`).
108
+ - `stripe` (a `StripeFactory`, default `paymentAccessOf(ctx).stripeClient`) is the gateway's own Stripe client —
109
+ `paymentAccessOf(ctx).gateway().stripe(ctx)`, used by its methods, its boot bootstrap and the `paymentGate` handlers.
110
+ The consumer-rights service takes its own (`appendConsumerRights({ stripe })`).
111
+ - Every process that registers the gateway runs the collection validators (`collMod`) of all
112
+ twelve records at init: a process of an older version narrows the validators again, so roll
113
+ the processes of one deployment out together.
114
+ - Gateway methods take the stable `entityId`. A public handler resolves it from its request
115
+ entity first; protocol bodies carry `entitySlug`.
116
+ - A value in `stripeSecrets` / `portalBranding` that starts with `/` is read from that file at
117
+ boot, and a missing file fails the boot — so leave `webhook` out unless the file exists.
118
+
119
+ ## Declaring plans
120
+
121
+ - **`rank`** orders a product's plans; a higher rank is an upgrade. A safe integer `>= 0`; absent
122
+ reads as `0`.
123
+ - **The free plan is a plan**: `free: true`, `price: 0`, no `gateways`. It is never synchronized to
124
+ Stripe and never checked out; an entity without an entitling subscription resolves to it.
125
+ - `gateways` names the paygates a plan is sold through; absent inherits the product's.
126
+ - **`currencyPrices`** (`{ usd: 20 }`, major units, lowercase codes) are exact prices in further
127
+ currencies, synced as the reusable Price's `currency_options`; a declared price in the Price's
128
+ default currency replaces its converted amount. Only for recurring and quantity plans.
129
+ - **`withdrawal.components`** state the separately priced parts of a subscription for a withdrawal
130
+ (CJEU C-641/19): `{ key, basis: 'time' | 'units', shareMinor }`, the shares summing to
131
+ `round(price × 100)`. Absent: the whole price is one `time` component. They also pick the start
132
+ statement: a `units` part → the `_units` variant, otherwise the base (time-only) texts
133
+ (`consumerCopyHelper.startContextOf`, `@owlmeans/payment`).
134
+ - `declarePaymentPlan` refuses before recording: a bad rank or a priced/gatewayed free plan
135
+ (`PlanRankConflict`), a capability set under the reserved `limit` scope, a malformed limit
136
+ (`LimitMisdeclared('<key>:<reason>')`), a malformed or amount-mode `currencyPrices` or components
137
+ that do not add up (`ProductError('currency-prices:…' | 'withdrawal:<sku>:…')`).
138
+ - `makePlanDeclarationsModel(cfg).assertPlans()` runs when the gateway initializes and fails the boot on two free plans
139
+ at one rank, or two paid non-consumable plans of one product at one rank.
140
+ - A limit key may use a different kind on different plans; each kind counts separately and a
141
+ lifetime count stays with the entity through upgrades, downgrades and cancellations.
142
+
143
+ ## Records
144
+
145
+ None declares an ObjectId reference: `entityId` is an organization key and every other id is Stripe's.
146
+
147
+ | Collection | One row per | Indexes |
148
+ |---|---|---|
149
+ | `payment-paygate-customer` | Stripe customer (`country`, `currency` from its webhooks; `deletedAt`) | `{paygate, externalId}` unique · `{paygate, entityId}` · `{paygate, profileId}` |
150
+ | `payment-subscription` | subscription: `sub_…`, `free:<entityId>`, `internal:<planSku>:<entityId>` (+ `currency` and the checkout evidence) | `{paygate, externalId}` unique · `{entityId, status, rank:-1}` · `{entityId, planSku}` · `{paygate, customerId}` · `{paygate, itemId}` sparse · `{paygate, status, updatedAt}` |
151
+ | `payment-fulfillment` | one-time checkout session (+ evidence: country, e-mail, totals, terms, `purchaseId`) | `{paygate, externalId}` unique · `{paygate, paymentIntentId}` sparse · `{paygate, chargeId}` sparse · `{entityId, createdAt:-1}` |
152
+ | `payment-webhook` | managed webhook endpoint of an owner (`service` = the gateway's `owner`; `secret` is `secure: true`) | `{paygate, service, url}` unique |
153
+ | `payment-usage` | usage event — the ledger | `{entityId, limitKey, eventKey}` unique · `{entityId, limitKey, window}` · `{entityId, limitKey, ref}` sparse · `{entityId, createdAt:-1}` |
154
+ | `payment-usage-counter` | (entity, limit, window) projection | `{entityId, limitKey, window}` unique |
155
+ | `payment-fingerprint` | synchronized product (`<productSku>`, with `prices[]`) or portal (`portal:<owner>`) | `{sku}` unique |
156
+ | `payment-billing-profile` | organization: the billing country fixed at the first purchase | `{entityId}` unique · `{paygate, customerId}` sparse |
157
+ | `payment-purchase` | purchase = contract + withdrawal window (a paid one-time checkout, a subscription's first invoice) | `{purchaseId}` unique · `{contractRef}` unique · `{entityId, deadline:-1}` · `{entityId, purchasedAt:-1}` · `{sessionId}` unique sparse · `{paygate, subscriptionId}` · `{invoiceId}` · `{invoiceNumber}` · `{paygate, paymentIntentId}` |
158
+ | `payment-consumer-consent` | append-only: a performance consent or a subscription start request | `{entityId, decidedAt:-1}` · `{kind, entityId, planSku, decidedAt:-1}` |
159
+ | `payment-consumer-declaration` | append-only: a withdrawal or cancellation declaration | `{kind, receivedAt:-1}` · `{entityId, receivedAt:-1}` sparse · `{purchaseId}` sparse |
160
+ | `payment-consumer-event` | append-only: one execution or audit step (mail, refund, lock …) | `{recordId, at}` · `{entityId, at:-1}` sparse · `{action, ok, at}` |
161
+
162
+ - Every stored property is declared in the record schema (`additionalProperties: false`): the
163
+ resource writes a property its schema does not know as a string, and the collection validator
164
+ rejects it. New fields on existing records are optional, so old rows stay valid.
165
+ - **A map is stored as an array** (`prices[].options: [{ currency, unitAmount }]`): the resource
166
+ coerces a currency-keyed map's values to strings.
167
+ - A compound sparse index still indexes a row that lacks only some of its keys — a unique index
168
+ that must skip absent values is single-field (`{sessionId}`).
169
+ - Conditional writes (`consentedAt`, `withdrawnAt`) are raw `$set`s guarded by the old value
170
+ (`paymentUtils.conditionalSet`), so a concurrent declaration of the same purchase loses; declarations,
171
+ consents and events are only ever created.
172
+
173
+ ## Checkout
174
+
175
+ - `Amount`: one inline `price_data` item for the synchronized product, quantity 1, its
176
+ `tax_behavior` the declared `PricingPolicy.tax.behavior`, no promotion codes. `Quantity`: the
177
+ reusable price under the plan lookup key, adjustable quantity. Subscription: `planSku` (else the
178
+ product's first recurring plan), quantity 1. One-time Sessions enable `invoice_creation`.
179
+ - `CreateLinkParams.locale` is a caller-validated Stripe locale (Session `locale`, the Customer's
180
+ `preferred_locales`). `submitText` is trusted application copy — a string, or a function of
181
+ `CheckoutTextContext {language, currency, unitAmountMinor, interval, region, country}` — on
182
+ every mode; never caller-provided text.
183
+ - A plan the paygate does not sell is refused (`ProductError`). `checkoutOptions` puts automatic
184
+ tax, billing address collection, tax-id collection and Adaptive Pricing on the session exactly
185
+ as `PricingPolicy` declares them.
186
+ - Subscription methods come from `stripe.subscriptionPaymentMethodTypesByCurrency[chargeCurrency]`,
187
+ falling back to `subscriptionPaymentMethodTypes`; with neither, Stripe selects dynamically.
188
+ Currency keys and method names normalize to lowercase. Currency keys must have three letters;
189
+ method lists must be nonempty and unique. Choose account-supported methods for each currency:
190
+ a method the account cannot accept in that currency rejects the whole session.
191
+ - **Without a consumer-rights policy every session is what it always was**: the charge currency
192
+ is the settlement currency (FX from the catalogue), Adaptive Pricing as declared.
193
+
194
+ ### Under a consumer-rights policy
195
+
196
+ - **The country.** A locked billing profile overrides `params.country`; another declared country is
197
+ `BillingCountryLocked` (409), and so is a saved customer address that left the locked country
198
+ (an operator relocks). An organization that already paid before its country was locked is
199
+ locked lazily from its Stripe customer's address (`source: 'customer'`). Before any lock, the
200
+ declared country is prefilled on a customer without an address.
201
+ - **The currency.** Charge currency = the profile's currency, else `consumerRegionHelper.chargeCurrencyOf(region)`
202
+ (`policy.currencies`). An amount checkout whose policy currency IS the charge currency is
203
+ charged exactly — no FX call; any other goes through the FX reference rate (rounded up). A
204
+ subscription or quantity session is forced to the charge currency (`currency`) only when its
205
+ synced Price carries it (default or option) — otherwise it is left to Stripe and warned.
206
+ `adaptive_pricing.enabled` is explicitly true only when the policy enables it and the charge
207
+ currency is the settlement currency; every other session sends false. Omitting the flag lets
208
+ Stripe inherit the Dashboard setting and can enable localization for an exact USD checkout.
209
+ - **The lock at Stripe.** A locked profile whose customer carries the address:
210
+ `customer_update.address: 'never'` and `billing_address_collection: 'auto'` (tax follows the
211
+ saved address, Checkout cannot move it); `name: 'auto'` stays. A locked customer without a
212
+ tax-locatable address (`stripeSessionHelper.isTaxLocatable`) keeps `'auto'`/`'required'`, or Stripe refuses the
213
+ session ("Automatic tax calculation in Checkout requires a valid address on the Customer").
214
+ - **Terms.** `mechanisms.checkoutTerms` puts `consent_collection.terms_of_service: 'required'` and
215
+ `custom_text.terms_of_service_acceptance` (the `checkout.terms-acceptance.in-scope | other` copy
216
+ with the billing language's links, ≤ 1200 characters) on every session.
217
+ - **A missing Dashboard terms URL never breaks a payment.** Stripe refuses the checkbox then
218
+ (`invalid_request_error`, param `consent_collection[terms_of_service]`, "You cannot collect consent
219
+ to your terms of service unless a URL is set in the Stripe Dashboard", matched by
220
+ `stripeSessionHelper.isMissingTermsUrl`): the session is created ONCE more without `consent_collection` and without the
221
+ terms text, under the same plugin admissions, metadata `termsCollected: 'false'`; a
222
+ `checkout-terms-fallback` event (`recordKind: 'checkout'`, `recordId` = the entity, `externalId`
223
+ = the session, `ok: false`, the Stripe message in `detail`) is appended per fallback and one
224
+ `warn` log per context tells the operator what to set. Any other refusal is not retried; a
225
+ failing retry releases the admissions and propagates its own error.
226
+ - **Texts.** An in-scope top-up without `submitText` says what it buys (`checkout.top-up`, with the
227
+ country's name); a subscription without `submitText` shows the renewal price in the charge
228
+ currency (`checkout.renewal.<interval>`), and `after_submit` links the cancellation page while
229
+ that mechanism is on. Every text is asserted ≤ 1200 characters.
230
+ - **Start requests.** A subscription needs a fresh start request bound to the organization, the
231
+ plan and the current text version (`assertStartRequest` → `SubscriptionStartRequired`, 428),
232
+ unless the organization is already locked outside the territories — a country picked before
233
+ checkout may differ from the address typed at Stripe.
234
+ - **Metadata** (session and `subscription_data`): `region`, `country`, `language`, `termsVersion`,
235
+ `copyVersion`, `termsCollected` (`'true'` when the checkbox is on the session, `'false'` when the
236
+ policy has it off or after the fallback), `ipCountry` (the `cf-ipcountry` the app passes),
237
+ `startRequestId`, `profileId`, `countryPinned` (`'true'` only under `lockCustomerCountry` when the
238
+ session kept the pinned address). The purchase's `termsAccepted` comes only from the completed
239
+ session's `consent.terms_of_service` — absent when nothing was collected.
240
+
241
+ ### Customer locks (`declarePaymentPricing` → `stripe`)
242
+
243
+ Both are opt-in, backend-only, read per call from the Stripe pricing plugin config, and work with
244
+ or without a consumer-rights policy.
245
+
246
+ - **`lockCustomerEmail`** — every `createLink` must pass `CreateLinkParams.email` (the application's
247
+ verified e-mail of the buyer), else `PaygateError('customer-email')` before any Stripe call. It is
248
+ written to the Stripe customer (created with it, or updated — case-insensitively different — in
249
+ the same call as `preferred_locales`), never as `customer_email`: Checkout shows a customer's
250
+ valid e-mail read-only, and only while the customer has none asks for one and saves it. The
251
+ customer carries the LAST payer's e-mail (invoices and renewals go there). Without the lock
252
+ `email` is ignored.
253
+ - **`lockCustomerCountry`** — the known country (the locked profile's, else `params.country`) is
254
+ written to the customer as a country-only address (lines and postal code cleared) when the saved
255
+ one carries no or another country — over another country only before any lock (a locked
256
+ customer's other country is still `BillingCountryLocked`). When the saved address is
257
+ tax-locatable the session keeps it (`customer_update.address: 'never'`, collection `'auto'`):
258
+ no address form, tax on the pinned country; Stripe still shows the card form's "Country or
259
+ region" picker (a payment-method detail no parameter hides), which then moves neither tax nor the
260
+ saved address. Metadata `countryPinned: 'true'` makes the first completed purchase lock THAT
261
+ country (`lockFromSession`: `metadata.country` before `customer_details.address.country`).
262
+ - **Tax-locatable** (`stripeSessionHelper.isTaxLocatable`, from Stripe's customer-locations table): a country alone
263
+ everywhere except US (needs `postal_code`) and CA / IN (`postal_code` or `state`). A pinned US,
264
+ CA or IN customer without one is not kept: Checkout collects the address — country editable —
265
+ and the purchase locks what was typed, as without the option.
266
+ - **The portal** never offers `email` under `lockCustomerEmail`, nor `address` under
267
+ `lockCustomerCountry` (or `countryLock`); both flags are in its fingerprint.
268
+
269
+ ## Checkout plugins
270
+
271
+ `paymentAccessOf(ctx).gateway().use(plugin)` seats a `CheckoutPlugin` per gateway instance (the `ExecutionService.use`
272
+ registry: a plugin whose `alias` is registered already replaces it). All hooks are optional:
273
+
274
+ | Hook | Called | Contract |
275
+ |---|---|---|
276
+ | `narrow(ctx, {entityId, productSku, planSku?, base, at})` | every amount checkout and every `gateway.amountPolicy` | answers an `AmountNarrowing` or `null`; a throw fails the checkout closed |
277
+ | `admit(ctx, CheckoutAttempt)` | before the session, every mode | throw (`CheckoutLimitExceeded`) to veto; may return `{ reservationId }` |
278
+ | `created(ctx, CheckoutCreated)` | after the session | gets `sessionId`, `url`, `expiresAt`, amounts, its own `reservationId`; a throw expires the session, releases every hold and propagates |
279
+ | `settled(ctx, CheckoutSettled)` | webhook: `paid`, `expired`, `failed`; and `failed` for a checkout that never became usable | errors are logged, never raised |
280
+ | `sessionTtlSeconds` | — | the smallest declared, clamped to 30 min – 24 h, becomes `expires_at`; none declared: Stripe's default |
281
+
282
+ - **One narrowing path.** `createLink` and `gateway.amountPolicy(ctx, entityId, productSku,
283
+ planSku?)` both call `checkoutPluginsOf(ctx).narrowAmountFor` → `amountNarrowingHelper.narrowAmountPolicy` (`@owlmeans/payment`); an amount
284
+ above the narrowed maximum, or any amount while `blocked`, is `CheckoutLimitExceeded` (409). A
285
+ control and a refusal cannot disagree. An amount above the plan's own maximum stays the base
286
+ policy's error.
287
+ - A veto, a Stripe refusal or a throwing `created` releases what the earlier plugins admitted
288
+ (`settled` with `outcome: 'failed'` and their `reservationId`).
289
+ - `gateway.planPrices(ctx, productSku)` answers `PlanPriceView[]` (a default entry per Price plus
290
+ one per currency option) from the synced fingerprint rows — no Stripe call.
33
291
 
34
- ## Checkout modes
292
+ ## Price sync and the tax estimate
35
293
 
36
- - `Amount`: validate `amountMinor`, compute `chargeAmountMinor`, create one inline
37
- `price_data` item for the synchronized Stripe Product, quantity 1, tax-exclusive, with no
38
- adjustable quantity and no promotion codes. No reusable Stripe Price is created; a superseded
39
- quantity Price under the plan lookup key is deactivated.
40
- - `Quantity`: load the reusable Stripe Price and create an adjustable item from the quantity
41
- policy. This remains supported so existing consumers and already-open Checkout sessions work.
42
- - Subscription: reusable recurring Stripe Price, quantity 1, with billing portal management.
294
+ - `productSyncOf(ctx).syncStripeProducts` gives a matching, still-`unspecified` price the declared `tax.behavior` IN
295
+ PLACE and a fresh one on creation; a price carrying the OPPOSITE behavior is replaced. Before an
296
+ in-place update it checks the account's tax-settings default and skips — logging why — when that
297
+ would change existing renewals, unless `stripe.migrateUnspecifiedPrices` opts in.
298
+ - A recurring or quantity plan whose catalogue currency differs from `stripe.settlementCurrency` is
299
+ converted with an unlocked FX Quote (`reference_rate`, rounded up). Its **currency options**:
300
+ the declared `currencyPrices`, plus the catalogue currency (exact `round(price × 100)`) when it is
301
+ one of the policy's region currencies; each option carries the declared `tax_behavior`. Active
302
+ prices are listed with `expand: ['data.currency_options']`; a changed option replaces the Price
303
+ (deactivate + create with `transfer_lookup_key`) — options are never edited in place, so a
304
+ subscriber keeps the Price they accepted.
305
+ - The resolved amounts, currencies and options are fingerprinted; an unchanged fingerprint makes no
306
+ product/price call. Each sync stores the product's `prices[]` (`SyncedPrice`: price id, lookup
307
+ key, default currency and amount, options, tax behavior, interval, source amount) on the
308
+ fingerprint row — what `planPrices`, checkout currency forcing and the estimate read. A row from
309
+ before `prices[]` existed syncs once more.
310
+ - `estimatePrice` (`plugins/estimate.ts`) is a Stripe Tax calculation for one product/plan's
311
+ reference amount at a country — **$0.05 per distinct** (currency, country, amount, tax code,
312
+ behavior, matching tax ids) combination, cached per gateway-service instance for 24h; failures
313
+ are never cached. **A locked profile overrides the requested country** (`source: 'profile'`,
314
+ `locked: true`); the estimate carries `region`. Under a policy with region currencies a
315
+ recurring plan is estimated in the charge currency at its synced unit amount (default or
316
+ option); the `local` line (FX Quotes, a PREVIEW endpoint) only when the session's CHARGE currency
317
+ (the region's, or the locked profile's) is the settlement currency — the same test the session's
318
+ `adaptive_pricing` takes. An amount plan stays estimated in its policy currency but is charged in
319
+ the region's currency, so an EU top-up gets its local line through the policy → settlement →
320
+ local chain, and an exact-USD one none.
43
321
 
44
- Every session enables automatic tax, billing address and tax-id collection. Amount metadata carries
45
- pricing mode, net amount, adjusted pre-tax subtotal, currency, product, plan, service and owner.
322
+ ## The subscription store
46
323
 
47
- ## Fulfillment
324
+ - **The effective plan** is the highest-ranked row in `ENTITLING_STATUSES` (catalogue rank; the
325
+ newest on a tie), else the declared free plan, else `PlanRequired`.
326
+ - **A subscription row may override its plan.** `PaymentSubscriptionRecord.overrides`
327
+ (`SubscriptionOverrides`: `limits: { <key>: { limit } }`) is applied by `planHelper.overriddenPlan(plan, row)`
328
+ inside `resolveEffectivePlan` and `snapshotOf`, so ceilings, the entitlement view, both gates and
329
+ observers read one answer and nothing else needs to know. An override sets the ceiling of a key
330
+ the plan declares and drops that key's promo; kind and window stay the plan's (counters are keyed
331
+ by them); an undeclared key or a ceiling that is not a safe integer `>= 0` is ignored. An operator
332
+ writes it, no paygate does: every sync and re-grant spreads the previous row, so it survives. It
333
+ belongs to the row — it follows the subscription through a plan change, ends with it, and the
334
+ free-plan fallback (no row) has none. A new overridable parameter is a new optional key of
335
+ `SubscriptionOverrides`, declared in `PaymentSubscriptionSchema` and merged in `planHelper.overriddenPlan`.
336
+ - `mapStatus`: `active`→Active, `trialing`→Trial, `past_due`→PastDue, `unpaid`/`paused`→Suspended,
337
+ `incomplete`→Created, `incomplete_expired`→Ended, `canceled`→Canceled; paused collection is
338
+ Suspended with `pausedAt`.
339
+ - **State is written before observers, and classified against what observers were last told**
340
+ (`propagated`, stamped with `lastEventId` only after every observer succeeded).
341
+ `CommitOptions.beforePropagate(record, change)` runs between the write and the observers — the
342
+ Stripe path writes a subscription's purchase there on `created`, so the window exists before an
343
+ observer grants the bundle.
344
+ - Classification, first match: `created` · `canceled` · `paused` · `resumed` · `upgraded` /
345
+ `downgraded` · `cancel-scheduled` · `cancel-undone` · `renewed` (once per invoice) · `past-due` ·
346
+ `suspended` · `trial-ending`.
347
+ - A repeated delivery (`lastEventId`) is ignored; an older payload than the stored state is not
348
+ applied. `grantInternalPlan(ctx, entityId, planSku, { force?, periodEnd? })` upserts an internal
349
+ row; `resyncSubscription` / `resyncAll` re-read and apply as a webhook would.
48
350
 
49
- Handle both `checkout.session.completed` and `checkout.session.async_payment_succeeded`, but grant
50
- nothing until `payment_status === 'paid'`. Amount fulfillment compares Stripe currency and actual
51
- `amount_subtotal` with the server-created metadata. Completion is discriminated:
351
+ ## The usage ledger
52
352
 
53
- - `{ mode: 'amount', amountMinor, chargeAmountMinor, currency, ... }`
54
- - `{ mode: 'quantity', units, ... }`
353
+ `payment-usage` events are the source of truth; `payment-usage-counter` is the projection that
354
+ admission reads. `consume` increments the counter FIRST by one conditional upsert (`used <= limit
355
+ - amount`), then appends the event — **the counter may over-count, never over-admit**. An event
356
+ key is idempotent; `release` appends `release:<eventKey>` first. `reconcileCounters`,
357
+ `reconcileOccupancy`, `reconcileOf(ctx).reconcileEntity` and `.reconcileAll` repair it (details: the `entitlements`
358
+ skill).
55
359
 
56
- The session id is `externalId`. A pending fulfillment record is created before calling observers
57
- and gets `fulfilledAt` only after they succeed; observer failures escape so Stripe retries. Consumer
58
- callbacks must still use `externalId` as their own append-only event key, covering a crash after the
59
- side effect and before `fulfilledAt`. A session without pricing-mode metadata is a legacy quantity
60
- session and remains fulfillable.
360
+ ## Entitlements and gates
61
361
 
62
- Subscription observers receive `{ isNew, status, capabilities, limits, ... }`. One-time bundles go
63
- behind `isNew` and use an idempotent external event key. Status updates and entitlement provisioning
64
- must be safe to repeat.
362
+ - `paymentAccessOf(ctx).entitlements()` → `effectivePlan`, `entitlements` (the `EntitlementView`), `hasCapability`,
363
+ `limitState`, the ledger operations. Never calls Stripe.
364
+ - **Capability gate** (`ENTITLEMENT_GATE`) passes when the view grants ANY parameter; **limit gate**
365
+ (`LIMIT_GATE`) when any `limit:<key>[>=n]` has room — it never consumes. Both refuse with
366
+ `AuthForbidden` refusals (403) and fail closed on an unreadable store.
367
+ - The consumer-rights refusals (428/409) and `CheckoutLimitExceeded` are not entitlement refusals.
65
368
 
66
- ## Protocols and security
369
+ ## Consumer rights
67
370
 
68
- `paymentGate` is an immutable protocol tree. `paymentGateEntrypoints` binds its base, webhook, and
69
- resync declarations directly with `bind(protocol, handler)`; do not publish a flattened
70
- `paymentGateProtocols` compatibility list. The webhook is public because Stripe
71
- signature verification requires the untouched raw body, while resync carries the ED25519 guard.
72
- Never put an application auth guard on the Stripe webhook.
371
+ The EU right of withdrawal (with the Art. 11a withdrawal function), spend consent for prepaid
372
+ credits, subscription start requests, the cancellation function and a billing country fixed at the
373
+ first purchase. The service is `paymentAccessOf(ctx).consumerRights()` (`.consumerRightsOf()` is null-safe); its
374
+ full contract is in `reference.md`.
73
375
 
74
- `appendPaymentGatewayService` also registers `ENTITLEMENT_GATE`. Paid capabilities belong on shared
75
- route declarations through `entitled(...)`, not in handlers. The gate fails closed when the
76
- subscription store cannot be read.
376
+ - **A purchase** is a paid one-time checkout, or a subscription's FIRST invoice — renewals,
377
+ internal grants and manual credits never are. Its row (`purchaseId` `stripe:<cs_…>` /
378
+ `stripe:<sub_…>`, `contractRef` `CR-YYMMDD-XXXXXX` over an unambiguous alphabet, retried on a
379
+ collision) is written **before anything is granted**: in `checkout.session.completed` before
380
+ `onTopUp`, in the first subscription commit before the `created` observers. A completed
381
+ subscription checkout then refines it with the buyer's own country, e-mail, totals and terms
382
+ acceptance. Window = in scope, before `deadline` (`withdrawalDeadlineHelper.withdrawalDeadlineOf`, policy margin),
383
+ neither withdrawn nor refunded; a full refund closes it (`refundedAt`).
384
+ - **In scope** when the buyer's own country or the organization's locked country is in the policy's
385
+ territories; an unknown country is protected by default. A business tax id does not exempt.
386
+ - **The lock**: the first completed purchase's `customer_details.address.country` (else the
387
+ declared one) locks the profile — first write wins through the unique index; a later different
388
+ country is a `lock-mismatch` event, never a relock. The request's `cf-ipcountry` is stored as
389
+ `ipCountry` beside it, and the `lock` event flags `ipMismatch`. The profile's currency is an
390
+ entitling Stripe subscription's currency when one exists (two subscriptions of one customer can
391
+ not differ), else the region's. Only `lock(entityId, country, 'manual', { force: true, by,
392
+ reason })` replaces a lock, audited as `relock`.
393
+ - **Unlock** (an operator, Mongo only — unmanaged works): `unlock(entityId, { by, reason })` deletes
394
+ the profile (only while it still holds the country read) and appends an `unlock` event carrying the
395
+ whole row; it answers the profile as it was, or `null`. After an unlock no lock is taken from the
396
+ paygate customer's saved address — neither at checkout nor by `reconcile` — so the next COMPLETED
397
+ purchase locks the country again from its own address.
398
+ - **Performance consent** (top-ups only — a start request covers a subscription's own invoice):
399
+ required while an open in-scope top-up window has none. `assertConsent` is one indexed query and
400
+ throws `PerformanceConsentRequired` (428, `pending`, latest `deadline`); an application calls it
401
+ only where credits will actually be spent. `consentView` carries the policy's `consentContext`
402
+ as `context`. `recordConsent` renders the statement itself (`consumerCopyHelper.consentStatementOf` with the
403
+ trader's `name` and the policy's `consentContext`, so the stored verbatim text and the consent
404
+ mail are the `_<context>` variant the dialog showed; without a context the base statement),
405
+ stores that `context` on the record, refuses a stale `textVersion` with a fresh 428, covers only
406
+ the open windows the body lists, stamps `consentedAt` conditionally, mails the confirmation, then
407
+ tells `onConsent`.
408
+ - **Start requests follow the plan's withdrawal arithmetic.** `startView(entityId, planSku)` names
409
+ `context = consumerCopyHelper.startContextOf(plan)` (`'units'` for a plan with a `units` component, absent for a
410
+ time-only one); `recordStartRequest(subject, body, origin, { plan })` renders the statement with
411
+ the SAME `consumerCopyHelper.startContextOf(plan)` and the plan's short name the application passes (default: its
412
+ localized title), stores it verbatim with its `context`, and the start mail repeats it with
413
+ `email.start.rule` of that variant (the record's `context`). Usable `startRequestTtlSeconds`
414
+ (3600); the purchase takes it as `servicesStartedAt`/`consentedAt`.
415
+ - **Withdrawal** (`withdraw(subject | null, body, origin)`, managed only): in-app by `purchaseId`,
416
+ public by contract reference or invoice number plus an e-mail of the purchase, its profile or
417
+ its paygate customer. The declaration and the conditional `withdrawnAt` are written BEFORE
418
+ Stripe; the receipt is mailed at once; then the paygate steps under
419
+ `withdrawal:<id>:<step>` keys; then `onWithdrawal`. The refund comes from the application's
420
+ `UsageMeter` through the `@owlmeans/payment` calculators (deduction `usedAfter + settled +
421
+ clawed`); no meter, or `automaticRefunds` off, is `review` (no paygate call). A late
422
+ declaration is `expired` — recorded and acknowledged, nothing executed. A repeated one answers
423
+ the original receipt. The public answer is always the bare `DeclarationReceipt`.
424
+ - **Cancellation** (`cancel(subject | null, body, origin)`): in-app the organization's entitling
425
+ Stripe subscription; public a contract reference plus e-mail, else the one organization whose
426
+ paygate customer has that e-mail. Ordinary → `cancel_at_period_end`, or `cancel_at` a later
427
+ boundary (`cancellationEffectiveAt`, no proration); already scheduled → `already-scheduled`;
428
+ **extraordinary → `review`, the paygate untouched** (an operator decides, the receipt says so).
429
+ - **Observers `onConsent` / `onWithdrawal` / `onCancellation` run AFTER the records and the paygate
430
+ steps; a throw is recorded (`observers` event) and retried by `reconcile()` — unlike the paygate
431
+ callbacks, whose throw makes Stripe redeliver.** A `WithdrawalEvent` with `status: 'refunded'`
432
+ carries `units.returned` — take exactly those back; `review` means an operator refunds later,
433
+ and that refund arrives as an ordinary `RefundEvent`.
434
+ - **`RefundEvent.withdrawalId`**: a refund this package made for a withdrawal carries
435
+ `metadata.withdrawalId`; an `onRefund` observer MUST skip its own claw-back for it, or the units
436
+ are taken back twice.
437
+
438
+ ## Durable-medium mails
439
+
440
+ Sent through the optional mailer (`mail.alias`, default `MAILER_SERVICE`), text + HTML, in the
441
+ language the consumer was shown, from the `payment-consumer-rights` copy (`email.*`): purchase
442
+ confirmation (in-scope purchases with an e-mail; withdrawal information with the function's
443
+ address and the model form), consent confirmation, start confirmation, withdrawal receipt,
444
+ cancellation receipt. User values are HTML-escaped; a deadline is shown as its last included day.
445
+ The mails and the withdrawal information name the trader's `legalName, address, email`; the
446
+ statements its `name`. Every send, skip or failure is a `mail` event (step = kind); addresses on
447
+ `.test`/`.example`/`.invalid`/`.localhost` and every subdomain of them (`consumerFormatHelper.isReservedAddress`; case, a
448
+ display-name form and a trailing dot read through) are never sent (recorded as skipped); each `bcc`
449
+ address gets its own copy. **The purchase confirmation goes out once per purchase**: a delivery
450
+ sends it only after winning the conditional `confirmationMailAt` claim on the purchase row (a mail
451
+ event of it from before the claim counts too) — concurrent deliveries in several processes, a
452
+ redelivery and a subscription checkout refining its purchase mail nothing twice; a failed send is
453
+ retried by `reconcile` only. `useMailRenderer((kind, data, rendered) => message | null |
454
+ undefined)` replaces (`message`), suppresses (`null`, recorded as skipped) or keeps a mail. The
455
+ boot warns once when a mailing mechanism is on and the trader has no address or e-mail, or no
456
+ mailer is registered.
457
+
458
+ ## Reconcile
459
+
460
+ `paymentAccessOf(ctx).consumerRights().reconcile({ since?, limit? })` — the application's nightly job: retries the
461
+ paygate steps of withdrawals (refund, credit note, subscription cancel) and scheduled
462
+ cancellations, failed mails and failed observers; backfills purchases (records only, no mail) from
463
+ completed sessions of the last 16 days without a row; locks organizations that paid before the
464
+ lock from their paygate customer (never one an operator unlocked). A retry uses a fresh idempotency key (`…:<attempt>`; Stripe
465
+ replays a stored failure for a day) and first adopts a refund or credit note an earlier attempt
466
+ made (found by `metadata.withdrawalId`). Five failures of a step leave it to an operator. At most
467
+ `limit` (50) items per step.
468
+
469
+ ## Protocols, handlers and security
470
+
471
+ - `paymentGate` (bound by `paymentGateEntrypoints`): `webhook` is public because Stripe signs the
472
+ untouched raw body — never put an application guard on it; `resync` and `resyncSubscriptions`
473
+ carry `GUARD_ED25519`.
474
+ - **An application that declares the gate itself** — its own aliases, every route pinned to the
475
+ service that receives the webhook — binds its declarations to `paymentGateHandlers` (`webhook`,
476
+ `resync`, `resyncSubscriptions`) with `bind(own.webhook, paymentGateHandlers.webhook)` from
477
+ `@owlmeans/server-entrypoint` / `@owlmeans/server-app`, and never registers `paymentGate` or
478
+ `paymentGateEntrypoints`. A handler reads its context from the entrypoint it is bound to, so the
479
+ application's declaration is the one mounted and run. Its declarations keep the library's paths
480
+ (`webhookOf(ctx).webhookUrlOf` forms the Stripe URL from `paymentGate`'s), contracts (`PaygateParams`,
481
+ `ResyncResult`, `ResyncSubscriptionsResult` — `bind` refuses another shape at compile time) and
482
+ guards. Two ways to get it wrong: `bindAll(declarations, [paymentGateHandlers.webhook])` pairs by
483
+ object identity and leaves the application's declaration without a handler, and
484
+ `handlers().params(own.webhook, paymentGateHandlers.webhook)` answers every request with
485
+ `HandlerMisconfiguredError` (a handler bound to another protocol).
486
+ - `consumerRightsEntrypoints(protocols, { resolveEntity?, subjectOf?, guardMoney?, throttle?,
487
+ metaOf?, publicMinMs?, planNameOf?, serviceAlias? })` binds `makeConsumerRightsProtocols`' tree.
488
+ **Every hook gets the request's context as its LAST argument** — `resolveEntity(req, ctx)`,
489
+ `subjectOf(req, ctx)`, `guardMoney(req, action, ctx)`, `throttle(req, key, ctx)`,
490
+ `metaOf(req, ctx)`, `planNameOf(planSku, language, req, ctx)` — so an application reaches its
491
+ services (a throttle store) through it and keeps no module state. Account routes act for
492
+ `resolveEntity` (default `req.entity.id`, else `AuthForbidden`); consent, start, withdraw and
493
+ cancel pass `guardMoney` first (refuse API keys there). **A tree with a `public` subtree needs
494
+ `throttle` — a wiring error otherwise.** Public declarations are throttled with `{action, email,
495
+ ip}`, a filled `honeypot` gets a decoy receipt and nothing is recorded, and every answer takes at
496
+ least `publicMinMs` (1000 ms) so a match is not visible in the timing either.
497
+ - `checkoutReadEntrypoints(protocols, { resolveEntity?, gatewayAlias? })` binds
498
+ `makeCheckoutReadProtocols`: `amountPolicy` and `planPrices`; its `resolveEntity(req, ctx)` too.
499
+ - **`originHelper.requestOriginOf(req)`** is the evidence of every consumer act (the default `metaOf`): `ip` =
500
+ `cf-connecting-ip` → the LAST `x-forwarded-for` entry → `x-real-ip` → the socket; the raw
501
+ `x-forwarded-for`, `user-agent` (≤ 512), `cf-ipcountry`, `accept-language`.
502
+
503
+ ## Stripe self-management
504
+
505
+ Runs in `initialize()` of a managed gateway with `bootstrap` (default), after the context is
506
+ ready, and on a forced `stripeBootstrapOf(ctx).bootstrapStripe` from any managed process; each step independent: products
507
+ and prices (above), the portal configuration, the webhook endpoint. A deployment's identity is its
508
+ webhook URL (`webhookOf(ctx).webhookUrlOf()`: the `webhookService` alias's host and base); its rows carry the
509
+ `owner`.
510
+
511
+ - **The portal configuration**: customer update (tax id; email unless `stripe.lockCustomerEmail`;
512
+ address unless `mechanisms.countryLock` or `stripe.lockCustomerCountry`), invoice history,
513
+ payment method update, cancellation at period end without proration, price switching between the
514
+ active recurring prices. Its fingerprint covers the catalogue (incl. `currencyPrices`), the lock
515
+ flags, the region currencies, the branding and the deployment key. Each deployment owns its own configuration, tagged `{ owlmeans: 'payment',
516
+ service: <owner>, deployment: webhookOf(ctx).webhookUrlOf() }`; one tagged for another deployment (a former
517
+ webhook URL included) is never touched — a moved URL creates a new configuration.
518
+ - `portalLink(ctx, entityId, { flow, planSku?, returnUrl })`: a customer is required
519
+ (`PortalUnavailable`, 409); `Cancel`/`Update`/`Change` need an entitling Stripe subscription.
520
+ - **The webhook endpoint** at `webhookOf(ctx).webhookUrlOf()`, subscribed to `WEBHOOK_EVENTS`, on the API version
521
+ read back from the client; only an https public host. A deployment deletes only the endpoints its
522
+ own rows name. A new URL (a moved host, a new `webhookService`) creates the endpoint there, then
523
+ deletes the endpoints of the owner's rows at other URLs and those rows — no migration step. The
524
+ secret (create-only) is stored field-encrypted where the database has a key; the receiver reads
525
+ the newest row of its `owner`.
526
+ - **Do not bump the Stripe SDK** (17.x, API `2025-02-24.acacia`): `current_period_*`,
527
+ `invoice.subscription`, `invoice.payment_intent`, `charge.invoice` are top-level there and move
528
+ later; credit notes link a refund by `refund`; `presentment_details` is untyped. Read them through
529
+ narrow accessors.
530
+ - **Dashboard prerequisites** (test and live): a terms-of-service URL in Settings → Public details
531
+ before `checkoutTerms` is on (without it every checkout falls back to no checkbox — payments go on,
532
+ but the acceptance is not collected: watch for `checkout-terms-fallback` events); Checkout's return/refund
533
+ policy off or pointing at the Billing Terms, never "no refunds"; business name, address and
534
+ support e-mail in Public details; the "successful payments" and "refunds" customer e-mails.
535
+
536
+ ## Event dispatch
537
+
538
+ | Event | Persisted | Observer · event key |
539
+ |---|---|---|
540
+ | `customer.created` / `.updated` | customer upsert (`country`, `currency`) | — |
541
+ | `customer.deleted` | customer `deletedAt` | — |
542
+ | `checkout.session.completed` / `.async_payment_succeeded`, `payment` mode, paid | lock + purchase + confirmation mail, fulfillment (+ evidence), `fulfilledAt` after observers | `onTopUp` · session id; plugins `settled('paid')` |
543
+ | same, `subscription` mode | subscription applied if no webhook did (purchase in its first commit), purchase refined, lock, subscription evidence, confirmation mail | plugins `settled('paid')` |
544
+ | `checkout.session.async_payment_failed` | fulfillment `failedAt` | `onPaymentFailed {kind:'checkout'}` · `payment-failed:<session>:0`; `settled('failed')` |
545
+ | `checkout.session.expired` | unfulfilled fulfillment purged | plugins `settled('expired')` |
546
+ | `customer.subscription.created` / `.updated` / `.pending_update_*` / `.paused` / `.resumed` | subscription applied (+ `currency`) | `onSubscription` · classified |
547
+ | `customer.subscription.deleted` | applied as Canceled + `endedAt` | `canceled` |
548
+ | `customer.subscription.trial_will_end` | applied | `trial-ending` |
549
+ | `invoice.paid` | cycle invoice ⇒ re-read, applied as a renewal; otherwise `latestInvoiceId` | `renewed` |
550
+ | `invoice.payment_failed` / `.payment_action_required` | re-read and applied | classified, then `onPaymentFailed {kind:'invoice'}` · `payment-failed:<invoice>:<attempt>` |
551
+ | `invoice.upcoming` | nothing | — |
552
+ | `invoice.marked_uncollectible` / `invoice.voided` | re-read and applied / `latestInvoiceId` | classified / — |
553
+ | `charge.refunded`, `refund.created` / `.updated` (succeeded) | fulfillment `refundedMinor`/`refundedAt`; purchase `refundedMinor`, `refundedAt` on a whole refund | `onRefund` · `refund:<refund>` (`metadata`, `withdrawalId`) |
554
+ | `refund.failed` | — | — |
555
+ | `charge.dispute.*` | target `disputedAt`, `disputeStatus` | `onDispute {phase}` · `dispute:<dispute>:<phase>` |
556
+
557
+ A refund or dispute resolves to its target by payment intent (fulfillment), by charge, by invoice
558
+ (the subscription whose latest invoice it is, else the invoice's subscription); a subscription
559
+ refund touches the purchase only when it is of the purchase's own (first) invoice.
560
+
561
+ ## Observer API and idempotency keys
562
+
563
+ | Callback | Payload | Key | A throw |
564
+ |---|---|---|---|
565
+ | `onTopUp` | `TopUpCompletion` | session id | Stripe redelivers |
566
+ | `onSubscription` | `SubscriptionEvent` | `subscription:<id>:<change>:…` | Stripe redelivers |
567
+ | `onRefund` | `RefundEvent` (+ `metadata`, `withdrawalId`) | `refund:<refund>` | Stripe redelivers |
568
+ | `onDispute` | `DisputeEvent` | `dispute:<dispute>:<phase>` | Stripe redelivers |
569
+ | `onPaymentFailed` | `PaymentFailedEvent` | `payment-failed:<session\|invoice>:<attempt>` | Stripe redelivers |
570
+ | `onConsent` | `ConsentEvent` (`kind`, `consentId`, `purchaseIds`, `planSku`) | `consent:<id>` | recorded; `reconcile` retries |
571
+ | `onWithdrawal` | `WithdrawalEvent` (`status`, `purchase: PurchaseRef`, `refund`, `units`, `subscriptionCanceled`) | `withdrawal:<id>` | recorded; `reconcile` retries |
572
+ | `onCancellation` | `CancellationEvent` (`matched`, `kind`, `status`, `effectiveAt`) | `cancellation:<id>` | recorded; `reconcile` retries |
573
+
574
+ Every callback must be idempotent by its key. A proportional claw-back of an ordinary top-up
575
+ refund uses `netAmountMinor * refundedTotalMinor / paidMinor`.
576
+
577
+ ## Testing
578
+
579
+ Unit specs run a real server context — real catalogue, services and gates — over in-memory
580
+ resources (unique and sparse-unique indexes, raw conditional updates) and a fake Stripe that records
581
+ every SDK call and its request options (idempotency keys) and can fail a method's next calls
582
+ (`state.failures`: a message, a real SDK error such as `new Stripe.errors.StripeInvalidRequestError(…)`,
583
+ or a list of them, one per call), so "no Stripe call" is an assertion. `makeFakeContext` wires the
584
+ consumer-rights call before the gateway by default (`gatewayFirst`, `rights` and a pre-init `wire`
585
+ hook test the other orders); `gateway` passes the gateway's options (default `{ manage: false }`,
586
+ the fake always its Stripe client, so a managed one boot-bootstraps against it), `service` /
587
+ `host` / `base` / `services` shape `cfg.service` and `cfg.services`, and `seed` pre-fills stores
588
+ (a database shared between two processes). `gate-handlers.spec.ts` binds `paymentGateHandlers`
589
+ onto a re-declared, pinned gate and runs it in that process's context. The consumer-rights service there is
590
+ managed through the fake (`appendConsumerRights({ manage: true, stripe })`) with a console mailer
591
+ (`fake.mails`). The Mongo-gated specs prove admission under concurrency, the collection validators
592
+ of every record, the unique indexes, the concurrent first lock and the single winner of
593
+ concurrent withdrawals.
77
594
 
78
595
  ## External docs
79
596
 
80
- - https://docs.stripe.com/api/checkout/sessions/create — Checkout accepts inline `price_data` with integer minor-unit `unit_amount`; automatic tax is enabled on the Session and amount items are tax-exclusive.
81
- - https://docs.stripe.com/checkout/fulfillment — Fulfillment must be idempotent, check payment state and support delayed-payment success events rather than trusting completion alone.
597
+ - https://docs.stripe.com/api/checkout/sessions/create — `customer`: "If the Customer already has a valid email set, the email will be prefilled and not editable in Checkout" (else Checkout saves the typed one); `customer_email` only prefills a NEW customer and cannot be combined with `customer`. Inline `price_data`; `consent_collection.terms_of_service` needs a terms URL in the Dashboard (else `invalid_request_error` on param `consent_collection[terms_of_service]`); `custom_text.{submit, after_submit, terms_of_service_acceptance}` ≤ 1200 characters each; `currency` forces a Price's currency option; `expires_at` 30 min – 24 h.
598
+ - https://docs.stripe.com/tax/customer-locations — minimal tax location: country alone except US (postal code), CA and IN (postal code or province); https://docs.stripe.com/tax/checkout/page — an existing customer with `customer_update.address: 'never'` is taxed on its saved address. Verified against test mode (2026-10): a pinned DE customer keeps its VAT when the card country is switched to US; a country-only US/CA customer is refused at session creation.
599
+ - https://docs.stripe.com/payments/checkout/localize-prices/manual-currency-prices — `currency_options` on a Price, one reusable Price for several currencies; manual options override Adaptive Pricing for that currency.
600
+ - https://docs.stripe.com/payments/currencies/localize-prices/adaptive-pricing — Adaptive Pricing requires the price currency to be a settlement currency; webhook amounts stay in the integration currency.
601
+ - https://docs.stripe.com/api/checkout/sessions/create?query=adaptive_pricing — `adaptive_pricing.enabled` defaults to the Dashboard setting when omitted; send an explicit boolean for every session.
602
+ - https://docs.stripe.com/payments/klarna — Klarna eligibility depends on currency, customer location and purchase use case; verify the account's supported currencies before explicitly requesting it.
603
+ - https://docs.stripe.com/invoicing/multi-currency-customers — a customer's subscriptions share one currency; one-time payments may differ.
604
+ - https://docs.stripe.com/invoicing/integration/programmatic-credit-notes — preview a credit note on an invoice line; link an existing refund with `refund`; custom lines are not allowed with automatic tax.
605
+ - https://docs.stripe.com/tax/reports — a refund or a credit note lowers reported tax; only the credit note is the corrective document of an issued invoice.
606
+ - https://docs.stripe.com/api/refunds/create — `payment_intent`, `amount`, `reason: requested_by_customer`, `metadata`; an idempotency key replays the stored answer (failures too) for 24 h.
607
+ - https://docs.stripe.com/api/subscriptions/cancel and /update — `cancel(prorate, invoice_now, cancellation_details)`; `update(cancel_at_period_end | cancel_at, proration_behavior)`.
608
+ - https://docs.stripe.com/payments/checkout/receipts — one-time Checkout needs `invoice_creation.enabled` for a post-payment invoice; a Customer's `preferred_locales` localizes Stripe mails.
609
+ - https://docs.stripe.com/checkout/fulfillment — fulfillment must be idempotent and support delayed-payment success events.
610
+ - https://docs.stripe.com/api/webhook_endpoints/create — the signing `secret` is returned only by create; `api_version` is create-only.
611
+ - https://docs.stripe.com/api/customer_portal/configurations/create — `features.customer_update.allowed_updates`, subscription cancel/update features; configurations are never deletable.
612
+ - https://docs.stripe.com/api/tax/calculations/create — `percentage_decimal` is a STRING; parse it exactly.
613
+ - https://docs.stripe.com/api/fx_quotes/create — a PREVIEW endpoint (`stripe.rawRequest`); `reference_rate` for settlement conversion.
614
+
615
+ ## Related
616
+
617
+ - [[entitlements]] — the model across packages
618
+ - [[payment]] — the contracts: grammars, views, consumer-rights calculators, copy, refusals, factories
619
+ - [[web-payment]] — hooks and pieces, the consumer-rights dialogs and functions
620
+ - [[mailer]] — the mail transport the consumer-rights mails go through
621
+ - [[mongo-resource]] — raw collection access, duplicate-key detection, field locking