@owlmeans/server-payment 0.1.18-rc.2 → 0.1.18-rc.21

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 (222) hide show
  1. package/README.md +109 -25
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/server-payment/SKILL.md +475 -52
  4. package/build/actions/index.d.ts +1 -0
  5. package/build/actions/index.d.ts.map +1 -1
  6. package/build/actions/index.js +1 -0
  7. package/build/actions/index.js.map +1 -1
  8. package/build/actions/resync-subscriptions.d.ts +3 -0
  9. package/build/actions/resync-subscriptions.d.ts.map +1 -0
  10. package/build/actions/resync-subscriptions.js +7 -0
  11. package/build/actions/resync-subscriptions.js.map +1 -0
  12. package/build/actions/resync.d.ts +1 -0
  13. package/build/actions/resync.d.ts.map +1 -1
  14. package/build/actions/resync.js +7 -3
  15. package/build/actions/resync.js.map +1 -1
  16. package/build/actions/webhook.d.ts.map +1 -1
  17. package/build/actions/webhook.js +6 -3
  18. package/build/actions/webhook.js.map +1 -1
  19. package/build/config.d.ts +60 -5
  20. package/build/config.d.ts.map +1 -1
  21. package/build/config.js +237 -5
  22. package/build/config.js.map +1 -1
  23. package/build/consts.d.ts +75 -3
  24. package/build/consts.d.ts.map +1 -1
  25. package/build/consts.js +94 -5
  26. package/build/consts.js.map +1 -1
  27. package/build/consumer/capture.d.ts +74 -0
  28. package/build/consumer/capture.d.ts.map +1 -0
  29. package/build/consumer/capture.js +291 -0
  30. package/build/consumer/capture.js.map +1 -0
  31. package/build/consumer/format.d.ts +27 -0
  32. package/build/consumer/format.d.ts.map +1 -0
  33. package/build/consumer/format.js +81 -0
  34. package/build/consumer/format.js.map +1 -0
  35. package/build/consumer/handlers.d.ts +28 -0
  36. package/build/consumer/handlers.d.ts.map +1 -0
  37. package/build/consumer/handlers.js +173 -0
  38. package/build/consumer/handlers.js.map +1 -0
  39. package/build/consumer/index.d.ts +7 -0
  40. package/build/consumer/index.d.ts.map +1 -0
  41. package/build/consumer/index.js +6 -0
  42. package/build/consumer/index.js.map +1 -0
  43. package/build/consumer/mail.d.ts +27 -0
  44. package/build/consumer/mail.d.ts.map +1 -0
  45. package/build/consumer/mail.js +314 -0
  46. package/build/consumer/mail.js.map +1 -0
  47. package/build/consumer/origin.d.ts +14 -0
  48. package/build/consumer/origin.d.ts.map +1 -0
  49. package/build/consumer/origin.js +47 -0
  50. package/build/consumer/origin.js.map +1 -0
  51. package/build/consumer/reconcile.d.ts +12 -0
  52. package/build/consumer/reconcile.d.ts.map +1 -0
  53. package/build/consumer/reconcile.js +317 -0
  54. package/build/consumer/reconcile.js.map +1 -0
  55. package/build/consumer/records.d.ts +78 -0
  56. package/build/consumer/records.d.ts.map +1 -0
  57. package/build/consumer/records.js +296 -0
  58. package/build/consumer/records.js.map +1 -0
  59. package/build/consumer/service.d.ts +51 -0
  60. package/build/consumer/service.d.ts.map +1 -0
  61. package/build/consumer/service.js +760 -0
  62. package/build/consumer/service.js.map +1 -0
  63. package/build/consumer/withdrawal.d.ts +57 -0
  64. package/build/consumer/withdrawal.d.ts.map +1 -0
  65. package/build/consumer/withdrawal.js +247 -0
  66. package/build/consumer/withdrawal.js.map +1 -0
  67. package/build/entitlement.d.ts +10 -0
  68. package/build/entitlement.d.ts.map +1 -0
  69. package/build/entitlement.js +69 -0
  70. package/build/entitlement.js.map +1 -0
  71. package/build/entrypoints.d.ts +1 -1
  72. package/build/entrypoints.d.ts.map +1 -1
  73. package/build/entrypoints.js +2 -1
  74. package/build/entrypoints.js.map +1 -1
  75. package/build/gate.d.ts +32 -4
  76. package/build/gate.d.ts.map +1 -1
  77. package/build/gate.js +59 -19
  78. package/build/gate.js.map +1 -1
  79. package/build/index.d.ts +18 -3
  80. package/build/index.d.ts.map +1 -1
  81. package/build/index.js +15 -3
  82. package/build/index.js.map +1 -1
  83. package/build/limit.d.ts +13 -0
  84. package/build/limit.d.ts.map +1 -0
  85. package/build/limit.js +47 -0
  86. package/build/limit.js.map +1 -0
  87. package/build/model.d.ts +10 -1
  88. package/build/model.d.ts.map +1 -1
  89. package/build/model.js +215 -17
  90. package/build/model.js.map +1 -1
  91. package/build/observer.d.ts +9 -0
  92. package/build/observer.d.ts.map +1 -1
  93. package/build/observer.js +38 -12
  94. package/build/observer.js.map +1 -1
  95. package/build/plan.d.ts +22 -0
  96. package/build/plan.d.ts.map +1 -0
  97. package/build/plan.js +72 -0
  98. package/build/plan.js.map +1 -0
  99. package/build/plugins/checkout-plugins.d.ts +49 -0
  100. package/build/plugins/checkout-plugins.d.ts.map +1 -0
  101. package/build/plugins/checkout-plugins.js +124 -0
  102. package/build/plugins/checkout-plugins.js.map +1 -0
  103. package/build/plugins/estimate.d.ts +43 -0
  104. package/build/plugins/estimate.d.ts.map +1 -0
  105. package/build/plugins/estimate.js +268 -0
  106. package/build/plugins/estimate.js.map +1 -0
  107. package/build/plugins/events.d.ts +45 -10
  108. package/build/plugins/events.d.ts.map +1 -1
  109. package/build/plugins/events.js +605 -136
  110. package/build/plugins/events.js.map +1 -1
  111. package/build/plugins/fx.d.ts +31 -0
  112. package/build/plugins/fx.d.ts.map +1 -0
  113. package/build/plugins/fx.js +81 -0
  114. package/build/plugins/fx.js.map +1 -0
  115. package/build/plugins/portal.d.ts +37 -0
  116. package/build/plugins/portal.d.ts.map +1 -0
  117. package/build/plugins/portal.js +265 -0
  118. package/build/plugins/portal.js.map +1 -0
  119. package/build/plugins/refunds.d.ts +33 -0
  120. package/build/plugins/refunds.d.ts.map +1 -0
  121. package/build/plugins/refunds.js +80 -0
  122. package/build/plugins/refunds.js.map +1 -0
  123. package/build/plugins/stripe.d.ts +36 -4
  124. package/build/plugins/stripe.d.ts.map +1 -1
  125. package/build/plugins/stripe.js +492 -97
  126. package/build/plugins/stripe.js.map +1 -1
  127. package/build/plugins/webhook-manager.d.ts +46 -0
  128. package/build/plugins/webhook-manager.d.ts.map +1 -0
  129. package/build/plugins/webhook-manager.js +231 -0
  130. package/build/plugins/webhook-manager.js.map +1 -0
  131. package/build/reconcile.d.ts +15 -0
  132. package/build/reconcile.d.ts.map +1 -0
  133. package/build/reconcile.js +88 -0
  134. package/build/reconcile.js.map +1 -0
  135. package/build/resource.d.ts +12 -1
  136. package/build/resource.d.ts.map +1 -1
  137. package/build/resource.js +97 -6
  138. package/build/resource.js.map +1 -1
  139. package/build/service.d.ts +28 -3
  140. package/build/service.d.ts.map +1 -1
  141. package/build/service.js +146 -19
  142. package/build/service.js.map +1 -1
  143. package/build/subscription.d.ts +48 -0
  144. package/build/subscription.d.ts.map +1 -0
  145. package/build/subscription.js +176 -0
  146. package/build/subscription.js.map +1 -0
  147. package/build/sync.d.ts +28 -1
  148. package/build/sync.d.ts.map +1 -1
  149. package/build/sync.js +208 -31
  150. package/build/sync.js.map +1 -1
  151. package/build/types.d.ts +1115 -36
  152. package/build/types.d.ts.map +1 -1
  153. package/build/usage.d.ts +63 -0
  154. package/build/usage.d.ts.map +1 -0
  155. package/build/usage.js +363 -0
  156. package/build/usage.js.map +1 -0
  157. package/build/utils.d.ts +52 -1
  158. package/build/utils.d.ts.map +1 -1
  159. package/build/utils.js +69 -7
  160. package/build/utils.js.map +1 -1
  161. package/package.json +17 -13
  162. package/src/actions/index.ts +1 -0
  163. package/src/actions/resync-subscriptions.ts +10 -0
  164. package/src/actions/resync.ts +6 -3
  165. package/src/actions/webhook.ts +5 -3
  166. package/src/config.ts +264 -8
  167. package/src/consts.ts +114 -6
  168. package/src/consumer/capture.ts +362 -0
  169. package/src/consumer/format.ts +90 -0
  170. package/src/consumer/handlers.ts +211 -0
  171. package/src/consumer/index.ts +6 -0
  172. package/src/consumer/mail.ts +368 -0
  173. package/src/consumer/origin.ts +63 -0
  174. package/src/consumer/reconcile.ts +329 -0
  175. package/src/consumer/records.ts +374 -0
  176. package/src/consumer/service.ts +868 -0
  177. package/src/consumer/withdrawal.ts +302 -0
  178. package/src/entitlement.ts +84 -0
  179. package/src/entrypoints.ts +2 -1
  180. package/src/gate.ts +88 -21
  181. package/src/index.ts +24 -3
  182. package/src/limit.ts +57 -0
  183. package/src/model.ts +237 -18
  184. package/src/observer.ts +44 -11
  185. package/src/plan.ts +89 -0
  186. package/src/plugins/checkout-plugins.ts +155 -0
  187. package/src/plugins/estimate.ts +339 -0
  188. package/src/plugins/events.ts +677 -121
  189. package/src/plugins/fx.ts +122 -0
  190. package/src/plugins/portal.ts +306 -0
  191. package/src/plugins/refunds.ts +108 -0
  192. package/src/plugins/stripe.ts +581 -96
  193. package/src/plugins/webhook-manager.ts +270 -0
  194. package/src/reconcile.ts +103 -0
  195. package/src/resource.ts +152 -7
  196. package/src/service.ts +174 -18
  197. package/src/subscription.ts +231 -0
  198. package/src/sync.ts +249 -29
  199. package/src/types.ts +1227 -32
  200. package/src/usage.ts +453 -0
  201. package/src/utils.ts +127 -10
  202. package/tests/checkout-consumer.spec.ts +348 -0
  203. package/tests/checkout-plugins.spec.ts +164 -0
  204. package/tests/checkout.spec.ts +184 -83
  205. package/tests/consumer-events.spec.ts +218 -0
  206. package/tests/consumer-fixtures.ts +132 -0
  207. package/tests/consumer-ops.spec.ts +351 -0
  208. package/tests/consumer-rights.integration.spec.ts +150 -0
  209. package/tests/consumer-rights.spec.ts +501 -0
  210. package/tests/context.ts +109 -0
  211. package/tests/entitlement.spec.ts +103 -0
  212. package/tests/estimate.spec.ts +240 -0
  213. package/tests/events.spec.ts +356 -0
  214. package/tests/fake-stripe.ts +972 -0
  215. package/tests/gate.spec.ts +62 -72
  216. package/tests/limit-gate.spec.ts +68 -0
  217. package/tests/portal.spec.ts +200 -0
  218. package/tests/protocol.spec.ts +23 -6
  219. package/tests/sync.spec.ts +114 -0
  220. package/tests/usage.integration.spec.ts +101 -0
  221. package/tests/usage.spec.ts +171 -0
  222. package/tests/webhook-manager.spec.ts +152 -0
