@owlmeans/payment 0.1.18-rc.8 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (364) hide show
  1. package/README.md +95 -57
  2. package/agent-meta/manifest.json +10 -3
  3. package/agent-meta/skills/entitlements/SKILL.md +159 -0
  4. package/agent-meta/skills/payment/SKILL.md +491 -28
  5. package/build/advertise.d.ts +2 -0
  6. package/build/advertise.d.ts.map +1 -0
  7. package/build/advertise.js +10 -0
  8. package/build/advertise.js.map +1 -0
  9. package/build/checkout/consts.d.ts +2 -0
  10. package/build/checkout/consts.d.ts.map +1 -0
  11. package/build/checkout/consts.js +2 -0
  12. package/build/checkout/consts.js.map +1 -0
  13. package/build/checkout/index.d.ts +6 -0
  14. package/build/checkout/index.d.ts.map +1 -0
  15. package/build/checkout/index.js +4 -0
  16. package/build/checkout/index.js.map +1 -0
  17. package/build/checkout/narrow/types.d.ts +25 -0
  18. package/build/checkout/narrow/types.d.ts.map +1 -0
  19. package/build/checkout/narrow/types.js +2 -0
  20. package/build/checkout/narrow/types.js.map +1 -0
  21. package/build/checkout/narrow.d.ts +8 -0
  22. package/build/checkout/narrow.d.ts.map +1 -0
  23. package/build/checkout/narrow.js +47 -0
  24. package/build/checkout/narrow.js.map +1 -0
  25. package/build/checkout/protocols.d.ts +8 -0
  26. package/build/checkout/protocols.d.ts.map +1 -0
  27. package/build/checkout/protocols.js +28 -0
  28. package/build/checkout/protocols.js.map +1 -0
  29. package/build/checkout/types.d.ts +29 -0
  30. package/build/checkout/types.d.ts.map +1 -0
  31. package/build/checkout/types.js +2 -0
  32. package/build/checkout/types.js.map +1 -0
  33. package/build/consts.d.ts +242 -25
  34. package/build/consts.d.ts.map +1 -1
  35. package/build/consts.js +377 -24
  36. package/build/consts.js.map +1 -1
  37. package/build/consts.local.d.ts +20 -0
  38. package/build/consts.local.d.ts.map +1 -0
  39. package/build/consts.local.js +23 -0
  40. package/build/consts.local.js.map +1 -0
  41. package/build/consumer/cancel.d.ts +11 -0
  42. package/build/consumer/cancel.d.ts.map +1 -0
  43. package/build/consumer/cancel.js +33 -0
  44. package/build/consumer/cancel.js.map +1 -0
  45. package/build/consumer/consts.d.ts +20 -0
  46. package/build/consumer/consts.d.ts.map +1 -0
  47. package/build/consumer/consts.js +39 -0
  48. package/build/consumer/consts.js.map +1 -0
  49. package/build/consumer/consts.local.d.ts +7 -0
  50. package/build/consumer/consts.local.d.ts.map +1 -0
  51. package/build/consumer/consts.local.js +13 -0
  52. package/build/consumer/consts.local.js.map +1 -0
  53. package/build/consumer/copy/types.d.ts +56 -0
  54. package/build/consumer/copy/types.d.ts.map +1 -0
  55. package/build/consumer/copy/types.js +2 -0
  56. package/build/consumer/copy/types.js.map +1 -0
  57. package/build/consumer/copy.d.ts +22 -0
  58. package/build/consumer/copy.d.ts.map +1 -0
  59. package/build/consumer/copy.js +87 -0
  60. package/build/consumer/copy.js.map +1 -0
  61. package/build/consumer/deadline/types.d.ts +30 -0
  62. package/build/consumer/deadline/types.d.ts.map +1 -0
  63. package/build/consumer/deadline/types.js +2 -0
  64. package/build/consumer/deadline/types.js.map +1 -0
  65. package/build/consumer/deadline.d.ts +12 -0
  66. package/build/consumer/deadline.d.ts.map +1 -0
  67. package/build/consumer/deadline.js +44 -0
  68. package/build/consumer/deadline.js.map +1 -0
  69. package/build/consumer/fifo/types.d.ts +17 -0
  70. package/build/consumer/fifo/types.d.ts.map +1 -0
  71. package/build/consumer/fifo/types.js +2 -0
  72. package/build/consumer/fifo/types.js.map +1 -0
  73. package/build/consumer/fifo.d.ts +4 -0
  74. package/build/consumer/fifo.d.ts.map +1 -0
  75. package/build/consumer/fifo.js +34 -0
  76. package/build/consumer/fifo.js.map +1 -0
  77. package/build/consumer/index.d.ts +18 -0
  78. package/build/consumer/index.d.ts.map +1 -0
  79. package/build/consumer/index.js +11 -0
  80. package/build/consumer/index.js.map +1 -0
  81. package/build/consumer/policy/types.d.ts +20 -0
  82. package/build/consumer/policy/types.d.ts.map +1 -0
  83. package/build/consumer/policy/types.js +2 -0
  84. package/build/consumer/policy/types.js.map +1 -0
  85. package/build/consumer/policy.d.ts +17 -0
  86. package/build/consumer/policy.d.ts.map +1 -0
  87. package/build/consumer/policy.js +91 -0
  88. package/build/consumer/policy.js.map +1 -0
  89. package/build/consumer/protocols.d.ts +20 -0
  90. package/build/consumer/protocols.d.ts.map +1 -0
  91. package/build/consumer/protocols.js +70 -0
  92. package/build/consumer/protocols.js.map +1 -0
  93. package/build/consumer/refund/types.d.ts +27 -0
  94. package/build/consumer/refund/types.d.ts.map +1 -0
  95. package/build/consumer/refund/types.js +2 -0
  96. package/build/consumer/refund/types.js.map +1 -0
  97. package/build/consumer/refund.d.ts +9 -0
  98. package/build/consumer/refund.d.ts.map +1 -0
  99. package/build/consumer/refund.js +112 -0
  100. package/build/consumer/refund.js.map +1 -0
  101. package/build/consumer/refusal.d.ts +10 -0
  102. package/build/consumer/refusal.d.ts.map +1 -0
  103. package/build/consumer/refusal.js +35 -0
  104. package/build/consumer/refusal.js.map +1 -0
  105. package/build/consumer/revive/types.d.ts +19 -0
  106. package/build/consumer/revive/types.d.ts.map +1 -0
  107. package/build/consumer/revive/types.js +2 -0
  108. package/build/consumer/revive/types.js.map +1 -0
  109. package/build/consumer/revive.d.ts +13 -0
  110. package/build/consumer/revive.d.ts.map +1 -0
  111. package/build/consumer/revive.js +43 -0
  112. package/build/consumer/revive.js.map +1 -0
  113. package/build/consumer/types.d.ts +196 -0
  114. package/build/consumer/types.d.ts.map +1 -0
  115. package/build/consumer/types.js +2 -0
  116. package/build/consumer/types.js.map +1 -0
  117. package/build/consumer/types.local.d.ts +11 -0
  118. package/build/consumer/types.local.d.ts.map +1 -0
  119. package/build/consumer/types.local.js +2 -0
  120. package/build/consumer/types.local.js.map +1 -0
  121. package/build/countries.d.ts +3 -0
  122. package/build/countries.d.ts.map +1 -0
  123. package/build/countries.js +4 -0
  124. package/build/countries.js.map +1 -0
  125. package/build/entitled.d.ts +14 -0
  126. package/build/entitled.d.ts.map +1 -0
  127. package/build/entitled.js +17 -0
  128. package/build/entitled.js.map +1 -0
  129. package/build/entitlement/types.d.ts +29 -0
  130. package/build/entitlement/types.d.ts.map +1 -0
  131. package/build/entitlement/types.js +2 -0
  132. package/build/entitlement/types.js.map +1 -0
  133. package/build/entitlement.d.ts +12 -0
  134. package/build/entitlement.d.ts.map +1 -0
  135. package/build/entitlement.js +64 -0
  136. package/build/entitlement.js.map +1 -0
  137. package/build/errors.d.ts +189 -0
  138. package/build/errors.d.ts.map +1 -1
  139. package/build/errors.js +361 -0
  140. package/build/errors.js.map +1 -1
  141. package/build/estimate/types.d.ts +34 -0
  142. package/build/estimate/types.d.ts.map +1 -0
  143. package/build/estimate/types.js +2 -0
  144. package/build/estimate/types.js.map +1 -0
  145. package/build/estimate.d.ts +11 -0
  146. package/build/estimate.d.ts.map +1 -0
  147. package/build/estimate.js +76 -0
  148. package/build/estimate.js.map +1 -0
  149. package/build/i18n/consumer-rights/be.json +131 -0
  150. package/build/i18n/consumer-rights/de.json +131 -0
  151. package/build/i18n/consumer-rights/en.json +131 -0
  152. package/build/i18n/consumer-rights/es.json +131 -0
  153. package/build/i18n/consumer-rights/fr.json +131 -0
  154. package/build/i18n/consumer-rights/pl.json +131 -0
  155. package/build/i18n/consumer-rights/ru.json +131 -0
  156. package/build/i18n/consumer-rights/uk.json +131 -0
  157. package/build/i18n/errors/be.json +30 -0
  158. package/build/i18n/errors/de.json +30 -0
  159. package/build/i18n/errors/en.json +30 -0
  160. package/build/i18n/errors/es.json +30 -0
  161. package/build/i18n/errors/fr.json +30 -0
  162. package/build/i18n/errors/pl.json +30 -0
  163. package/build/i18n/errors/ru.json +30 -0
  164. package/build/i18n/errors/uk.json +30 -0
  165. package/build/i18n/fr.json +39 -0
  166. package/build/i18n.js +53 -8
  167. package/build/i18n.js.map +1 -1
  168. package/build/index.d.ts +19 -1
  169. package/build/index.d.ts.map +1 -1
  170. package/build/index.js +12 -1
  171. package/build/index.js.map +1 -1
  172. package/build/limit/types.d.ts +44 -0
  173. package/build/limit/types.d.ts.map +1 -0
  174. package/build/limit/types.js +2 -0
  175. package/build/limit/types.js.map +1 -0
  176. package/build/limit.d.ts +23 -0
  177. package/build/limit.d.ts.map +1 -0
  178. package/build/limit.js +97 -0
  179. package/build/limit.js.map +1 -0
  180. package/build/model/checkout.d.ts +2 -3
  181. package/build/model/checkout.d.ts.map +1 -1
  182. package/build/model/checkout.js +12 -7
  183. package/build/model/checkout.js.map +1 -1
  184. package/build/model/consts.d.ts +15 -0
  185. package/build/model/consts.d.ts.map +1 -0
  186. package/build/model/consts.js +193 -0
  187. package/build/model/consts.js.map +1 -0
  188. package/build/model/consts.local.d.ts +35 -0
  189. package/build/model/consts.local.d.ts.map +1 -0
  190. package/build/model/consts.local.js +65 -0
  191. package/build/model/consts.local.js.map +1 -0
  192. package/build/model/consumer.d.ts +23 -0
  193. package/build/model/consumer.d.ts.map +1 -0
  194. package/build/model/consumer.js +272 -0
  195. package/build/model/consumer.js.map +1 -0
  196. package/build/model/estimate.d.ts +5 -0
  197. package/build/model/estimate.d.ts.map +1 -0
  198. package/build/model/estimate.js +69 -0
  199. package/build/model/estimate.js.map +1 -0
  200. package/build/model/index.d.ts +7 -2
  201. package/build/model/index.d.ts.map +1 -1
  202. package/build/model/index.js +7 -2
  203. package/build/model/index.js.map +1 -1
  204. package/build/model/limit.d.ts +7 -0
  205. package/build/model/limit.d.ts.map +1 -0
  206. package/build/model/limit.js +32 -0
  207. package/build/model/limit.js.map +1 -0
  208. package/build/model/plan.d.ts.map +1 -1
  209. package/build/model/plan.js +24 -11
  210. package/build/model/plan.js.map +1 -1
  211. package/build/model/portal.d.ts +4 -0
  212. package/build/model/portal.d.ts.map +1 -0
  213. package/build/model/portal.js +22 -0
  214. package/build/model/portal.js.map +1 -0
  215. package/build/model/pricing.d.ts +4 -0
  216. package/build/model/pricing.d.ts.map +1 -0
  217. package/build/model/pricing.js +31 -0
  218. package/build/model/pricing.js.map +1 -0
  219. package/build/model/view.d.ts +3 -0
  220. package/build/model/view.d.ts.map +1 -0
  221. package/build/model/view.js +15 -0
  222. package/build/model/view.js.map +1 -0
  223. package/build/pricing/types.d.ts +13 -0
  224. package/build/pricing/types.d.ts.map +1 -0
  225. package/build/pricing/types.js +2 -0
  226. package/build/pricing/types.js.map +1 -0
  227. package/build/pricing.d.ts +13 -0
  228. package/build/pricing.d.ts.map +1 -0
  229. package/build/pricing.js +74 -0
  230. package/build/pricing.js.map +1 -0
  231. package/build/promo/types.d.ts +14 -0
  232. package/build/promo/types.d.ts.map +1 -0
  233. package/build/promo/types.js +2 -0
  234. package/build/promo/types.js.map +1 -0
  235. package/build/promo.d.ts +7 -0
  236. package/build/promo.d.ts.map +1 -0
  237. package/build/promo.js +25 -0
  238. package/build/promo.js.map +1 -0
  239. package/build/regions/types.d.ts +32 -0
  240. package/build/regions/types.d.ts.map +1 -0
  241. package/build/regions/types.js +2 -0
  242. package/build/regions/types.js.map +1 -0
  243. package/build/regions.d.ts +15 -0
  244. package/build/regions.d.ts.map +1 -0
  245. package/build/regions.js +43 -0
  246. package/build/regions.js.map +1 -0
  247. package/build/service.d.ts.map +1 -1
  248. package/build/service.js +24 -9
  249. package/build/service.js.map +1 -1
  250. package/build/types.d.ts +619 -52
  251. package/build/types.d.ts.map +1 -1
  252. package/build/types.local.d.ts +4 -0
  253. package/build/types.local.d.ts.map +1 -0
  254. package/build/types.local.js +2 -0
  255. package/build/types.local.js.map +1 -0
  256. package/build/view/types.d.ts +33 -0
  257. package/build/view/types.d.ts.map +1 -0
  258. package/build/view/types.js +2 -0
  259. package/build/view/types.js.map +1 -0
  260. package/build/view.d.ts +13 -0
  261. package/build/view.d.ts.map +1 -0
  262. package/build/view.js +78 -0
  263. package/build/view.js.map +1 -0
  264. package/package.json +14 -12
  265. package/src/advertise.ts +13 -0
  266. package/src/checkout/consts.ts +1 -0
  267. package/src/checkout/index.ts +5 -0
  268. package/src/checkout/narrow/types.ts +27 -0
  269. package/src/checkout/narrow.ts +63 -0
  270. package/src/checkout/protocols.ts +39 -0
  271. package/src/checkout/types.ts +27 -0
  272. package/src/consts.local.ts +34 -0
  273. package/src/consts.ts +417 -24
  274. package/src/consumer/cancel.ts +40 -0
  275. package/src/consumer/consts.local.ts +16 -0
  276. package/src/consumer/consts.ts +46 -0
  277. package/src/consumer/copy/types.ts +54 -0
  278. package/src/consumer/copy.ts +121 -0
  279. package/src/consumer/deadline/types.ts +30 -0
  280. package/src/consumer/deadline.ts +61 -0
  281. package/src/consumer/fifo/types.ts +17 -0
  282. package/src/consumer/fifo.ts +42 -0
  283. package/src/consumer/index.ts +17 -0
  284. package/src/consumer/policy/types.ts +20 -0
  285. package/src/consumer/policy.ts +101 -0
  286. package/src/consumer/protocols.ts +116 -0
  287. package/src/consumer/refund/types.ts +27 -0
  288. package/src/consumer/refund.ts +136 -0
  289. package/src/consumer/refusal.ts +35 -0
  290. package/src/consumer/revive/types.ts +23 -0
  291. package/src/consumer/revive.ts +82 -0
  292. package/src/consumer/types.local.ts +10 -0
  293. package/src/consumer/types.ts +189 -0
  294. package/src/countries.ts +5 -0
  295. package/src/entitled.ts +20 -0
  296. package/src/entitlement/types.ts +29 -0
  297. package/src/entitlement.ts +86 -0
  298. package/src/errors.ts +445 -0
  299. package/src/estimate/types.ts +34 -0
  300. package/src/estimate.ts +90 -0
  301. package/src/i18n/consumer-rights/be.json +131 -0
  302. package/src/i18n/consumer-rights/de.json +131 -0
  303. package/src/i18n/consumer-rights/en.json +131 -0
  304. package/src/i18n/consumer-rights/es.json +131 -0
  305. package/src/i18n/consumer-rights/fr.json +131 -0
  306. package/src/i18n/consumer-rights/pl.json +131 -0
  307. package/src/i18n/consumer-rights/ru.json +131 -0
  308. package/src/i18n/consumer-rights/uk.json +131 -0
  309. package/src/i18n/errors/be.json +30 -0
  310. package/src/i18n/errors/de.json +30 -0
  311. package/src/i18n/errors/en.json +30 -0
  312. package/src/i18n/errors/es.json +30 -0
  313. package/src/i18n/errors/fr.json +30 -0
  314. package/src/i18n/errors/pl.json +30 -0
  315. package/src/i18n/errors/ru.json +30 -0
  316. package/src/i18n/errors/uk.json +30 -0
  317. package/src/i18n/fr.json +39 -0
  318. package/src/i18n.ts +58 -8
  319. package/src/index.ts +19 -2
  320. package/src/limit/types.ts +41 -0
  321. package/src/limit.ts +132 -0
  322. package/src/model/checkout.ts +12 -7
  323. package/src/model/consts.local.ts +83 -0
  324. package/src/model/consts.ts +206 -0
  325. package/src/model/consumer.ts +294 -0
  326. package/src/model/estimate.ts +73 -0
  327. package/src/model/index.ts +7 -2
  328. package/src/model/limit.ts +36 -0
  329. package/src/model/plan.ts +25 -12
  330. package/src/model/portal.ts +25 -0
  331. package/src/model/pricing.ts +34 -0
  332. package/src/model/view.ts +17 -0
  333. package/src/pricing/types.ts +13 -0
  334. package/src/pricing.ts +110 -0
  335. package/src/promo/types.ts +18 -0
  336. package/src/promo.ts +46 -0
  337. package/src/regions/types.ts +42 -0
  338. package/src/regions.ts +85 -0
  339. package/src/service.ts +32 -15
  340. package/src/types.local.ts +5 -0
  341. package/src/types.ts +669 -49
  342. package/src/view/types.ts +39 -0
  343. package/src/view.ts +119 -0
  344. package/tests/checkout-narrow.spec.ts +75 -0
  345. package/tests/consumer-copy.spec.ts +378 -0
  346. package/tests/consumer-deadline.spec.ts +85 -0
  347. package/tests/consumer-fifo.spec.ts +61 -0
  348. package/tests/consumer-protocols.spec.ts +103 -0
  349. package/tests/consumer-refund.spec.ts +134 -0
  350. package/tests/consumer-regions.spec.ts +140 -0
  351. package/tests/consumer-wire.spec.ts +170 -0
  352. package/tests/contract.spec.ts +183 -0
  353. package/tests/entitlement.spec.ts +103 -0
  354. package/tests/errors.spec.ts +168 -0
  355. package/tests/estimate.spec.ts +139 -0
  356. package/tests/limit.spec.ts +117 -0
  357. package/tests/pricing.spec.ts +30 -0
  358. package/tests/tsconfig.json +11 -0
  359. package/tests/view.spec.ts +130 -0
  360. package/tsconfig.json +6 -1
  361. package/build/.gitkeep +0 -0
  362. package/src/model/subscription.ts +0 -63
  363. package/src/model/utils.ts +0 -32
  364. package/src/modules.ts +0 -43
