@voltro/plugin-billing 0.5.0 → 0.7.0
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.
- package/CHANGELOG.md +68 -1
- package/README.md +1 -1
- package/THIRD-PARTY-NOTICES.md +28 -1
- package/dist/index.d.ts +149 -184
- package/dist/index.js +601 -581
- package/dist/rpc.d.ts +30 -0
- package/dist/rpc.js +42 -4
- package/dist/types.d.ts +95 -40
- package/package.json +8 -7
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ColumnDefinition } from '@voltro/database';
|
|
2
2
|
import { Context } from 'effect';
|
|
3
3
|
import { DataStore } from '@voltro/database';
|
|
4
|
+
import { default as default_2 } from 'stripe';
|
|
4
5
|
import { Effect } from 'effect';
|
|
5
6
|
import { IncomingRequest } from '@voltro/plugin-webhooks';
|
|
6
7
|
import { IncomingResponse } from '@voltro/plugin-webhooks';
|
|
@@ -15,18 +16,8 @@ import { TableIndex } from '@voltro/database';
|
|
|
15
16
|
import { TableLike } from '@voltro/database';
|
|
16
17
|
import { VoltroPlugin } from '@voltro/protocol';
|
|
17
18
|
|
|
18
|
-
/**
|
|
19
|
-
* Advance one due dunning record: if there is a NEXT offset in the schedule,
|
|
20
|
-
* move to it (CAS on `attempt`); otherwise exhaust — cancel the subscription
|
|
21
|
-
* and close the record. Returns which happened, or `null` if another tick won
|
|
22
|
-
* the advance first (the CAS lost).
|
|
23
|
-
*/
|
|
24
|
-
export declare const advanceDunning: (opts: DunningEngineOptions, record: DunningState) => Effect.Effect<"retried" | "canceled" | null, BillingError>;
|
|
25
|
-
|
|
26
19
|
export declare const BILLING_CUSTOMERS_TABLE = "_voltro_billing_customers";
|
|
27
20
|
|
|
28
|
-
export declare const BILLING_DUNNING_TABLE = "_voltro_billing_dunning";
|
|
29
|
-
|
|
30
21
|
export declare const BILLING_FLUSH_CLAIMS_TABLE = "_voltro_billing_flush_claims";
|
|
31
22
|
|
|
32
23
|
export declare const BILLING_INVOICES_TABLE = "_voltro_billing_invoices";
|
|
@@ -54,15 +45,6 @@ export declare interface BillingContext {
|
|
|
54
45
|
/** tenant ↔ provider customer link. */
|
|
55
46
|
export declare const billingCustomersTable: BillingTable;
|
|
56
47
|
|
|
57
|
-
/**
|
|
58
|
-
* Dunning ledger — one row per subscription with a failed payment being
|
|
59
|
-
* retried. `providerSubscriptionId` is UNIQUE so the create-path `insertIgnore`
|
|
60
|
-
* gate makes opening dunning idempotent under a replayed payment-failed
|
|
61
|
-
* webhook. Deleted on recovery / cancellation, so the table is bounded by the
|
|
62
|
-
* count of subscriptions CURRENTLY in dunning (small).
|
|
63
|
-
*/
|
|
64
|
-
export declare const billingDunningTable: BillingTable;
|
|
65
|
-
|
|
66
48
|
/**
|
|
67
49
|
* A billing failure. `transient: true` marks failures the service retries
|
|
68
50
|
* (network blip talking to the provider); non-transient failures (a 4xx
|
|
@@ -155,15 +137,25 @@ export declare interface BillingPluginOptions {
|
|
|
155
137
|
* self-scheduled flush — call `billing.flushUsage()` from your own
|
|
156
138
|
* `*.cron.tsx` instead (e.g. for cluster-coordinated flushing). */
|
|
157
139
|
readonly flushIntervalMs?: number;
|
|
158
|
-
/** Dunning retry schedule — day-offsets from the original payment failure at
|
|
159
|
-
* which to retry before canceling. Default `[1, 3, 5, 7]`. A failed payment
|
|
160
|
-
* transitions the subscription `active → pastDue`; exhausting the schedule
|
|
161
|
-
* transitions it to `canceled`; a recovered payment returns it to `active`. */
|
|
162
|
-
readonly dunningSchedule?: DunningSchedule;
|
|
163
140
|
/** Typed per-event side effects, run AFTER the row is updated. */
|
|
164
141
|
readonly onEvent?: OnEventMap;
|
|
165
142
|
/** Transient-failure retries on provider calls. Default 3. */
|
|
166
143
|
readonly attempts?: number;
|
|
144
|
+
/**
|
|
145
|
+
* Checkout behaviour, handed straight to the provider.
|
|
146
|
+
*
|
|
147
|
+
* `adjustableQuantity` renders STRIPE's seat stepper on STRIPE's checkout
|
|
148
|
+
* page — set it for a per-seat plan so a customer can buy the number of
|
|
149
|
+
* seats they want in one go. `trialDays` makes Stripe run the trial and
|
|
150
|
+
* report `trialing`; we neither count trial days nor expire them.
|
|
151
|
+
*/
|
|
152
|
+
readonly checkout?: {
|
|
153
|
+
readonly adjustableQuantity?: {
|
|
154
|
+
readonly min: number;
|
|
155
|
+
readonly max: number;
|
|
156
|
+
};
|
|
157
|
+
readonly trialDays?: number;
|
|
158
|
+
};
|
|
167
159
|
/**
|
|
168
160
|
* Per-tenant entitlement-limit override — the seam for a cloud-issued license
|
|
169
161
|
* snapshot. Pass `@voltro/plugin-licensing`'s `entitlementResolver` here to let
|
|
@@ -199,6 +191,39 @@ export declare interface BillingProvider {
|
|
|
199
191
|
url: string;
|
|
200
192
|
}, BillingError>;
|
|
201
193
|
readonly reportUsage: (input: UsagePush) => Effect.Effect<void, BillingError>;
|
|
194
|
+
/**
|
|
195
|
+
* Apply a plan / seat change to the LIVE subscription.
|
|
196
|
+
*
|
|
197
|
+
* This is the call whose absence made `changeSeats` a lie: without it the
|
|
198
|
+
* service patched its own table and the provider went on billing the old
|
|
199
|
+
* quantity, so a customer could be granted seats nobody was charging for.
|
|
200
|
+
* The provider — not us — computes and bills the proration.
|
|
201
|
+
*/
|
|
202
|
+
readonly updateSubscription: (input: SubscriptionUpdateInput) => Effect.Effect<SubscriptionChangeResult, BillingError>;
|
|
203
|
+
/**
|
|
204
|
+
* What WOULD a change cost, without applying it? Backed by the provider's
|
|
205
|
+
* own invoice preview, so the number quoted to the customer is the number
|
|
206
|
+
* they are charged — a locally-computed estimate can differ from the
|
|
207
|
+
* provider's rounding, its tax, and its credit balance, and any difference
|
|
208
|
+
* is a support ticket.
|
|
209
|
+
*/
|
|
210
|
+
readonly previewSubscriptionChange: (input: SubscriptionUpdateInput) => Effect.Effect<SubscriptionChangeResult, BillingError>;
|
|
211
|
+
/** The provider's invoice history. We mirror it for listing but never
|
|
212
|
+
* render an invoice ourselves — `hostedUrl` / `pdfUrl` are theirs. */
|
|
213
|
+
readonly listInvoices: (input: {
|
|
214
|
+
readonly providerCustomerId: string;
|
|
215
|
+
readonly limit?: number;
|
|
216
|
+
}) => Effect.Effect<ReadonlyArray<InvoiceRecord>, BillingError>;
|
|
217
|
+
/**
|
|
218
|
+
* Verify a webhook signature and decode the payload, using the provider's
|
|
219
|
+
* OWN verification. Returns `null` when this provider does not do its own
|
|
220
|
+
* verification (the mount's generic check then stands alone).
|
|
221
|
+
*/
|
|
222
|
+
readonly verifyWebhook?: (input: {
|
|
223
|
+
readonly rawBody: Uint8Array;
|
|
224
|
+
readonly signatureHeader: string;
|
|
225
|
+
readonly secret: string;
|
|
226
|
+
}) => Effect.Effect<unknown, BillingError>;
|
|
202
227
|
/** Map a verified, decoded webhook payload to a `BillingEvent`, or
|
|
203
228
|
* `null` for an event type this provider doesn't model (handler
|
|
204
229
|
* no-ops, still 200). */
|
|
@@ -223,11 +248,19 @@ export declare interface BillingServiceOptions {
|
|
|
223
248
|
readonly stores?: BillingStores;
|
|
224
249
|
/** Transient-failure retries on provider calls. Default 3. */
|
|
225
250
|
readonly attempts?: number;
|
|
251
|
+
/** Checkout behaviour handed straight to the provider — we render none of
|
|
252
|
+
* it ourselves. */
|
|
253
|
+
readonly checkout?: {
|
|
254
|
+
/** Seat stepper bounds on the provider's checkout page. */
|
|
255
|
+
readonly adjustableQuantity?: {
|
|
256
|
+
readonly min: number;
|
|
257
|
+
readonly max: number;
|
|
258
|
+
};
|
|
259
|
+
/** Free-trial length. The provider runs the trial and reports `trialing`. */
|
|
260
|
+
readonly trialDays?: number;
|
|
261
|
+
};
|
|
226
262
|
/** Clock injection for deterministic period windowing in tests. */
|
|
227
263
|
readonly now?: () => Date;
|
|
228
|
-
/** Dunning retry schedule (day-offsets from the original failure). Default
|
|
229
|
-
* `[1, 3, 5, 7]`. */
|
|
230
|
-
readonly dunningSchedule?: DunningSchedule;
|
|
231
264
|
/**
|
|
232
265
|
* Optional per-tenant entitlement-limit override — the seam for a
|
|
233
266
|
* cloud-issued license snapshot (e.g. `@voltro/plugin-licensing`'s
|
|
@@ -251,6 +284,15 @@ export declare interface BillingServiceShape {
|
|
|
251
284
|
readonly reportUsage: (tenantId: string, key: string, qty: number) => Effect.Effect<void, BillingError>;
|
|
252
285
|
/** Flush all pending local usage counters to the provider. */
|
|
253
286
|
readonly flushUsage: () => Effect.Effect<void, BillingError>;
|
|
287
|
+
/** What a plan/seat change WOULD cost, from the provider's own invoice
|
|
288
|
+
* preview. Quote this, not a local estimate — the two differ by the
|
|
289
|
+
* provider's rounding, tax and credit balance. */
|
|
290
|
+
readonly previewChange: (tenantId: string, next: {
|
|
291
|
+
readonly plan?: PlanId;
|
|
292
|
+
readonly quantity?: number;
|
|
293
|
+
}) => Effect.Effect<SubscriptionChange, BillingError>;
|
|
294
|
+
/** The provider's invoice history for this tenant. */
|
|
295
|
+
readonly invoices: (tenantId: string) => Effect.Effect<ReadonlyArray<InvoiceRecord>, BillingError>;
|
|
254
296
|
/** Mint a provider-hosted checkout URL for `plan`. */
|
|
255
297
|
readonly startCheckout: (input: {
|
|
256
298
|
readonly tenantId: string;
|
|
@@ -282,35 +324,13 @@ export declare interface BillingServiceShape {
|
|
|
282
324
|
* positive integer. Fails `BillingError` on no subscription / bad quantity.
|
|
283
325
|
*/
|
|
284
326
|
readonly changeSeats: (tenantId: string, quantity: number, changeAt?: Date) => Effect.Effect<SubscriptionChange, BillingError>;
|
|
285
|
-
/**
|
|
286
|
-
* Record a failed payment: schedule the first dunning retry (per the
|
|
287
|
-
* configured schedule) and transition the subscription `active → pastDue`.
|
|
288
|
-
* Idempotent — a replayed failure for a subscription already in dunning does
|
|
289
|
-
* not re-arm or double-schedule.
|
|
290
|
-
*/
|
|
291
|
-
readonly recordPaymentFailure: (tenantId: string, at?: Date) => Effect.Effect<DunningState | null, BillingError>;
|
|
292
|
-
/**
|
|
293
|
-
* Record a recovered payment: clear any open dunning and transition the
|
|
294
|
-
* subscription back to `active`. Idempotent / a no-op when not in dunning.
|
|
295
|
-
*/
|
|
296
|
-
readonly recordPaymentSuccess: (tenantId: string, at?: Date) => Effect.Effect<void, BillingError>;
|
|
297
|
-
/**
|
|
298
|
-
* Advance every due dunning entry: subscriptions whose next retry is due move
|
|
299
|
-
* to the next offset in the schedule; those that have exhausted the schedule
|
|
300
|
-
* transition `pastDue → canceled`. Idempotent per due-time (a claim gate
|
|
301
|
-
* makes a re-run over the same window a no-op). Driven by the self-scheduled
|
|
302
|
-
* interval, or call it from your own `*.cron.tsx`.
|
|
303
|
-
*/
|
|
304
|
-
readonly runDunningCycle: (now?: Date) => Effect.Effect<DunningCycleResult, BillingError>;
|
|
305
327
|
}
|
|
306
328
|
|
|
307
|
-
/** The five billing stores bundled — swapped wholesale by `bindDataStore`. */
|
|
308
329
|
export declare interface BillingStores {
|
|
309
330
|
readonly customers: CustomerStore;
|
|
310
331
|
readonly subscriptions: SubscriptionStore;
|
|
311
332
|
readonly invoices: InvoiceStore;
|
|
312
333
|
readonly usage: UsageStore;
|
|
313
|
-
readonly dunning: DunningStore;
|
|
314
334
|
}
|
|
315
335
|
|
|
316
336
|
/** One active subscription per tenant. */
|
|
@@ -327,7 +347,6 @@ export declare interface BillingTable extends TableLike {
|
|
|
327
347
|
readonly appliedIndexes: ReadonlyArray<TableIndex>;
|
|
328
348
|
}
|
|
329
349
|
|
|
330
|
-
/** All billing tables, ready to spread into `extendSchema.tables`. */
|
|
331
350
|
export declare const billingTables: () => ReadonlyArray<BillingTable>;
|
|
332
351
|
|
|
333
352
|
/** Per-tenant metered counters driving the entitlement engine. */
|
|
@@ -344,6 +363,9 @@ declare interface BillingWebhookOptions {
|
|
|
344
363
|
readonly provider: BillingProvider;
|
|
345
364
|
readonly service: BillingServiceShape;
|
|
346
365
|
readonly onEvent?: OnEventMap;
|
|
366
|
+
/** The signing secret, so the provider can run its OWN verification on the
|
|
367
|
+
* raw bytes. Null → only the mount's generic check applies. */
|
|
368
|
+
readonly webhookSecret?: string | null;
|
|
347
369
|
}
|
|
348
370
|
|
|
349
371
|
/**
|
|
@@ -379,6 +401,16 @@ export declare interface CheckoutInput {
|
|
|
379
401
|
readonly cancelUrl: string;
|
|
380
402
|
/** Optional existing provider customer id to attach the checkout to. */
|
|
381
403
|
readonly providerCustomerId?: string;
|
|
404
|
+
/** Seats to buy up front. Defaults to 1. */
|
|
405
|
+
readonly quantity?: number;
|
|
406
|
+
/** Let the buyer change the seat count on the checkout page itself.
|
|
407
|
+
* Stripe renders the stepper; we neither build nor validate it. */
|
|
408
|
+
readonly adjustableQuantity?: {
|
|
409
|
+
readonly min: number;
|
|
410
|
+
readonly max: number;
|
|
411
|
+
};
|
|
412
|
+
/** Days of free trial. Stripe runs the trial and emits `trialing`. */
|
|
413
|
+
readonly trialDays?: number;
|
|
382
414
|
}
|
|
383
415
|
|
|
384
416
|
/**
|
|
@@ -390,16 +422,6 @@ export declare interface CheckoutInput {
|
|
|
390
422
|
*/
|
|
391
423
|
export declare const claimFlushWindow: (store: DataStore, windowKey: string, replicaId: string, nowMs: number) => Promise<boolean>;
|
|
392
424
|
|
|
393
|
-
/**
|
|
394
|
-
* Compute the prorated settlement for a mid-cycle amount change.
|
|
395
|
-
*
|
|
396
|
-
* Fails `BillingError` (non-transient) when the period is degenerate
|
|
397
|
-
* (`periodEnd <= periodStart`) — a caller must supply a real paid period; a
|
|
398
|
-
* plan with no period (`currentPeriodEnd === null`) is not prorated at all
|
|
399
|
-
* (the service short-circuits before calling this).
|
|
400
|
-
*/
|
|
401
|
-
export declare const computeProration: (input: ProrationInput) => ProrationResult;
|
|
402
|
-
|
|
403
425
|
declare interface CustomerRecord {
|
|
404
426
|
readonly tenantId: string;
|
|
405
427
|
readonly provider: string;
|
|
@@ -413,62 +435,9 @@ export declare interface CustomerStore {
|
|
|
413
435
|
|
|
414
436
|
export declare const dataStoreStores: (store: DataStore) => BillingStores;
|
|
415
437
|
|
|
416
|
-
export declare const DEFAULT_DUNNING_SCHEDULE: DunningSchedule;
|
|
417
|
-
|
|
418
438
|
/** The free/fallback plan id every app implicitly has. */
|
|
419
439
|
export declare const DEFAULT_PLAN: PlanId;
|
|
420
440
|
|
|
421
|
-
/** What one `runDunningCycle` did. */
|
|
422
|
-
export declare interface DunningCycleResult {
|
|
423
|
-
/** Subscriptions advanced to the next retry offset. */
|
|
424
|
-
readonly retried: number;
|
|
425
|
-
/** Subscriptions that exhausted the schedule and were canceled. */
|
|
426
|
-
readonly canceled: number;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
export declare interface DunningEngineOptions {
|
|
430
|
-
readonly subscriptions: SubscriptionStore;
|
|
431
|
-
readonly dunning: DunningStore;
|
|
432
|
-
readonly schedule: DunningSchedule;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
/** The dunning retry schedule: day-offsets from the original failure at which
|
|
436
|
-
* to retry. Default `[1, 3, 5, 7]` (four retries over a week, then cancel). */
|
|
437
|
-
export declare type DunningSchedule = ReadonlyArray<number>;
|
|
438
|
-
|
|
439
|
-
/** A tenant's live dunning record — a failed payment being retried. */
|
|
440
|
-
export declare interface DunningState {
|
|
441
|
-
readonly tenantId: string;
|
|
442
|
-
readonly providerSubscriptionId: string;
|
|
443
|
-
/** 0-based index into the configured retry-offset schedule. */
|
|
444
|
-
readonly attempt: number;
|
|
445
|
-
/** When the current retry is due. */
|
|
446
|
-
readonly nextRetryAt: Date;
|
|
447
|
-
/** When dunning was first opened (the original failure). */
|
|
448
|
-
readonly startedAt: Date;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
export declare interface DunningStore {
|
|
452
|
-
/**
|
|
453
|
-
* Open a dunning record for a subscription (attempt 0, due at `nextRetryAt`),
|
|
454
|
-
* IDEMPOTENTLY — if one already exists it is left untouched and `false` is
|
|
455
|
-
* returned. Returns `true` only when THIS call opened it. The create-path
|
|
456
|
-
* `insertIgnore` over the `providerSubscriptionId` UNIQUE makes a replayed
|
|
457
|
-
* payment-failed webhook a no-op across instances.
|
|
458
|
-
*/
|
|
459
|
-
readonly open: (tenantId: string, providerSubscriptionId: string, nextRetryAt: Date, at: Date) => Effect.Effect<boolean, BillingError>;
|
|
460
|
-
readonly getByTenant: (tenantId: string) => Effect.Effect<DunningState | null, BillingError>;
|
|
461
|
-
/** All dunning records due at/before `at` (`nextRetryAt <= at`). */
|
|
462
|
-
readonly due: (at: Date) => Effect.Effect<ReadonlyArray<DunningState>, BillingError>;
|
|
463
|
-
/** Advance a record to the next attempt/retry time, CONDITIONALLY on the
|
|
464
|
-
* record still being at `fromAttempt` (a CAS so two ticks can't both advance
|
|
465
|
-
* the same record). Returns `true` iff this call won the advance. */
|
|
466
|
-
readonly advance: (providerSubscriptionId: string, fromAttempt: number, toAttempt: number, nextRetryAt: Date) => Effect.Effect<boolean, BillingError>;
|
|
467
|
-
/** Close (delete) the dunning record for a subscription — on recovery or on
|
|
468
|
-
* exhaustion→cancel. No-op if absent. */
|
|
469
|
-
readonly close: (providerSubscriptionId: string) => Effect.Effect<void, BillingError>;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
441
|
/** A registry with a single zero-quota `free` plan — the zero-config default. */
|
|
473
442
|
export declare const emptyPlanRegistry: () => PlanRegistry;
|
|
474
443
|
|
|
@@ -534,6 +503,17 @@ export declare const evaluateEntitlement: (entitlement: string, limit: number, u
|
|
|
534
503
|
export declare const flushWindowKey: (nowMs: number, intervalMs: number) => string;
|
|
535
504
|
|
|
536
505
|
declare interface InvoiceRecord {
|
|
506
|
+
readonly providerInvoiceId: string;
|
|
507
|
+
readonly amountMinor: number;
|
|
508
|
+
readonly currency: string;
|
|
509
|
+
readonly status: string;
|
|
510
|
+
readonly createdAt: Date | null;
|
|
511
|
+
/** Provider-hosted invoice page — we never render one ourselves. */
|
|
512
|
+
readonly hostedUrl: string | null;
|
|
513
|
+
readonly pdfUrl: string | null;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
declare interface InvoiceRecord_2 {
|
|
537
517
|
readonly tenantId: string;
|
|
538
518
|
readonly provider: string;
|
|
539
519
|
readonly providerInvoiceId: string;
|
|
@@ -545,8 +525,8 @@ declare interface InvoiceRecord {
|
|
|
545
525
|
|
|
546
526
|
export declare interface InvoiceStore {
|
|
547
527
|
/** Upsert by `providerInvoiceId` (idempotent under webhook replay). */
|
|
548
|
-
readonly upsert: (invoice:
|
|
549
|
-
readonly listByTenant: (tenantId: string) => Effect.Effect<ReadonlyArray<
|
|
528
|
+
readonly upsert: (invoice: InvoiceRecord_2) => Effect.Effect<void, BillingError>;
|
|
529
|
+
readonly listByTenant: (tenantId: string) => Effect.Effect<ReadonlyArray<InvoiceRecord_2>, BillingError>;
|
|
550
530
|
}
|
|
551
531
|
|
|
552
532
|
export declare const memoryStores: () => BillingStores;
|
|
@@ -587,6 +567,10 @@ declare type MeterSource =
|
|
|
587
567
|
export declare interface MockProvider extends BillingProvider {
|
|
588
568
|
/** Recorded usage pushes — assertion surface for tests. */
|
|
589
569
|
readonly pushed: ReadonlyArray<UsagePush>;
|
|
570
|
+
/** Recorded subscription updates. The point of asserting on these is that
|
|
571
|
+
* a change which never reaches the provider is a change the customer is
|
|
572
|
+
* not billed for — the exact defect this contract was widened to fix. */
|
|
573
|
+
readonly updates: ReadonlyArray<SubscriptionUpdateInput>;
|
|
590
574
|
}
|
|
591
575
|
|
|
592
576
|
export declare const mockProvider: (options?: {
|
|
@@ -617,23 +601,15 @@ declare interface MountBillingWebhookOptions extends BillingWebhookOptions {
|
|
|
617
601
|
}
|
|
618
602
|
|
|
619
603
|
/**
|
|
620
|
-
* Map a verified
|
|
621
|
-
*
|
|
622
|
-
*
|
|
604
|
+
* Map a verified Stripe event to a `BillingEvent`, or null for a type we do
|
|
605
|
+
* not model (the handler no-ops and still answers 200, which is what stops
|
|
606
|
+
* Stripe retrying an event we will never care about).
|
|
623
607
|
*/
|
|
624
|
-
export declare const normalizeStripeEvent: (
|
|
608
|
+
export declare const normalizeStripeEvent: (event: default_2.Event, resolvePrice?: (priceId: string) => string | null) => BillingEvent | null;
|
|
625
609
|
|
|
626
610
|
/** Per-event side effect, run AFTER the row is updated. */
|
|
627
611
|
export declare type OnEventMap = Readonly<Record<string, (event: BillingEvent) => Effect.Effect<void, unknown>>>;
|
|
628
612
|
|
|
629
|
-
/**
|
|
630
|
-
* Open dunning for a subscription on a payment failure: schedule the first
|
|
631
|
-
* retry (offset[0]) and transition `active → pastDue`. Idempotent — a replayed
|
|
632
|
-
* failure whose record already exists is a no-op (returns `null`). Returns the
|
|
633
|
-
* opened `DunningState` when this call opened it.
|
|
634
|
-
*/
|
|
635
|
-
export declare const openDunning: (opts: DunningEngineOptions, tenantId: string, providerSubscriptionId: string, at: Date) => Effect.Effect<DunningState | null, BillingError>;
|
|
636
|
-
|
|
637
613
|
/** Calendar-month window key (`'YYYY-MM'`) for metered counters. */
|
|
638
614
|
export declare const periodKey: (now?: Date) => string;
|
|
639
615
|
|
|
@@ -687,35 +663,6 @@ export declare interface PortalInput {
|
|
|
687
663
|
readonly returnUrl: string;
|
|
688
664
|
}
|
|
689
665
|
|
|
690
|
-
/** The inputs to one proration computation. All amounts are integer minor units. */
|
|
691
|
-
export declare interface ProrationInput {
|
|
692
|
-
/** The amount the subscription was billing for the current period (minor units). */
|
|
693
|
-
readonly oldAmountMinor: number;
|
|
694
|
-
/** The amount it will bill after the change (minor units). */
|
|
695
|
-
readonly newAmountMinor: number;
|
|
696
|
-
/** Start of the current paid period. */
|
|
697
|
-
readonly periodStart: Date;
|
|
698
|
-
/** End of the current paid period (`currentPeriodEnd`). */
|
|
699
|
-
readonly periodEnd: Date;
|
|
700
|
-
/** When the change takes effect (usually "now"). */
|
|
701
|
-
readonly changeAt: Date;
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
/** The result of a proration: the settlement delta + the fraction it was based on. */
|
|
705
|
-
export declare interface ProrationResult {
|
|
706
|
-
/** Integer minor-unit settlement: > 0 charge (upgrade), < 0 credit
|
|
707
|
-
* (downgrade), 0 at the period boundary or when the amount is unchanged. */
|
|
708
|
-
readonly deltaMinor: number;
|
|
709
|
-
/** The unused fraction of the period the delta was prorated over, in [0, 1]. */
|
|
710
|
-
readonly unusedFraction: number;
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
/**
|
|
714
|
-
* Recover a subscription: close any open dunning record and set it back to
|
|
715
|
-
* `active`. Idempotent / no-op when not in dunning.
|
|
716
|
-
*/
|
|
717
|
-
export declare const recoverDunning: (opts: DunningEngineOptions, providerSubscriptionId: string) => Effect.Effect<void, BillingError>;
|
|
718
|
-
|
|
719
666
|
/**
|
|
720
667
|
* In-handler entitlement guard. Resolves `ctx.request.subject.tenantId`,
|
|
721
668
|
* consumes `cost` units of `key`, and fails with the typed
|
|
@@ -745,29 +692,10 @@ declare interface ResolveProviderInput {
|
|
|
745
692
|
readonly provider?: BillingProviderName | BillingProvider;
|
|
746
693
|
readonly apiKey?: string;
|
|
747
694
|
readonly env: NodeJS.ProcessEnv;
|
|
695
|
+
/** Price id → plan id, from the plugin's plan registry. */
|
|
696
|
+
readonly planForPriceId?: (priceId: string) => string | null;
|
|
748
697
|
}
|
|
749
698
|
|
|
750
|
-
/**
|
|
751
|
-
* The retry time for attempt `index` (0-based) given the original failure
|
|
752
|
-
* `startedAt` and the schedule of day-offsets. Returns `null` when `index` is
|
|
753
|
-
* past the end of the schedule — the signal to CANCEL rather than reschedule.
|
|
754
|
-
*/
|
|
755
|
-
export declare const retryTimeFor: (startedAt: Date, index: number, schedule: DunningSchedule) => Date | null;
|
|
756
|
-
|
|
757
|
-
/** Round half away from zero — symmetric for charges (+) and credits (−) so
|
|
758
|
-
* proration doesn't systematically favour either party. `Math.round` rounds
|
|
759
|
-
* half toward +∞ (−0.5 → 0, +0.5 → 1), which WOULD bias credits; this
|
|
760
|
-
* corrects the negative side. Operates on a float intermediate and returns an
|
|
761
|
-
* integer. */
|
|
762
|
-
export declare const roundHalfAwayFromZero: (value: number) => number;
|
|
763
|
-
|
|
764
|
-
/**
|
|
765
|
-
* Run one dunning cycle: for every record due at `now`, advance it (retry to
|
|
766
|
-
* the next offset, or cancel if exhausted). Idempotent per due-time — the CAS
|
|
767
|
-
* on `attempt` means a re-run over an already-advanced record is a no-op.
|
|
768
|
-
*/
|
|
769
|
-
export declare const runDunningCycle: (opts: DunningEngineOptions, now: Date) => Effect.Effect<DunningCycleResult, BillingError>;
|
|
770
|
-
|
|
771
699
|
/**
|
|
772
700
|
* A mutable holder so `bindDataStore` can swap the stores AFTER the service
|
|
773
701
|
* layer is built. The service reads `holder.stores` on every call rather
|
|
@@ -784,6 +712,18 @@ export declare interface StripeProviderOptions {
|
|
|
784
712
|
readonly apiKey: string;
|
|
785
713
|
/** Override metered-usage support (Stripe supports it by default). */
|
|
786
714
|
readonly supportsMeteredUsage?: boolean;
|
|
715
|
+
/**
|
|
716
|
+
* Map a Stripe price id to one of our plan ids.
|
|
717
|
+
*
|
|
718
|
+
* Without it, a subscription is only recognised when it carries
|
|
719
|
+
* `metadata.plan` — which our own checkout sets, but a subscription created
|
|
720
|
+
* in the Stripe Dashboard, by a sales-led flow, or by a migration does NOT.
|
|
721
|
+
* Those events were previously dropped on the floor and the customer stayed
|
|
722
|
+
* on `free` while paying. The plugin passes its plan registry here.
|
|
723
|
+
*/
|
|
724
|
+
readonly planForPriceId?: (priceId: string) => string | null;
|
|
725
|
+
/** Injected in tests. */
|
|
726
|
+
readonly client?: default_2;
|
|
787
727
|
}
|
|
788
728
|
|
|
789
729
|
/** A tenant's subscription row, normalized across providers. */
|
|
@@ -815,6 +755,18 @@ export declare interface SubscriptionChange {
|
|
|
815
755
|
readonly currency: string;
|
|
816
756
|
}
|
|
817
757
|
|
|
758
|
+
/** What a provider reports back after (or before) a subscription change. */
|
|
759
|
+
declare interface SubscriptionChangeResult {
|
|
760
|
+
readonly plan: PlanId;
|
|
761
|
+
readonly quantity: number;
|
|
762
|
+
/** The proration the PROVIDER computed — and, unless `proration: 'none'`,
|
|
763
|
+
* will actually bill. Positive = owed, negative = credited. */
|
|
764
|
+
readonly prorationMinor: number;
|
|
765
|
+
readonly currency: string;
|
|
766
|
+
readonly currentPeriodStart: Date | null;
|
|
767
|
+
readonly currentPeriodEnd: Date | null;
|
|
768
|
+
}
|
|
769
|
+
|
|
818
770
|
export declare type SubscriptionStatus = 'active' | 'trialing' | 'pastDue' | 'canceled' | 'incomplete';
|
|
819
771
|
|
|
820
772
|
export declare interface SubscriptionStore {
|
|
@@ -835,6 +787,26 @@ export declare interface SubscriptionStore {
|
|
|
835
787
|
readonly setStatus: (providerSubscriptionId: string, status: SubscriptionStatus) => Effect.Effect<void, BillingError>;
|
|
836
788
|
}
|
|
837
789
|
|
|
790
|
+
/** Apply a plan / seat change to the LIVE subscription at the provider. */
|
|
791
|
+
declare interface SubscriptionUpdateInput {
|
|
792
|
+
readonly providerSubscriptionId: string;
|
|
793
|
+
/** New price to move to. Omit to keep the current one. */
|
|
794
|
+
readonly priceId?: string;
|
|
795
|
+
/** New seat quantity. Omit to keep the current one. */
|
|
796
|
+
readonly quantity?: number;
|
|
797
|
+
/**
|
|
798
|
+
* How the provider should handle the mid-period money.
|
|
799
|
+
*
|
|
800
|
+
* - `'prorate'` — bill or credit the difference for the remaining period
|
|
801
|
+
* (Stripe's `create_prorations`). The default, and the only option that
|
|
802
|
+
* charges what the customer actually used.
|
|
803
|
+
* - `'none'` — change takes effect with no adjustment.
|
|
804
|
+
* - `'always_invoice'` — prorate AND invoice immediately rather than at
|
|
805
|
+
* the next cycle.
|
|
806
|
+
*/
|
|
807
|
+
readonly proration?: 'prorate' | 'none' | 'always_invoice';
|
|
808
|
+
}
|
|
809
|
+
|
|
838
810
|
export declare interface UsagePush {
|
|
839
811
|
readonly tenantId: string;
|
|
840
812
|
readonly entitlementKey: string;
|
|
@@ -875,13 +847,6 @@ export declare interface UsageStore {
|
|
|
875
847
|
readonly markReported: (tenantId: string, key: string, period: string, reported: number) => Effect.Effect<void, BillingError>;
|
|
876
848
|
}
|
|
877
849
|
|
|
878
|
-
/**
|
|
879
|
-
* Validate a dunning schedule at boot — fails LOUD on a bad config rather than
|
|
880
|
-
* silently never retrying. Offsets must be a non-empty list of strictly
|
|
881
|
-
* increasing positive numbers (days).
|
|
882
|
-
*/
|
|
883
|
-
export declare const validateDunningSchedule: (schedule: DunningSchedule) => void;
|
|
884
|
-
|
|
885
850
|
export { VoltroPlugin }
|
|
886
851
|
|
|
887
852
|
export { }
|