@arnaudjnn/billing-tools 0.44.0 → 0.46.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/dist/checkout.d.ts.map +1 -1
- package/dist/checkout.js +65 -50
- package/dist/checkout.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/plans.d.ts +33 -0
- package/dist/plans.d.ts.map +1 -1
- package/dist/plans.js +170 -99
- package/dist/plans.js.map +1 -1
- package/dist/session.d.ts +8 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +2 -0
- package/dist/session.js.map +1 -1
- package/dist/ui/authkit.d.ts +11 -0
- package/dist/ui/authkit.d.ts.map +1 -0
- package/dist/ui/authkit.js +48 -0
- package/dist/ui/authkit.js.map +1 -0
- package/dist/ui/index.d.ts +75 -5
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +153 -9
- package/dist/ui/index.js.map +1 -1
- package/package.json +11 -1
package/dist/checkout.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkout.d.ts","sourceRoot":"","sources":["../src/checkout.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"checkout.d.ts","sourceRoot":"","sources":["../src/checkout.ts"],"names":[],"mappings":"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"}
|
package/dist/checkout.js
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
import { getStripe } from "./billing.js";
|
|
2
|
-
import {
|
|
2
|
+
import { invalidatePlanPrices, lookupKeyFor, resolvePlanPrices, } from "./plans.js";
|
|
3
|
+
/** The seat types with a quantity, in a stable order. Zeros are dropped rather
|
|
4
|
+
* than sent: Stripe rejects a zero-quantity line, and "no Premium seats" must
|
|
5
|
+
* mean "no Premium line". */
|
|
6
|
+
function selected(seats) {
|
|
7
|
+
const wanted = Object.entries(seats).filter(([, qty]) => qty > 0);
|
|
8
|
+
if (wanted.length === 0)
|
|
9
|
+
throw new Error("No seats selected");
|
|
10
|
+
return wanted;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Price id per selected seat type, from the memoised resolver.
|
|
14
|
+
*
|
|
15
|
+
* One in-memory lookup per line instead of one `prices.list` per line — the
|
|
16
|
+
* prices were already resolved (and provisioned, on first use) by
|
|
17
|
+
* `resolvePlanPrices`.
|
|
18
|
+
*/
|
|
19
|
+
function priceIdsFor(prices, plan, interval, wanted) {
|
|
20
|
+
return wanted.map(([seatType, quantity]) => {
|
|
21
|
+
const lookupKey = lookupKeyFor(plan, interval, seatType);
|
|
22
|
+
const price = prices.get(lookupKey);
|
|
23
|
+
if (!price) {
|
|
24
|
+
// The memo can only be wrong about a price that vanished from Stripe;
|
|
25
|
+
// drop it so the next attempt reconciles instead of failing again.
|
|
26
|
+
invalidatePlanPrices();
|
|
27
|
+
throw new Error(`No Stripe price for ${lookupKey}`);
|
|
28
|
+
}
|
|
29
|
+
return [price, quantity];
|
|
30
|
+
});
|
|
31
|
+
}
|
|
3
32
|
/**
|
|
4
33
|
* Create a seat Checkout Session whose tax Stripe calculates itself.
|
|
5
34
|
*
|
|
@@ -12,26 +41,22 @@ import { ensurePlans, lookupKeyFor, planPriceId } from "./plans.js";
|
|
|
12
41
|
*/
|
|
13
42
|
export async function createCheckoutSession(opts) {
|
|
14
43
|
const stripe = getStripe();
|
|
15
|
-
const wanted =
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
const customerId = opts.customerId ??
|
|
34
|
-
(await stripe.customers.create({ email: opts.email, metadata: opts.metadata })).id;
|
|
44
|
+
const wanted = selected(opts.seats);
|
|
45
|
+
// Independent, and both on the critical path of a customer waiting for the
|
|
46
|
+
// payment form: resolve the prices (memoised; provisions them the first time a
|
|
47
|
+
// seat type is ever sold) while the customer is created.
|
|
48
|
+
const [prices, customerId] = await Promise.all([
|
|
49
|
+
resolvePlanPrices(opts.plans, { currency: opts.currency }),
|
|
50
|
+
opts.customerId ??
|
|
51
|
+
stripe.customers
|
|
52
|
+
.create({ email: opts.email, metadata: opts.metadata })
|
|
53
|
+
.then((c) => c.id),
|
|
54
|
+
]);
|
|
55
|
+
const line_items = priceIdsFor(prices, opts.plan, opts.interval, wanted).map(([price, quantity]) => ({
|
|
56
|
+
price,
|
|
57
|
+
quantity,
|
|
58
|
+
...(opts.taxRates?.length ? { tax_rates: opts.taxRates } : {}),
|
|
59
|
+
}));
|
|
35
60
|
const session = await stripe.checkout.sessions.create({
|
|
36
61
|
mode: "subscription",
|
|
37
62
|
// Elements mode: Stripe hosts no page, we mount the elements and own the
|
|
@@ -120,22 +145,17 @@ export async function expireCheckoutSession(sessionId) {
|
|
|
120
145
|
*/
|
|
121
146
|
export async function createSubscription(opts) {
|
|
122
147
|
const stripe = getStripe();
|
|
123
|
-
const wanted =
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
items.push({ price, quantity });
|
|
136
|
-
}
|
|
137
|
-
const customerId = opts.customerId ??
|
|
138
|
-
(await stripe.customers.create({ email: opts.email, metadata: opts.metadata })).id;
|
|
148
|
+
const wanted = selected(opts.seats);
|
|
149
|
+
// Independent — resolve the prices (memoised; provisions them the first time a
|
|
150
|
+
// seat type is ever sold) while the customer is created.
|
|
151
|
+
const [prices, customerId] = await Promise.all([
|
|
152
|
+
resolvePlanPrices(opts.plans, { currency: opts.currency }),
|
|
153
|
+
opts.customerId ??
|
|
154
|
+
stripe.customers
|
|
155
|
+
.create({ email: opts.email, metadata: opts.metadata })
|
|
156
|
+
.then((c) => c.id),
|
|
157
|
+
]);
|
|
158
|
+
const items = priceIdsFor(prices, opts.plan, opts.interval, wanted).map(([price, quantity]) => ({ price, quantity }));
|
|
139
159
|
// Reuse a matching rate rather than creating one per checkout: Stripe tax
|
|
140
160
|
// rates are immutable and accumulate forever otherwise.
|
|
141
161
|
let defaultTaxRates;
|
|
@@ -197,21 +217,16 @@ export async function createSubscription(opts) {
|
|
|
197
217
|
*/
|
|
198
218
|
export async function updateSubscription(opts) {
|
|
199
219
|
const stripe = getStripe();
|
|
200
|
-
const wanted =
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
220
|
+
const wanted = selected(opts.seats);
|
|
221
|
+
// The live subscription is needed to diff against, and the prices to diff it
|
|
222
|
+
// WITH — neither depends on the other.
|
|
223
|
+
const [prices, sub] = await Promise.all([
|
|
224
|
+
resolvePlanPrices(opts.plans, { currency: opts.currency }),
|
|
225
|
+
stripe.subscriptions.retrieve(opts.subscriptionId),
|
|
226
|
+
]);
|
|
204
227
|
// Desired priceId → quantity for the new basket.
|
|
205
|
-
const desired = new Map();
|
|
206
|
-
for (const [seatType, quantity] of wanted) {
|
|
207
|
-
const price = await planPriceId(opts.plan, opts.interval, seatType);
|
|
208
|
-
if (!price) {
|
|
209
|
-
throw new Error(`No Stripe price for ${lookupKeyFor(opts.plan, opts.interval, seatType)}`);
|
|
210
|
-
}
|
|
211
|
-
desired.set(price, quantity);
|
|
212
|
-
}
|
|
228
|
+
const desired = new Map(priceIdsFor(prices, opts.plan, opts.interval, wanted));
|
|
213
229
|
// Diff against the live items: bump quantities, delete removed lines, add new.
|
|
214
|
-
const sub = await stripe.subscriptions.retrieve(opts.subscriptionId);
|
|
215
230
|
const items = [];
|
|
216
231
|
const seen = new Set();
|
|
217
232
|
for (const it of sub.items.data) {
|
package/dist/checkout.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkout.js","sourceRoot":"","sources":["../src/checkout.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,
|
|
1
|
+
{"version":3,"file":"checkout.js","sourceRoot":"","sources":["../src/checkout.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export type { BillingAdapter, BillingUser, ApiKeyInfo, BillingConfig, ResolvedCo
|
|
|
2
2
|
export { resolveConfig, internalDomainsFromEnv } from "./types.js";
|
|
3
3
|
export { authContext, runWithAuth, runWithResolvedOrg, enforceAccess, enforceTokens, } from "./auth.js";
|
|
4
4
|
export { getStripe, stripeConfigured, ensureStripeCustomer, getBillingCustomerId, getTokenBalance, deductTokens, creditTokens, usageSince, createTokenCheckoutSession, createBillingPortalSession, getAutoReloadSettings, setAutoReloadSettings, tryAutoReload, listInvoices, listSubscriptionPrices, resolveSubscriptionPrice, type StripePrice, } from "./billing.js";
|
|
5
|
-
export { ensurePlans, planPriceId, planForPriceId, seatTypeForPriceId, seatLimit, includedTokens, includedTokensByType, lookupKeyFor, DEFAULT_SEAT_TYPES, type PlanDef, type SeatTypeDef, type PlansConfig, type BillingInterval, type EnsuredPrice, } from "./plans.js";
|
|
5
|
+
export { ensurePlans, resolvePlanPrices, invalidatePlanPrices, planPriceId, planForPriceId, seatTypeForPriceId, seatLimit, includedTokens, includedTokensByType, lookupKeyFor, DEFAULT_SEAT_TYPES, type PlanDef, type SeatTypeDef, type PlansConfig, type BillingInterval, type EnsuredPrice, type PlanPrices, } from "./plans.js";
|
|
6
6
|
export { meterUsage, createMeter, createApiMeterGuard, type MeterCaller, type MeterInput, type MeterResult, type MeterConfig, type MeterCallOpts, type Meter, type ApiMeterGuard, } from "./metering.js";
|
|
7
7
|
export { requestTopUp, listTopUpRequests, approveTopUp, denyTopUp, extraAllowance, type TopUpRequest, } from "./topup.js";
|
|
8
8
|
export { assignSeatType, listSeatAssignments, getSeatType, } from "./seats.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,YAAY,EACV,cAAc,EACd,WAAW,EACX,UAAU,EACV,aAAa,EACb,cAAc,EACd,UAAU,EACV,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAGnE,OAAO,EACL,WAAW,EACX,WAAW,EACX,kBAAkB,EAClB,aAAa,EACb,aAAa,GACd,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,SAAS,EACT,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACpB,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,0BAA0B,EAC1B,0BAA0B,EAC1B,qBAAqB,EACrB,qBAAqB,EACrB,aAAa,EACb,YAAY,EACZ,sBAAsB,EACtB,wBAAwB,EACxB,KAAK,WAAW,GACjB,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,WAAW,EACX,WAAW,EACX,cAAc,EACd,kBAAkB,EAClB,SAAS,EACT,cAAc,EACd,oBAAoB,EACpB,YAAY,EACZ,kBAAkB,EAClB,KAAK,OAAO,EACZ,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ export { authContext, runWithAuth, runWithResolvedOrg, enforceAccess, enforceTok
|
|
|
5
5
|
// Billing engine (Stripe math)
|
|
6
6
|
export { getStripe, stripeConfigured, ensureStripeCustomer, getBillingCustomerId, getTokenBalance, deductTokens, creditTokens, usageSince, createTokenCheckoutSession, createBillingPortalSession, getAutoReloadSettings, setAutoReloadSettings, tryAutoReload, listInvoices, listSubscriptionPrices, resolveSubscriptionPrice, } from "./billing.js";
|
|
7
7
|
// Declarative plans (auto-provision Stripe products/prices from config)
|
|
8
|
-
export { ensurePlans, planPriceId, planForPriceId, seatTypeForPriceId, seatLimit, includedTokens, includedTokensByType, lookupKeyFor, DEFAULT_SEAT_TYPES, } from "./plans.js";
|
|
8
|
+
export { ensurePlans, resolvePlanPrices, invalidatePlanPrices, planPriceId, planForPriceId, seatTypeForPriceId, seatLimit, includedTokens, includedTokensByType, lookupKeyFor, DEFAULT_SEAT_TYPES, } from "./plans.js";
|
|
9
9
|
// Per-execution metering engine (prepaid balance; per-seat packs or a global
|
|
10
10
|
// pool; usage summed from Stripe balance-transaction metadata — no new backend).
|
|
11
11
|
export { meterUsage, createMeter, createApiMeterGuard, } from "./metering.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mEAAmE;AAYnE,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEnE,cAAc;AACd,OAAO,EACL,WAAW,EACX,WAAW,EACX,kBAAkB,EAClB,aAAa,EACb,aAAa,GACd,MAAM,WAAW,CAAC;AAEnB,+BAA+B;AAC/B,OAAO,EACL,SAAS,EACT,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACpB,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,0BAA0B,EAC1B,0BAA0B,EAC1B,qBAAqB,EACrB,qBAAqB,EACrB,aAAa,EACb,YAAY,EACZ,sBAAsB,EACtB,wBAAwB,GAEzB,MAAM,cAAc,CAAC;AAEtB,wEAAwE;AACxE,OAAO,EACL,WAAW,EACX,WAAW,EACX,cAAc,EACd,kBAAkB,EAClB,SAAS,EACT,cAAc,EACd,oBAAoB,EACpB,YAAY,EACZ,kBAAkB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/plans.d.ts
CHANGED
|
@@ -59,7 +59,40 @@ export interface EnsuredPrice {
|
|
|
59
59
|
export declare function ensurePlans(plans: PlansConfig, opts?: {
|
|
60
60
|
currency?: string;
|
|
61
61
|
taxBehavior?: Stripe.Price.TaxBehavior;
|
|
62
|
+
/**
|
|
63
|
+
* Sweep the account for managed prices/products this config no longer wants.
|
|
64
|
+
*
|
|
65
|
+
* It is a FULL account scan (every active price, every active product), so
|
|
66
|
+
* it is not something to do on a request: `true` (the default) for an
|
|
67
|
+
* explicit reconcile — a CLI/sync/boot call — and `"background"` on a hot
|
|
68
|
+
* path, which starts the sweep without awaiting it. `false` skips it.
|
|
69
|
+
*/
|
|
70
|
+
archive?: boolean | "background";
|
|
62
71
|
}): Promise<EnsuredPrice[]>;
|
|
72
|
+
/** Resolved prices for one plans config: lookup_key → price id. */
|
|
73
|
+
export type PlanPrices = ReadonlyMap<string, string>;
|
|
74
|
+
/**
|
|
75
|
+
* The price ids for `plans`, provisioning them on first use.
|
|
76
|
+
*
|
|
77
|
+
* Same guarantee as `ensurePlans` — the Stripe objects exist and match the
|
|
78
|
+
* config — without paying for the check every time. Use this wherever a price id
|
|
79
|
+
* is needed to serve a request; call `ensurePlans` directly when the point IS
|
|
80
|
+
* the reconcile (a deploy hook, `billing sync`, a test).
|
|
81
|
+
*
|
|
82
|
+
* Look ids up with `lookupKeyFor(plan, interval, seatType)`.
|
|
83
|
+
*/
|
|
84
|
+
export declare function resolvePlanPrices(plans: PlansConfig, opts?: {
|
|
85
|
+
currency?: string;
|
|
86
|
+
taxBehavior?: Stripe.Price.TaxBehavior;
|
|
87
|
+
}): Promise<PlanPrices>;
|
|
88
|
+
/**
|
|
89
|
+
* Drop the memo, so the next `resolvePlanPrices` reconciles against Stripe again.
|
|
90
|
+
*
|
|
91
|
+
* Worth calling when a price id turns out to be stale — Stripe rejecting a
|
|
92
|
+
* checkout because a price was archived in the dashboard is exactly the case the
|
|
93
|
+
* TTL alone would leave broken for ten minutes.
|
|
94
|
+
*/
|
|
95
|
+
export declare function invalidatePlanPrices(): void;
|
|
63
96
|
/** Resolve the current Stripe price id for a plan + interval (via lookup_key).
|
|
64
97
|
* Returns null for a free/absent price. */
|
|
65
98
|
export declare function planPriceId(plan: string, interval: BillingInterval, seatType?: string): Promise<string | null>;
|
package/dist/plans.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plans.d.ts","sourceRoot":"","sources":["../src/plans.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAWjC;;;0EAG0E;AAC1E,MAAM,WAAW,WAAW;IAC1B,oEAAoE;IACpE,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,wEAAwE;IACxE,cAAc,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,OAAO;IACtB,mDAAmD;IACnD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,wEAAwE;IACxE,aAAa,EAAE,MAAM,CAAC;IACtB,yFAAyF;IACzF,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C;;;;8EAI0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACxC;;;;oFAIgF;IAChF,aAAa,CAAC,EAAE,UAAU,GAAG,QAAQ,CAAC;CACvC;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAClD,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,QAAQ,CAAC;AAQnD,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAI1D,CAAC;AASF,eAAO,MAAM,YAAY,GACvB,MAAM,MAAM,EACZ,UAAU,eAAe,EACzB,WAAW,MAAM,KAChB,MAAgF,CAAC;AAEpF,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,eAAe,CAAC;IAC1B,qCAAqC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;
|
|
1
|
+
{"version":3,"file":"plans.d.ts","sourceRoot":"","sources":["../src/plans.ts"],"names":[],"mappings":"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"}
|
package/dist/plans.js
CHANGED
|
@@ -18,29 +18,67 @@ const STRIPE_INTERVAL = {
|
|
|
18
18
|
const MANAGED_BY = "billing-tools";
|
|
19
19
|
export const lookupKeyFor = (plan, interval, seatType) => (seatType ? `${plan}_${seatType}_${interval}` : `${plan}_${interval}`);
|
|
20
20
|
const cap = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
21
|
-
/**
|
|
22
|
-
*
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// Returning null instead makes ensurePlans mint a fresh product.
|
|
37
|
-
if (typeof p.product !== "string" && !p.product.deleted && !p.product.active) {
|
|
38
|
-
continue;
|
|
21
|
+
/** Every price a plans config expects to exist, in a fixed order. Free
|
|
22
|
+
* intervals/seat types produce no Stripe object, so they're dropped here. */
|
|
23
|
+
function priceSpecs(plans) {
|
|
24
|
+
const specs = [];
|
|
25
|
+
for (const [plan, def] of Object.entries(plans)) {
|
|
26
|
+
const of = (interval, amount, seatType) => {
|
|
27
|
+
if (amount > 0) {
|
|
28
|
+
specs.push({ plan, interval, seatType, amount, lookupKey: lookupKeyFor(plan, interval, seatType) });
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
if (def.seatTypes) {
|
|
32
|
+
for (const [seatType, st] of Object.entries(def.seatTypes)) {
|
|
33
|
+
for (const interval of INTERVALS)
|
|
34
|
+
of(interval, st.price[interval], seatType);
|
|
35
|
+
}
|
|
39
36
|
}
|
|
40
|
-
|
|
37
|
+
else {
|
|
38
|
+
for (const interval of INTERVALS)
|
|
39
|
+
of(interval, def.price[interval]);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return specs;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Look every wanted lookup_key up in ONE round trip per 10 keys.
|
|
46
|
+
*
|
|
47
|
+
* This used to be a `prices.list` per key plus two more per plan to find the
|
|
48
|
+
* product — sixteen sequential calls for a three-plan config, ~175ms each, on
|
|
49
|
+
* the critical path of every checkout. `lookup_keys` takes up to 10 at a time,
|
|
50
|
+
* so the same information costs one or two calls.
|
|
51
|
+
*/
|
|
52
|
+
async function fetchByLookupKey(stripe, keys) {
|
|
53
|
+
const found = new Map();
|
|
54
|
+
const chunks = [];
|
|
55
|
+
for (let i = 0; i < keys.length; i += 10)
|
|
56
|
+
chunks.push(keys.slice(i, i + 10));
|
|
57
|
+
const pages = await Promise.all(chunks.map((lookup_keys) => stripe.prices.list({
|
|
58
|
+
lookup_keys,
|
|
59
|
+
active: true,
|
|
60
|
+
limit: 100,
|
|
61
|
+
// The product matters as much as the price: `active` filters PRICES, and
|
|
62
|
+
// a price on an archived product stays active and listable while being
|
|
63
|
+
// unusable for a new subscription.
|
|
64
|
+
expand: ["data.product"],
|
|
65
|
+
})));
|
|
66
|
+
for (const page of pages) {
|
|
67
|
+
for (const price of page.data)
|
|
68
|
+
if (price.lookup_key)
|
|
69
|
+
found.set(price.lookup_key, price);
|
|
41
70
|
}
|
|
42
|
-
return
|
|
71
|
+
return found;
|
|
72
|
+
}
|
|
73
|
+
/** A price Stripe will actually accept on a new subscription: its product must
|
|
74
|
+
* be live too, or Stripe refuses with "product … is marked as inactive". */
|
|
75
|
+
function usable(price) {
|
|
76
|
+
if (!price)
|
|
77
|
+
return false;
|
|
78
|
+
const product = price.product;
|
|
79
|
+
return typeof product === "string" || (!product.deleted && product.active);
|
|
43
80
|
}
|
|
81
|
+
const productIdOf = (price) => typeof price.product === "string" ? price.product : price.product.id;
|
|
44
82
|
/** Idempotently create/reconcile Stripe products + prices for the paid plans.
|
|
45
83
|
* Returns the resolved price for every paid plan × interval. Free plans (both
|
|
46
84
|
* prices 0) create no Stripe objects. Safe to call on every boot / first use. */
|
|
@@ -57,90 +95,78 @@ export async function ensurePlans(plans, opts = {}) {
|
|
|
57
95
|
// prices are.
|
|
58
96
|
const taxBehavior = opts.taxBehavior ?? "exclusive";
|
|
59
97
|
const result = [];
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
98
|
+
const specs = priceSpecs(plans);
|
|
99
|
+
const wanted = new Set(specs.map((s) => s.lookupKey));
|
|
100
|
+
// Everything the config wants, in one or two calls. Also the ONLY place the
|
|
101
|
+
// managed product per plan is discovered: any existing price of the plan
|
|
102
|
+
// carries it, so a separate product lookup per plan is redundant.
|
|
103
|
+
const existingByKey = await fetchByLookupKey(stripe, [...wanted]);
|
|
104
|
+
// Reuse one product per plan across price versions. Seeded from whatever the
|
|
105
|
+
// batch found (a price on an archived product doesn't count — Stripe refuses
|
|
106
|
+
// new subscriptions against it, so the plan gets a fresh product).
|
|
107
|
+
const productByPlan = new Map();
|
|
108
|
+
for (const spec of specs) {
|
|
109
|
+
const price = existingByKey.get(spec.lookupKey);
|
|
110
|
+
if (price && usable(price) && !productByPlan.has(spec.plan)) {
|
|
111
|
+
productByPlan.set(spec.plan, productIdOf(price));
|
|
73
112
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
// the lookup key onto a fresh price (transfer_lookup_key) and archives the
|
|
96
|
-
// old one.
|
|
97
|
-
const productLive = !existing ||
|
|
98
|
-
typeof existing.product === "string" ||
|
|
99
|
-
(!existing.product.deleted && existing.product.active);
|
|
100
|
-
const matches = existing &&
|
|
101
|
-
productLive &&
|
|
102
|
-
existing.unit_amount === amount &&
|
|
103
|
-
existing.currency === currency &&
|
|
104
|
-
existing.recurring?.interval === STRIPE_INTERVAL[interval];
|
|
105
|
-
if (existing && matches) {
|
|
106
|
-
// Backfill: prices minted before this package set tax_behavior are
|
|
107
|
-
// `unspecified`, which Stripe Tax won't calculate on. It is settable once
|
|
108
|
-
// (unspecified → inclusive|exclusive) and immutable after, so this
|
|
109
|
-
// upgrades old prices in place rather than replacing them and moving
|
|
110
|
-
// every subscriber onto a new price id.
|
|
111
|
-
if (existing.tax_behavior === "unspecified") {
|
|
112
|
-
await stripe.prices.update(existing.id, { tax_behavior: taxBehavior });
|
|
113
|
-
}
|
|
114
|
-
productId =
|
|
115
|
-
typeof existing.product === "string" ? existing.product : existing.product.id;
|
|
116
|
-
result.push({ plan, interval, seatType, priceId: existing.id, productId, amount, lookupKey });
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
if (!productId) {
|
|
120
|
-
const product = await stripe.products.create({
|
|
121
|
-
name: cap(plan),
|
|
122
|
-
metadata: { managedBy: MANAGED_BY, plan },
|
|
123
|
-
});
|
|
124
|
-
productId = product.id;
|
|
113
|
+
}
|
|
114
|
+
for (const { plan, interval, seatType, amount, lookupKey } of specs) {
|
|
115
|
+
const existing = existingByKey.get(lookupKey);
|
|
116
|
+
// A price on an ARCHIVED product looks perfectly reusable — same amount,
|
|
117
|
+
// currency and interval, still active — but Stripe refuses new subscriptions
|
|
118
|
+
// against it. Treating it as a non-match falls through to the create branch
|
|
119
|
+
// below, which moves the lookup key onto a fresh price
|
|
120
|
+
// (transfer_lookup_key) and archives the old one.
|
|
121
|
+
const matches = existing &&
|
|
122
|
+
usable(existing) &&
|
|
123
|
+
existing.unit_amount === amount &&
|
|
124
|
+
existing.currency === currency &&
|
|
125
|
+
existing.recurring?.interval === STRIPE_INTERVAL[interval];
|
|
126
|
+
if (existing && matches) {
|
|
127
|
+
// Backfill: prices minted before this package set tax_behavior are
|
|
128
|
+
// `unspecified`, which Stripe Tax won't calculate on. It is settable once
|
|
129
|
+
// (unspecified → inclusive|exclusive) and immutable after, so this
|
|
130
|
+
// upgrades old prices in place rather than replacing them and moving every
|
|
131
|
+
// subscriber onto a new price id.
|
|
132
|
+
if (existing.tax_behavior === "unspecified") {
|
|
133
|
+
await stripe.prices.update(existing.id, { tax_behavior: taxBehavior });
|
|
125
134
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
metadata: { managedBy: MANAGED_BY, plan, interval, ...(seatType ? { seatType } : {}) },
|
|
135
|
+
const productId = productIdOf(existing);
|
|
136
|
+
productByPlan.set(plan, productId);
|
|
137
|
+
result.push({ plan, interval, seatType, priceId: existing.id, productId, amount, lookupKey });
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
let productId = productByPlan.get(plan);
|
|
141
|
+
if (!productId) {
|
|
142
|
+
const product = await stripe.products.create({
|
|
143
|
+
name: cap(plan),
|
|
144
|
+
metadata: { managedBy: MANAGED_BY, plan },
|
|
137
145
|
});
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
result.push({ plan, interval, seatType, priceId: created.id, productId, amount, lookupKey });
|
|
146
|
+
productId = product.id;
|
|
147
|
+
productByPlan.set(plan, productId);
|
|
141
148
|
}
|
|
149
|
+
// Create the new price and move the lookup_key onto it (frees it from the
|
|
150
|
+
// old price), then archive the old price. Subscribers on it are untouched.
|
|
151
|
+
const created = await stripe.prices.create({
|
|
152
|
+
product: productId,
|
|
153
|
+
currency,
|
|
154
|
+
unit_amount: amount,
|
|
155
|
+
recurring: { interval: STRIPE_INTERVAL[interval] },
|
|
156
|
+
lookup_key: lookupKey,
|
|
157
|
+
transfer_lookup_key: true,
|
|
158
|
+
tax_behavior: taxBehavior,
|
|
159
|
+
metadata: { managedBy: MANAGED_BY, plan, interval, ...(seatType ? { seatType } : {}) },
|
|
160
|
+
});
|
|
161
|
+
if (existing)
|
|
162
|
+
await stripe.prices.update(existing.id, { active: false });
|
|
163
|
+
result.push({ plan, interval, seatType, priceId: created.id, productId, amount, lookupKey });
|
|
142
164
|
}
|
|
143
|
-
|
|
165
|
+
const archive = opts.archive ?? true;
|
|
166
|
+
if (archive === "background")
|
|
167
|
+
void archiveOrphans(stripe, wanted).catch(() => { });
|
|
168
|
+
else if (archive)
|
|
169
|
+
await archiveOrphans(stripe, wanted);
|
|
144
170
|
return result;
|
|
145
171
|
}
|
|
146
172
|
/** Archive managed prices whose lookup_key is no longer configured (a plan or
|
|
@@ -166,6 +192,51 @@ async function archiveOrphans(stripe, wanted) {
|
|
|
166
192
|
}
|
|
167
193
|
}
|
|
168
194
|
}
|
|
195
|
+
const MEMO_TTL_MS = 10 * 60 * 1000;
|
|
196
|
+
let memo = null;
|
|
197
|
+
// Concurrent cold requests share one reconcile rather than each starting their own.
|
|
198
|
+
let inflight = null;
|
|
199
|
+
const memoKey = (plans, opts) => JSON.stringify([plans, opts.currency ?? "usd", opts.taxBehavior ?? "exclusive"]);
|
|
200
|
+
/**
|
|
201
|
+
* The price ids for `plans`, provisioning them on first use.
|
|
202
|
+
*
|
|
203
|
+
* Same guarantee as `ensurePlans` — the Stripe objects exist and match the
|
|
204
|
+
* config — without paying for the check every time. Use this wherever a price id
|
|
205
|
+
* is needed to serve a request; call `ensurePlans` directly when the point IS
|
|
206
|
+
* the reconcile (a deploy hook, `billing sync`, a test).
|
|
207
|
+
*
|
|
208
|
+
* Look ids up with `lookupKeyFor(plan, interval, seatType)`.
|
|
209
|
+
*/
|
|
210
|
+
export async function resolvePlanPrices(plans, opts = {}) {
|
|
211
|
+
const key = memoKey(plans, opts);
|
|
212
|
+
if (memo && memo.key === key && Date.now() - memo.at < MEMO_TTL_MS)
|
|
213
|
+
return memo.prices;
|
|
214
|
+
if (inflight && inflight.key === key)
|
|
215
|
+
return inflight.promise;
|
|
216
|
+
const promise = (async () => {
|
|
217
|
+
// Orphan archiving is account hygiene, not something this request needs:
|
|
218
|
+
// start it, don't wait for it.
|
|
219
|
+
const ensured = await ensurePlans(plans, { ...opts, archive: "background" });
|
|
220
|
+
const prices = new Map(ensured.map((e) => [e.lookupKey, e.priceId]));
|
|
221
|
+
memo = { key, at: Date.now(), prices };
|
|
222
|
+
return prices;
|
|
223
|
+
})().finally(() => {
|
|
224
|
+
if (inflight?.key === key)
|
|
225
|
+
inflight = null;
|
|
226
|
+
});
|
|
227
|
+
inflight = { key, promise };
|
|
228
|
+
return promise;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Drop the memo, so the next `resolvePlanPrices` reconciles against Stripe again.
|
|
232
|
+
*
|
|
233
|
+
* Worth calling when a price id turns out to be stale — Stripe rejecting a
|
|
234
|
+
* checkout because a price was archived in the dashboard is exactly the case the
|
|
235
|
+
* TTL alone would leave broken for ten minutes.
|
|
236
|
+
*/
|
|
237
|
+
export function invalidatePlanPrices() {
|
|
238
|
+
memo = null;
|
|
239
|
+
}
|
|
169
240
|
/** Resolve the current Stripe price id for a plan + interval (via lookup_key).
|
|
170
241
|
* Returns null for a free/absent price. */
|
|
171
242
|
export async function planPriceId(plan, interval, seatType) {
|