package/src/errors.ts CHANGED
@@ -1,5 +1,8 @@
1
1
 
2
2
  import { ResilientError } from '@owlmeans/error'
3
+ import { AuthForbidden } from '@owlmeans/auth'
4
+ import { BILLING_COUNTRY_LOCKED_MARKER, CANCELLATION_UNAVAILABLE_MARKER, CAPABILITY_REQUIRED_MARKER, CHECKOUT_LIMIT_MARKER, LIMIT_EXHAUSTED_FIELDS, LIMIT_EXHAUSTED_MARKER, PERFORMANCE_CONSENT_MARKER, SUBSCRIPTION_START_MARKER, WITHDRAWAL_UNAVAILABLE_MARKER } from './consts.local.js'
5
+ import type { BillingCountryLockedDetails, CheckoutLimitExceededDetails, LimitExhaustedDetails, PerformanceConsentRequiredDetails } from './types.js'
3
6
 
4
7
  export class PaymentError extends ResilientError {
5
8
  public static override typeName: string = 'PaymentError'
@@ -36,6 +39,22 @@ export class PaygateMappingError extends PaygateError {
36
39
  }
37
40
  }
38
41
 
42
+ /**
43
+ * A webhook delivery whose signature is missing or verifies against no known secret. The sender is
44
+ * refused as a client error (400): a forged or unsigned POST at a public hook is not a server fault,
45
+ * and answering 500 would page operators for it and invite the sender to retry it.
46
+ */
47
+ export class PaygateSignatureError extends PaygateError {
48
+ public static override typeName: string = `${PaygateError.typeName}Signature`
49
+ /** Read by the HTTP boundary (`server-api` `declaredErrorStatus`). */
50
+ public static httpStatus: number = 400
51
+
52
+ constructor() {
53
+ super('signature')
54
+ this.type = PaygateSignatureError.typeName
55
+ }
56
+ }
57
+
39
58
  export class ProductError extends PaymentError {
40
59
  public static override typeName: string = `${PaymentError.typeName}Product`
41
60
 
@@ -90,13 +109,439 @@ export class UnknownSubscription extends SubscriptionError {
90
109
  }
91
110
  }
