@voltro/plugin-billing 0.81.0 → 0.83.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 +1806 -6
- package/THIRD-PARTY-NOTICES.md +1 -1
- package/dist/index.d.ts +664 -17
- package/dist/index.js +1923 -597
- package/dist/rpc.d.ts +36 -0
- package/dist/rpc.js +62 -20
- package/dist/types.d.ts +205 -0
- package/dist/web/index.d.ts +57 -0
- package/dist/web/index.js +26 -1
- package/package.json +6 -6
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { IncomingResponse } from '@voltro/plugin-webhooks';
|
|
|
8
8
|
import { IncomingWebhookDescriptor } from '@voltro/plugin-webhooks';
|
|
9
9
|
import { Layer } from 'effect';
|
|
10
10
|
import { mountIncomingWebhook } from '@voltro/plugin-webhooks';
|
|
11
|
+
import { PluginEgressDeclaration } from '@voltro/protocol';
|
|
11
12
|
import { RpcInterceptor } from '@voltro/protocol';
|
|
12
13
|
import { RpcKind } from '@voltro/protocol';
|
|
13
14
|
import { Schema } from 'effect';
|
|
@@ -16,6 +17,14 @@ import { TableIndex } from '@voltro/database';
|
|
|
16
17
|
import { TableLike } from '@voltro/database';
|
|
17
18
|
import { VoltroPlugin } from '@voltro/protocol';
|
|
18
19
|
|
|
20
|
+
export declare const assertBillingProductionReady: (input: {
|
|
21
|
+
readonly env: NodeJS.ProcessEnv;
|
|
22
|
+
readonly providerOption: BillingProviderName | BillingProvider | undefined;
|
|
23
|
+
readonly resolvedName: string;
|
|
24
|
+
readonly webhookSecret: string | null;
|
|
25
|
+
readonly redirectOrigins: ReadonlyArray<string> | undefined;
|
|
26
|
+
}) => void;
|
|
27
|
+
|
|
19
28
|
export declare const BILLING_CUSTOMERS_TABLE = "_voltro_billing_customers";
|
|
20
29
|
|
|
21
30
|
export declare const BILLING_DUNNING_NOTICES_TABLE = "_voltro_billing_dunning_notices";
|
|
@@ -33,6 +42,14 @@ export declare const BILLING_WEBHOOK_ID = "billing";
|
|
|
33
42
|
|
|
34
43
|
export declare const BILLING_WEBHOOK_PATH = "/billing/webhook";
|
|
35
44
|
|
|
45
|
+
/**
|
|
46
|
+
* What a billing call would do to what the tenant pays — the unit an app's
|
|
47
|
+
* `admit` hook decides on. `checkout` starts paying, `change` moves a plan or
|
|
48
|
+
* seat count, `resume` takes a cancellation back, `portal` opens the provider's
|
|
49
|
+
* self-service page (which can do all of those), `cancel` stops paying.
|
|
50
|
+
*/
|
|
51
|
+
export declare type BillingAction = 'checkout' | 'change' | 'resume' | 'portal' | 'cancel';
|
|
52
|
+
|
|
36
53
|
/**
|
|
37
54
|
* The shape every handler executor receives carries the resolved subject
|
|
38
55
|
* under `ctx.request.subject` (mirrors `@voltro/plugin-rbac`'s
|
|
@@ -58,6 +75,14 @@ export declare const billingDunningNoticesTable: TableLike & {
|
|
|
58
75
|
readonly appliedIndexes: ReadonlyArray<TableIndex>;
|
|
59
76
|
};
|
|
60
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Where billing data leaves the installation, for `voltro compliance generate`
|
|
80
|
+
* (egress.json): Stripe's API for the stripe provider (customers, their email
|
|
81
|
+
* and the subscription items), nothing for the mock, and the provider's own
|
|
82
|
+
* name for a custom one.
|
|
83
|
+
*/
|
|
84
|
+
export declare const billingEgressOf: (provider: Pick<BillingProvider, "name">) => ReadonlyArray<PluginEgressDeclaration>;
|
|
85
|
+
|
|
61
86
|
/**
|
|
62
87
|
* A billing failure. `transient: true` marks failures the service retries
|
|
63
88
|
* (network blip talking to the provider); non-transient failures (a 4xx
|
|
@@ -122,6 +147,14 @@ export declare type BillingEvent = {
|
|
|
122
147
|
readonly tenantId: string;
|
|
123
148
|
readonly providerCustomerId: string;
|
|
124
149
|
readonly occurredAt: Date | null;
|
|
150
|
+
} | {
|
|
151
|
+
/** The provider customer was deleted (e.g. in the provider's dashboard).
|
|
152
|
+
* `tenantId` is null when the customer carried no tenant metadata — the
|
|
153
|
+
* link is then resolved from our own customer table. */
|
|
154
|
+
readonly _tag: 'customerUnlinked';
|
|
155
|
+
readonly tenantId: string | null;
|
|
156
|
+
readonly providerCustomerId: string;
|
|
157
|
+
readonly occurredAt: Date | null;
|
|
125
158
|
};
|
|
126
159
|
|
|
127
160
|
/** The event tag union — handy for `onEvent` keys. */
|
|
@@ -195,6 +228,29 @@ export declare interface BillingPluginOptions {
|
|
|
195
228
|
* a signed license decide limits per-tenant; a `null` result falls back to the
|
|
196
229
|
* static `plans` registry. Absent → static registry only.
|
|
197
230
|
*/
|
|
231
|
+
/**
|
|
232
|
+
* The app's say on whether a tenant may change what it pays right now —
|
|
233
|
+
* refuse a checkout, a plan change or the portal for an organization that is
|
|
234
|
+
* deleted or suspended. Fail with a `BillingError` to refuse; the provider
|
|
235
|
+
* is not called. The roles check (`billing:manage`) answers WHO may act; this
|
|
236
|
+
* answers whether the TENANT may, which a scope cannot see.
|
|
237
|
+
*/
|
|
238
|
+
readonly admit?: (tenantId: string, action: BillingAction) => Effect.Effect<void, BillingError>;
|
|
239
|
+
/**
|
|
240
|
+
* Origins the provider may send the customer back to (`successUrl`,
|
|
241
|
+
* `cancelUrl`, the portal's `returnUrl`) — e.g. `['https://app.example.com']`.
|
|
242
|
+
* Without it any URL is accepted, which makes checkout a redirect to
|
|
243
|
+
* anywhere; the plugin says so at boot when the Stripe provider is active.
|
|
244
|
+
*/
|
|
245
|
+
readonly redirectOrigins?: ReadonlyArray<string>;
|
|
246
|
+
/**
|
|
247
|
+
* Whether the app has closed (deleted) a tenant — positive evidence only.
|
|
248
|
+
* The next sync of a closed tenant that is still paying (any event, the
|
|
249
|
+
* catch-up, a checkpoint) ends its subscriptions: the safety net for a
|
|
250
|
+
* deletion path that did not call `closeAccount`. An unknown tenant is NOT closed (a subscription pointed at
|
|
251
|
+
* the wrong database must never be ended by it).
|
|
252
|
+
*/
|
|
253
|
+
readonly isTenantClosed?: (tenantId: string) => Effect.Effect<boolean, BillingError>;
|
|
198
254
|
readonly resolveEntitlementLimit?: (tenantId: string, key: string) => Effect.Effect<number | null, BillingError>;
|
|
199
255
|
/**
|
|
200
256
|
* Namespace for this plugin's rpc tags + inspect endpoints. Default `billing`.
|
|
@@ -237,6 +293,10 @@ export declare interface BillingProvider {
|
|
|
237
293
|
* When false, `BillingService.reportUsage` records locally but never
|
|
238
294
|
* calls `provider.reportUsage`. */
|
|
239
295
|
readonly supportsMeteredUsage: boolean;
|
|
296
|
+
/** Whether a usage push must name a linked provider customer. Stripe
|
|
297
|
+
* meters map events to a customer by id, so an unlinked tenant's usage
|
|
298
|
+
* stays pending instead of being sent unattributable. */
|
|
299
|
+
readonly usageRequiresCustomer?: boolean;
|
|
240
300
|
readonly createCheckoutSession: (input: CheckoutInput) => Effect.Effect<{
|
|
241
301
|
url: string;
|
|
242
302
|
}, BillingError>;
|
|
@@ -272,6 +332,43 @@ export declare interface BillingProvider {
|
|
|
272
332
|
* provider does not know the id.
|
|
273
333
|
*/
|
|
274
334
|
readonly fetchSubscription: (providerSubscriptionId: string) => Effect.Effect<ProviderSubscriptionState | null, BillingError>;
|
|
335
|
+
/**
|
|
336
|
+
* Every live (not yet ended) subscription at the provider, optionally for
|
|
337
|
+
* one customer. The provider-side half of reconciliation: what it lists is
|
|
338
|
+
* the truth our rows are corrected to.
|
|
339
|
+
*/
|
|
340
|
+
readonly listSubscriptions: (input: {
|
|
341
|
+
readonly providerCustomerId?: string;
|
|
342
|
+
}) => Effect.Effect<ReadonlyArray<ProviderSubscriptionSnapshot>, BillingError>;
|
|
343
|
+
/** End a subscription — at period end (reversible) or immediately with a
|
|
344
|
+
* final invoice. Returns the provider's state afterwards. */
|
|
345
|
+
readonly cancelSubscription: (input: {
|
|
346
|
+
readonly providerSubscriptionId: string;
|
|
347
|
+
readonly mode: CancelMode;
|
|
348
|
+
}) => Effect.Effect<ProviderSubscriptionState, BillingError>;
|
|
349
|
+
/** Undo a period-end cancellation that has not taken effect yet. */
|
|
350
|
+
readonly resumeSubscription: (providerSubscriptionId: string) => Effect.Effect<ProviderSubscriptionState, BillingError>;
|
|
351
|
+
/**
|
|
352
|
+
* The provider customer for a tenant, created when it has none — BEFORE the
|
|
353
|
+
* first checkout, so every checkout names its customer. That is what lets
|
|
354
|
+
* the provider refuse a second subscription for the same customer, and
|
|
355
|
+
* lets open checkouts be found and expired. Must be idempotent per tenant:
|
|
356
|
+
* two concurrent first checkouts get the same customer.
|
|
357
|
+
*/
|
|
358
|
+
readonly ensureCustomer: (input: {
|
|
359
|
+
readonly tenantId: string;
|
|
360
|
+
}) => Effect.Effect<string, BillingError>;
|
|
361
|
+
/** Expire every checkout still open for a customer, so only the newest one
|
|
362
|
+
* can be paid. Returns how many were expired. */
|
|
363
|
+
readonly expireOpenCheckouts: (input: {
|
|
364
|
+
readonly providerCustomerId: string;
|
|
365
|
+
}) => Effect.Effect<number, BillingError>;
|
|
366
|
+
/** Add prices the subscription is missing (metered items), without
|
|
367
|
+
* proration. Prices already present are left alone. */
|
|
368
|
+
readonly addSubscriptionPrices: (input: {
|
|
369
|
+
readonly providerSubscriptionId: string;
|
|
370
|
+
readonly priceIds: ReadonlyArray<string>;
|
|
371
|
+
}) => Effect.Effect<void, BillingError>;
|
|
275
372
|
/**
|
|
276
373
|
* The billing contact the PROVIDER has on file, when it has one. Used as the
|
|
277
374
|
* dunning recipient of last resort — `dunning.resolveRecipient` wins when
|
|
@@ -298,6 +395,23 @@ export declare interface BillingProvider {
|
|
|
298
395
|
* `null` for an event type this provider doesn't model (handler
|
|
299
396
|
* no-ops, still 200). */
|
|
300
397
|
readonly normalizeEvent: (raw: unknown) => Effect.Effect<BillingEvent | null, BillingError>;
|
|
398
|
+
/**
|
|
399
|
+
* Who and when a verified payload is about: the provider customer it names
|
|
400
|
+
* and when the provider created it. The webhook syncs that customer from a
|
|
401
|
+
* fresh read instead of trusting the payload, and an event created long
|
|
402
|
+
* before it arrives means deliveries were failing — the cue to catch up.
|
|
403
|
+
* Optional: without it the webhook applies the normalized event as is.
|
|
404
|
+
*/
|
|
405
|
+
readonly eventContext?: (raw: unknown) => {
|
|
406
|
+
readonly providerCustomerId: string | null;
|
|
407
|
+
readonly createdAt: Date | null;
|
|
408
|
+
};
|
|
409
|
+
/**
|
|
410
|
+
* Events the provider could not deliver to the app's webhook, as far back as
|
|
411
|
+
* it keeps them (Stripe: 30 days) — what an outage of the endpoint left
|
|
412
|
+
* behind. Optional: a provider without it has no catch-up.
|
|
413
|
+
*/
|
|
414
|
+
readonly listUndeliveredEvents?: () => Effect.Effect<ReadonlyArray<UndeliveredEvent>, BillingError>;
|
|
301
415
|
}
|
|
302
416
|
|
|
303
417
|
export declare type BillingProviderName = 'stripe' | 'mock';
|
|
@@ -329,6 +443,31 @@ export declare interface BillingServiceOptions {
|
|
|
329
443
|
/** Free-trial length. The provider runs the trial and reports `trialing`. */
|
|
330
444
|
readonly trialDays?: number;
|
|
331
445
|
};
|
|
446
|
+
/**
|
|
447
|
+
* The app's say on whether a tenant may change what it pays right now —
|
|
448
|
+
* e.g. refuse a checkout for a deleted or suspended organization. Called
|
|
449
|
+
* before the provider is; a failure refuses the call. Absent → every
|
|
450
|
+
* authorized caller is admitted.
|
|
451
|
+
*/
|
|
452
|
+
readonly admit?: (tenantId: string, action: BillingAction) => Effect.Effect<void, BillingError>;
|
|
453
|
+
/**
|
|
454
|
+
* Origins a checkout's `successUrl` / `cancelUrl` and a portal's `returnUrl`
|
|
455
|
+
* may point at. The provider redirects the customer there, so an arbitrary
|
|
456
|
+
* URL would make our checkout a redirect to anywhere. Absent → any URL.
|
|
457
|
+
*/
|
|
458
|
+
readonly redirectOrigins?: ReadonlyArray<string>;
|
|
459
|
+
/**
|
|
460
|
+
* Whether the app has closed (deleted) a tenant. The reconcile ends every
|
|
461
|
+
* subscription of a closed tenant still paying — the safety net for a
|
|
462
|
+
* deletion path that did not call `closeAccount`. Answer `true` only on
|
|
463
|
+
* positive evidence (a row marked deleted): an unknown tenant is not proof,
|
|
464
|
+
* and a failing check is treated as "not closed".
|
|
465
|
+
*/
|
|
466
|
+
readonly isTenantClosed?: (tenantId: string) => Effect.Effect<boolean, BillingError>;
|
|
467
|
+
/** The app's per-event side effects, run for every correction a sync or
|
|
468
|
+
* reconcile makes (the webhook runs them itself, and fails the delivery on
|
|
469
|
+
* an error). */
|
|
470
|
+
readonly onEvent?: OnEventMap;
|
|
332
471
|
/** Clock injection for deterministic period + grace windowing in tests. */
|
|
333
472
|
readonly now?: () => Date;
|
|
334
473
|
/**
|
|
@@ -400,6 +539,8 @@ export declare interface BillingServiceShape {
|
|
|
400
539
|
readonly plan: PlanId;
|
|
401
540
|
readonly successUrl: string;
|
|
402
541
|
readonly cancelUrl: string;
|
|
542
|
+
/** Seats to buy up front. Omit for 1. */
|
|
543
|
+
readonly quantity?: number;
|
|
403
544
|
}) => Effect.Effect<{
|
|
404
545
|
url: string;
|
|
405
546
|
}, BillingError>;
|
|
@@ -407,6 +548,48 @@ export declare interface BillingServiceShape {
|
|
|
407
548
|
readonly portalUrl: (tenantId: string, returnUrl: string) => Effect.Effect<{
|
|
408
549
|
url: string;
|
|
409
550
|
}, BillingError>;
|
|
551
|
+
/**
|
|
552
|
+
* The customer's own cancellation: the subscription stays active until the
|
|
553
|
+
* paid period ends, then ends. The provider applies it first; the row
|
|
554
|
+
* mirrors its answer (`cancelAt`). Reversible with `resumeSubscription`.
|
|
555
|
+
*/
|
|
556
|
+
readonly cancelSubscription: (tenantId: string) => Effect.Effect<Subscription, BillingError>;
|
|
557
|
+
/** Take back a period-end cancellation before it takes effect. */
|
|
558
|
+
readonly resumeSubscription: (tenantId: string) => Effect.Effect<Subscription, BillingError>;
|
|
559
|
+
/**
|
|
560
|
+
* End every provider subscription of a tenant — for deleting an
|
|
561
|
+
* organization or account. Pending usage is flushed first; each subscription
|
|
562
|
+
* then ends once that usage has settled on the meter (minutes, see
|
|
563
|
+
* `CancelMode` 'close') with a final invoice for it and a proration credit.
|
|
564
|
+
* The rows carry the end as `cancelAt` until the provider's deletion event
|
|
565
|
+
* marks them canceled. The provider customer and its invoices are KEPT: they
|
|
566
|
+
* are the accounting record. Fails without ending anything when the flush
|
|
567
|
+
* fails, so no usage is lost; the caller retries.
|
|
568
|
+
*/
|
|
569
|
+
readonly closeAccount: (tenantId: string) => Effect.Effect<CloseAccountResult, BillingError>;
|
|
570
|
+
/**
|
|
571
|
+
* Correct every local row from the provider: what the provider lists wins,
|
|
572
|
+
* a local live row the provider no longer lists is re-read directly, missing
|
|
573
|
+
* metered items are added back. Run it on a schedule — it heals whatever a
|
|
574
|
+
* missed or failed webhook left behind.
|
|
575
|
+
*/
|
|
576
|
+
readonly reconcileFromProvider: () => Effect.Effect<ProviderReconcileResult, BillingError>;
|
|
577
|
+
/**
|
|
578
|
+
* Bring one provider customer's rows to what the provider says now, and
|
|
579
|
+
* return its current state as events (all of it, not just changes). The
|
|
580
|
+
* webhook uses it for the customer an event names. Does not run `onEvent`.
|
|
581
|
+
*/
|
|
582
|
+
readonly syncCustomer: (providerCustomerId: string, hintTenantId?: string) => Effect.Effect<{
|
|
583
|
+
readonly tenantId: string | null;
|
|
584
|
+
readonly events: ReadonlyArray<BillingEvent>;
|
|
585
|
+
}, BillingError>;
|
|
586
|
+
/**
|
|
587
|
+
* The checkpoint: sync a tenant from the provider, run `onEvent` for what it
|
|
588
|
+
* found, and answer with its current subscription. Call it where a stale row
|
|
589
|
+
* would matter — the return from checkout or the portal, a billing page
|
|
590
|
+
* load. Every call that changes what a tenant pays already runs it first.
|
|
591
|
+
*/
|
|
592
|
+
readonly syncTenant: (tenantId: string) => Effect.Effect<Subscription | null, BillingError>;
|
|
410
593
|
/** Apply a normalized provider event to the DB rows (idempotent upsert). */
|
|
411
594
|
readonly applyEvent: (event: BillingEvent) => Effect.Effect<void, BillingError>;
|
|
412
595
|
/**
|
|
@@ -471,13 +654,11 @@ declare interface BillingWebhookOptions {
|
|
|
471
654
|
* `constructEvent`) uses. Null → the mount answers 503: an unverifiable
|
|
472
655
|
* billing webhook must not reach subscription state. */
|
|
473
656
|
readonly webhookSecret?: string | null;
|
|
657
|
+
/** Called when an event arrives long after it was created — deliveries were
|
|
658
|
+
* failing; the plugin catches up on what the endpoint missed. */
|
|
659
|
+
readonly onLateDelivery?: () => void;
|
|
474
660
|
}
|
|
475
661
|
|
|
476
|
-
/**
|
|
477
|
-
* Build the BillingService implementation. The interceptor + the plugin's
|
|
478
|
-
* rpc routes share this single instance (so `enforce` and the rpc routes
|
|
479
|
-
* see the same swapped-in stores).
|
|
480
|
-
*/
|
|
481
662
|
export declare const buildBillingService: (options: BillingServiceOptions) => {
|
|
482
663
|
readonly service: BillingServiceShape;
|
|
483
664
|
readonly holder: StoreHolder;
|
|
@@ -498,10 +679,49 @@ export declare const buildEnforceInterceptor: (enforce: EnforceMap, service: Bil
|
|
|
498
679
|
/** Build the registry from `billingPlugin({ plans })`. */
|
|
499
680
|
export declare const buildPlanRegistry: (plans: Readonly<Record<PlanId, PlanConfig>>) => PlanRegistry;
|
|
500
681
|
|
|
682
|
+
/** How a subscription ends. */
|
|
683
|
+
export declare type CancelMode =
|
|
684
|
+
/** Stays active until the paid period ends, then ends — the customer's own
|
|
685
|
+
* cancellation. Reversible with `resumeSubscription` until then. */
|
|
686
|
+
'periodEnd'
|
|
687
|
+
/**
|
|
688
|
+
* Closing an account: ends as soon as the last usage has settled on the
|
|
689
|
+
* meter — a few minutes, not a period — with a final invoice for that usage
|
|
690
|
+
* and a proration credit for the unused time.
|
|
691
|
+
*
|
|
692
|
+
* Not "now", on purpose: meter events aggregate asynchronously, and a
|
|
693
|
+
* subscription ended the moment its last usage was sent is invoiced for none
|
|
694
|
+
* of it (measured: the meter showed 250, the final invoice 0, and a finalized
|
|
695
|
+
* invoice never picks the usage up later).
|
|
696
|
+
*/
|
|
697
|
+
| 'close';
|
|
698
|
+
|
|
699
|
+
/** One finding. `apply` is the request that resolves it; absent → a human. */
|
|
700
|
+
export declare type CatalogFinding = {
|
|
701
|
+
readonly kind: 'ok';
|
|
702
|
+
readonly object: string;
|
|
703
|
+
} | {
|
|
704
|
+
readonly kind: 'create' | 'update' | 'adopt';
|
|
705
|
+
readonly object: string;
|
|
706
|
+
readonly detail: string;
|
|
707
|
+
readonly apply: StripeRequest;
|
|
708
|
+
} | {
|
|
709
|
+
readonly kind: 'drift';
|
|
710
|
+
readonly object: string;
|
|
711
|
+
readonly detail: string;
|
|
712
|
+
readonly apply?: StripeRequest;
|
|
713
|
+
} | {
|
|
714
|
+
readonly kind: 'manual';
|
|
715
|
+
readonly object: string;
|
|
716
|
+
readonly detail: string;
|
|
717
|
+
};
|
|
718
|
+
|
|
501
719
|
export declare interface CheckoutInput {
|
|
502
720
|
readonly tenantId: string;
|
|
503
721
|
/** Provider price id to subscribe to. */
|
|
504
722
|
readonly priceId: string;
|
|
723
|
+
/** Usage-based prices added next to `priceId` (no quantity). */
|
|
724
|
+
readonly meteredPriceIds?: ReadonlyArray<string>;
|
|
505
725
|
readonly successUrl: string;
|
|
506
726
|
readonly cancelUrl: string;
|
|
507
727
|
/** Optional existing provider customer id to attach the checkout to. */
|
|
@@ -527,6 +747,13 @@ export declare interface CheckoutInput {
|
|
|
527
747
|
*/
|
|
528
748
|
export declare const claimFlushWindow: (store: DataStore, windowKey: string, replicaId: string, nowMs: number) => Promise<boolean>;
|
|
529
749
|
|
|
750
|
+
/** What `closeAccount` did at the provider. */
|
|
751
|
+
export declare interface CloseAccountResult {
|
|
752
|
+
readonly tenantId: string;
|
|
753
|
+
/** Provider subscriptions ended (immediately, with a final invoice). */
|
|
754
|
+
readonly canceled: ReadonlyArray<string>;
|
|
755
|
+
}
|
|
756
|
+
|
|
530
757
|
declare interface CustomerRecord {
|
|
531
758
|
readonly tenantId: string;
|
|
532
759
|
readonly provider: string;
|
|
@@ -536,6 +763,12 @@ declare interface CustomerRecord {
|
|
|
536
763
|
export declare interface CustomerStore {
|
|
537
764
|
readonly upsert: (record: CustomerRecord) => Effect.Effect<void, BillingError>;
|
|
538
765
|
readonly getByTenant: (tenantId: string) => Effect.Effect<CustomerRecord | null, BillingError>;
|
|
766
|
+
readonly getByProviderCustomerId: (providerCustomerId: string) => Effect.Effect<CustomerRecord | null, BillingError>;
|
|
767
|
+
/** Every linked customer — the provider reconcile's per-customer pass. */
|
|
768
|
+
readonly list: () => Effect.Effect<ReadonlyArray<CustomerRecord>, BillingError>;
|
|
769
|
+
/** Drop the link to a provider customer that no longer exists. Returns the
|
|
770
|
+
* tenant it belonged to, or null when it was not linked. */
|
|
771
|
+
readonly removeByProviderCustomerId: (providerCustomerId: string) => Effect.Effect<string | null, BillingError>;
|
|
539
772
|
}
|
|
540
773
|
|
|
541
774
|
export declare const dataStoreDunningNoticeStore: (store: DataStore) => DunningNoticeStore;
|
|
@@ -576,6 +809,50 @@ export declare const defaultDunningEmail: (notice: {
|
|
|
576
809
|
readonly text: string;
|
|
577
810
|
};
|
|
578
811
|
|
|
812
|
+
/** A meter: found by event name (Stripe keeps it unique per account). Only the
|
|
813
|
+
* display name is editable; anything else is a human's call. */
|
|
814
|
+
export declare const diffMeter: (spec: StripeCatalogMeter, meters: ReadonlyArray<StripeMeter>) => {
|
|
815
|
+
finding: CatalogFinding;
|
|
816
|
+
id: string | null;
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
/** The portal configuration: found by `metadata.voltro_key`, else adopted by
|
|
820
|
+
* its name; must be the account default, since sessions name none. */
|
|
821
|
+
export declare const diffPortal: (portal: StripeCatalog["portal"], env: StripeCatalogEnvironment, plans: ReadonlyArray<{
|
|
822
|
+
readonly productId: string;
|
|
823
|
+
readonly priceId: string;
|
|
824
|
+
}> | null, configs: ReadonlyArray<StripePortalConfiguration>) => ReadonlyArray<CatalogFinding>;
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* A price: found by `lookup_key`. A price's amount, currency, interval, meter
|
|
828
|
+
* and tax behaviour cannot be edited at Stripe — a mismatch means a NEW price
|
|
829
|
+
* that takes the key over, which is a pricing change and runs only when
|
|
830
|
+
* `allowPriceChange` says so. Existing subscriptions stay on their old price
|
|
831
|
+
* (Stripe does not move them); that is the customer-facing half of the
|
|
832
|
+
* decision, and why it is never automatic.
|
|
833
|
+
*/
|
|
834
|
+
export declare const diffPrice: (spec: StripeCatalogPrice, productId: string | null, meterId: string | null, byLookup: StripePrice | null, productPrices: ReadonlyArray<StripePrice>, allowPriceChange: boolean) => {
|
|
835
|
+
finding: CatalogFinding;
|
|
836
|
+
id: string | null;
|
|
837
|
+
};
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* A product: found by `metadata.voltro_key`; failing that, ADOPTED from an
|
|
841
|
+
* active product carrying the same identifying metadata (one created before
|
|
842
|
+
* the catalog existed) — only when exactly one qualifies, never by guessing.
|
|
843
|
+
*/
|
|
844
|
+
export declare const diffProduct: (spec: StripeCatalogProduct, products: ReadonlyArray<StripeProduct>) => {
|
|
845
|
+
finding: CatalogFinding;
|
|
846
|
+
id: string | null;
|
|
847
|
+
};
|
|
848
|
+
|
|
849
|
+
export declare const diffTaxSettings: (tax: StripeCatalog["tax"], settings: StripeTaxSettings) => CatalogFinding;
|
|
850
|
+
|
|
851
|
+
/** The webhook endpoint, where the environment has one: every event the plugin
|
|
852
|
+
* maps, on the SDK's API version. Its signing secret exists only in the
|
|
853
|
+
* response to its creation. */
|
|
854
|
+
export declare const diffWebhook: (env: StripeCatalogEnvironment, endpoints: ReadonlyArray<StripeWebhookEndpoint>) => CatalogFinding;
|
|
855
|
+
|
|
579
856
|
/** The steps whose `afterHours` have elapsed. Ledger claims — not this
|
|
580
857
|
* function — decide which of them actually SEND. */
|
|
581
858
|
export declare const dueSteps: (steps: ReadonlyArray<DunningStep>, pastDueSince: Date, now: Date) => ReadonlyArray<DunningStep>;
|
|
@@ -776,6 +1053,13 @@ export declare interface EntitlementStatus {
|
|
|
776
1053
|
readonly lockout: LockoutMode;
|
|
777
1054
|
}
|
|
778
1055
|
|
|
1056
|
+
/**
|
|
1057
|
+
* Why `path` must not receive secrets, or null when it may: a file inside a git
|
|
1058
|
+
* work tree that git would track (not ignored) is one `git add .` from being
|
|
1059
|
+
* committed with a live account's price ids and webhook secret in it.
|
|
1060
|
+
*/
|
|
1061
|
+
export declare const envOutRefusal: (path: string, git?: (args: ReadonlyArray<string>, cwd: string) => boolean, realpath?: (p: string) => string) => string | null;
|
|
1062
|
+
|
|
779
1063
|
/** The episode a `pastDueSince` identifies. Recovery clears the column, so the
|
|
780
1064
|
* next failure necessarily produces a different key. */
|
|
781
1065
|
export declare const episodeKeyOf: (pastDueSince: Date) => string;
|
|
@@ -805,6 +1089,9 @@ export declare const evaluateEntitlement: (entitlement: string, limit: number, u
|
|
|
805
1089
|
* flushing within the same interval land on the same key → one wins. */
|
|
806
1090
|
export declare const flushWindowKey: (nowMs: number, intervalMs: number) => string;
|
|
807
1091
|
|
|
1092
|
+
/** Stripe's form encoding: `a[b][0][c]=v`. `null` unsets a field (empty value). */
|
|
1093
|
+
export declare const formEncode: (params: Readonly<Record<string, unknown>>) => string;
|
|
1094
|
+
|
|
808
1095
|
/** When the grace for an episode runs out. */
|
|
809
1096
|
export declare const graceEndOf: (pastDueSince: Date, graceHours: number) => Date;
|
|
810
1097
|
|
|
@@ -843,12 +1130,9 @@ export declare interface InvoiceStore {
|
|
|
843
1130
|
readonly listByTenant: (tenantId: string) => Effect.Effect<ReadonlyArray<InvoiceRecord_2>, BillingError>;
|
|
844
1131
|
}
|
|
845
1132
|
|
|
846
|
-
/**
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
* that ordering is what makes a hand-built (undated) payload usable in tests
|
|
850
|
-
* without letting it stomp production ordering.
|
|
851
|
-
*/
|
|
1133
|
+
/** Is this a real deployment boot? Only an explicitly dev-like NODE_ENV is not. */
|
|
1134
|
+
export declare const isProductionBoot: (nodeEnv: string | undefined) => boolean;
|
|
1135
|
+
|
|
852
1136
|
export declare const isStaleStatusEvent: (existing: Date | null, incoming: Date | null) => boolean;
|
|
853
1137
|
|
|
854
1138
|
/**
|
|
@@ -866,6 +1150,9 @@ export declare const memoryDunningNoticeStore: () => DunningNoticeStore;
|
|
|
866
1150
|
|
|
867
1151
|
export declare const memoryStores: () => BillingStores;
|
|
868
1152
|
|
|
1153
|
+
/** Replace (or add) `NAME=value` lines; every other line is kept as it was. */
|
|
1154
|
+
export declare const mergeEnvFile: (text: string, values: Readonly<Record<string, string>>) => string;
|
|
1155
|
+
|
|
869
1156
|
/** `meterKey → source`. The meterKey is the entitlement/usage key reported to
|
|
870
1157
|
* the provider (`billing.reportUsage(tenant, meterKey, qty)`). */
|
|
871
1158
|
declare type MeteringConfig = Readonly<Record<string, MeterSource>>;
|
|
@@ -917,6 +1204,32 @@ export declare interface MockProvider extends BillingProvider {
|
|
|
917
1204
|
readonly setCustomerEmail: (providerCustomerId: string, email: string | null) => void;
|
|
918
1205
|
/** Every `fetchSubscription` id asked for — proves the reconcile happened. */
|
|
919
1206
|
readonly fetched: ReadonlyArray<string>;
|
|
1207
|
+
/** Put a subscription on the provider's LIST (and its direct read) — what
|
|
1208
|
+
* `listSubscriptions` returns and a reconcile corrects local rows to. */
|
|
1209
|
+
readonly listSubscription: (snapshot: ProviderSubscriptionSnapshot) => void;
|
|
1210
|
+
/** Recorded cancellations, in order. */
|
|
1211
|
+
readonly cancels: ReadonlyArray<{
|
|
1212
|
+
readonly providerSubscriptionId: string;
|
|
1213
|
+
readonly mode: CancelMode;
|
|
1214
|
+
}>;
|
|
1215
|
+
/** Recorded resumptions. */
|
|
1216
|
+
readonly resumes: ReadonlyArray<string>;
|
|
1217
|
+
/** Recorded `addSubscriptionPrices` calls. */
|
|
1218
|
+
readonly priceAdds: ReadonlyArray<{
|
|
1219
|
+
readonly providerSubscriptionId: string;
|
|
1220
|
+
readonly priceIds: ReadonlyArray<string>;
|
|
1221
|
+
}>;
|
|
1222
|
+
/** Recorded checkout sessions. */
|
|
1223
|
+
readonly checkouts: ReadonlyArray<CheckoutInput>;
|
|
1224
|
+
/** Customers created by `ensureCustomer`, by tenant. */
|
|
1225
|
+
readonly customersCreated: ReadonlyArray<string>;
|
|
1226
|
+
/** Every `expireOpenCheckouts` call, by customer. */
|
|
1227
|
+
readonly expiredFor: ReadonlyArray<string>;
|
|
1228
|
+
/** Set what `listUndeliveredEvents` reports — events the provider could
|
|
1229
|
+
* not deliver to the app's webhook. */
|
|
1230
|
+
readonly setUndelivered: (events: ReadonlyArray<UndeliveredEvent>) => void;
|
|
1231
|
+
/** How often `listUndeliveredEvents` was asked — proves a catch-up ran. */
|
|
1232
|
+
readonly undeliveredReads: () => number;
|
|
920
1233
|
}
|
|
921
1234
|
|
|
922
1235
|
export declare const mockProvider: (options?: {
|
|
@@ -957,6 +1270,19 @@ export declare const normalizeStripeEvent: (event: default_2.Event, resolvePrice
|
|
|
957
1270
|
/** Per-event side effect, run AFTER the row is updated. */
|
|
958
1271
|
export declare type OnEventMap = Readonly<Record<string, (event: BillingEvent) => Effect.Effect<void, unknown>>>;
|
|
959
1272
|
|
|
1273
|
+
/** Findings the account still differs by (after `apply`: what it could not fix). */
|
|
1274
|
+
export declare const openFindings: (findings: ProvisionResult["findings"]) => (CatalogFinding & {
|
|
1275
|
+
readonly applied?: boolean;
|
|
1276
|
+
})[];
|
|
1277
|
+
|
|
1278
|
+
export declare interface ParsedProvisionArgs extends ProvisionOptions {
|
|
1279
|
+
readonly confirmLive: boolean;
|
|
1280
|
+
readonly strict: boolean;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
/** Parse the command line; a string is the refusal. */
|
|
1284
|
+
export declare const parseProvisionArgs: (argv: ReadonlyArray<string>) => ParsedProvisionArgs | string;
|
|
1285
|
+
|
|
960
1286
|
/** Calendar-month window key (`'YYYY-MM'`) for metered counters. */
|
|
961
1287
|
export declare const periodKey: (now?: Date) => string;
|
|
962
1288
|
|
|
@@ -964,6 +1290,13 @@ export declare const periodKey: (now?: Date) => string;
|
|
|
964
1290
|
export declare interface PlanConfig {
|
|
965
1291
|
/** Provider price id (Stripe `price_…`). Absent for free/zero-cost plans. */
|
|
966
1292
|
readonly priceId?: string;
|
|
1293
|
+
/**
|
|
1294
|
+
* Usage-based provider prices subscribed ALONGSIDE `priceId` (Stripe prices
|
|
1295
|
+
* with `recurring.meter`). Checkout adds each as a quantity-less line item,
|
|
1296
|
+
* so the subscription carries the metered item from its first invoice —
|
|
1297
|
+
* without it, meter events are recorded at Stripe but never billed.
|
|
1298
|
+
*/
|
|
1299
|
+
readonly meteredPriceIds?: ReadonlyArray<string>;
|
|
967
1300
|
/** Per-entitlement-key limits. Values are `number | 'unlimited'`. */
|
|
968
1301
|
readonly entitlements: Readonly<Record<string, EntitlementLimit>>;
|
|
969
1302
|
/** The plan's per-period price in INTEGER minor units (cents) — the unit
|
|
@@ -994,6 +1327,8 @@ export declare interface PlanRegistry {
|
|
|
994
1327
|
readonly planForPriceId: (priceId: string) => PlanId | null;
|
|
995
1328
|
/** The provider price id for a plan, or null (free plans have none). */
|
|
996
1329
|
readonly priceIdFor: (plan: PlanId) => string | null;
|
|
1330
|
+
/** The usage-based price ids subscribed next to the plan price. */
|
|
1331
|
+
readonly meteredPriceIdsFor: (plan: PlanId) => ReadonlyArray<string>;
|
|
997
1332
|
/** The per-seat unit amount in integer minor units for a plan; `0` for a
|
|
998
1333
|
* free/undeclared plan. Multiplied by seat quantity for the billed amount. */
|
|
999
1334
|
readonly unitAmountFor: (plan: PlanId) => number;
|
|
@@ -1010,6 +1345,51 @@ export declare interface PortalInput {
|
|
|
1010
1345
|
readonly returnUrl: string;
|
|
1011
1346
|
}
|
|
1012
1347
|
|
|
1348
|
+
/** The portal configuration the plugin's sessions use (the account default). */
|
|
1349
|
+
export declare const portalParams: (portal: StripeCatalog["portal"], env: StripeCatalogEnvironment, plans: ReadonlyArray<{
|
|
1350
|
+
readonly productId: string;
|
|
1351
|
+
readonly priceId: string;
|
|
1352
|
+
}>) => Record<string, unknown>;
|
|
1353
|
+
|
|
1354
|
+
/** What `reconcileFromProvider` found and corrected. */
|
|
1355
|
+
export declare interface ProviderReconcileResult {
|
|
1356
|
+
/** Subscriptions compared (provider-listed plus local rows it did not list). */
|
|
1357
|
+
readonly checked: number;
|
|
1358
|
+
/** Tenants whose local rows changed because the provider said otherwise. */
|
|
1359
|
+
readonly corrected: ReadonlyArray<string>;
|
|
1360
|
+
/** Tenants with more than one live subscription at the provider — billed
|
|
1361
|
+
* twice. Never resolved automatically: refunding is a human decision. */
|
|
1362
|
+
readonly duplicates: ReadonlyArray<string>;
|
|
1363
|
+
/** Provider subscriptions naming no tenant; nothing to attribute them to. */
|
|
1364
|
+
readonly unattributed: number;
|
|
1365
|
+
/** Subscriptions that had a plan's metered price added back. */
|
|
1366
|
+
readonly meteredItemsRestored: number;
|
|
1367
|
+
/** Tenants the app reports closed whose subscriptions were still paying —
|
|
1368
|
+
* ended now (see `isTenantClosed`). */
|
|
1369
|
+
readonly closedTenants: ReadonlyArray<string>;
|
|
1370
|
+
/** Live rows the provider has never heard of that were NOT canceled, because
|
|
1371
|
+
* so many at once means the wrong account or provider (0 normally). */
|
|
1372
|
+
readonly refusedUnknown: number;
|
|
1373
|
+
/** Every correction as the event it stands for, for the app's `onEvent`
|
|
1374
|
+
* projections; the plugin's scheduled reconcile dispatches them. */
|
|
1375
|
+
readonly events: ReadonlyArray<BillingEvent>;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
/**
|
|
1379
|
+
* One subscription as the provider lists it — the input of the provider-side
|
|
1380
|
+
* reconcile. Carries the ids a local row is matched by and the price ids a
|
|
1381
|
+
* plan's metered items are checked against.
|
|
1382
|
+
*/
|
|
1383
|
+
export declare interface ProviderSubscriptionSnapshot extends ProviderSubscriptionState {
|
|
1384
|
+
readonly providerSubscriptionId: string;
|
|
1385
|
+
readonly providerCustomerId: string | null;
|
|
1386
|
+
/** From the subscription's own metadata; null when the provider object was
|
|
1387
|
+
* not created through our checkout and names no tenant. */
|
|
1388
|
+
readonly tenantId: string | null;
|
|
1389
|
+
/** Every price the subscription carries, licensed and metered. */
|
|
1390
|
+
readonly priceIds: ReadonlyArray<string>;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1013
1393
|
/** The provider's CURRENT view of a subscription — read directly, not from an
|
|
1014
1394
|
* event body. `reconcileDunning` asks for this before it will lock anyone
|
|
1015
1395
|
* out. */
|
|
@@ -1023,6 +1403,34 @@ export declare interface ProviderSubscriptionState {
|
|
|
1023
1403
|
readonly cancelAt: Date | null;
|
|
1024
1404
|
}
|
|
1025
1405
|
|
|
1406
|
+
export declare interface ProvisionOptions {
|
|
1407
|
+
readonly env: StripeEnvironment;
|
|
1408
|
+
readonly apply: boolean;
|
|
1409
|
+
readonly allowPriceChange: boolean;
|
|
1410
|
+
/** Where `STRIPE_PRICE_*` ids and a created webhook's secret go; null →
|
|
1411
|
+
* nowhere (and a webhook endpoint is then not created). */
|
|
1412
|
+
readonly envOut: string | null;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
export declare interface ProvisionResult {
|
|
1416
|
+
readonly livemode: boolean;
|
|
1417
|
+
readonly findings: ReadonlyArray<CatalogFinding & {
|
|
1418
|
+
readonly applied?: boolean;
|
|
1419
|
+
}>;
|
|
1420
|
+
/** Price ids by env name, and a created webhook's `STRIPE_WEBHOOK_SECRET`. */
|
|
1421
|
+
readonly envOut: Readonly<Record<string, string>>;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
/**
|
|
1425
|
+
* Read the account, diff it against the catalog, and (with `apply`) fix what
|
|
1426
|
+
* can be fixed — in dependency order: meters, products, prices, tax, portal,
|
|
1427
|
+
* webhook.
|
|
1428
|
+
*/
|
|
1429
|
+
export declare const provisionStripeCatalog: (rest: StripeRest, catalog: StripeCatalog, options: ProvisionOptions) => Promise<ProvisionResult>;
|
|
1430
|
+
|
|
1431
|
+
/** Stripe echoes a masked key in auth errors, and the mask ends in real characters. */
|
|
1432
|
+
export declare const redactStripeKeys: (text: string) => string;
|
|
1433
|
+
|
|
1026
1434
|
/**
|
|
1027
1435
|
* In-handler DUNNING guard: refuse when the tenant's grace period has run out.
|
|
1028
1436
|
*
|
|
@@ -1097,14 +1505,36 @@ declare interface ResolveProviderInput {
|
|
|
1097
1505
|
readonly env: NodeJS.ProcessEnv;
|
|
1098
1506
|
/** Price id → plan id, from the plugin's plan registry. */
|
|
1099
1507
|
readonly planForPriceId?: (priceId: string) => string | null;
|
|
1100
|
-
/** Where the mock-by-absence warning goes. Optional: a library caller
|
|
1101
|
-
* resolving a provider outside a boot has nowhere to put it, and a missing
|
|
1102
|
-
* logger must not be a reason to fail. */
|
|
1103
|
-
readonly log?: {
|
|
1104
|
-
readonly warn: (message: string) => void;
|
|
1105
|
-
};
|
|
1106
1508
|
}
|
|
1107
1509
|
|
|
1510
|
+
/** The key for `env`, checked against its mode. */
|
|
1511
|
+
export declare const resolveProvisionKey: (env: StripeEnvironment, vars: NodeJS.ProcessEnv, dotenvKey: string | undefined) => {
|
|
1512
|
+
key: string;
|
|
1513
|
+
} | {
|
|
1514
|
+
refusal: string;
|
|
1515
|
+
};
|
|
1516
|
+
|
|
1517
|
+
/**
|
|
1518
|
+
* The provisioning command. Exit code: 0 when the account matches the catalog
|
|
1519
|
+
* (after `--apply`: when everything it could fix is fixed); 1 when something
|
|
1520
|
+
* still differs; 2 on a refused invocation. `manual` items fail the run only
|
|
1521
|
+
* with `--strict` — the launch-readiness check.
|
|
1522
|
+
*/
|
|
1523
|
+
export declare const runStripeProvision: (catalog: StripeCatalog, argv: ReadonlyArray<string>, options?: {
|
|
1524
|
+
readonly dotenv?: string;
|
|
1525
|
+
readonly rest?: (key: string) => StripeRest;
|
|
1526
|
+
readonly vars?: NodeJS.ProcessEnv;
|
|
1527
|
+
}) => Promise<number>;
|
|
1528
|
+
|
|
1529
|
+
/** What only a human can settle before an environment sells. */
|
|
1530
|
+
export declare const sellingReadiness: (input: {
|
|
1531
|
+
readonly catalog: StripeCatalog;
|
|
1532
|
+
readonly env: StripeCatalogEnvironment;
|
|
1533
|
+
readonly chargesEnabled: boolean | null;
|
|
1534
|
+
readonly accountTaxIds: number | null;
|
|
1535
|
+
readonly activeRegistrations: ReadonlyArray<string>;
|
|
1536
|
+
}) => ReadonlyArray<CatalogFinding>;
|
|
1537
|
+
|
|
1108
1538
|
/**
|
|
1109
1539
|
* A mutable holder so `bindDataStore` can swap the stores AFTER the service
|
|
1110
1540
|
* layer is built. The service reads `holder.stores` on every call rather
|
|
@@ -1115,6 +1545,157 @@ declare interface StoreHolder {
|
|
|
1115
1545
|
stores: BillingStores;
|
|
1116
1546
|
}
|
|
1117
1547
|
|
|
1548
|
+
/** The API version every provisioning request and webhook endpoint uses — the
|
|
1549
|
+
* one the SDK the plugin bills with pins. */
|
|
1550
|
+
export declare const STRIPE_API_VERSION: string;
|
|
1551
|
+
|
|
1552
|
+
/** The Dashboard steps no API can check, which every Stripe billing app needs. */
|
|
1553
|
+
export declare const STRIPE_DASHBOARD_REMINDERS: ReadonlyArray<string>;
|
|
1554
|
+
|
|
1555
|
+
/**
|
|
1556
|
+
* Every Stripe event type `normalizeStripeEvent` maps — the list a webhook
|
|
1557
|
+
* endpoint subscribes to. Subscribing to fewer loses state changes; a test
|
|
1558
|
+
* holds this list to the switch below.
|
|
1559
|
+
*/
|
|
1560
|
+
export declare const STRIPE_WEBHOOK_EVENTS: ReadonlyArray<string>;
|
|
1561
|
+
|
|
1562
|
+
export declare interface StripeCatalog {
|
|
1563
|
+
readonly products: ReadonlyArray<StripeCatalogProduct>;
|
|
1564
|
+
readonly meters: ReadonlyArray<StripeCatalogMeter>;
|
|
1565
|
+
readonly tax: {
|
|
1566
|
+
/** The business location Stripe Tax computes from. */
|
|
1567
|
+
readonly headOffice: StripeCatalogAddress;
|
|
1568
|
+
readonly defaultTaxCode: string;
|
|
1569
|
+
readonly defaultTaxBehavior: 'exclusive' | 'inclusive';
|
|
1570
|
+
/** Countries an active registration must exist for before tax is collected. */
|
|
1571
|
+
readonly registrationCountries: ReadonlyArray<string>;
|
|
1572
|
+
};
|
|
1573
|
+
readonly portal: {
|
|
1574
|
+
readonly key: string;
|
|
1575
|
+
readonly name: string;
|
|
1576
|
+
readonly headline: string;
|
|
1577
|
+
readonly privacyPolicyUrl: string;
|
|
1578
|
+
readonly termsOfServiceUrl: string;
|
|
1579
|
+
};
|
|
1580
|
+
readonly environments: Readonly<Record<StripeEnvironment, StripeCatalogEnvironment>>;
|
|
1581
|
+
/** Dashboard-only steps, listed as reminders where selling readiness is required. */
|
|
1582
|
+
readonly dashboardReminders?: ReadonlyArray<string>;
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
export declare interface StripeCatalogAddress {
|
|
1586
|
+
readonly line1: string;
|
|
1587
|
+
readonly line2?: string;
|
|
1588
|
+
readonly postal_code: string;
|
|
1589
|
+
readonly city: string;
|
|
1590
|
+
readonly state?: string;
|
|
1591
|
+
readonly country: string;
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
export declare interface StripeCatalogEnvironment {
|
|
1595
|
+
/** Where the customer portal returns to. */
|
|
1596
|
+
readonly portalReturnUrl: string;
|
|
1597
|
+
/** The app's `POST /billing/webhook` URL, or null where `stripe listen`
|
|
1598
|
+
* forwards instead (local development against a sandbox). */
|
|
1599
|
+
readonly webhookUrl: string | null;
|
|
1600
|
+
/** Whether account-level steps (activation, tax ids, Dashboard-only
|
|
1601
|
+
* settings) must be done before this environment sells. */
|
|
1602
|
+
readonly requireSellingReadiness: boolean;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
export declare interface StripeCatalogMeter {
|
|
1606
|
+
readonly eventName: string;
|
|
1607
|
+
readonly displayName: string;
|
|
1608
|
+
readonly formula: 'sum' | 'count' | 'last';
|
|
1609
|
+
/** Payload key naming the customer — `stripe_customer_id` for this plugin. */
|
|
1610
|
+
readonly customerPayloadKey: string;
|
|
1611
|
+
readonly valuePayloadKey: string;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
export declare interface StripeCatalogPrice {
|
|
1615
|
+
readonly lookupKey: string;
|
|
1616
|
+
/** The env var the app reads this price's id from (`--env-out` writes it). */
|
|
1617
|
+
readonly envName: string;
|
|
1618
|
+
readonly currency: string;
|
|
1619
|
+
readonly unitAmount: number;
|
|
1620
|
+
readonly interval: 'day' | 'week' | 'month' | 'year';
|
|
1621
|
+
readonly usageType: 'licensed' | 'metered';
|
|
1622
|
+
/** For a metered price: the event name of the catalog meter it bills. */
|
|
1623
|
+
readonly meter?: string;
|
|
1624
|
+
readonly taxBehavior: 'exclusive' | 'inclusive';
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
export declare interface StripeCatalogProduct {
|
|
1628
|
+
/** Stable identity, stored as `metadata.voltro_key`. */
|
|
1629
|
+
readonly key: string;
|
|
1630
|
+
readonly name: string;
|
|
1631
|
+
readonly description: string;
|
|
1632
|
+
readonly unitLabel: string;
|
|
1633
|
+
readonly taxCode: string;
|
|
1634
|
+
/** Extra metadata beside `voltro_key` — also what a product created before
|
|
1635
|
+
* the catalog is ADOPTED by (exactly one active product must carry it). */
|
|
1636
|
+
readonly metadata: Readonly<Record<string, string>>;
|
|
1637
|
+
readonly price: StripeCatalogPrice;
|
|
1638
|
+
/** Whether customers may switch to this product in the portal. */
|
|
1639
|
+
readonly inPortal: boolean;
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
export declare type StripeEnvironment = 'sandbox' | 'live';
|
|
1643
|
+
|
|
1644
|
+
declare interface StripeMeter {
|
|
1645
|
+
readonly id: string;
|
|
1646
|
+
readonly status: string;
|
|
1647
|
+
readonly event_name: string;
|
|
1648
|
+
readonly display_name: string;
|
|
1649
|
+
readonly default_aggregation: {
|
|
1650
|
+
readonly formula: string;
|
|
1651
|
+
};
|
|
1652
|
+
readonly customer_mapping: {
|
|
1653
|
+
readonly type: string;
|
|
1654
|
+
readonly event_payload_key: string;
|
|
1655
|
+
};
|
|
1656
|
+
readonly value_settings: {
|
|
1657
|
+
readonly event_payload_key: string;
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
declare interface StripePortalConfiguration {
|
|
1662
|
+
readonly id: string;
|
|
1663
|
+
readonly active: boolean;
|
|
1664
|
+
readonly is_default: boolean;
|
|
1665
|
+
readonly name: string | null;
|
|
1666
|
+
readonly metadata: Readonly<Record<string, string>>;
|
|
1667
|
+
readonly default_return_url: string | null;
|
|
1668
|
+
readonly business_profile: Readonly<Record<string, unknown>>;
|
|
1669
|
+
readonly features: Readonly<Record<string, unknown>>;
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
declare interface StripePrice {
|
|
1673
|
+
readonly id: string;
|
|
1674
|
+
readonly active: boolean;
|
|
1675
|
+
readonly product: string;
|
|
1676
|
+
readonly currency: string;
|
|
1677
|
+
readonly unit_amount: number | null;
|
|
1678
|
+
readonly lookup_key: string | null;
|
|
1679
|
+
readonly tax_behavior: string | null;
|
|
1680
|
+
readonly recurring: {
|
|
1681
|
+
readonly interval: string;
|
|
1682
|
+
readonly usage_type: string;
|
|
1683
|
+
readonly meter: string | null;
|
|
1684
|
+
} | null;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
declare interface StripeProduct {
|
|
1688
|
+
readonly id: string;
|
|
1689
|
+
readonly active: boolean;
|
|
1690
|
+
readonly name: string;
|
|
1691
|
+
readonly description: string | null;
|
|
1692
|
+
readonly unit_label: string | null;
|
|
1693
|
+
readonly tax_code: string | null | {
|
|
1694
|
+
readonly id: string;
|
|
1695
|
+
};
|
|
1696
|
+
readonly metadata: Readonly<Record<string, string>>;
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1118
1699
|
export declare const stripeProvider: (options: StripeProviderOptions) => BillingProvider;
|
|
1119
1700
|
|
|
1120
1701
|
export declare interface StripeProviderOptions {
|
|
@@ -1131,10 +1712,57 @@ export declare interface StripeProviderOptions {
|
|
|
1131
1712
|
* on `free` while paying. The plugin passes its plan registry here.
|
|
1132
1713
|
*/
|
|
1133
1714
|
readonly planForPriceId?: (priceId: string) => string | null;
|
|
1715
|
+
/**
|
|
1716
|
+
* How long a closed account's subscription runs on so its last metered usage
|
|
1717
|
+
* settles before the final invoice (seconds, default 600). Meter events
|
|
1718
|
+
* aggregate asynchronously; ending the subscription sooner invoices usage the
|
|
1719
|
+
* meter has not counted yet as zero.
|
|
1720
|
+
*/
|
|
1721
|
+
readonly closeSettleSeconds?: number;
|
|
1134
1722
|
/** Injected in tests. */
|
|
1135
1723
|
readonly client?: default_2;
|
|
1136
1724
|
}
|
|
1137
1725
|
|
|
1726
|
+
declare interface StripeRequest {
|
|
1727
|
+
readonly method: 'POST';
|
|
1728
|
+
readonly path: string;
|
|
1729
|
+
readonly params: Readonly<Record<string, unknown>>;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
export declare interface StripeRest {
|
|
1733
|
+
readonly get: <T>(path: string, query?: Readonly<Record<string, unknown>>) => Promise<T>;
|
|
1734
|
+
readonly post: <T>(path: string, params: Readonly<Record<string, unknown>>) => Promise<T>;
|
|
1735
|
+
/** Every page of a list endpoint. */
|
|
1736
|
+
readonly list: <T>(path: string, query?: Readonly<Record<string, unknown>>) => Promise<ReadonlyArray<T>>;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
export declare const stripeRest: (apiKey: string, fetchImpl?: typeof fetch) => StripeRest;
|
|
1740
|
+
|
|
1741
|
+
export declare class StripeRestError extends Error {
|
|
1742
|
+
readonly status: number;
|
|
1743
|
+
constructor(status: number, message: string);
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
declare interface StripeTaxSettings {
|
|
1747
|
+
readonly status: string;
|
|
1748
|
+
readonly livemode: boolean;
|
|
1749
|
+
readonly head_office: {
|
|
1750
|
+
readonly address: Readonly<Record<string, string | null>>;
|
|
1751
|
+
} | null;
|
|
1752
|
+
readonly defaults: {
|
|
1753
|
+
readonly tax_code: string | null;
|
|
1754
|
+
readonly tax_behavior: string | null;
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
declare interface StripeWebhookEndpoint {
|
|
1759
|
+
readonly id: string;
|
|
1760
|
+
readonly url: string;
|
|
1761
|
+
readonly status: string;
|
|
1762
|
+
readonly enabled_events: ReadonlyArray<string>;
|
|
1763
|
+
readonly api_version: string | null;
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1138
1766
|
/** A tenant's subscription row, normalized across providers. */
|
|
1139
1767
|
export declare interface Subscription {
|
|
1140
1768
|
readonly tenantId: string;
|
|
@@ -1250,7 +1878,11 @@ export declare interface SubscriptionStore {
|
|
|
1250
1878
|
* only a provider-confirmed reconcile sets it.
|
|
1251
1879
|
*/
|
|
1252
1880
|
readonly upsert: (sub: Subscription) => Effect.Effect<void, BillingError>;
|
|
1881
|
+
/** The tenant's CURRENT subscription: a live one when there is one, else
|
|
1882
|
+
* the most recently changed. An old canceled row never hides a live one. */
|
|
1253
1883
|
readonly getByTenant: (tenantId: string) => Effect.Effect<Subscription | null, BillingError>;
|
|
1884
|
+
/** Every subscription row of a tenant, newest first. */
|
|
1885
|
+
readonly listByTenant: (tenantId: string) => Effect.Effect<ReadonlyArray<Subscription>, BillingError>;
|
|
1254
1886
|
/** Every subscription in a given status — the dunning sweep's only query. */
|
|
1255
1887
|
readonly listByStatus: (status: SubscriptionStatus) => Effect.Effect<ReadonlyArray<Subscription>, BillingError>;
|
|
1256
1888
|
/** Mark a subscription canceled by provider id (no-op if absent). */
|
|
@@ -1282,6 +1914,13 @@ declare interface SubscriptionUpdateInput {
|
|
|
1282
1914
|
readonly proration?: 'prorate' | 'none' | 'always_invoice';
|
|
1283
1915
|
}
|
|
1284
1916
|
|
|
1917
|
+
/** An event the provider failed to deliver, reduced to what a catch-up needs. */
|
|
1918
|
+
declare interface UndeliveredEvent {
|
|
1919
|
+
readonly id: string;
|
|
1920
|
+
readonly providerCustomerId: string | null;
|
|
1921
|
+
readonly createdAt: Date | null;
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1285
1924
|
export declare interface UsagePush {
|
|
1286
1925
|
readonly tenantId: string;
|
|
1287
1926
|
readonly entitlementKey: string;
|
|
@@ -1289,6 +1928,14 @@ export declare interface UsagePush {
|
|
|
1289
1928
|
readonly quantity: number;
|
|
1290
1929
|
/** End of the period the usage falls in (provider timestamp anchor). */
|
|
1291
1930
|
readonly periodEnd?: Date;
|
|
1931
|
+
/** The provider customer the usage is billed to, when one is linked. */
|
|
1932
|
+
readonly providerCustomerId?: string;
|
|
1933
|
+
/**
|
|
1934
|
+
* Names THIS delta: the same flush replayed carries the same key (so the
|
|
1935
|
+
* provider can dedupe it), the next delta carries a new one. A key per
|
|
1936
|
+
* (tenant, key, period) alone would make every later delta a "duplicate".
|
|
1937
|
+
*/
|
|
1938
|
+
readonly idempotencyKey?: string;
|
|
1292
1939
|
}
|
|
1293
1940
|
|
|
1294
1941
|
declare interface UsageRow {
|