package/build/types.d.ts CHANGED
@@ -1,10 +1,13 @@
1
+ import type Stripe from 'stripe';
1
2
  import type { InitializedService, LazyService } from '@owlmeans/context';
2
3
  import type { ResourceRecord } from '@owlmeans/resource';
3
4
  import type { MongoResource } from '@owlmeans/mongo-resource';
4
5
  import type { PluginConfig } from '@owlmeans/config';
5
6
  import type { PermissionSet } from '@owlmeans/auth';
7
+ import type { AbstractRequest } from '@owlmeans/entrypoint';
8
+ import type { MailMessage } from '@owlmeans/mailer';
6
9
  import type { Config as ApiConfig, Context as ApiContext } from '@owlmeans/server-api';
7
- import type { AmountCheckoutPolicy, CheckoutPricingMode, LimitConfig, PlanDuration, Product, ProductPlan, ProductType, QuantityCheckoutPolicy, SubscriptionStatus } from '@owlmeans/payment';
10
+ import type { AmountCheckoutPolicy, AmountNarrowing, AmountPolicyView, BillingProfileView, CancellationBody, CancellationKind, CancellationReceipt, CancellationStatus, CheckoutPricingMode, ConsentKind, ConsumerRegion, ConsumerRightsDeclaration, ConsumerRightsLinks, ConsumerRightsPolicy, DeclarationChannel, DeclarationKind, EntitlementView, LimitDeclaration, LimitView, PerformanceConsentBody, PerformanceConsentResponse, PerformanceConsentView, PlanCapability, PlanDuration, PlanPriceView, PlanWithdrawalComponent, PortalFlow, PriceEstimate, PricingPolicy, Product, ProductPlan, ProductType, PurchaseKind, PurchaseView, QuantityCheckoutPolicy, SubscriptionStartBody, SubscriptionStartResponse, SubscriptionStartView, SubscriptionStatus, WithdrawalBody, WithdrawalCandidateList, WithdrawalReceipt, WithdrawalStatus } from '@owlmeans/payment';
8
11
  export interface Config extends ApiConfig {
9
12
  }
