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