92
111
 
112
+ export class LimitUnknown extends ProductError {
113
+ public static override typeName: string = `${ProductError.typeName}LimitUnknown`
114
+
115
+ constructor(message: string = 'error') {
116
+ super(`limit-unknown:${message}`)
117
+ this.type = LimitUnknown.typeName
118
+ }
119
+ }
120
+
121
+ export class LimitMisdeclared extends ProductError {
122
+ public static override typeName: string = `${ProductError.typeName}LimitMisdeclared`
123
+
124
+ constructor(message: string = 'error') {
125
+ super(`limit-misdeclared:${message}`)
126
+ this.type = LimitMisdeclared.typeName
127
+ }
128
+ }
129
+
130
+ export class PlanRequired extends ProductError {
131
+ public static override typeName: string = `${ProductError.typeName}PlanRequired`
132
+
133
+ constructor(message: string = 'error') {
134
+ super(`plan-required:${message}`)
135
+ this.type = PlanRequired.typeName
136
+ }
137
+ }
138
+
139
+ export class PlanRankConflict extends ProductError {
140
+ public static override typeName: string = `${ProductError.typeName}PlanRankConflict`
141
+
142
+ constructor(message: string = 'error') {
143
+ super(`plan-rank-conflict:${message}`)
144
+ this.type = PlanRankConflict.typeName
145
+ }
146
+ }
147
+
148
+ export class WebhookSetupError extends PaygateError {
149
+ public static override typeName: string = `${PaygateError.typeName}WebhookSetup`
150
+
151
+ constructor(message: string = 'error') {
152
+ super(`webhook-setup:${message}`)
153
+ this.type = WebhookSetupError.typeName
154
+ }
155
+ }
156
+
157
+ /**
158
+ * The entity has nothing the requested portal flow can act on — no paygate customer, no entitling
159
+ * subscription, no plan or item to change to. The entity's own state, not a fault: an HTTP boundary
160
+ * that honours `httpStatus` (`@owlmeans/server-api`) answers 409 Conflict.
161
+ */
162
+ export class PortalUnavailable extends PaygateError {
163
+ public static override typeName: string = `${PaygateError.typeName}PortalUnavailable`
164
+ public static httpStatus = 409
165
+
166
+ constructor(message: string = 'error') {
167
+ super(`portal-unavailable:${message}`)
168
+ this.type = PortalUnavailable.typeName
169
+ }
170
+ }
171
+
172
+ /**
173
+ * The entity's plan does not allow the request.
174
+ *
175
+ * Extends `AuthForbidden` so an HTTP boundary answers 403, never 500. Only `type` and `message`
176
+ * survive a marshal round trip, so each subclass packs its fields into the message and rebuilds
177
+ * them in `finalizeUnmarshal()`.
178
+ */
179
+ export class EntitlementRefusal extends AuthForbidden {
180
+ public static override typeName: string = 'EntitlementRefusal'
181
+
182
+ constructor(message: string = 'error') {
183
+ super(`entitlement:${message}`)
184
+ this.type = EntitlementRefusal.typeName
185
+ }
186
+ }
187
+
188
+ /** None of the capability parameters (OR'd) is granted. Message marker `capability-required:<a|b>`. */
189
+ export class CapabilityRequired extends EntitlementRefusal {
190
+ public static override typeName: string = `${EntitlementRefusal.typeName}CapabilityRequired`
191
+
192
+ /** The refused parameters, as the gate took them. */
193
+ public params: string[] = []
194
+
195
+ constructor(params: string | string[] = []) {
196
+ const list = (Array.isArray(params) ? params : [params]).filter(param => param !== '')
197
+ super(`${CAPABILITY_REQUIRED_MARKER}${list.join('|')}`)
198
+ this.type = CapabilityRequired.typeName
199
+ this.applyFields()
200
+ }
201
+
202
+ private applyFields(): void {
203
+ const at = this.message.indexOf(CAPABILITY_REQUIRED_MARKER)
204
+ const packed = at < 0 ? '' : this.message.slice(at + CAPABILITY_REQUIRED_MARKER.length)
205
+ this.params = packed.split('|').filter(param => param !== '')
206
+ }
207
+
208
+ override finalizeUnmarshal(): void {
209
+ this.applyFields()
210
+ }
211
+ }
212
+
213
+
214
+ /**
215
+ * A limit has no room left. Message marker `limit-exhausted:<key>:<used>/<limit>[:<resetsAt ISO>]`.
216
+ */
217
+ export class LimitExhausted extends EntitlementRefusal {
218
+ public static override typeName: string = `${EntitlementRefusal.typeName}LimitExhausted`
219
+
220
+ public limitKey: string = ''
221
+ public used: number = 0
222
+ public limit: number = 0
223
+ public resetsAt?: Date
224
+
225
+ /** The packed marker body of a refusal. */
226
+ static encode(details: LimitExhaustedDetails): string {
227
+ const resetsAt = details.resetsAt != null && !Number.isNaN(details.resetsAt.getTime())
228
+ ? `:${details.resetsAt.toISOString()}` : ''
229
+ return `${details.key}:${details.used}/${details.limit}${resetsAt}`
230
+ }
231
+
232
+ constructor(details: LimitExhaustedDetails)
233
+ /** A packed marker body (`LimitExhausted.encode`) or a marshaled message — the error registry's path. */
234
+ constructor(message: string)
235
+ constructor(details: LimitExhaustedDetails | string = 'error') {
236
+ super(`${LIMIT_EXHAUSTED_MARKER}${typeof details === 'string' ? details : LimitExhausted.encode(details)}`)
237
+ this.type = LimitExhausted.typeName
238
+ this.applyFields()
239
+ }
240
+
241
+ private applyFields(): void {
242
+ const at = this.message.indexOf(LIMIT_EXHAUSTED_MARKER)
243
+ const match = at < 0 ? null
244
+ : LIMIT_EXHAUSTED_FIELDS.exec(this.message.slice(at + LIMIT_EXHAUSTED_MARKER.length))
245
+ if (match == null) {
246
+ return
247
+ }
248
+ const [, key, used, limit, resetsAt] = match
249
+ this.limitKey = key
250
+ this.used = Number(used) || 0
251
+ this.limit = Number(limit) || 0
252
+ const date = resetsAt != null ? new Date(resetsAt) : null
253
+ this.resetsAt = date != null && !Number.isNaN(date.getTime()) ? date : undefined
254
+ }
255
+
256
+ override finalizeUnmarshal(): void {
257
+ this.applyFields()
258
+ }
259
+ }
260
+
261
+ /**
262
+ * A consumer-rights fault — a misdeclared policy (`policy:<field>`), a missing usage meter or
263
+ * mailer, a copy template without a value (`copy:<path>`). Declares no status: 500.
264
+ */
265
+ export class ConsumerRightsError extends PaymentError {
266
+ public static override typeName: string = `${PaymentError.typeName}ConsumerRights`
267
+
268
+ constructor(message: string = 'error') {
269
+ super(`consumer-rights:${message}`)
270
+ this.type = ConsumerRightsError.typeName
271
+ }
272
+ }
273
+
274
+ /**
275
+ * The consumer's own position refuses the request — never a fault. Deliberately NOT an
276
+ * `AuthForbidden`: an HTTP boundary tests that family first, and a 403 would hide the declared
277
+ * 428/409 a client acts on.
278
+ */
279
+ export class ConsumerRightsRefusal extends ConsumerRightsError {
280
+ public static override typeName: string = `${ConsumerRightsError.typeName}Refusal`
281
+
282
+ constructor(message: string = 'error') {
283
+ super(message)
284
+ this.type = ConsumerRightsRefusal.typeName
285
+ }
286
+ }
287
+
288
+ /** The packed body after the LAST marker occurrence — a registry rebuild doubles the prefix. */
289
+ const packedAfter = (message: string, marker: string): string | null => {
290
+ const at = message.lastIndexOf(marker)
291
+
292
+ return at < 0 ? null : message.slice(at + marker.length)
293
+ }
294
+
295
+ const dateOf = (value: string | undefined): Date | undefined => {
296
+ const date = value != null && value !== '' ? new Date(value) : null
297
+
298
+ return date != null && !Number.isNaN(date.getTime()) ? date : undefined
299
+ }
300
+
301
+ const isoOf = (date: Date | undefined): string =>
302
+ date != null && !Number.isNaN(date.getTime()) ? `:${date.toISOString()}` : ''
303
+
304
+ const decode = (value: string): string => {
305
+ try {
306
+ return decodeURIComponent(value)
307
+ } catch {
308
+ return value
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Billed work would spend credits of a purchase still inside its withdrawal window, and the
314
+ * consumer has not expressly requested performance. HTTP 428. Marker
315
+ * `performance-consent-required:<pending>[:<deadline ISO>]`.
316
+ */
317
+ export class PerformanceConsentRequired extends ConsumerRightsRefusal {
318
+ public static override typeName: string = `${ConsumerRightsRefusal.typeName}PerformanceConsentRequired`
319
+ public static httpStatus = 428
320
+
321
+ public pending: number = 0
322
+ public deadline?: Date
323
+
324
+ static encode(details: PerformanceConsentRequiredDetails): string {
325
+ return `${Math.max(0, Math.floor(details.pending))}${isoOf(details.deadline)}`
326
+ }
327
+
328
+ constructor(details: PerformanceConsentRequiredDetails)
329
+ /** A packed marker body (`encode`) or a marshaled message — the error registry's path. */
330
+ constructor(message: string)
331
+ constructor(details: PerformanceConsentRequiredDetails | string = 'error') {
332
+ super(`${PERFORMANCE_CONSENT_MARKER}${typeof details === 'string' ? details : PerformanceConsentRequired.encode(details)}`)
333
+ this.type = PerformanceConsentRequired.typeName
334
+ this.applyFields()
335
+ }
336
+
337
+ private applyFields(): void {
338
+ const match = /^(\d+)(?::(.+))?$/.exec(packedAfter(this.message, PERFORMANCE_CONSENT_MARKER) ?? '')
339
+ if (match == null) {
340
+ return
341
+ }
342
+ this.pending = Number(match[1])
343
+ this.deadline = dateOf(match[2])
344
+ }
345
+
346
+ override finalizeUnmarshal(): void {
347
+ this.applyFields()
348
+ }
349
+ }
350
+
351
+ /**
352
+ * A subscription checkout needs a fresh express start request bound to this plan. HTTP 428.
353
+ * Marker `subscription-start-required:<encodeURIComponent(planSku)>`.
354
+ */
355
+ export class SubscriptionStartRequired extends ConsumerRightsRefusal {
356
+ public static override typeName: string = `${ConsumerRightsRefusal.typeName}SubscriptionStartRequired`
357
+ public static httpStatus = 428
358
+
359
+ public planSku: string = ''
360
+
361
+ static encode(planSku: string): string {
362
+ return encodeURIComponent(planSku)
363
+ }
364
+
365
+ /** `planSku` as is, or a packed marker body / marshaled message (the registry's path). */
366
+ constructor(planSku: string | { planSku: string } = 'error') {
367
+ super(`${SUBSCRIPTION_START_MARKER}${typeof planSku === 'string' ? SubscriptionStartRequired.encodeLoose(planSku) : SubscriptionStartRequired.encode(planSku.planSku)}`)
368
+ this.type = SubscriptionStartRequired.typeName
369
+ this.applyFields()
370
+ }
371
+
372
+ /** A string that already is a marker (the registry path) passes through unencoded. */
373
+ private static encodeLoose(value: string): string {
374
+ return value.includes(SUBSCRIPTION_START_MARKER) ? value : SubscriptionStartRequired.encode(value)
375
+ }
376
+
377
+ private applyFields(): void {
378
+ const packed = packedAfter(this.message, SUBSCRIPTION_START_MARKER)
379
+ if (packed != null) {
380
+ this.planSku = decode(packed)
381
+ }
382
+ }
383
+
384
+ override finalizeUnmarshal(): void {
385
+ this.applyFields()
386
+ }
387
+ }
388
+
389
+ /**
390
+ * The entity's billing country is fixed and the request declared another. HTTP 409. Marker
391
+ * `billing-country-locked:<country>[:<requested>]`.
392
+ */
393
+ export class BillingCountryLocked extends ConsumerRightsRefusal {
394
+ public static override typeName: string = `${ConsumerRightsRefusal.typeName}BillingCountryLocked`
395
+ public static httpStatus = 409
396
+
397
+ public country: string = ''
398
+ public requested?: string
399
+
400
+ static encode(details: BillingCountryLockedDetails): string {
401
+ return details.requested != null && details.requested !== ''
402
+ ? `${details.country}:${details.requested}` : details.country
403
+ }
404
+
405
+ constructor(details: BillingCountryLockedDetails)
406
+ constructor(message: string)
407
+ constructor(details: BillingCountryLockedDetails | string = 'error') {
408
+ super(`${BILLING_COUNTRY_LOCKED_MARKER}${typeof details === 'string' ? details : BillingCountryLocked.encode(details)}`)
409
+ this.type = BillingCountryLocked.typeName
410
+ this.applyFields()
411
+ }
412
+
413
+ private applyFields(): void {
414
+ const match = /^([A-Za-z]{2})(?::([A-Za-z]{2}))?$/.exec(packedAfter(this.message, BILLING_COUNTRY_LOCKED_MARKER) ?? '')
415
+ if (match == null) {
416
+ return
417
+ }
418
+ this.country = match[1]
419
+ this.requested = match[2]
420
+ }
421
+
422
+ override finalizeUnmarshal(): void {
423
+ this.applyFields()
424
+ }
425
+ }
426
+
427
+ /**
428
+ * The named purchase cannot be withdrawn from (`WithdrawalUnavailableReason`). HTTP 409. Marker
429
+ * `withdrawal-unavailable:<reason>`. Never raised on the public page, which discloses nothing.
430
+ */
431
+ export class WithdrawalUnavailable extends ConsumerRightsRefusal {
432
+ public static override typeName: string = `${ConsumerRightsRefusal.typeName}WithdrawalUnavailable`
433
+ public static httpStatus = 409
434
+
435
+ public reason: string = ''
436
+
437
+ constructor(reason: string = 'error') {
438
+ super(`${WITHDRAWAL_UNAVAILABLE_MARKER}${reason}`)
439
+ this.type = WithdrawalUnavailable.typeName
440
+ this.applyFields()
441
+ }
442
+
443
+ private applyFields(): void {
444
+ this.reason = /^[\w-]*/.exec(packedAfter(this.message, WITHDRAWAL_UNAVAILABLE_MARKER) ?? '')?.[0] ?? ''
445
+ }
446
+
447
+ override finalizeUnmarshal(): void {
448
+ this.applyFields()
449
+ }
450
+ }
451
+
452
+ /**
453
+ * Nothing to cancel in-app (`CancellationUnavailableReason`). HTTP 409. Marker
454
+ * `cancellation-unavailable:<reason>`.
455
+ */
456
+ export class CancellationUnavailable extends ConsumerRightsRefusal {
457
+ public static override typeName: string = `${ConsumerRightsRefusal.typeName}CancellationUnavailable`
458
+ public static httpStatus = 409
459
+
460
+ public reason: string = ''
461
+
462
+ constructor(reason: string = 'error') {
463
+ super(`${CANCELLATION_UNAVAILABLE_MARKER}${reason}`)
464
+ this.type = CancellationUnavailable.typeName
465
+ this.applyFields()
466
+ }
467
+
468
+ private applyFields(): void {
469
+ this.reason = /^[\w-]*/.exec(packedAfter(this.message, CANCELLATION_UNAVAILABLE_MARKER) ?? '')?.[0] ?? ''
470
+ }
471
+
472
+ override finalizeUnmarshal(): void {
473
+ this.applyFields()
474
+ }
475
+ }
476
+
477
+ /**
478
+ * The amount is above what this entity may buy now (a narrowing of the plan's policy). HTTP 409.
479
+ * Marker `checkout-limit-exceeded:<encodeURIComponent(reason)>:<maximumMinor>:<currency>[:<ISO>]`.
480
+ * A payment refusal, not a consumer-rights one.
481
+ */
482
+ export class CheckoutLimitExceeded extends PaymentError {
483
+ public static override typeName: string = `${PaymentError.typeName}CheckoutLimitExceeded`
484
+ public static httpStatus = 409
485
+
486
+ public reason: string = ''
487
+ public maximumMinor: number = 0
488
+ public currency: string = ''
489
+ public resetsAt?: Date
490
+
491
+ static encode(details: CheckoutLimitExceededDetails): string {
492
+ return `${encodeURIComponent(details.reason)}:${Math.max(0, Math.floor(details.maximumMinor))}`
493
+ + `:${details.currency.toLowerCase()}${isoOf(details.resetsAt)}`
494
+ }
495
+
496
+ constructor(details: CheckoutLimitExceededDetails)
497
+ constructor(message: string)
498
+ constructor(details: CheckoutLimitExceededDetails | string = 'error') {
499
+ super(`${CHECKOUT_LIMIT_MARKER}${typeof details === 'string' ? details : CheckoutLimitExceeded.encode(details)}`)
500
+ this.type = CheckoutLimitExceeded.typeName
501
+ this.applyFields()
502
+ }
503
+
504
+ private applyFields(): void {
505
+ const match = /^([^:]*):(\d+):([a-z]{3})(?::(.+))?$/.exec(packedAfter(this.message, CHECKOUT_LIMIT_MARKER) ?? '')
506
+ if (match == null) {
507
+ return
508
+ }
509
+ this.reason = decode(match[1])
510
+ this.maximumMinor = Number(match[2])
511
+ this.currency = match[3]
512
+ this.resetsAt = dateOf(match[4])
513
+ }
514
+
515
+ override finalizeUnmarshal(): void {
516
+ this.applyFields()
517
+ }
518
+ }
519
+
93
520
  ResilientError.registerErrorClass(PaymentError)
94
521
  ResilientError.registerErrorClass(PaygateError)
95
522
  ResilientError.registerErrorClass(UnknownPaygate)
96
523
  ResilientError.registerErrorClass(PaygateMappingError)
524
+ ResilientError.registerErrorClass(PaygateSignatureError)
97
525
  ResilientError.registerErrorClass(ProductError)
98
526
  ResilientError.registerErrorClass(UnknownProduct)
99
527
  ResilientError.registerErrorClass(UnknownPlan)
100
528
  ResilientError.registerErrorClass(PaymentIdentificationError)
101
529
  ResilientError.registerErrorClass(SubscriptionError)
102
530
  ResilientError.registerErrorClass(UnknownSubscription)
531
+ ResilientError.registerErrorClass(LimitUnknown)
532
+ ResilientError.registerErrorClass(LimitMisdeclared)
533
+ ResilientError.registerErrorClass(PlanRequired)
534
+ ResilientError.registerErrorClass(PlanRankConflict)
535
+ ResilientError.registerErrorClass(WebhookSetupError)
536
+ ResilientError.registerErrorClass(PortalUnavailable)
537
+ ResilientError.registerErrorClass(EntitlementRefusal)
538
+ ResilientError.registerErrorClass(CapabilityRequired)
539
+ ResilientError.registerErrorClass(LimitExhausted)
540
+ ResilientError.registerErrorClass(ConsumerRightsError)
541
+ ResilientError.registerErrorClass(ConsumerRightsRefusal)
542
+ ResilientError.registerErrorClass(PerformanceConsentRequired)
543
+ ResilientError.registerErrorClass(SubscriptionStartRequired)
544
+ ResilientError.registerErrorClass(BillingCountryLocked)
545
+ ResilientError.registerErrorClass(WithdrawalUnavailable)
546
+ ResilientError.registerErrorClass(CancellationUnavailable)
547
+ ResilientError.registerErrorClass(CheckoutLimitExceeded)
@@ -0,0 +1,34 @@
1
+ import type { AmountEstimate, PriceEstimate, PricingPolicy } from '../types.js'
2
+
3
+ /** The pricing policy's invariants and the tax / local-currency estimate of an amount. */
4
+ export interface PriceEstimateHelper {
5
+ /**
6
+ * @throws PaymentError when a tax estimate is served without automatic tax, a currency estimate is
7
+ * served without Adaptive Pricing, or a TTL is not a positive integer.
8
+ */
9
+ assertPricingPolicy: (policy: PricingPolicy) => PricingPolicy
10
+ /**
11
+ * Stripe's `percentage_decimal` (e.g. `"23"`, `"8.875"`) as parts per million — `1_000_000` is
12
+ * `100%` — parsed digit by digit so it is exact where a float parse (`Number(x) * 10_000`) is not.
13
+ * Fractional digits past the fourth are truncated, not rounded: a quarter of a hundredth of a
14
+ * percent is already far finer than any real tax rate.
15
+ *
16
+ * @throws PaymentError when the string is not a plain (unsigned, non-exponential) decimal.
17
+ */
18
+ ratePpmOf: (percentageDecimal: string) => number
19
+ /**
20
+ * Re-derive tax and the local-currency total for `amountMinor` from a `PriceEstimate` already
21
+ * fetched for the SAME product/plan/country, so a UI can re-price a custom amount (the credit
22
+ * dialog) without a second Stripe Tax call ($0.05 each).
23
+ *
24
+ * Exact for `amountMinor === estimate.tax.subtotalMinor` (Stripe's own numbers, echoed back).
25
+ * Otherwise exact only for `TaxBehavior.Exclusive` with `estimate.tax.scalable` — a linear
26
+ * percentage rate scales; a flat fee, a reduced-rate portion, or several inclusive rates do not, and
27
+ * neither does rescaling an inclusive amount (its tax portion is carved OUT of the total by a
28
+ * different formula this helper does not attempt). Both cases return `null` amounts, meaning
29
+ * "computed at checkout".
30
+ *
31
+ * @throws PaymentError when `amountMinor` is not a non-negative safe integer.
32
+ */
33
+ estimateOf: (amountMinor: number, estimate: PriceEstimate) => AmountEstimate
34
+ }
@@ -0,0 +1,90 @@
1
+ import { PaymentError } from './errors.js'
2
+ import { TaxBehavior } from './consts.js'
3
+ import type { PriceEstimate, PricingPolicy, AmountEstimate } from './types.js'
4
+ import type { PriceEstimateHelper } from './estimate/types.js'
5
+
6
+ /** Stripe's own convention for a tax amount: half a unit rounds up. */
7
+ const roundHalfUp = (value: number): number => Math.floor(value + 0.5)
8
+
9
+ export const createPriceEstimateHelper = (): PriceEstimateHelper => {
10
+ const assertPricingPolicy = (policy: PricingPolicy): PricingPolicy => {
11
+ if (policy.tax.estimate && !policy.tax.automatic) {
12
+ throw new PaymentError('pricing-policy:tax-estimate-requires-automatic')
13
+ }
14
+ if (policy.currency.estimate && policy.currency.adaptive !== true) {
15
+ throw new PaymentError('pricing-policy:currency-estimate-requires-adaptive')
16
+ }
17
+ const feeRate = policy.currency.adaptiveFeeRate
18
+ if (feeRate != null) {
19
+ if (policy.currency.adaptive !== true) {
20
+ throw new PaymentError('pricing-policy:adaptive-fee-requires-adaptive')
21
+ }
22
+ if (!Number.isFinite(feeRate) || feeRate < 0 || feeRate >= 1) {
23
+ throw new PaymentError('pricing-policy:adaptive-fee-rate')
24
+ }
25
+ }
26
+ for (const ttl of [policy.tax.estimateTtlSeconds, policy.currency.estimateTtlSeconds]) {
27
+ if (ttl != null && (!Number.isSafeInteger(ttl) || ttl < 1)) {
28
+ throw new PaymentError('pricing-policy:ttl')
29
+ }
30
+ }
31
+ return policy
32
+ }
33
+
34
+ const ratePpmOf = (percentageDecimal: string): number => {
35
+ const match = /^(\d+)(?:\.(\d+))?$/.exec(percentageDecimal.trim())
36
+ if (match == null) {
37
+ throw new PaymentError('tax-rate:percentage')
38
+ }
39
+ const [, whole, fraction = ''] = match
40
+ const fractionPpm = Number(`${fraction}0000`.slice(0, 4))
41
+ const ppm = Number(whole) * 10_000 + fractionPpm
42
+ if (!Number.isSafeInteger(ppm)) {
43
+ throw new PaymentError('tax-rate:percentage')
44
+ }
45
+ return ppm
46
+ }
47
+
48
+ const estimateOf = (amountMinor: number, estimate: PriceEstimate): AmountEstimate => {
49
+ if (!Number.isSafeInteger(amountMinor) || amountMinor < 0) {
50
+ throw new PaymentError('price-estimate:amount')
51
+ }
52
+ const { tax, behavior, local } = estimate
53
+ const sameAmount = amountMinor === tax.subtotalMinor
54
+ const rescalable = sameAmount || (behavior === TaxBehavior.Exclusive && tax.scalable)
55
+ if (!rescalable) {
56
+ return { subtotalMinor: amountMinor, taxMinor: null, totalMinor: null }
57
+ }
58
+ // `sameAmount` trusts Stripe's own numbers outright; otherwise only Exclusive ever reaches here
59
+ // (`rescalable` above excludes an Inclusive amount that differs from the reference).
60
+ const taxMinor = sameAmount ? tax.taxMinor : tax.rates.reduce(
61
+ (sum, rate) => sum + roundHalfUp(amountMinor * rate.ratePpm / 1_000_000), 0,
62
+ )
63
+ const totalMinor = sameAmount ? tax.totalMinor : amountMinor + taxMinor
64
+ const result: AmountEstimate = { subtotalMinor: amountMinor, taxMinor, totalMinor }
65
+ if (local != null) {
66
+ result.local = {
67
+ currency: local.currency,
68
+ subtotalAmount: amountMinor / 100 / local.exchangeRate,
69
+ taxAmount: taxMinor / 100 / local.exchangeRate,
70
+ totalAmount: totalMinor / 100 / local.exchangeRate,
71
+ }
72
+ }
73
+ return result
74
+ }
75
+
76
+ return { assertPricingPolicy, ratePpmOf, estimateOf }
77
+ }
78
+
79
+ export const priceEstimateHelper = createPriceEstimateHelper()
80
+
81
+ /** @deprecated compat:factory-refactor — use `priceEstimateHelper.assertPricingPolicy(…)` */
82
+ export const assertPricingPolicy = (policy: PricingPolicy): PricingPolicy =>
83
+ priceEstimateHelper.assertPricingPolicy(policy)
84
+
85
+ /** @deprecated compat:factory-refactor — use `priceEstimateHelper.ratePpmOf(…)` */
86
+ export const ratePpmOf = (percentageDecimal: string): number => priceEstimateHelper.ratePpmOf(percentageDecimal)
87
+
88
+ /** @deprecated compat:factory-refactor — use `priceEstimateHelper.estimateOf(…)` */
89
+ export const estimateOf = (amountMinor: number, estimate: PriceEstimate): AmountEstimate =>
90
+ priceEstimateHelper.estimateOf(amountMinor, estimate)