10
13
  export interface Context<C extends Config = Config> extends ApiContext<C> {
@@ -23,6 +26,11 @@ export interface PaymentPlan extends ProductPlan {
23
26
  pricingMode?: CheckoutPricingMode;
24
27
  amountPolicy?: AmountCheckoutPolicy;
25
28
  quantityPolicy?: QuantityCheckoutPolicy;
29
+ /**
30
+ * Exact prices in further currencies, major units by lowercase ISO 4217 code (`{ usd: 20 }`).
31
+ * Synced as the reusable Price's `currency_options`, never converted.
32
+ */
33
+ currencyPrices?: Record<string, number>;
26
34
  }
27
35
  export interface PaymentProductDef {
28
36
  sku: string;
@@ -40,6 +48,12 @@ export interface PaymentPlanDef {
40
48
  productSku: string;
41
49
  sku: string;
42
50
  duration: PlanDuration;
51
+ /** Position among the plans of one product; a higher rank is an upgrade. Absent reads as `0`. */
52
+ rank?: number;
53
+ /** Held without paying: `price: 0` and no gateway. */
54
+ free?: boolean;
55
+ /** Paygates the plan is sold through. Absent: the product's gateways. A free plan has none. */
56
+ gateways?: string[];
43
57
  /** Reference unit price in major units; amount checkout uses it only for fulfillment conversion. */
44
58
  price: number;
45
59
  currency?: string;
@@ -48,9 +62,9 @@ export interface PaymentPlanDef {
48
62
  };
49
63
  order?: number;
50
64
  title?: string;
51
- capabilities?: PermissionSet[];
65
+ capabilities?: PlanCapability[];
52
66
  limits?: {
53
- [key: string]: LimitConfig;
67
+ [key: string]: LimitDeclaration;
54
68
  };
55
69
  pricingMode?: CheckoutPricingMode;
56
70
  amountPolicy?: AmountCheckoutPolicy;
@@ -59,10 +73,93 @@ export interface PaymentPlanDef {
59
73
  minQuantity?: number;
60
74
  maxQuantity?: number;
61
75
  defaultQuantity?: number;
76
+ /**
77
+ * Exact prices in further currencies, major units by ISO 4217 code (`{ usd: 20 }`): a recurring
78
+ * or quantity plan's synced Price carries each as a `currency_options` entry at exactly this
79
+ * amount, while its default currency (the settlement currency) is converted as before.
80
+ */
81
+ currencyPrices?: Record<string, number>;
82
+ /**
83
+ * The separately priced parts of a subscription for a withdrawal (CJEU C-641/19): their
84
+ * `shareMinor` sum to `round(price × 100)`. Absent: the whole price is one `time` component.
85
+ */
86
+ withdrawal?: {
87
+ components: PlanWithdrawalComponent[];
88
+ };
89
+ }
90
+ /** File paths (or values) of the Stripe secrets. The webhook secret is an optional override. */
91
+ export interface StripeSecretsDef {
92
+ api: string;
93
+ webhook?: string;
62
94
  }
63
95
  export interface StripePluginConfig extends PluginConfig {
64
96
  api: string;
65
- webhook: string;
97
+ webhook?: string;
98
+ }
99
+ /** Stripe-only pricing settings — never a browser-visible field (see `PricingPolicy` for those). */
100
+ export interface StripePricingDef {
101
+ /** Overrides `STRIPE_FX_QUOTES_API_VERSION`, when Stripe moves or renames the preview. */
102
+ fxApiVersion?: string;
103
+ /**
104
+ * Stripe settlement currency used when a catalogue price is declared in another currency.
105
+ * Recurring prices are converted during product sync; amount checkout is converted per session.
106
+ */
107
+ settlementCurrency?: string;
108
+ /** Explicit Stripe payment methods for subscription Checkout; absent keeps Stripe's dynamic selection. */
109
+ subscriptionPaymentMethodTypes?: string[];
110
+ /**
111
+ * Let a matching `unspecified` price take the declared `tax.behavior` even when the Stripe
112
+ * account's own tax-settings default resolves to the opposite one — which changes what an
113
+ * existing subscriber is charged at their next renewal. Absent/`false`: such a price is left
114
+ * `unspecified` and a `console.error` explains why.
115
+ */
116
+ migrateUnspecifiedPrices?: boolean;
117
+ }
118
+ export interface StripePricingPluginConfig extends PluginConfig, StripePricingDef {
119
+ }
120
+ /** `declarePaymentPricing`'s argument: the browser-safe `PricingPolicy` plus Stripe-only settings. */
121
+ export interface PricingDef extends PricingPolicy {
122
+ stripe?: StripePricingDef;
123
+ }
124
+ /**
125
+ * The business the consumer contracts with — backend only, never advertised. `name` is what the
126
+ * express statements say (`{{trader}}` in a consent or start request); the mails' identity line,
127
+ * the withdrawal information and the model form ("To: …") say `legalName`, `address` and `email`.
128
+ */
129
+ export type TraderDef = {
130
+ name: string;
131
+ legalName: string;
132
+ /** Postal address; without it (or `email`) the mails render without it and the boot warns. */
133
+ address?: string;
134
+ email?: string;
135
+ website?: string;
136
+ };
137
+ /** The consumer-rights mail options — backend only, never advertised. */
138
+ export interface ConsumerMailDef {
139
+ /** The mailer service alias. Default `MAILER_SERVICE`. */
140
+ alias?: string;
141
+ from?: string;
142
+ replyTo?: string;
143
+ /** Evidence archive addresses: each receives its own copy of every consumer-rights mail. */
144
+ bcc?: string[];
145
+ }
146
+ export interface ConsumerMailPluginConfig extends PluginConfig, ConsumerMailDef {
147
+ trader?: TraderDef;
148
+ }
149
+ /** `declareConsumerRights`'s argument: the advertised policy plus the backend-only trader and mail options. */
150
+ export type ConsumerRightsDef = ConsumerRightsDeclaration & {
151
+ trader?: TraderDef;
152
+ mail?: ConsumerMailDef;
153
+ };
154
+ /** What the managed customer portal configuration shows. */
155
+ export interface PortalBrandingDef {
156
+ headline?: string;
157
+ privacyPolicyUrl?: string;
158
+ termsOfServiceUrl?: string;
159
+ /** Where the portal's "return" link leads when a session names none. Absolute URL. */
160
+ returnUrl: string;
161
+ }
162
+ export interface PortalBrandingConfig extends PluginConfig, PortalBrandingDef {
66
163
  }
67
164
  export interface CreateLinkParams {
68
165
  productSku: string;
@@ -72,29 +169,179 @@ export interface CreateLinkParams {
72
169
  service: string;
73
170
  planSku?: string;
74
171
  amountMinor?: number;
172
+ /** Stripe Checkout and future paid-invoice language. Caller supplies a Stripe-supported locale. */
173
+ locale?: string;
174
+ /**
175
+ * Trusted application copy shown beside the Checkout confirmation button — never caller-provided
176
+ * text. A function receives the charge currency and the price the session charges.
177
+ */
178
+ submitText?: string | ((context: CheckoutTextContext) => string);
75
179
  successUrl?: string;
76
180
  cancelUrl?: string;
181
+ /** The billing country the buyer declared (ISO 3166-1 alpha-2). A locked profile overrides it. */
182
+ country?: string;
183
+ /** The subscription start request recorded right before a subscription checkout. */
184
+ startRequestId?: string;
185
+ /** The language the buyer is shown legal copy in; default: the billing country's. */
186
+ consumerLanguage?: string;
187
+ /** The request's geolocated country (`cf-ipcountry`) — second location evidence, stored at lock. */
188
+ ipCountry?: string;
77
189
  }
78
- export interface PaymentPlugin {
79
- createLink: (ctx: ApiContext, params: CreateLinkParams) => Promise<string>;
80
- handleWebhook: (request: unknown, ctx: ApiContext) => Promise<void>;
81
- initialize?: (ctx: ApiContext) => Promise<void>;
82
- manageSubscription: (ctx: ApiContext, entityId: string, returnUrl: string) => Promise<string>;
190
+ /** What a submit-text function is told about the session it labels. */
191
+ export interface CheckoutTextContext {
192
+ language: string;
193
+ /** The charge currency, lowercase. */
194
+ currency: string;
195
+ /** A subscription's unit price in the charge currency; an amount checkout's pre-tax charge. */
196
+ unitAmountMinor?: number;
197
+ interval?: 'month' | 'year';
198
+ region: ConsumerRegion | null;
199
+ country?: string;
200
+ }
201
+ /** What a plugin narrows: one entity's amount checkout of one plan, at one instant. */
202
+ export interface CheckoutNarrowInput {
203
+ entityId: string;
204
+ productSku: string;
205
+ planSku?: string;
206
+ /** The plan's own amount policy — the same for everyone. */
207
+ base: AmountCheckoutPolicy;
208
+ at: Date;
209
+ }
210
+ /** A checkout about to be created — what `admit` may veto. */
211
+ export interface CheckoutAttempt {
212
+ entityId: string;
213
+ productSku: string;
214
+ planSku?: string;
215
+ mode: 'amount' | 'quantity' | 'subscription';
216
+ /** Amount mode: the net value bought, in the amount policy's currency. */
217
+ amountMinor?: number;
218
+ /** The amount policy's currency (amount mode), else the charge currency. */
219
+ amountCurrency?: string;
220
+ /** Amount mode: the pre-tax charge in `currency`. */
221
+ chargeMinor?: number;
222
+ currency: string;
223
+ /** The session lifetime the gateway will set; absent: Stripe's default (24 h). */
224
+ sessionTtlSeconds?: number;
225
+ expiresAt?: Date;
226
+ at: Date;
227
+ }
228
+ /** A plugin's admission: an optional reservation it binds to the session in `created`. */
229
+ export interface CheckoutAdmission {
230
+ reservationId?: string;
231
+ }
232
+ /** The session a checkout became. */
233
+ export interface CheckoutCreated extends CheckoutAttempt {
234
+ sessionId: string;
235
+ url: string;
236
+ /** What this plugin's own `admit` answered. */
237
+ reservationId?: string;
238
+ }
239
+ export type CheckoutOutcome = 'paid' | 'expired' | 'failed';
240
+ /**
241
+ * How a checkout ended: `paid` (completed and paid), `expired`, or `failed` — an asynchronous
242
+ * payment failure, or a session that never became usable (a later plugin vetoed, Stripe refused,
243
+ * a `created` threw; then `sessionId` may be absent and `reservationId` names the hold to release).
244
+ */
245
+ export interface CheckoutSettled {
246
+ entityId: string;
247
+ productSku?: string;
248
+ planSku?: string;
249
+ sessionId?: string;
250
+ reservationId?: string;
251
+ outcome: CheckoutOutcome;
252
+ amountMinor?: number;
253
+ at: Date;
254
+ }
255
+ /**
256
+ * A seam around amount and subscription checkout, registered with `gateway(ctx).use(plugin)` (a
257
+ * plugin with an `alias` registered twice replaces the first). Errors from `narrow` and `admit`
258
+ * propagate — a plugin fails closed; `settled` errors are logged, since holds carry their own TTL.
259
+ */
260
+ export interface CheckoutPlugin {
261
+ alias?: string;
262
+ /** Lower what this entity may buy now; `null` for no opinion. */
263
+ narrow?: (ctx: ApiContext, input: CheckoutNarrowInput) => Promise<AmountNarrowing | null>;
264
+ /** Veto (throw, e.g. `CheckoutLimitExceeded`) or hold a reservation for the session. */
265
+ admit?: (ctx: ApiContext, attempt: CheckoutAttempt) => Promise<CheckoutAdmission | void>;
266
+ /** The session exists — bind the reservation. A throw expires the session and rethrows. */
267
+ created?: (ctx: ApiContext, created: CheckoutCreated) => Promise<void>;
268
+ /** From the webhook (paid, expired, failed) or from a checkout that never became usable. */
269
+ settled?: (ctx: ApiContext, settled: CheckoutSettled) => Promise<void>;
270
+ /** The longest a session may stay open; the gateway uses the smallest, clamped to 30 min–24 h. */
271
+ sessionTtlSeconds?: number;
272
+ }
273
+ export interface PortalLinkOptions {
274
+ flow: PortalFlow;
275
+ /** The target plan of `PortalFlow.Change`. */
276
+ planSku?: string;
277
+ returnUrl: string;
278
+ }
279
+ export interface PriceEstimateParams {
280
+ productSku: string;
281
+ /** Stable database key resolved by the application before this in-process call. */
282
+ entityId: string;
283
+ /** Absent: the product's own reference plan (an amount-priced consumable). */
284
+ planSku?: string;
285
+ /** ISO 3166-1 alpha-2. Absent: taken from the entity's paygate customer address. */
286
+ country?: string;
287
+ }
288
+ export interface GrantInternalPlanOptions {
289
+ /** Required to grant a plan that is not `free`. */
290
+ force?: boolean;
291
+ /** When the grant stops entitling. Absent: never. */
292
+ periodEnd?: Date;
293
+ }
294
+ export interface SubscriptionRef {
295
+ entityId?: string;
296
+ /** The paygate subscription id (`sub_…`). */
297
+ subscriptionId?: string;
83
298
  }
84
299
  export interface GatewayService extends InitializedService {
85
- createLink: PaymentPlugin['createLink'];
86
- manageSubscription: PaymentPlugin['manageSubscription'];
300
+ /** `false` when registered with `manage: false`: every Stripe-calling method throws. */
301
+ managed: boolean;
302
+ createLink: (ctx: ApiContext, params: CreateLinkParams) => Promise<string>;
303
+ portalLink: (ctx: ApiContext, entityId: string, opts: PortalLinkOptions) => Promise<string>;
304
+ /** @throws PaygateError('unmanaged') when `managed` is `false`. */
305
+ estimatePrice: (ctx: ApiContext, params: PriceEstimateParams) => Promise<PriceEstimate>;
306
+ grantInternalPlan: (ctx: ApiContext, entityId: string, planSku: string, opts?: GrantInternalPlanOptions) => Promise<PaymentSubscriptionRecord>;
307
+ /** @returns how many subscription rows changed */
308
+ resyncSubscription: (ctx: ApiContext, ref: SubscriptionRef) => Promise<number>;
309
+ resyncAll: (ctx: ApiContext) => Promise<{
310
+ scanned: number;
311
+ updated: number;
312
+ }>;
313
+ /** Register a checkout plugin; one with an `alias` already registered replaces it. */
314
+ use: (plugin: CheckoutPlugin) => void;
315
+ /** The registered checkout plugins, in registration order. */
316
+ checkoutPlugins: () => readonly CheckoutPlugin[];
317
+ /**
318
+ * The entity's amount policy of an amount plan as narrowed NOW — the very computation
319
+ * `createLink` enforces. Mongo and plugins only; works unmanaged.
320
+ */
321
+ amountPolicy: (ctx: ApiContext, entityId: string, productSku: string, planSku?: string) => Promise<AmountPolicyView>;
322
+ /** The synced prices of a product's plans, per currency — stored rows, no Stripe call. */
323
+ planPrices: (ctx: ApiContext, productSku: string) => Promise<PlanPriceView[]>;
87
324
  }
88
- export interface PaymentResourceOptions {
325
+ export interface PaymentGatewayOptions {
89
326
  dbAlias?: string;
90
327
  serviceAlias?: string;
328
+ /**
329
+ * `false` for a process that must read entitlements but never talk to Stripe: no Stripe
330
+ * bootstrap at init, and `createLink` / `portalLink` / `resync*` throw `PaygateError('unmanaged')`.
331
+ */
332
+ manage?: boolean;
91
333
  }
334
+ /** A Stripe client for one context — the default reads the configured secret. */
335
+ export type StripeFactory = (ctx: ApiContext) => Promise<Stripe>;
336
+ /** @deprecated use `PaymentGatewayOptions` */
337
+ export type PaymentResourceOptions = PaymentGatewayOptions;
92
338
  interface TopUpBase {
93
339
  entityId: string;
94
340
  productSku: string;
95
341
  planSku?: string;
96
342
  service: string;
97
343
  paygate: string;
344
+ /** The checkout session id — the consumer's idempotency key (`payment:<externalId>`). */
98
345
  externalId: string;
99
346
  }
100
347
  export interface QuantityTopUpCompletion extends TopUpBase {
@@ -103,36 +350,218 @@ export interface QuantityTopUpCompletion extends TopUpBase {
103
350
  }
104
351
  export interface AmountTopUpCompletion extends TopUpBase {
105
352
  mode: 'amount';
353
+ /** Net value in the amount policy's catalogue currency. */
106
354
  amountMinor: number;
355
+ /** Grossed-up value in the amount policy's catalogue currency, before settlement conversion. */
356
+ sourceChargeAmountMinor: number;
357
+ /** The amount policy's catalogue currency. */
358
+ amountCurrency: string;
359
+ /** Pre-tax subtotal actually charged by Stripe, in `currency`. */
107
360
  chargeAmountMinor: number;
361
+ /** Stripe integration/settlement currency. */
108
362
  currency: string;
109
363
  }
110
364
  export type TopUpCompletion = QuantityTopUpCompletion | AmountTopUpCompletion;
111
- export interface SubscriptionCompletion {
365
+ export type SubscriptionChange = 'created' | 'renewed' | 'upgraded' | 'downgraded' | 'cancel-scheduled' | 'cancel-undone' | 'canceled' | 'paused' | 'resumed' | 'past-due' | 'suspended' | 'trial-ending';
366
+ export interface SubscriptionSnapshot {
112
367
  entityId: string;
113
- status: SubscriptionStatus;
114
- productSku: string;
115
368
  planSku: string;
116
- service: string;
369
+ productSku: string;
370
+ rank: number;
371
+ status: SubscriptionStatus;
117
372
  paygate: string;
118
- externalId: string;
119
- isNew: boolean;
120
- capabilities?: PermissionSet[];
373
+ /** The subscription row's `externalId`. */
374
+ subscriptionId: string;
375
+ service: string;
376
+ periodStart?: Date;
377
+ periodEnd?: Date;
378
+ cancelAtPeriodEnd?: boolean;
379
+ trialEnd?: Date;
380
+ pausedAt?: Date;
381
+ createdAt?: Date;
382
+ capabilities?: PlanCapability[];
121
383
  limits?: {
122
- [key: string]: LimitConfig;
384
+ [key: string]: LimitDeclaration;
385
+ };
386
+ }
387
+ export interface SubscriptionEvent {
388
+ change: SubscriptionChange;
389
+ /** The state last propagated to observers; `null` for `created`. */
390
+ previous: SubscriptionSnapshot | null;
391
+ current: SubscriptionSnapshot;
392
+ /** `ENTITLING_STATUSES.includes(current.status)`. */
393
+ active: boolean;
394
+ eventKey: string;
395
+ invoiceId?: string;
396
+ externalEventId?: string;
397
+ }
398
+ export type PaymentTargetKind = 'fulfillment' | 'subscription';
399
+ export interface RefundEvent {
400
+ entityId: string;
401
+ target: PaymentTargetKind;
402
+ productSku?: string;
403
+ planSku?: string;
404
+ /** The target row's `externalId` (checkout session or subscription). */
405
+ externalId: string;
406
+ refundId: string;
407
+ paymentIntentId?: string;
408
+ invoiceId?: string;
409
+ /** This refund. */
410
+ amountMinor: number;
411
+ /** Everything refunded on the charge so far, this refund included. */
412
+ refundedTotalMinor: number;
413
+ /** What the customer paid on the refunded charge, tax included. */
414
+ paidMinor?: number;
415
+ currency: string;
416
+ /** Less than the whole charge has been refunded. */
417
+ partial: boolean;
418
+ eventKey: string;
419
+ /** Fulfillment targets: the net value credited at checkout. */
420
+ netAmountMinor?: number;
421
+ /** Fulfillment targets: the pre-tax subtotal charged at checkout. */
422
+ chargeAmountMinor?: number;
423
+ /** The refund's own metadata. */
424
+ metadata?: Record<string, string>;
425
+ /**
426
+ * Set when the refund executes a withdrawal (`refund.metadata.withdrawalId`): the `onWithdrawal`
427
+ * observer takes back exactly the unused units — an `onRefund` observer must skip its own
428
+ * claw-back for such a refund, or the units are taken back twice.
429
+ */
430
+ withdrawalId?: string;
431
+ }
432
+ export type DisputePhase = 'opened' | 'funds-withdrawn' | 'funds-reinstated' | 'closed';
433
+ export interface DisputeEvent {
434
+ entityId: string;
435
+ target: PaymentTargetKind;
436
+ productSku?: string;
437
+ planSku?: string;
438
+ externalId: string;
439
+ disputeId: string;
440
+ phase: DisputePhase;
441
+ /** The paygate's dispute status (`needs_response`, `won`, `lost`, …). */
442
+ status: string;
443
+ amountMinor: number;
444
+ currency: string;
445
+ eventKey: string;
446
+ netAmountMinor?: number;
447
+ chargeAmountMinor?: number;
448
+ }
449
+ export interface PaymentFailedEvent {
450
+ entityId: string;
451
+ kind: 'checkout' | 'invoice';
452
+ /** The checkout session or invoice id. */
453
+ externalId: string;
454
+ subscriptionId?: string;
455
+ invoiceId?: string;
456
+ attempt?: number;
457
+ actionRequired?: boolean;
458
+ nextAttemptAt?: Date;
459
+ eventKey: string;
460
+ }
461
+ /** A consumer's express request was recorded (performance consent or subscription start). */
462
+ export interface ConsentEvent {
463
+ kind: ConsentKind;
464
+ consentId: string;
465
+ entityId: string;
466
+ profileId?: string;
467
+ /** The purchases the consent covers (none for a start request, which precedes its purchase). */
468
+ purchaseIds: string[];
469
+ planSku?: string;
470
+ textVersion: string;
471
+ language: string;
472
+ decidedAt: Date;
473
+ expiresAt?: Date;
474
+ /** `consent:<consentId>`. */
475
+ eventKey: string;
476
+ }
477
+ /** A withdrawal executed (`refunded`) or left to an operator (`review`). */
478
+ export interface WithdrawalEvent {
479
+ withdrawalId: string;
480
+ /** `withdrawal:<withdrawalId>`. */
481
+ eventKey: string;
482
+ entityId: string;
483
+ channel: DeclarationChannel;
484
+ purchase: PurchaseRef;
485
+ declaredAt: Date;
486
+ status: WithdrawalStatus;
487
+ refund: {
488
+ amountMinor: number;
489
+ currency: string;
490
+ netMinor?: number;
491
+ refundId?: string;
492
+ creditNoteId?: string;
123
493
  };
494
+ /**
495
+ * The purchase's units as the usage meter read them: `returned` is what the application takes
496
+ * back (`granted − used`), `null` without a meter.
497
+ */
498
+ units: {
499
+ granted: number;
500
+ used: number;
501
+ returned: number;
502
+ } | null;
503
+ subscriptionCanceled: boolean;
504
+ }
505
+ /** A cancellation was declared (matched to a subscription or not). */
506
+ export interface CancellationEvent {
507
+ cancellationId: string;
508
+ /** `cancellation:<cancellationId>`. */
509
+ eventKey: string;
510
+ entityId?: string;
511
+ matched: boolean;
512
+ channel: DeclarationChannel;
513
+ kind: CancellationKind;
514
+ status: CancellationStatus;
515
+ subscriptionId?: string;
516
+ effectiveAt?: Date;
517
+ declaredAt: Date;
124
518
  }
125
519
  export interface TopUpCallback {
126
520
  (completion: TopUpCompletion, ctx: ApiContext): Promise<void>;
127
521
  }
128
522
  export interface SubscriptionCallback {
129
- (completion: SubscriptionCompletion, ctx: ApiContext): Promise<void>;
523
+ (event: SubscriptionEvent, ctx: ApiContext): Promise<void>;
524
+ }
525
+ export interface RefundCallback {
526
+ (event: RefundEvent, ctx: ApiContext): Promise<void>;
527
+ }
528
+ export interface DisputeCallback {
529
+ (event: DisputeEvent, ctx: ApiContext): Promise<void>;
530
+ }
531
+ export interface PaymentFailedCallback {
532
+ (event: PaymentFailedEvent, ctx: ApiContext): Promise<void>;
533
+ }
534
+ export interface ConsentCallback {
535
+ (event: ConsentEvent, ctx: ApiContext): Promise<void>;
536
+ }
537
+ export interface WithdrawalCallback {
538
+ (event: WithdrawalEvent, ctx: ApiContext): Promise<void>;
130
539
  }
540
+ export interface CancellationCallback {
541
+ (event: CancellationEvent, ctx: ApiContext): Promise<void>;
542
+ }
543
+ /**
544
+ * Callbacks run sequentially and are awaited. A paygate callback's throw escapes so the paygate
545
+ * retries; a consumer-rights callback (`onConsent`, `onWithdrawal`, `onCancellation`) runs AFTER
546
+ * the records and the paygate steps, and its throw is recorded and retried by `reconcile()`.
547
+ */
131
548
  export interface CompletionObserver extends LazyService {
132
549
  onTopUp: (cb: TopUpCallback) => void;
133
550
  onSubscription: (cb: SubscriptionCallback) => void;
551
+ onRefund: (cb: RefundCallback) => void;
552
+ onDispute: (cb: DisputeCallback) => void;
553
+ onPaymentFailed: (cb: PaymentFailedCallback) => void;
554
+ onConsent: (cb: ConsentCallback) => void;
555
+ onWithdrawal: (cb: WithdrawalCallback) => void;
556
+ onCancellation: (cb: CancellationCallback) => void;
134
557
  propagateTopUp: (completion: TopUpCompletion, ctx: ApiContext) => Promise<void>;
135
- propagateSubscription: (completion: SubscriptionCompletion, ctx: ApiContext) => Promise<void>;
558
+ propagateSubscription: (event: SubscriptionEvent, ctx: ApiContext) => Promise<void>;
559
+ propagateRefund: (event: RefundEvent, ctx: ApiContext) => Promise<void>;
560
+ propagateDispute: (event: DisputeEvent, ctx: ApiContext) => Promise<void>;
561
+ propagatePaymentFailed: (event: PaymentFailedEvent, ctx: ApiContext) => Promise<void>;
562
+ propagateConsent: (event: ConsentEvent, ctx: ApiContext) => Promise<void>;
563
+ propagateWithdrawal: (event: WithdrawalEvent, ctx: ApiContext) => Promise<void>;
564
+ propagateCancellation: (event: CancellationEvent, ctx: ApiContext) => Promise<void>;
136
565
  }
137
566
  export interface PaygateCustomerRecord extends ResourceRecord {
138
567
  paygate: string;
@@ -142,48 +571,698 @@ export interface PaygateCustomerRecord extends ResourceRecord {
142
571
  email?: string;
143
572
  name?: string;
144
573
  taxId?: string;
574
+ /** The paygate customer's billing address country, as its last webhook stated it. */
575
+ country?: string;
576
+ /** The paygate customer's own currency (set by its first subscription or invoice). */
577
+ currency?: string;
578
+ deletedAt?: Date;
145
579
  }
146
580
  export interface PaygateCustomerResource extends MongoResource<PaygateCustomerRecord> {
147
581
  loadByPgId: (externalId: string, paygate: string) => Promise<PaygateCustomerRecord | null>;
148
582
  byEntity: (entityId: string, paygate: string) => Promise<PaygateCustomerRecord | null>;
149
583
  }
584
+ /** The classification inputs last propagated to observers. */
585
+ export interface PropagatedState {
586
+ planSku: string;
587
+ rank: number;
588
+ status: SubscriptionStatus;
589
+ cancelAtPeriodEnd?: boolean;
590
+ pausedAt?: Date;
591
+ /** The renewal invoice last reported as `renewed` — a renewal is reported once per invoice. */
592
+ renewedInvoiceId?: string;
593
+ }
594
+ /** One row per subscription — paygate (`sub_…`) or internal (`free:<entityId>`, `internal:<planSku>:<entityId>`). */
150
595
  export interface PaymentSubscriptionRecord extends ResourceRecord {
151
- sku: string;
152
- productSku: string;
153
596
  entityId: string;
597
+ planSku: string;
598
+ productSku: string;
154
599
  service: string;
155
600
  paygate: string;
156
601
  externalId: string;
602
+ itemId?: string;
603
+ priceId?: string;
157
604
  status: SubscriptionStatus;
158
- kind: 'consumable' | 'subscription';
159
- capabilities?: PermissionSet[];
160
- limits?: {
161
- [key: string]: LimitConfig;
162
- };
163
- pricingMode?: CheckoutPricingMode;
605
+ externalStatus?: string;
606
+ rank: number;
607
+ periodStart?: Date;
608
+ periodEnd?: Date;
609
+ cancelAtPeriodEnd?: boolean;
610
+ canceledAt?: Date;
611
+ endedAt?: Date;
612
+ pausedAt?: Date;
613
+ trialEnd?: Date;
614
+ latestInvoiceId?: string;
615
+ customerId?: string;
616
+ disputedAt?: Date;
617
+ disputeStatus?: string;
618
+ createdAt: Date;
619
+ updatedAt?: Date;
620
+ /** The paygate-side instant of the state stored; an older event payload is not applied. */
621
+ syncedAt?: Date;
622
+ lastEventId?: string;
623
+ initialPropagatedAt?: Date;
624
+ propagated?: PropagatedState;
625
+ /** The subscription's currency (lowercase). */
626
+ currency?: string;
627
+ checkoutSessionId?: string;
628
+ purchaseId?: string;
629
+ firstInvoiceId?: string;
630
+ country?: string;
631
+ email?: string;
632
+ amountTotalMinor?: number;
633
+ amountTaxMinor?: number;
634
+ termsAccepted?: boolean;
635
+ startRequestId?: string;
636
+ withdrawnAt?: Date;
637
+ }
638
+ export interface PaymentSubscriptionResource extends MongoResource<PaymentSubscriptionRecord> {
639
+ byExternalId: (externalId: string, paygate: string) => Promise<PaymentSubscriptionRecord | null>;
640
+ byItemId: (itemId: string, paygate: string) => Promise<PaymentSubscriptionRecord | null>;
641
+ }
642
+ /** A one-time checkout session: pending until observers succeed, then `fulfilledAt`. */
643
+ export interface PaymentFulfillmentRecord extends ResourceRecord {
644
+ entityId: string;
645
+ productSku: string;
646
+ planSku?: string;
647
+ service: string;
648
+ paygate: string;
649
+ externalId: string;
650
+ paymentIntentId?: string;
651
+ chargeId?: string;
652
+ invoiceId?: string;
653
+ mode: CheckoutPricingMode;
164
654
  units?: number;
165
655
  amountMinor?: number;
656
+ sourceChargeAmountMinor?: number;
657
+ amountCurrency?: string;
166
658
  chargeAmountMinor?: number;
167
659
  currency?: string;
168
660
  createdAt: Date;
169
- endsAt?: Date;
170
- trialUntil?: Date;
171
- canceledAt?: Date;
172
661
  fulfilledAt?: Date;
173
- initialPropagatedAt?: Date;
662
+ failedAt?: Date;
663
+ refundedMinor?: number;
664
+ refundedAt?: Date;
665
+ disputedAt?: Date;
666
+ disputeStatus?: string;
667
+ country?: string;
668
+ email?: string;
669
+ profileId?: string;
670
+ amountTotalMinor?: number;
671
+ amountTaxMinor?: number;
672
+ termsAccepted?: boolean;
673
+ purchaseId?: string;
174
674
  }
175
- export interface PaymentSubscriptionResource extends MongoResource<PaymentSubscriptionRecord> {
176
- byExternalId: (externalId: string, paygate: string) => Promise<PaymentSubscriptionRecord | null>;
675
+ export interface PaymentFulfillmentResource extends MongoResource<PaymentFulfillmentRecord> {
676
+ byExternalId: (externalId: string, paygate: string) => Promise<PaymentFulfillmentRecord | null>;
677
+ }
678
+ /** The paygate webhook endpoint this deployment owns, with its signing secret. */
679
+ export interface PaymentWebhookRecord extends ResourceRecord {
680
+ paygate: string;
681
+ service: string;
682
+ url: string;
683
+ externalId: string;
684
+ secret: string;
685
+ apiVersion: string;
686
+ events: string[];
687
+ hash: string;
688
+ createdAt: Date;
689
+ updatedAt?: Date;
690
+ }
691
+ export interface PaymentWebhookResource extends MongoResource<PaymentWebhookRecord> {
692
+ }
693
+ /** A usage event — the source of truth of every limit counter. */
694
+ export interface PaymentUsageRecord extends ResourceRecord {
695
+ entityId: string;
696
+ limitKey: string;
697
+ /** `windowKeyOf` of the limit when the event was written. */
698
+ window: string;
699
+ /** `+n` consumed, `-n` released, any sign for a reconciliation adjustment. */
700
+ delta: number;
701
+ eventKey: string;
702
+ ref?: string;
703
+ reason?: string;
704
+ planSku?: string;
705
+ createdAt: Date;
706
+ releasedAt?: Date;
707
+ }
708
+ export interface PaymentUsageResource extends MongoResource<PaymentUsageRecord> {
709
+ }
710
+ /** The projection of the usage ledger per (entity, limit, window). */
711
+ export interface PaymentUsageCounterRecord extends ResourceRecord {
712
+ entityId: string;
713
+ limitKey: string;
714
+ window: string;
715
+ used: number;
716
+ limit: number;
717
+ planSku?: string;
718
+ overSince?: Date;
719
+ updatedAt: Date;
720
+ reconciledAt?: Date;
721
+ }
722
+ export interface PaymentUsageCounterResource extends MongoResource<PaymentUsageCounterRecord> {
723
+ }
724
+ /** One currency option of a synced price. */
725
+ export interface SyncedPriceOption {
726
+ currency: string;
727
+ unitAmount: number;
728
+ }
729
+ /** A reusable price as the last sync left it — what `planPrices` and checkout read without Stripe. */
730
+ export interface SyncedPrice {
731
+ planSku: string;
732
+ priceId: string;
733
+ lookupKey: string;
734
+ /** The default currency and its amount. */
735
+ currency: string;
736
+ unitAmount: number;
737
+ /** `currency_options` besides the default currency. */
738
+ options: SyncedPriceOption[];
739
+ taxBehavior?: string;
740
+ interval?: 'month' | 'year';
741
+ sourceUnitAmount: number;
742
+ sourceCurrency: string;
743
+ syncedAt: Date;
177
744
  }
178
745
  export interface FingerprintRecord extends ResourceRecord {
179
746
  sku: string;
180
747
  hash: string;
181
748
  productId?: string;
749
+ /** The paygate object the fingerprint describes (a portal configuration id). */
750
+ externalId?: string;
751
+ /** A product's synced reusable prices. */
752
+ prices?: SyncedPrice[];
182
753
  updatedAt: Date;
183
754
  }
184
755
  export interface FingerprintResource extends MongoResource<FingerprintRecord> {
185
756
  bySku: (sku: string) => Promise<FingerprintRecord | null>;
186
757
  clear: () => Promise<void>;
187
758
  }
759
+ export interface EffectivePlan {
760
+ plan: PaymentPlan;
761
+ /** The entitling row behind the plan; `null` when the entity falls back to the free plan. */
762
+ subscription: PaymentSubscriptionRecord | null;
763
+ /** The declared free plan, when one exists. */
764
+ fallback: PaymentPlan | null;
765
+ }
766
+ export interface ConsumeRequest {
767
+ entityId: string;
768
+ limitKey: string;
769
+ /** Idempotency key — key it by the record the unit pays for. */
770
+ eventKey: string;
771
+ /** Positive safe integer; default `1`. */
772
+ amount?: number;
773
+ ref?: string;
774
+ reason?: string;
775
+ }
776
+ export interface ReleaseRequest {
777
+ entityId: string;
778
+ limitKey: string;
779
+ /** The event key the unit was consumed under. */
780
+ eventKey: string;
781
+ /** Default: the consumed amount. */
782
+ amount?: number;
783
+ }
784
+ export interface LimitOutcome {
785
+ /** A consume: the unit is held. A release: the release is in effect. */
786
+ admitted: boolean;
787
+ /** The event key had been seen before; nothing new was written. */
788
+ replayed: boolean;
789
+ limitKey: string;
790
+ window: string;
791
+ used: number;
792
+ limit: number;
793
+ remaining: number;
794
+ resetsAt?: Date;
795
+ eventKey: string;
796
+ }
797
+ export interface OccupancyOutcome {
798
+ limitKey: string;
799
+ used: number;
800
+ limit: number;
801
+ /** `max(0, used - limit)`. */
802
+ over: number;
803
+ /** When the entity first went over; absent while it is within the limit. */
804
+ overSince?: Date;
805
+ }
806
+ export interface CounterReconciliation {
807
+ counters: number;
808
+ repaired: number;
809
+ }
810
+ export interface EntitlementService extends InitializedService {
811
+ effectivePlan: (entityId: string) => Promise<EffectivePlan>;
812
+ entitlements: (entityId: string) => Promise<EntitlementView>;
813
+ hasCapability: (entityId: string, param: string) => Promise<boolean>;
814
+ /** @throws LimitUnknown */
815
+ limitState: (entityId: string, key: string) => Promise<LimitView>;
816
+ /** @throws LimitExhausted | LimitUnknown */
817
+ consume: (req: ConsumeRequest) => Promise<LimitOutcome>;
818
+ release: (req: ReleaseRequest) => Promise<LimitOutcome>;
819
+ /** @throws LimitUnknown | LimitMisdeclared */
820
+ reconcileOccupancy: (entityId: string, limitKey: string, actual: number) => Promise<OccupancyOutcome>;
821
+ reconcileCounters: (entityId?: string) => Promise<CounterReconciliation>;
822
+ /**
823
+ * The ACTIVE consume event for this key (not released), or null. This is how a consumer asks
824
+ * "was a unit already spent on this record?" — a consumer keys allowances by the record they pay
825
+ * for (`eventKey = <anything>:<recordId>`, `ref = recordId`) instead of writing a marker onto
826
+ * the record, so a retry replays the same event and a resumed run can re-derive that it is free.
827
+ */
828
+ consumption: (entityId: string, limitKey: string, eventKey: string) => Promise<PaymentUsageRecord | null>;
829
+ /**
830
+ * The newest ACTIVE consume event (not released) whose `ref` is this record, or null. For a unit
831
+ * consumed under a fresh event key per cycle (`<key>:<recordId>:<timestamp>`, `ref = recordId`):
832
+ * whether the record holds a unit now, and the event key to release it by.
833
+ */
834
+ consumptionByRef: (entityId: string, limitKey: string, ref: string) => Promise<PaymentUsageRecord | null>;
835
+ }
836
+ export interface ReconcileEntityOptions {
837
+ /** Grant this plan (an internal row) when the entity holds no entitling subscription. */
838
+ freePlanSku?: string;
839
+ /** Re-read the entity's paygate subscriptions first (managed gateways only). */
840
+ resync?: boolean;
841
+ }
842
+ export interface ReconcileAllOptions extends ReconcileEntityOptions {
843
+ /** Entities to cover besides those the payment store already knows. */
844
+ entities?: Iterable<string> | AsyncIterable<string>;
845
+ }
846
+ export interface ReconcileAllResult {
847
+ entities: number;
848
+ granted: number;
849
+ counters: number;
850
+ repaired: number;
851
+ failed: number;
852
+ }
853
+ /** Where a billing profile's country came from. */
854
+ export type BillingProfileSource = 'checkout' | 'customer' | 'manual';
855
+ /** One per organization: the billing country fixed at the first purchase (first write wins). */
856
+ export interface BillingProfileRecord extends ResourceRecord {
857
+ entityId: string;
858
+ country: string;
859
+ region: ConsumerRegion;
860
+ /** The charge currency, fixed with the country. */
861
+ currency: string;
862
+ /** The language of the legal copy. */
863
+ language: string;
864
+ source: BillingProfileSource;
865
+ paygate: string;
866
+ customerId?: string;
867
+ sessionId?: string;
868
+ /** The geolocated country of the request that started the locking checkout — VAT evidence. */
869
+ ipCountry?: string;
870
+ email?: string;
871
+ name?: string;
872
+ /** A business tax id was given at the locking checkout. */
873
+ business?: boolean;
874
+ lockedAt: Date;
875
+ createdAt: Date;
876
+ updatedAt?: Date;
877
+ }
878
+ export interface BillingProfileResource extends MongoResource<BillingProfileRecord> {
879
+ byEntity: (entityId: string) => Promise<BillingProfileRecord | null>;
880
+ }
881
+ /**
882
+ * One purchase — a contract and its withdrawal window: a paid one-time checkout (`top-up`) or a
883
+ * subscription's first invoice (`subscription`). Renewals are not purchases.
884
+ */
885
+ export interface PurchaseRecord extends ResourceRecord {
886
+ /** `stripe:<session id>` (one-time) or `stripe:<subscription id>`. */
887
+ purchaseId: string;
888
+ /** `CR-YYMMDD-XXXXXX` — what a consumer quotes. */
889
+ contractRef: string;
890
+ entityId: string;
891
+ kind: PurchaseKind;
892
+ paygate: string;
893
+ sessionId?: string;
894
+ subscriptionId?: string;
895
+ paymentIntentId?: string;
896
+ invoiceId?: string;
897
+ invoiceNumber?: string;
898
+ invoiceLineId?: string;
899
+ productSku: string;
900
+ planSku?: string;
901
+ profileId?: string;
902
+ country?: string;
903
+ region?: ConsumerRegion;
904
+ ipCountry?: string;
905
+ inScope: boolean;
906
+ language: string;
907
+ email?: string;
908
+ name?: string;
909
+ business?: boolean;
910
+ /** The charge currency. */
911
+ currency: string;
912
+ amountSubtotalMinor: number;
913
+ amountTaxMinor: number;
914
+ amountTotalMinor: number;
915
+ presentmentCurrency?: string;
916
+ presentmentAmountMinor?: number;
917
+ /** Amount checkout: the net value credited, in `amountCurrency`. */
918
+ netAmountMinor?: number;
919
+ amountCurrency?: string;
920
+ /** Quantity checkout: the units bought. */
921
+ units?: number;
922
+ taxBehavior?: string;
923
+ /**
924
+ * Stripe's own `consent.terms_of_service` of the checkout — absent when the session did not
925
+ * collect it (the policy's `checkoutTerms` off, or the fallback of a Dashboard without a terms URL).
926
+ */
927
+ termsAccepted?: boolean;
928
+ textVersion?: string;
929
+ copyVersion?: string;
930
+ startRequestId?: string;
931
+ /** When the consumer expressly requested the services (the start request) — time deductions count from here. */
932
+ servicesStartedAt?: Date;
933
+ /** When the confirmation mail was claimed — one claim per purchase, whatever the outcome of the send. */
934
+ confirmationMailAt?: Date;
935
+ purchasedAt: Date;
936
+ /** Exclusive end of the withdrawal window; absent outside the consumer-rights territories. */
937
+ deadline?: Date;
938
+ consentId?: string;
939
+ consentedAt?: Date;
940
+ withdrawalId?: string;
941
+ withdrawnAt?: Date;
942
+ /** Refunded so far on this purchase's payment, tax included. */
943
+ refundedMinor?: number;
944
+ /** Set once the whole payment is refunded — the window closes. */
945
+ refundedAt?: Date;
946
+ cancellationId?: string;
947
+ cancelEffectiveAt?: Date;
948
+ createdAt: Date;
949
+ updatedAt?: Date;
950
+ }
951
+ export interface PurchaseResource extends MongoResource<PurchaseRecord> {
952
+ byPurchaseId: (purchaseId: string) => Promise<PurchaseRecord | null>;
953
+ }
954
+ /** The request evidence a consumer act is recorded with (`requestOriginOf`). */
955
+ export interface RequestOrigin {
956
+ /** `cf-connecting-ip`, else the LAST `x-forwarded-for` entry, else `x-real-ip`, else the socket. */
957
+ ip?: string;
958
+ /** The raw `x-forwarded-for` header. */
959
+ forwardedFor?: string;
960
+ /** `user-agent`, at most 512 characters. */
961
+ userAgent?: string;
962
+ /** `cf-ipcountry`. */
963
+ ipCountry?: string;
964
+ acceptLanguage?: string;
965
+ /** The channel the request came through (`http`, `mcp`, …), when the application knows. */
966
+ via?: string;
967
+ }
968
+ /** Append-only: one express request — a performance consent or a subscription start request. */
969
+ export interface ConsumerConsentRecord extends ResourceRecord, RequestOrigin {
970
+ kind: ConsentKind;
971
+ entityId: string;
972
+ profileId?: string;
973
+ /** The person who gave it — the confirmation is mailed here. */
974
+ name?: string;
975
+ email?: string;
976
+ purchaseIds: string[];
977
+ planSku?: string;
978
+ /** The plan's name as the statement said it (`{{plan}}`). */
979
+ planName?: string;
980
+ textVersion: string;
981
+ copyVersion: string;
982
+ /** The language the statement was shown in. */
983
+ language: string;
984
+ uiLanguage?: string;
985
+ trader: string;
986
+ /** The statement exactly as rendered and recorded. */
987
+ text: {
988
+ request: string;
989
+ acknowledgement: string;
990
+ checkbox: string;
991
+ };
992
+ links: ConsumerRightsLinks;
993
+ /** The latest deadline among the covered purchases. */
994
+ deadline?: Date;
995
+ decidedAt: Date;
996
+ /** A start request stops being usable at this instant. */
997
+ expiresAt?: Date;
998
+ }
999
+ export interface ConsumerConsentResource extends MongoResource<ConsumerConsentRecord> {
1000
+ }
1001
+ /** Append-only: one withdrawal or cancellation declaration, matched to a contract or not. */
1002
+ export interface ConsumerDeclarationRecord extends ResourceRecord, RequestOrigin {
1003
+ kind: DeclarationKind;
1004
+ channel: DeclarationChannel;
1005
+ entityId?: string;
1006
+ purchaseId?: string;
1007
+ subscriptionId?: string;
1008
+ /** The contract as the consumer named it. */
1009
+ contractRef?: string;
1010
+ name: string;
1011
+ email: string;
1012
+ cancellationKind?: CancellationKind;
1013
+ reason?: string;
1014
+ effective?: 'earliest' | 'date';
1015
+ requestedDate?: string;
1016
+ language: string;
1017
+ textVersion?: string;
1018
+ copyVersion: string;
1019
+ receivedAt: Date;
1020
+ matched: boolean;
1021
+ profileId?: string;
1022
+ /** A repeated declaration of a contract already withdrawn from: the original's id. */
1023
+ duplicateOf?: string;
1024
+ /** The status decided on receipt (`WithdrawalStatus` / `CancellationStatus`); execution steps are events. */
1025
+ status: string;
1026
+ refundMinor?: number;
1027
+ currency?: string;
1028
+ effectiveAt?: Date;
1029
+ }
1030
+ export interface ConsumerDeclarationResource extends MongoResource<ConsumerDeclarationRecord> {
1031
+ }
1032
+ export type ConsumerEventAction = 'mail' | 'computed' | 'meter' | 'refund' | 'credit-note' | 'subscription-cancel' | 'cancel-scheduled' | 'observers' | 'lock' | 'lock-mismatch' | 'relock' | 'unlock' | 'duplicate' | 'checkout-terms-fallback';
1033
+ export type ConsumerRecordKind = 'purchase' | 'consent' | 'declaration' | 'profile' | 'checkout';
1034
+ /** Append-only: one execution or audit step. */
1035
+ export interface ConsumerEventRecord extends ResourceRecord {
1036
+ /** The purchase id, consent id, declaration id, or the entity id of a profile or a checkout. */
1037
+ recordId: string;
1038
+ recordKind: ConsumerRecordKind;
1039
+ entityId?: string;
1040
+ action: ConsumerEventAction;
1041
+ /** The mail kind of a `mail` event; the observer family of an `observers` one. */
1042
+ step?: string;
1043
+ ok: boolean;
1044
+ /** A mail deliberately not sent (a reserved domain, a renderer that suppressed it). */
1045
+ skipped?: boolean;
1046
+ externalId?: string;
1047
+ amountMinor?: number;
1048
+ currency?: string;
1049
+ /** JSON. */
1050
+ detail?: string;
1051
+ error?: string;
1052
+ at: Date;
1053
+ }
1054
+ export interface ConsumerEventResource extends MongoResource<ConsumerEventRecord> {
1055
+ }
1056
+ /** What a usage meter is told about the purchase it reads. */
1057
+ export interface PurchaseRef {
1058
+ purchaseId: string;
1059
+ kind: PurchaseKind;
1060
+ entityId: string;
1061
+ contractRef: string;
1062
+ productSku: string;
1063
+ planSku?: string;
1064
+ /** One-time purchases: the checkout session id. */
1065
+ sessionId?: string;
1066
+ /** Subscription purchases: the paygate subscription id. */
1067
+ subscriptionId?: string;
1068
+ invoiceId?: string;
1069
+ purchasedAt: Date;
1070
+ }
1071
+ export interface UsageQuery {
1072
+ entityId: string;
1073
+ purchase: PurchaseRef;
1074
+ /** The purchase's consent — before it the consumer bears no cost; absent: nothing is deductible. */
1075
+ after?: Date;
1076
+ at: Date;
1077
+ }
1078
+ export interface UsageReading {
1079
+ /** Units the purchase granted. */
1080
+ granted: number;
1081
+ /** Units of it used so far. */
1082
+ used: number;
1083
+ /** Units of it used strictly after `after` (0 without `after`). */
1084
+ usedAfter: number;
1085
+ /** Units of it that paid off an earlier overdraft when it was granted. */
1086
+ settled?: number;
1087
+ /** Units of it already taken back by an earlier (money) refund. */
1088
+ clawed?: number;
1089
+ remaining?: number;
1090
+ }
1091
+ /**
1092
+ * The application's reading of how much of one purchase was used — for a withdrawal's refund.
1093
+ * The deduction is `usedAfter + settled + clawed`.
1094
+ */
1095
+ export interface UsageMeter {
1096
+ used: (ctx: ApiContext, query: UsageQuery) => Promise<UsageReading>;
1097
+ }
1098
+ /** Who acts: the organization, and the person's prefill. */
1099
+ export interface ConsumerSubject {
1100
+ entityId: string;
1101
+ profileId?: string;
1102
+ name?: string;
1103
+ email?: string;
1104
+ /** `public` when an application matched a public declaration to this organization itself. */
1105
+ channel?: DeclarationChannel;
1106
+ }
1107
+ export type ConsumerMailKind = 'purchase' | 'consent' | 'start' | 'withdrawal' | 'cancellation';
1108
+ /** What a consumer-rights mail is rendered from. */
1109
+ export interface ConsumerMailData {
1110
+ kind: ConsumerMailKind;
1111
+ /** The purchase id, consent id or declaration id. */
1112
+ recordId: string;
1113
+ entityId?: string;
1114
+ language: string;
1115
+ to: string;
1116
+ name?: string;
1117
+ trader: TraderDef;
1118
+ links: ConsumerRightsLinks;
1119
+ purchase?: PurchaseRecord;
1120
+ purchases?: PurchaseRecord[];
1121
+ consent?: ConsumerConsentRecord;
1122
+ declaration?: ConsumerDeclarationRecord;
1123
+ }
1124
+ /**
1125
+ * Replace or suppress a consumer-rights mail: answer a message to send it instead, `null` to send
1126
+ * nothing (recorded as skipped), `undefined` to send the rendered one.
1127
+ */
1128
+ export type ConsumerMailRenderer = (kind: ConsumerMailKind, data: ConsumerMailData, rendered: MailMessage) => MailMessage | null | undefined | Promise<MailMessage | null | undefined>;
1129
+ export interface LockOptions {
1130
+ customerId?: string;
1131
+ sessionId?: string;
1132
+ ipCountry?: string;
1133
+ email?: string;
1134
+ name?: string;
1135
+ business?: boolean;
1136
+ /** The charge currency; default: an active subscription's, else the region's. */
1137
+ currency?: string;
1138
+ language?: string;
1139
+ /** `manual` only: replace an existing lock (audited as a `relock` event). */
1140
+ force?: boolean;
1141
+ by?: string;
1142
+ reason?: string;
1143
+ }
1144
+ /** Who removes a lock, and why — kept on the `unlock` event. */
1145
+ export interface UnlockOptions {
1146
+ by?: string;
1147
+ reason?: string;
1148
+ }
1149
+ export interface ConsumerReconcileOptions {
1150
+ /** How far back declarations, mails and observers are retried. Default 30 days. */
1151
+ since?: Date;
1152
+ /** The most items per step. Default 50. */
1153
+ limit?: number;
1154
+ }
1155
+ export interface ConsumerReconcileResult {
1156
+ retried: number;
1157
+ mailed: number;
1158
+ observed: number;
1159
+ backfilled: number;
1160
+ locked: number;
1161
+ failed: number;
1162
+ }
1163
+ /**
1164
+ * Lazy: reachable while the application is still being wired (`useMeter`, `useMailRenderer` before
1165
+ * the context initializes); the gateway's initialization initializes it at boot.
1166
+ */
1167
+ export interface ConsumerRightsService extends LazyService {
1168
+ /**
1169
+ * `false`: every paygate-calling method (`withdraw`, `cancel`, `reconcile`'s paygate steps) refuses.
1170
+ * An explicit `manage` of `appendConsumerRights` wins over the gateway's, whatever the order.
1171
+ */
1172
+ readonly managed: boolean;
1173
+ policy: () => Promise<ConsumerRightsPolicy | null>;
1174
+ /** The locked billing profile, or `null`. */
1175
+ profile: (entityId: string) => Promise<BillingProfileView | null>;
1176
+ /** Lock the billing country; the first write wins unless a `manual` lock is `force`d. */
1177
+ lock: (entityId: string, country: string, source: BillingProfileSource, opts?: LockOptions) => Promise<BillingProfileView>;
1178
+ /**
1179
+ * An operator removes the lock: the profile is deleted and an `unlock` event keeps it. No lazy lock
1180
+ * from the paygate customer follows (checkout, reconcile); the next completed purchase locks again.
1181
+ * @returns the profile as it was, or `null` when nothing was locked
1182
+ */
1183
+ unlock: (entityId: string, opts?: UnlockOptions) => Promise<BillingProfileView | null>;
1184
+ purchases: (entityId: string, opts?: {
1185
+ open?: boolean;
1186
+ at?: Date;
1187
+ }) => Promise<PurchaseView[]>;
1188
+ consentView: (entityId: string, at?: Date) => Promise<PerformanceConsentView>;
1189
+ /** @throws PerformanceConsentRequired (428) for a stale text version */
1190
+ recordConsent: (subject: ConsumerSubject, body: PerformanceConsentBody, origin?: RequestOrigin) => Promise<PerformanceConsentResponse>;
1191
+ /** @throws PerformanceConsentRequired while an open in-scope window has no consent */
1192
+ assertConsent: (entityId: string, at?: Date) => Promise<void>;
1193
+ startView: (entityId: string, planSku: string, opts?: {
1194
+ language?: string;
1195
+ }) => Promise<SubscriptionStartView>;
1196
+ /** @throws SubscriptionStartRequired (428) for a stale text version */
1197
+ /** `plan`: the plan's short name the statement says (default: its localized catalogue title). */
1198
+ recordStartRequest: (subject: ConsumerSubject, body: SubscriptionStartBody, origin?: RequestOrigin, opts?: {
1199
+ plan?: string;
1200
+ }) => Promise<SubscriptionStartResponse>;
1201
+ /**
1202
+ * The fresh start request bound to this entity and plan, or `null` when none is required.
1203
+ * @throws SubscriptionStartRequired
1204
+ */
1205
+ assertStartRequest: (entityId: string, planSku: string, startRequestId?: string) => Promise<ConsumerConsentRecord | null>;
1206
+ withdrawalCandidates: (entityId: string, subject?: Partial<ConsumerSubject>) => Promise<WithdrawalCandidateList>;
1207
+ /** `subject: null` — the public function: matched by contract reference and e-mail, never disclosed. */
1208
+ withdraw: (subject: ConsumerSubject | null, body: WithdrawalBody, origin?: RequestOrigin) => Promise<WithdrawalReceipt>;
1209
+ cancel: (subject: ConsumerSubject | null, body: CancellationBody, origin?: RequestOrigin) => Promise<CancellationReceipt>;
1210
+ useMeter: (meter: UsageMeter | null) => void;
1211
+ useMailRenderer: (renderer: ConsumerMailRenderer | null) => void;
1212
+ usageMeter: () => UsageMeter | null;
1213
+ mailRenderer: () => ConsumerMailRenderer | null;
1214
+ /** Retry refunds, credit notes, cancellations, mails and observers; backfill purchases and legacy locks. */
1215
+ reconcile: (opts?: ConsumerReconcileOptions) => Promise<ConsumerReconcileResult>;
1216
+ }
1217
+ /**
1218
+ * `appendConsumerRights` options. `manage`, `usage` and `stripe` apply to the service whenever the
1219
+ * call comes — before or after the gateway registered it; the resources' `dbAlias` / `serviceAlias`
1220
+ * are those of the first registration.
1221
+ */
1222
+ export interface ConsumerRightsOptions {
1223
+ /**
1224
+ * `false` in a process that never talks to the paygate (reads and consent asserts only). Absent:
1225
+ * the gateway's `manage`, else managed.
1226
+ */
1227
+ manage?: boolean;
1228
+ /** The application's usage meter; without one a withdrawal is left to an operator (`review`). */
1229
+ usage?: UsageMeter;
1230
+ alias?: string;
1231
+ /** The paygate client; default: the configured Stripe secret. */
1232
+ stripe?: StripeFactory;
1233
+ dbAlias?: string;
1234
+ serviceAlias?: string;
1235
+ }
1236
+ /** What a public declaration is throttled by. */
1237
+ export interface ConsumerThrottleKey {
1238
+ action: 'withdrawal' | 'cancellation';
1239
+ email: string;
1240
+ ip?: string;
1241
+ }
1242
+ /**
1243
+ * The hooks of `consumerRightsEntrypoints`. Every hook gets the request's context last, so an
1244
+ * application keeps its counters and lookups on the context, never in module state.
1245
+ */
1246
+ export interface ConsumerRightsHandlerOptions {
1247
+ /** The organization a signed-in request acts for. Default: `req.entity.id`. */
1248
+ resolveEntity?: (req: AbstractRequest, ctx: ApiContext) => string | null | undefined;
1249
+ /** The person's profile id and prefill. */
1250
+ subjectOf?: (req: AbstractRequest, ctx: ApiContext) => Partial<ConsumerSubject> | Promise<Partial<ConsumerSubject>>;
1251
+ /** Refuse a money-moving act (consent, start, withdraw, cancel) — e.g. an API-key request. */
1252
+ guardMoney?: (req: AbstractRequest, action: string, ctx: ApiContext) => void | Promise<void>;
1253
+ /** REQUIRED when the public subtree is bound: throw (e.g. 429) to refuse a public declaration. */
1254
+ throttle?: (req: AbstractRequest, key: ConsumerThrottleKey, ctx: ApiContext) => void | Promise<void>;
1255
+ /** The request evidence; default `requestOriginOf`. */
1256
+ metaOf?: (req: AbstractRequest, ctx: ApiContext) => RequestOrigin;
1257
+ /** The least time a public declaration takes to answer, matched or not. Default 1000 ms. */
1258
+ publicMinMs?: number;
1259
+ /** The plan's short name a start request's statement says (default: its localized catalogue title). */
1260
+ planNameOf?: (planSku: string, language: string, req: AbstractRequest, ctx: ApiContext) => string | undefined | Promise<string | undefined>;
1261
+ serviceAlias?: string;
1262
+ }
1263
+ export interface CheckoutReadHandlerOptions {
1264
+ resolveEntity?: (req: AbstractRequest, ctx: ApiContext) => string | null | undefined;
1265
+ gatewayAlias?: string;
1266
+ }
188
1267
  export {};
189
1268
  //# sourceMappingURL=types.d.ts.map