@voltro/plugin-billing 0.81.0 → 0.82.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 +1520 -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/rpc.d.ts
CHANGED
|
@@ -27,6 +27,18 @@ declare const BillingError_base: Schema.TaggedErrorClass<BillingError, "BillingE
|
|
|
27
27
|
* emitted into the generated client group. Kept in lockstep with the exports. */
|
|
28
28
|
export declare const billingRpcClientImports: ReadonlyArray<PluginRpcClientDescriptor>;
|
|
29
29
|
|
|
30
|
+
/** The customer's own cancellation: active until the paid period ends, then
|
|
31
|
+
* ended. `cancelAt` on the answer is when. */
|
|
32
|
+
export declare const cancelSubscriptionDescriptor: ActionProcedureDescriptor<"billing.cancel", Schema.Struct<{}>, Schema.Struct<{
|
|
33
|
+
subscription: Schema.Struct<{
|
|
34
|
+
plan: typeof Schema.String;
|
|
35
|
+
status: typeof Schema.String;
|
|
36
|
+
quantity: typeof Schema.Number;
|
|
37
|
+
currentPeriodEnd: Schema.NullOr<typeof Schema.String>;
|
|
38
|
+
cancelAt: Schema.NullOr<typeof Schema.String>;
|
|
39
|
+
}>;
|
|
40
|
+
}>, typeof BillingError>;
|
|
41
|
+
|
|
30
42
|
export declare const changePlanDescriptor: ActionProcedureDescriptor<"billing.changePlan", Schema.Struct<{
|
|
31
43
|
plan: typeof Schema.String;
|
|
32
44
|
}>, Schema.Struct<{
|
|
@@ -106,6 +118,17 @@ export declare const reportUsageDescriptor: ActionProcedureDescriptor<"billing.r
|
|
|
106
118
|
ok: typeof Schema.Boolean;
|
|
107
119
|
}>, typeof BillingError>;
|
|
108
120
|
|
|
121
|
+
/** Take back a cancellation before the period ends. */
|
|
122
|
+
export declare const resumeSubscriptionDescriptor: ActionProcedureDescriptor<"billing.resume", Schema.Struct<{}>, Schema.Struct<{
|
|
123
|
+
subscription: Schema.Struct<{
|
|
124
|
+
plan: typeof Schema.String;
|
|
125
|
+
status: typeof Schema.String;
|
|
126
|
+
quantity: typeof Schema.Number;
|
|
127
|
+
currentPeriodEnd: Schema.NullOr<typeof Schema.String>;
|
|
128
|
+
cancelAt: Schema.NullOr<typeof Schema.String>;
|
|
129
|
+
}>;
|
|
130
|
+
}>, typeof BillingError>;
|
|
131
|
+
|
|
109
132
|
export declare const startCheckoutDescriptor: ActionProcedureDescriptor<"billing.startCheckout", Schema.Struct<{
|
|
110
133
|
plan: typeof Schema.String;
|
|
111
134
|
successUrl: typeof Schema.String;
|
|
@@ -126,4 +149,17 @@ export declare const subscriptionDescriptor: QueryProcedureDescriptor<"billing.s
|
|
|
126
149
|
}>>;
|
|
127
150
|
}>, typeof BillingError>;
|
|
128
151
|
|
|
152
|
+
/** The checkpoint: sync the caller's tenant from the provider and answer with
|
|
153
|
+
* its current subscription. Call it on the return from checkout or the
|
|
154
|
+
* portal, and when a billing page loads. */
|
|
155
|
+
export declare const syncDescriptor: ActionProcedureDescriptor<"billing.sync", Schema.Struct<{}>, Schema.Struct<{
|
|
156
|
+
subscription: Schema.NullOr<Schema.Struct<{
|
|
157
|
+
plan: typeof Schema.String;
|
|
158
|
+
status: typeof Schema.String;
|
|
159
|
+
quantity: typeof Schema.Number;
|
|
160
|
+
currentPeriodEnd: Schema.NullOr<typeof Schema.String>;
|
|
161
|
+
cancelAt: Schema.NullOr<typeof Schema.String>;
|
|
162
|
+
}>>;
|
|
163
|
+
}>, typeof BillingError>;
|
|
164
|
+
|
|
129
165
|
export { }
|
package/dist/rpc.js
CHANGED
|
@@ -19,19 +19,19 @@ var i = n({
|
|
|
19
19
|
output: t.Struct({ url: t.String }),
|
|
20
20
|
error: e,
|
|
21
21
|
guards: [{ scope: "billing:manage" }]
|
|
22
|
-
}), o =
|
|
22
|
+
}), o = t.Struct({
|
|
23
|
+
plan: t.String,
|
|
24
|
+
status: t.String,
|
|
25
|
+
quantity: t.Number,
|
|
26
|
+
currentPeriodEnd: t.NullOr(t.String),
|
|
27
|
+
cancelAt: t.NullOr(t.String)
|
|
28
|
+
}), s = r({
|
|
23
29
|
name: "billing.subscription",
|
|
24
30
|
input: t.Struct({}),
|
|
25
|
-
output: t.Struct({ subscription: t.NullOr(
|
|
26
|
-
plan: t.String,
|
|
27
|
-
status: t.String,
|
|
28
|
-
quantity: t.Number,
|
|
29
|
-
currentPeriodEnd: t.NullOr(t.String),
|
|
30
|
-
cancelAt: t.NullOr(t.String)
|
|
31
|
-
})) }),
|
|
31
|
+
output: t.Struct({ subscription: t.NullOr(o) }),
|
|
32
32
|
error: e,
|
|
33
33
|
openAccess: "tenant-scoped read of the caller's own tenant's plan/seat state for account UI every member renders; carries no payment credentials"
|
|
34
|
-
}),
|
|
34
|
+
}), c = r({
|
|
35
35
|
name: "billing.entitlementStatus",
|
|
36
36
|
input: t.Struct({}),
|
|
37
37
|
output: t.Struct({
|
|
@@ -46,7 +46,7 @@ var i = n({
|
|
|
46
46
|
}),
|
|
47
47
|
error: e,
|
|
48
48
|
openAccess: "tenant-scoped read of the caller's own tenant's entitlement/grace state — the banner every signed-in member renders on payment failure"
|
|
49
|
-
}),
|
|
49
|
+
}), l = n({
|
|
50
50
|
name: "billing.reportUsage",
|
|
51
51
|
input: t.Struct({
|
|
52
52
|
key: t.String,
|
|
@@ -55,21 +55,21 @@ var i = n({
|
|
|
55
55
|
output: t.Struct({ ok: t.Boolean }),
|
|
56
56
|
error: e,
|
|
57
57
|
guards: [{ scope: "billing:report" }]
|
|
58
|
-
}),
|
|
58
|
+
}), u = t.Struct({
|
|
59
59
|
plan: t.String,
|
|
60
60
|
quantity: t.Number,
|
|
61
61
|
prorationMinor: t.Number,
|
|
62
62
|
currency: t.String
|
|
63
|
-
}),
|
|
63
|
+
}), d = n({
|
|
64
64
|
name: "billing.previewChange",
|
|
65
65
|
input: t.Struct({
|
|
66
66
|
plan: t.optional(t.String),
|
|
67
67
|
quantity: t.optional(t.Number)
|
|
68
68
|
}),
|
|
69
|
-
output:
|
|
69
|
+
output: u,
|
|
70
70
|
error: e,
|
|
71
71
|
guards: [{ scope: "billing:manage" }]
|
|
72
|
-
}),
|
|
72
|
+
}), f = r({
|
|
73
73
|
name: "billing.invoices",
|
|
74
74
|
input: t.Struct({}),
|
|
75
75
|
output: t.Struct({ invoices: t.Array(t.Struct({
|
|
@@ -83,19 +83,37 @@ var i = n({
|
|
|
83
83
|
})) }),
|
|
84
84
|
error: e,
|
|
85
85
|
guards: [{ scope: "billing:manage" }]
|
|
86
|
-
}),
|
|
86
|
+
}), p = n({
|
|
87
87
|
name: "billing.changePlan",
|
|
88
88
|
input: t.Struct({ plan: t.String }),
|
|
89
|
-
output:
|
|
89
|
+
output: u,
|
|
90
90
|
error: e,
|
|
91
91
|
guards: [{ scope: "billing:manage" }]
|
|
92
|
-
}),
|
|
92
|
+
}), m = n({
|
|
93
93
|
name: "billing.changeSeats",
|
|
94
94
|
input: t.Struct({ quantity: t.Number }),
|
|
95
|
-
output:
|
|
95
|
+
output: u,
|
|
96
|
+
error: e,
|
|
97
|
+
guards: [{ scope: "billing:manage" }]
|
|
98
|
+
}), h = n({
|
|
99
|
+
name: "billing.cancel",
|
|
100
|
+
input: t.Struct({}),
|
|
101
|
+
output: t.Struct({ subscription: o }),
|
|
102
|
+
error: e,
|
|
103
|
+
guards: [{ scope: "billing:manage" }]
|
|
104
|
+
}), g = n({
|
|
105
|
+
name: "billing.sync",
|
|
106
|
+
input: t.Struct({}),
|
|
107
|
+
output: t.Struct({ subscription: t.NullOr(o) }),
|
|
108
|
+
error: e,
|
|
109
|
+
guards: [{ scope: "billing:manage" }]
|
|
110
|
+
}), _ = n({
|
|
111
|
+
name: "billing.resume",
|
|
112
|
+
input: t.Struct({}),
|
|
113
|
+
output: t.Struct({ subscription: o }),
|
|
96
114
|
error: e,
|
|
97
115
|
guards: [{ scope: "billing:manage" }]
|
|
98
|
-
}),
|
|
116
|
+
}), v = [
|
|
99
117
|
{
|
|
100
118
|
tag: "billing.startCheckout",
|
|
101
119
|
kind: "action",
|
|
@@ -152,6 +170,30 @@ var i = n({
|
|
|
152
170
|
name: "changeSeatsDescriptor"
|
|
153
171
|
}
|
|
154
172
|
},
|
|
173
|
+
{
|
|
174
|
+
tag: "billing.cancel",
|
|
175
|
+
kind: "action",
|
|
176
|
+
import: {
|
|
177
|
+
module: "@voltro/plugin-billing/rpc",
|
|
178
|
+
name: "cancelSubscriptionDescriptor"
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
tag: "billing.resume",
|
|
183
|
+
kind: "action",
|
|
184
|
+
import: {
|
|
185
|
+
module: "@voltro/plugin-billing/rpc",
|
|
186
|
+
name: "resumeSubscriptionDescriptor"
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
tag: "billing.sync",
|
|
191
|
+
kind: "action",
|
|
192
|
+
import: {
|
|
193
|
+
module: "@voltro/plugin-billing/rpc",
|
|
194
|
+
name: "syncDescriptor"
|
|
195
|
+
}
|
|
196
|
+
},
|
|
155
197
|
{
|
|
156
198
|
tag: "billing.previewChange",
|
|
157
199
|
kind: "action",
|
|
@@ -170,4 +212,4 @@ var i = n({
|
|
|
170
212
|
}
|
|
171
213
|
];
|
|
172
214
|
//#endregion
|
|
173
|
-
export {
|
|
215
|
+
export { v as billingRpcClientImports, h as cancelSubscriptionDescriptor, p as changePlanDescriptor, m as changeSeatsDescriptor, c as entitlementStatusDescriptor, f as invoicesDescriptor, a as portalUrlDescriptor, d as previewChangeDescriptor, l as reportUsageDescriptor, _ as resumeSubscriptionDescriptor, i as startCheckoutDescriptor, s as subscriptionDescriptor, g as syncDescriptor };
|
package/dist/types.d.ts
CHANGED
|
@@ -2,6 +2,14 @@ import { Context } from 'effect';
|
|
|
2
2
|
import { Effect } from 'effect';
|
|
3
3
|
import { Schema } from 'effect';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* What a billing call would do to what the tenant pays — the unit an app's
|
|
7
|
+
* `admit` hook decides on. `checkout` starts paying, `change` moves a plan or
|
|
8
|
+
* seat count, `resume` takes a cancellation back, `portal` opens the provider's
|
|
9
|
+
* self-service page (which can do all of those), `cancel` stops paying.
|
|
10
|
+
*/
|
|
11
|
+
export declare type BillingAction = 'checkout' | 'change' | 'resume' | 'portal' | 'cancel';
|
|
12
|
+
|
|
5
13
|
/**
|
|
6
14
|
* A billing failure. `transient: true` marks failures the service retries
|
|
7
15
|
* (network blip talking to the provider); non-transient failures (a 4xx
|
|
@@ -66,6 +74,14 @@ export declare type BillingEvent = {
|
|
|
66
74
|
readonly tenantId: string;
|
|
67
75
|
readonly providerCustomerId: string;
|
|
68
76
|
readonly occurredAt: Date | null;
|
|
77
|
+
} | {
|
|
78
|
+
/** The provider customer was deleted (e.g. in the provider's dashboard).
|
|
79
|
+
* `tenantId` is null when the customer carried no tenant metadata — the
|
|
80
|
+
* link is then resolved from our own customer table. */
|
|
81
|
+
readonly _tag: 'customerUnlinked';
|
|
82
|
+
readonly tenantId: string | null;
|
|
83
|
+
readonly providerCustomerId: string;
|
|
84
|
+
readonly occurredAt: Date | null;
|
|
69
85
|
};
|
|
70
86
|
|
|
71
87
|
/** The event tag union — handy for `onEvent` keys. */
|
|
@@ -88,6 +104,10 @@ export declare interface BillingProvider {
|
|
|
88
104
|
* When false, `BillingService.reportUsage` records locally but never
|
|
89
105
|
* calls `provider.reportUsage`. */
|
|
90
106
|
readonly supportsMeteredUsage: boolean;
|
|
107
|
+
/** Whether a usage push must name a linked provider customer. Stripe
|
|
108
|
+
* meters map events to a customer by id, so an unlinked tenant's usage
|
|
109
|
+
* stays pending instead of being sent unattributable. */
|
|
110
|
+
readonly usageRequiresCustomer?: boolean;
|
|
91
111
|
readonly createCheckoutSession: (input: CheckoutInput) => Effect.Effect<{
|
|
92
112
|
url: string;
|
|
93
113
|
}, BillingError>;
|
|
@@ -123,6 +143,43 @@ export declare interface BillingProvider {
|
|
|
123
143
|
* provider does not know the id.
|
|
124
144
|
*/
|
|
125
145
|
readonly fetchSubscription: (providerSubscriptionId: string) => Effect.Effect<ProviderSubscriptionState | null, BillingError>;
|
|
146
|
+
/**
|
|
147
|
+
* Every live (not yet ended) subscription at the provider, optionally for
|
|
148
|
+
* one customer. The provider-side half of reconciliation: what it lists is
|
|
149
|
+
* the truth our rows are corrected to.
|
|
150
|
+
*/
|
|
151
|
+
readonly listSubscriptions: (input: {
|
|
152
|
+
readonly providerCustomerId?: string;
|
|
153
|
+
}) => Effect.Effect<ReadonlyArray<ProviderSubscriptionSnapshot>, BillingError>;
|
|
154
|
+
/** End a subscription — at period end (reversible) or immediately with a
|
|
155
|
+
* final invoice. Returns the provider's state afterwards. */
|
|
156
|
+
readonly cancelSubscription: (input: {
|
|
157
|
+
readonly providerSubscriptionId: string;
|
|
158
|
+
readonly mode: CancelMode;
|
|
159
|
+
}) => Effect.Effect<ProviderSubscriptionState, BillingError>;
|
|
160
|
+
/** Undo a period-end cancellation that has not taken effect yet. */
|
|
161
|
+
readonly resumeSubscription: (providerSubscriptionId: string) => Effect.Effect<ProviderSubscriptionState, BillingError>;
|
|
162
|
+
/**
|
|
163
|
+
* The provider customer for a tenant, created when it has none — BEFORE the
|
|
164
|
+
* first checkout, so every checkout names its customer. That is what lets
|
|
165
|
+
* the provider refuse a second subscription for the same customer, and
|
|
166
|
+
* lets open checkouts be found and expired. Must be idempotent per tenant:
|
|
167
|
+
* two concurrent first checkouts get the same customer.
|
|
168
|
+
*/
|
|
169
|
+
readonly ensureCustomer: (input: {
|
|
170
|
+
readonly tenantId: string;
|
|
171
|
+
}) => Effect.Effect<string, BillingError>;
|
|
172
|
+
/** Expire every checkout still open for a customer, so only the newest one
|
|
173
|
+
* can be paid. Returns how many were expired. */
|
|
174
|
+
readonly expireOpenCheckouts: (input: {
|
|
175
|
+
readonly providerCustomerId: string;
|
|
176
|
+
}) => Effect.Effect<number, BillingError>;
|
|
177
|
+
/** Add prices the subscription is missing (metered items), without
|
|
178
|
+
* proration. Prices already present are left alone. */
|
|
179
|
+
readonly addSubscriptionPrices: (input: {
|
|
180
|
+
readonly providerSubscriptionId: string;
|
|
181
|
+
readonly priceIds: ReadonlyArray<string>;
|
|
182
|
+
}) => Effect.Effect<void, BillingError>;
|
|
126
183
|
/**
|
|
127
184
|
* The billing contact the PROVIDER has on file, when it has one. Used as the
|
|
128
185
|
* dunning recipient of last resort — `dunning.resolveRecipient` wins when
|
|
@@ -149,6 +206,23 @@ export declare interface BillingProvider {
|
|
|
149
206
|
* `null` for an event type this provider doesn't model (handler
|
|
150
207
|
* no-ops, still 200). */
|
|
151
208
|
readonly normalizeEvent: (raw: unknown) => Effect.Effect<BillingEvent | null, BillingError>;
|
|
209
|
+
/**
|
|
210
|
+
* Who and when a verified payload is about: the provider customer it names
|
|
211
|
+
* and when the provider created it. The webhook syncs that customer from a
|
|
212
|
+
* fresh read instead of trusting the payload, and an event created long
|
|
213
|
+
* before it arrives means deliveries were failing — the cue to catch up.
|
|
214
|
+
* Optional: without it the webhook applies the normalized event as is.
|
|
215
|
+
*/
|
|
216
|
+
readonly eventContext?: (raw: unknown) => {
|
|
217
|
+
readonly providerCustomerId: string | null;
|
|
218
|
+
readonly createdAt: Date | null;
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* Events the provider could not deliver to the app's webhook, as far back as
|
|
222
|
+
* it keeps them (Stripe: 30 days) — what an outage of the endpoint left
|
|
223
|
+
* behind. Optional: a provider without it has no catch-up.
|
|
224
|
+
*/
|
|
225
|
+
readonly listUndeliveredEvents?: () => Effect.Effect<ReadonlyArray<UndeliveredEvent>, BillingError>;
|
|
152
226
|
}
|
|
153
227
|
|
|
154
228
|
/** The billing service Tag — `const billing = yield* BillingService`. */
|
|
@@ -210,6 +284,8 @@ export declare interface BillingServiceShape {
|
|
|
210
284
|
readonly plan: PlanId;
|
|
211
285
|
readonly successUrl: string;
|
|
212
286
|
readonly cancelUrl: string;
|
|
287
|
+
/** Seats to buy up front. Omit for 1. */
|
|
288
|
+
readonly quantity?: number;
|
|
213
289
|
}) => Effect.Effect<{
|
|
214
290
|
url: string;
|
|
215
291
|
}, BillingError>;
|
|
@@ -217,6 +293,48 @@ export declare interface BillingServiceShape {
|
|
|
217
293
|
readonly portalUrl: (tenantId: string, returnUrl: string) => Effect.Effect<{
|
|
218
294
|
url: string;
|
|
219
295
|
}, BillingError>;
|
|
296
|
+
/**
|
|
297
|
+
* The customer's own cancellation: the subscription stays active until the
|
|
298
|
+
* paid period ends, then ends. The provider applies it first; the row
|
|
299
|
+
* mirrors its answer (`cancelAt`). Reversible with `resumeSubscription`.
|
|
300
|
+
*/
|
|
301
|
+
readonly cancelSubscription: (tenantId: string) => Effect.Effect<Subscription, BillingError>;
|
|
302
|
+
/** Take back a period-end cancellation before it takes effect. */
|
|
303
|
+
readonly resumeSubscription: (tenantId: string) => Effect.Effect<Subscription, BillingError>;
|
|
304
|
+
/**
|
|
305
|
+
* End every provider subscription of a tenant — for deleting an
|
|
306
|
+
* organization or account. Pending usage is flushed first; each subscription
|
|
307
|
+
* then ends once that usage has settled on the meter (minutes, see
|
|
308
|
+
* `CancelMode` 'close') with a final invoice for it and a proration credit.
|
|
309
|
+
* The rows carry the end as `cancelAt` until the provider's deletion event
|
|
310
|
+
* marks them canceled. The provider customer and its invoices are KEPT: they
|
|
311
|
+
* are the accounting record. Fails without ending anything when the flush
|
|
312
|
+
* fails, so no usage is lost; the caller retries.
|
|
313
|
+
*/
|
|
314
|
+
readonly closeAccount: (tenantId: string) => Effect.Effect<CloseAccountResult, BillingError>;
|
|
315
|
+
/**
|
|
316
|
+
* Correct every local row from the provider: what the provider lists wins,
|
|
317
|
+
* a local live row the provider no longer lists is re-read directly, missing
|
|
318
|
+
* metered items are added back. Run it on a schedule — it heals whatever a
|
|
319
|
+
* missed or failed webhook left behind.
|
|
320
|
+
*/
|
|
321
|
+
readonly reconcileFromProvider: () => Effect.Effect<ProviderReconcileResult, BillingError>;
|
|
322
|
+
/**
|
|
323
|
+
* Bring one provider customer's rows to what the provider says now, and
|
|
324
|
+
* return its current state as events (all of it, not just changes). The
|
|
325
|
+
* webhook uses it for the customer an event names. Does not run `onEvent`.
|
|
326
|
+
*/
|
|
327
|
+
readonly syncCustomer: (providerCustomerId: string, hintTenantId?: string) => Effect.Effect<{
|
|
328
|
+
readonly tenantId: string | null;
|
|
329
|
+
readonly events: ReadonlyArray<BillingEvent>;
|
|
330
|
+
}, BillingError>;
|
|
331
|
+
/**
|
|
332
|
+
* The checkpoint: sync a tenant from the provider, run `onEvent` for what it
|
|
333
|
+
* found, and answer with its current subscription. Call it where a stale row
|
|
334
|
+
* would matter — the return from checkout or the portal, a billing page
|
|
335
|
+
* load. Every call that changes what a tenant pays already runs it first.
|
|
336
|
+
*/
|
|
337
|
+
readonly syncTenant: (tenantId: string) => Effect.Effect<Subscription | null, BillingError>;
|
|
220
338
|
/** Apply a normalized provider event to the DB rows (idempotent upsert). */
|
|
221
339
|
readonly applyEvent: (event: BillingEvent) => Effect.Effect<void, BillingError>;
|
|
222
340
|
/**
|
|
@@ -237,10 +355,29 @@ export declare interface BillingServiceShape {
|
|
|
237
355
|
readonly changeSeats: (tenantId: string, quantity: number, changeAt?: Date) => Effect.Effect<SubscriptionChange, BillingError>;
|
|
238
356
|
}
|
|
239
357
|
|
|
358
|
+
/** How a subscription ends. */
|
|
359
|
+
export declare type CancelMode =
|
|
360
|
+
/** Stays active until the paid period ends, then ends — the customer's own
|
|
361
|
+
* cancellation. Reversible with `resumeSubscription` until then. */
|
|
362
|
+
'periodEnd'
|
|
363
|
+
/**
|
|
364
|
+
* Closing an account: ends as soon as the last usage has settled on the
|
|
365
|
+
* meter — a few minutes, not a period — with a final invoice for that usage
|
|
366
|
+
* and a proration credit for the unused time.
|
|
367
|
+
*
|
|
368
|
+
* Not "now", on purpose: meter events aggregate asynchronously, and a
|
|
369
|
+
* subscription ended the moment its last usage was sent is invoiced for none
|
|
370
|
+
* of it (measured: the meter showed 250, the final invoice 0, and a finalized
|
|
371
|
+
* invoice never picks the usage up later).
|
|
372
|
+
*/
|
|
373
|
+
| 'close';
|
|
374
|
+
|
|
240
375
|
export declare interface CheckoutInput {
|
|
241
376
|
readonly tenantId: string;
|
|
242
377
|
/** Provider price id to subscribe to. */
|
|
243
378
|
readonly priceId: string;
|
|
379
|
+
/** Usage-based prices added next to `priceId` (no quantity). */
|
|
380
|
+
readonly meteredPriceIds?: ReadonlyArray<string>;
|
|
244
381
|
readonly successUrl: string;
|
|
245
382
|
readonly cancelUrl: string;
|
|
246
383
|
/** Optional existing provider customer id to attach the checkout to. */
|
|
@@ -257,6 +394,13 @@ export declare interface CheckoutInput {
|
|
|
257
394
|
readonly trialDays?: number;
|
|
258
395
|
}
|
|
259
396
|
|
|
397
|
+
/** What `closeAccount` did at the provider. */
|
|
398
|
+
export declare interface CloseAccountResult {
|
|
399
|
+
readonly tenantId: string;
|
|
400
|
+
/** Provider subscriptions ended (immediately, with a final invoice). */
|
|
401
|
+
readonly canceled: ReadonlyArray<string>;
|
|
402
|
+
}
|
|
403
|
+
|
|
260
404
|
/** The free/fallback plan id every app implicitly has. */
|
|
261
405
|
export declare const DEFAULT_PLAN: PlanId;
|
|
262
406
|
|
|
@@ -359,6 +503,13 @@ export declare interface Money {
|
|
|
359
503
|
export declare interface PlanConfig {
|
|
360
504
|
/** Provider price id (Stripe `price_…`). Absent for free/zero-cost plans. */
|
|
361
505
|
readonly priceId?: string;
|
|
506
|
+
/**
|
|
507
|
+
* Usage-based provider prices subscribed ALONGSIDE `priceId` (Stripe prices
|
|
508
|
+
* with `recurring.meter`). Checkout adds each as a quantity-less line item,
|
|
509
|
+
* so the subscription carries the metered item from its first invoice —
|
|
510
|
+
* without it, meter events are recorded at Stripe but never billed.
|
|
511
|
+
*/
|
|
512
|
+
readonly meteredPriceIds?: ReadonlyArray<string>;
|
|
362
513
|
/** Per-entitlement-key limits. Values are `number | 'unlimited'`. */
|
|
363
514
|
readonly entitlements: Readonly<Record<string, EntitlementLimit>>;
|
|
364
515
|
/** The plan's per-period price in INTEGER minor units (cents) — the unit
|
|
@@ -381,6 +532,45 @@ export declare interface PortalInput {
|
|
|
381
532
|
readonly returnUrl: string;
|
|
382
533
|
}
|
|
383
534
|
|
|
535
|
+
/** What `reconcileFromProvider` found and corrected. */
|
|
536
|
+
export declare interface ProviderReconcileResult {
|
|
537
|
+
/** Subscriptions compared (provider-listed plus local rows it did not list). */
|
|
538
|
+
readonly checked: number;
|
|
539
|
+
/** Tenants whose local rows changed because the provider said otherwise. */
|
|
540
|
+
readonly corrected: ReadonlyArray<string>;
|
|
541
|
+
/** Tenants with more than one live subscription at the provider — billed
|
|
542
|
+
* twice. Never resolved automatically: refunding is a human decision. */
|
|
543
|
+
readonly duplicates: ReadonlyArray<string>;
|
|
544
|
+
/** Provider subscriptions naming no tenant; nothing to attribute them to. */
|
|
545
|
+
readonly unattributed: number;
|
|
546
|
+
/** Subscriptions that had a plan's metered price added back. */
|
|
547
|
+
readonly meteredItemsRestored: number;
|
|
548
|
+
/** Tenants the app reports closed whose subscriptions were still paying —
|
|
549
|
+
* ended now (see `isTenantClosed`). */
|
|
550
|
+
readonly closedTenants: ReadonlyArray<string>;
|
|
551
|
+
/** Live rows the provider has never heard of that were NOT canceled, because
|
|
552
|
+
* so many at once means the wrong account or provider (0 normally). */
|
|
553
|
+
readonly refusedUnknown: number;
|
|
554
|
+
/** Every correction as the event it stands for, for the app's `onEvent`
|
|
555
|
+
* projections; the plugin's scheduled reconcile dispatches them. */
|
|
556
|
+
readonly events: ReadonlyArray<BillingEvent>;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* One subscription as the provider lists it — the input of the provider-side
|
|
561
|
+
* reconcile. Carries the ids a local row is matched by and the price ids a
|
|
562
|
+
* plan's metered items are checked against.
|
|
563
|
+
*/
|
|
564
|
+
export declare interface ProviderSubscriptionSnapshot extends ProviderSubscriptionState {
|
|
565
|
+
readonly providerSubscriptionId: string;
|
|
566
|
+
readonly providerCustomerId: string | null;
|
|
567
|
+
/** From the subscription's own metadata; null when the provider object was
|
|
568
|
+
* not created through our checkout and names no tenant. */
|
|
569
|
+
readonly tenantId: string | null;
|
|
570
|
+
/** Every price the subscription carries, licensed and metered. */
|
|
571
|
+
readonly priceIds: ReadonlyArray<string>;
|
|
572
|
+
}
|
|
573
|
+
|
|
384
574
|
/** The provider's CURRENT view of a subscription — read directly, not from an
|
|
385
575
|
* event body. `reconcileDunning` asks for this before it will lock anyone
|
|
386
576
|
* out. */
|
|
@@ -504,6 +694,13 @@ export declare interface SubscriptionUpdateInput {
|
|
|
504
694
|
readonly proration?: 'prorate' | 'none' | 'always_invoice';
|
|
505
695
|
}
|
|
506
696
|
|
|
697
|
+
/** An event the provider failed to deliver, reduced to what a catch-up needs. */
|
|
698
|
+
export declare interface UndeliveredEvent {
|
|
699
|
+
readonly id: string;
|
|
700
|
+
readonly providerCustomerId: string | null;
|
|
701
|
+
readonly createdAt: Date | null;
|
|
702
|
+
}
|
|
703
|
+
|
|
507
704
|
export declare interface UsagePush {
|
|
508
705
|
readonly tenantId: string;
|
|
509
706
|
readonly entitlementKey: string;
|
|
@@ -511,6 +708,14 @@ export declare interface UsagePush {
|
|
|
511
708
|
readonly quantity: number;
|
|
512
709
|
/** End of the period the usage falls in (provider timestamp anchor). */
|
|
513
710
|
readonly periodEnd?: Date;
|
|
711
|
+
/** The provider customer the usage is billed to, when one is linked. */
|
|
712
|
+
readonly providerCustomerId?: string;
|
|
713
|
+
/**
|
|
714
|
+
* Names THIS delta: the same flush replayed carries the same key (so the
|
|
715
|
+
* provider can dedupe it), the next delta carries a new one. A key per
|
|
716
|
+
* (tenant, key, period) alone would make every later delta a "duplicate".
|
|
717
|
+
*/
|
|
718
|
+
readonly idempotencyKey?: string;
|
|
514
719
|
}
|
|
515
720
|
|
|
516
721
|
export { }
|
package/dist/web/index.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/** The fields of `billing.subscription` the cancel flow reads. */
|
|
2
|
+
export declare interface CancelableSubscription {
|
|
3
|
+
readonly status: string;
|
|
4
|
+
/** ISO instant a scheduled cancellation takes effect, else null. */
|
|
5
|
+
readonly cancelAt: string | null;
|
|
6
|
+
/** ISO instant the paid period ends. */
|
|
7
|
+
readonly currentPeriodEnd: string | null;
|
|
8
|
+
}
|
|
9
|
+
|
|
1
10
|
/** The plan id + redirect URLs a checkout needs. */
|
|
2
11
|
export declare interface StartCheckoutInput {
|
|
3
12
|
readonly plan: string;
|
|
@@ -15,6 +24,9 @@ export declare type StartCheckoutRpc = (input: StartCheckoutInput) => Promise<{
|
|
|
15
24
|
readonly url: string;
|
|
16
25
|
}>;
|
|
17
26
|
|
|
27
|
+
/** The generated `billing.cancel` / `billing.resume` action bindings. */
|
|
28
|
+
export declare type SubscriptionActionRpc = (input: Record<string, never>) => Promise<unknown>;
|
|
29
|
+
|
|
18
30
|
/**
|
|
19
31
|
* React hook that drives provider-hosted checkout. Pass the generated
|
|
20
32
|
* `billing.startCheckout` rpc binding; the hook tracks pending/error and
|
|
@@ -38,4 +50,49 @@ export declare interface UseStartCheckoutResult {
|
|
|
38
50
|
readonly error: Error | null;
|
|
39
51
|
}
|
|
40
52
|
|
|
53
|
+
/**
|
|
54
|
+
* The two-step cancellation an app renders on its own billing page: an entry,
|
|
55
|
+
* then a separate confirmation that names the end date, then — once scheduled —
|
|
56
|
+
* the way back. The cancellation itself is the provider's period-end cancel
|
|
57
|
+
* (`billing.cancel`); the subscription stays active until it ends.
|
|
58
|
+
*
|
|
59
|
+
* The state comes from the subscription the app already reads, so a
|
|
60
|
+
* cancellation made in the provider's portal shows here too.
|
|
61
|
+
*
|
|
62
|
+
* ```tsx
|
|
63
|
+
* const flow = useSubscriptionCancel({ subscription, cancel: rpc.billing.cancel, resume: rpc.billing.resume })
|
|
64
|
+
* flow.stage === 'active' && <button onClick={flow.begin}>Cancel your subscription here</button>
|
|
65
|
+
* flow.stage === 'confirming' && <><p>Ends on {flow.endsOn}</p><button onClick={flow.confirm}>Cancel now</button><button onClick={flow.keep}>Keep</button></>
|
|
66
|
+
* flow.stage === 'scheduled' && <button onClick={flow.resume}>Take the cancellation back</button>
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export declare const useSubscriptionCancel: (input: {
|
|
70
|
+
readonly subscription: CancelableSubscription;
|
|
71
|
+
readonly cancel: SubscriptionActionRpc;
|
|
72
|
+
readonly resume: SubscriptionActionRpc;
|
|
73
|
+
}) => UseSubscriptionCancelResult;
|
|
74
|
+
|
|
75
|
+
export declare interface UseSubscriptionCancelResult {
|
|
76
|
+
/**
|
|
77
|
+
* - `active` — nothing scheduled; show the entry ("cancel your subscription here")
|
|
78
|
+
* - `confirming` — the separate confirmation step; show `endsOn`, "cancel now" and "keep"
|
|
79
|
+
* - `scheduled` — cancelled, ends on `endsOn`; show "take it back"
|
|
80
|
+
* - `ended` — the subscription has ended; nothing to offer
|
|
81
|
+
*/
|
|
82
|
+
readonly stage: 'active' | 'confirming' | 'scheduled' | 'ended';
|
|
83
|
+
/** When the subscription ends (scheduled) or would end (confirming), ISO. */
|
|
84
|
+
readonly endsOn: string | null;
|
|
85
|
+
/** Open the confirmation step. Calls nothing. */
|
|
86
|
+
readonly begin: () => void;
|
|
87
|
+
/** Leave the confirmation step. Calls nothing. */
|
|
88
|
+
readonly keep: () => void;
|
|
89
|
+
/** The confirmed cancellation — at period end, reversible until then. */
|
|
90
|
+
readonly confirm: () => Promise<void>;
|
|
91
|
+
/** Take a scheduled cancellation back. */
|
|
92
|
+
readonly resume: () => Promise<void>;
|
|
93
|
+
readonly pending: boolean;
|
|
94
|
+
/** The last failure, or null. The subscription is unchanged when set. */
|
|
95
|
+
readonly error: Error | null;
|
|
96
|
+
}
|
|
97
|
+
|
|
41
98
|
export { }
|
package/dist/web/index.js
CHANGED
|
@@ -17,6 +17,31 @@ var n = () => globalThis.window, r = (r) => {
|
|
|
17
17
|
pending: i,
|
|
18
18
|
error: o
|
|
19
19
|
};
|
|
20
|
+
}, i = (n) => {
|
|
21
|
+
let [r, i] = t(!1), [a, o] = t(!1), [s, c] = t(null), { subscription: l, cancel: u, resume: d } = n, f = e(async (e) => {
|
|
22
|
+
o(!0), c(null);
|
|
23
|
+
try {
|
|
24
|
+
await e(), i(!1);
|
|
25
|
+
} catch (e) {
|
|
26
|
+
c(e instanceof Error ? e : Error(String(e)));
|
|
27
|
+
} finally {
|
|
28
|
+
o(!1);
|
|
29
|
+
}
|
|
30
|
+
}, []), p = e(() => f(() => u({})), [f, u]), m = e(() => f(() => d({})), [f, d]), h = e(() => {
|
|
31
|
+
c(null), i(!0);
|
|
32
|
+
}, []), g = e(() => {
|
|
33
|
+
i(!1);
|
|
34
|
+
}, []);
|
|
35
|
+
return {
|
|
36
|
+
stage: l.status === "canceled" ? "ended" : l.cancelAt === null ? r ? "confirming" : "active" : "scheduled",
|
|
37
|
+
endsOn: l.cancelAt ?? l.currentPeriodEnd,
|
|
38
|
+
begin: h,
|
|
39
|
+
keep: g,
|
|
40
|
+
confirm: p,
|
|
41
|
+
resume: m,
|
|
42
|
+
pending: a,
|
|
43
|
+
error: s
|
|
44
|
+
};
|
|
20
45
|
};
|
|
21
46
|
//#endregion
|
|
22
|
-
export { r as useStartCheckout };
|
|
47
|
+
export { r as useStartCheckout, i as useSubscriptionCancel };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/plugin-billing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.82.0",
|
|
4
4
|
"description": "Billing on the official Stripe SDK — subscriptions, plans, entitlements and usage metering. Proration, failed-payment retries, tax, promotion codes, trials and the checkout seat stepper are Stripe's own; the plugin adds the entitlement engine, the enforce interceptor, typed BillingError/EntitlementExceeded, the /billing/webhook mount with Stripe's signature verification, the BillingService Tag, requireEntitlement(), and a browser-safe useStartCheckout() hook. Money is integer minor units end-to-end.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"voltro",
|
|
@@ -48,11 +48,11 @@
|
|
|
48
48
|
"node": ">=24.0.0"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@voltro/database": "0.
|
|
52
|
-
"@voltro/env": "0.
|
|
53
|
-
"@voltro/logger": "0.
|
|
54
|
-
"@voltro/plugin-webhooks": "0.
|
|
55
|
-
"@voltro/protocol": "0.
|
|
51
|
+
"@voltro/database": "0.82.0",
|
|
52
|
+
"@voltro/env": "0.82.0",
|
|
53
|
+
"@voltro/logger": "0.82.0",
|
|
54
|
+
"@voltro/plugin-webhooks": "0.82.0",
|
|
55
|
+
"@voltro/protocol": "0.82.0"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"effect": "^3.22.2",
|