@auth-gate/billing 0.8.0 → 0.9.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.
|
@@ -13,8 +13,11 @@ function validateTiers(price, prefix) {
|
|
|
13
13
|
}
|
|
14
14
|
for (let j = 0; j < price.tiers.length; j++) {
|
|
15
15
|
const tier = price.tiers[j];
|
|
16
|
-
if (typeof tier.unitAmount !== "number" || tier.unitAmount < 0) {
|
|
17
|
-
throw new Error(`${prefix}.tiers[${j}].unitAmount must be a non-negative number.`);
|
|
16
|
+
if (typeof tier.unitAmount !== "number" || !Number.isFinite(tier.unitAmount) || tier.unitAmount < 0) {
|
|
17
|
+
throw new Error(`${prefix}.tiers[${j}].unitAmount must be a finite non-negative number.`);
|
|
18
|
+
}
|
|
19
|
+
if (tier.flatAmount !== void 0 && (typeof tier.flatAmount !== "number" || !Number.isFinite(tier.flatAmount) || tier.flatAmount < 0)) {
|
|
20
|
+
throw new Error(`${prefix}.tiers[${j}].flatAmount must be a finite non-negative number.`);
|
|
18
21
|
}
|
|
19
22
|
}
|
|
20
23
|
}
|
|
@@ -206,7 +209,7 @@ function normalizeConfig(config) {
|
|
|
206
209
|
const plans = {};
|
|
207
210
|
for (const [key, plan] of Object.entries(config.plans)) {
|
|
208
211
|
if (plan.entitlements) {
|
|
209
|
-
plans[key] = plan;
|
|
212
|
+
plans[key] = __spreadValues({}, plan);
|
|
210
213
|
} else if (plan.features && plan.features.length > 0) {
|
|
211
214
|
const entitlements = {};
|
|
212
215
|
for (const f of plan.features) {
|
|
@@ -214,13 +217,28 @@ function normalizeConfig(config) {
|
|
|
214
217
|
}
|
|
215
218
|
plans[key] = __spreadProps(__spreadValues({}, plan), { entitlements });
|
|
216
219
|
} else {
|
|
217
|
-
plans[key] = plan;
|
|
220
|
+
plans[key] = __spreadValues({}, plan);
|
|
218
221
|
}
|
|
219
222
|
}
|
|
220
223
|
return __spreadProps(__spreadValues({}, config), { plans });
|
|
221
224
|
}
|
|
222
225
|
|
|
223
226
|
// src/diff.ts
|
|
227
|
+
function deepEqual(a, b) {
|
|
228
|
+
if (a === b) return true;
|
|
229
|
+
if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
|
|
230
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
231
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
232
|
+
if (a.length !== b.length) return false;
|
|
233
|
+
return a.every((val, i) => deepEqual(val, b[i]));
|
|
234
|
+
}
|
|
235
|
+
const keysA = Object.keys(a).sort();
|
|
236
|
+
const keysB = Object.keys(b).sort();
|
|
237
|
+
if (keysA.length !== keysB.length) return false;
|
|
238
|
+
return keysA.every(
|
|
239
|
+
(key, i) => key === keysB[i] && deepEqual(a[key], b[key])
|
|
240
|
+
);
|
|
241
|
+
}
|
|
224
242
|
function priceConfigKey(planKey, price) {
|
|
225
243
|
var _a, _b;
|
|
226
244
|
const interval = price.interval;
|
|
@@ -242,6 +260,30 @@ function priceConfigKey(planKey, price) {
|
|
|
242
260
|
const p = price;
|
|
243
261
|
return `${planKey}_${interval}${suffix}_${p.amount}_${p.currency}`;
|
|
244
262
|
}
|
|
263
|
+
function diffPrices(planKey, configPrices, serverPrices) {
|
|
264
|
+
const serverPricesByKey = /* @__PURE__ */ new Map();
|
|
265
|
+
for (const sp of serverPrices) {
|
|
266
|
+
if (sp.configKey && sp.isActive) {
|
|
267
|
+
serverPricesByKey.set(sp.configKey, sp);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const creates = [];
|
|
271
|
+
const archives = [];
|
|
272
|
+
const configPriceKeys = /* @__PURE__ */ new Set();
|
|
273
|
+
for (const price of configPrices) {
|
|
274
|
+
const pk = priceConfigKey(planKey, price);
|
|
275
|
+
configPriceKeys.add(pk);
|
|
276
|
+
if (!serverPricesByKey.has(pk)) {
|
|
277
|
+
creates.push({ type: "create", planKey, price, configKey: pk });
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
for (const [pk, sp] of serverPricesByKey) {
|
|
281
|
+
if (!configPriceKeys.has(pk)) {
|
|
282
|
+
archives.push({ type: "archive", planKey, existing: sp, configKey: pk });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return { creates, archives };
|
|
286
|
+
}
|
|
245
287
|
function computeDiff(config, server, subscriberCounts) {
|
|
246
288
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
247
289
|
const planOps = [];
|
|
@@ -270,25 +312,8 @@ function computeDiff(config, server, subscriberCounts) {
|
|
|
270
312
|
if (existing) {
|
|
271
313
|
const subs = (_c = subscriberCounts[existing.id]) != null ? _c : 0;
|
|
272
314
|
planOps.push({ type: "rename", key, oldKey: plan.renamedFrom, plan, existing, activeSubscribers: subs });
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
if (sp.configKey && sp.isActive) {
|
|
276
|
-
serverPricesByKey.set(sp.configKey, sp);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
const configPriceKeys = /* @__PURE__ */ new Set();
|
|
280
|
-
for (const price of plan.prices) {
|
|
281
|
-
const pk = priceConfigKey(key, price);
|
|
282
|
-
configPriceKeys.add(pk);
|
|
283
|
-
if (!serverPricesByKey.has(pk)) {
|
|
284
|
-
priceOps.push({ type: "create", planKey: key, price, configKey: pk });
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
for (const [pk, sp] of serverPricesByKey) {
|
|
288
|
-
if (!configPriceKeys.has(pk)) {
|
|
289
|
-
priceOps.push({ type: "archive", planKey: key, existing: sp, configKey: pk });
|
|
290
|
-
}
|
|
291
|
-
}
|
|
315
|
+
const priceDiff = diffPrices(key, plan.prices, existing.prices);
|
|
316
|
+
priceOps.push(...priceDiff.creates, ...priceDiff.archives);
|
|
292
317
|
serverByKey.delete(existing.configKey);
|
|
293
318
|
serverByKey.delete(key);
|
|
294
319
|
continue;
|
|
@@ -313,30 +338,12 @@ function computeDiff(config, server, subscriberCounts) {
|
|
|
313
338
|
const existingFeatures = ((_f = existing.features) != null ? _f : []).sort().join(",");
|
|
314
339
|
const configFeatures = ((_g = plan.features) != null ? _g : []).sort().join(",");
|
|
315
340
|
if (existingFeatures !== configFeatures) changes.push(`features changed`);
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if (existingEntitlements !== configEntitlements) changes.push(`entitlements changed`);
|
|
319
|
-
const serverPricesByKey = /* @__PURE__ */ new Map();
|
|
320
|
-
for (const sp of existing.prices) {
|
|
321
|
-
if (sp.configKey && sp.isActive) {
|
|
322
|
-
serverPricesByKey.set(sp.configKey, sp);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
const configPriceKeys = /* @__PURE__ */ new Set();
|
|
326
|
-
for (const price of plan.prices) {
|
|
327
|
-
const pk = priceConfigKey(key, price);
|
|
328
|
-
configPriceKeys.add(pk);
|
|
329
|
-
if (!serverPricesByKey.has(pk)) {
|
|
330
|
-
priceOps.push({ type: "create", planKey: key, price, configKey: pk });
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
let hasPriceArchives = false;
|
|
334
|
-
for (const [pk, sp] of serverPricesByKey) {
|
|
335
|
-
if (!configPriceKeys.has(pk)) {
|
|
336
|
-
priceOps.push({ type: "archive", planKey: key, existing: sp, configKey: pk });
|
|
337
|
-
hasPriceArchives = true;
|
|
338
|
-
}
|
|
341
|
+
if (!deepEqual((_h = existing.entitlements) != null ? _h : null, (_i = plan.entitlements) != null ? _i : null)) {
|
|
342
|
+
changes.push(`entitlements changed`);
|
|
339
343
|
}
|
|
344
|
+
const priceDiff = diffPrices(key, plan.prices, existing.prices);
|
|
345
|
+
priceOps.push(...priceDiff.creates, ...priceDiff.archives);
|
|
346
|
+
const hasPriceArchives = priceDiff.archives.length > 0;
|
|
340
347
|
if (changes.length > 0 || hasPriceArchives) {
|
|
341
348
|
const versionBump = hasPriceArchives;
|
|
342
349
|
if (versionBump) changes.push("prices changed");
|
package/dist/cli.cjs
CHANGED
|
@@ -54,11 +54,11 @@ async function fetchStripeState(stripeKey) {
|
|
|
54
54
|
const Stripe = (await import("stripe")).default;
|
|
55
55
|
const stripe = new Stripe(stripeKey);
|
|
56
56
|
const [products, prices] = await Promise.all([
|
|
57
|
-
stripe.products.list({ active: true, limit: 100 }),
|
|
58
|
-
stripe.prices.list({ active: true, limit: 100 })
|
|
57
|
+
stripe.products.list({ active: true, limit: 100 }).autoPagingToArray({ limit: 1e4 }),
|
|
58
|
+
stripe.prices.list({ active: true, limit: 100 }).autoPagingToArray({ limit: 1e4 })
|
|
59
59
|
]);
|
|
60
60
|
const pricesByProduct = /* @__PURE__ */ new Map();
|
|
61
|
-
for (const sp of prices
|
|
61
|
+
for (const sp of prices) {
|
|
62
62
|
const productId = typeof sp.product === "string" ? sp.product : sp.product.id;
|
|
63
63
|
const mapped = {
|
|
64
64
|
id: sp.id,
|
|
@@ -75,7 +75,7 @@ async function fetchStripeState(stripeKey) {
|
|
|
75
75
|
mapped
|
|
76
76
|
]);
|
|
77
77
|
}
|
|
78
|
-
const serverProducts = products.
|
|
78
|
+
const serverProducts = products.map((p) => {
|
|
79
79
|
var _a2;
|
|
80
80
|
return {
|
|
81
81
|
id: p.id,
|
|
@@ -112,8 +112,11 @@ function validateTiers(price, prefix) {
|
|
|
112
112
|
}
|
|
113
113
|
for (let j = 0; j < price.tiers.length; j++) {
|
|
114
114
|
const tier = price.tiers[j];
|
|
115
|
-
if (typeof tier.unitAmount !== "number" || tier.unitAmount < 0) {
|
|
116
|
-
throw new Error(`${prefix}.tiers[${j}].unitAmount must be a non-negative number.`);
|
|
115
|
+
if (typeof tier.unitAmount !== "number" || !Number.isFinite(tier.unitAmount) || tier.unitAmount < 0) {
|
|
116
|
+
throw new Error(`${prefix}.tiers[${j}].unitAmount must be a finite non-negative number.`);
|
|
117
|
+
}
|
|
118
|
+
if (tier.flatAmount !== void 0 && (typeof tier.flatAmount !== "number" || !Number.isFinite(tier.flatAmount) || tier.flatAmount < 0)) {
|
|
119
|
+
throw new Error(`${prefix}.tiers[${j}].flatAmount must be a finite non-negative number.`);
|
|
117
120
|
}
|
|
118
121
|
}
|
|
119
122
|
}
|
|
@@ -333,6 +336,21 @@ Run \`npx @auth-gate/billing init\` to create one.`
|
|
|
333
336
|
}
|
|
334
337
|
|
|
335
338
|
// src/diff.ts
|
|
339
|
+
function deepEqual(a, b) {
|
|
340
|
+
if (a === b) return true;
|
|
341
|
+
if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
|
|
342
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
343
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
344
|
+
if (a.length !== b.length) return false;
|
|
345
|
+
return a.every((val, i) => deepEqual(val, b[i]));
|
|
346
|
+
}
|
|
347
|
+
const keysA = Object.keys(a).sort();
|
|
348
|
+
const keysB = Object.keys(b).sort();
|
|
349
|
+
if (keysA.length !== keysB.length) return false;
|
|
350
|
+
return keysA.every(
|
|
351
|
+
(key, i) => key === keysB[i] && deepEqual(a[key], b[key])
|
|
352
|
+
);
|
|
353
|
+
}
|
|
336
354
|
function priceConfigKey(planKey, price) {
|
|
337
355
|
var _a, _b;
|
|
338
356
|
const interval = price.interval;
|
|
@@ -354,6 +372,30 @@ function priceConfigKey(planKey, price) {
|
|
|
354
372
|
const p = price;
|
|
355
373
|
return `${planKey}_${interval}${suffix}_${p.amount}_${p.currency}`;
|
|
356
374
|
}
|
|
375
|
+
function diffPrices(planKey, configPrices, serverPrices) {
|
|
376
|
+
const serverPricesByKey = /* @__PURE__ */ new Map();
|
|
377
|
+
for (const sp of serverPrices) {
|
|
378
|
+
if (sp.configKey && sp.isActive) {
|
|
379
|
+
serverPricesByKey.set(sp.configKey, sp);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
const creates = [];
|
|
383
|
+
const archives = [];
|
|
384
|
+
const configPriceKeys = /* @__PURE__ */ new Set();
|
|
385
|
+
for (const price of configPrices) {
|
|
386
|
+
const pk = priceConfigKey(planKey, price);
|
|
387
|
+
configPriceKeys.add(pk);
|
|
388
|
+
if (!serverPricesByKey.has(pk)) {
|
|
389
|
+
creates.push({ type: "create", planKey, price, configKey: pk });
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
for (const [pk, sp] of serverPricesByKey) {
|
|
393
|
+
if (!configPriceKeys.has(pk)) {
|
|
394
|
+
archives.push({ type: "archive", planKey, existing: sp, configKey: pk });
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return { creates, archives };
|
|
398
|
+
}
|
|
357
399
|
function computeDiff(config, server, subscriberCounts) {
|
|
358
400
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
359
401
|
const planOps = [];
|
|
@@ -382,25 +424,8 @@ function computeDiff(config, server, subscriberCounts) {
|
|
|
382
424
|
if (existing) {
|
|
383
425
|
const subs = (_c = subscriberCounts[existing.id]) != null ? _c : 0;
|
|
384
426
|
planOps.push({ type: "rename", key, oldKey: plan.renamedFrom, plan, existing, activeSubscribers: subs });
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
if (sp.configKey && sp.isActive) {
|
|
388
|
-
serverPricesByKey.set(sp.configKey, sp);
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
const configPriceKeys = /* @__PURE__ */ new Set();
|
|
392
|
-
for (const price of plan.prices) {
|
|
393
|
-
const pk = priceConfigKey(key, price);
|
|
394
|
-
configPriceKeys.add(pk);
|
|
395
|
-
if (!serverPricesByKey.has(pk)) {
|
|
396
|
-
priceOps.push({ type: "create", planKey: key, price, configKey: pk });
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
for (const [pk, sp] of serverPricesByKey) {
|
|
400
|
-
if (!configPriceKeys.has(pk)) {
|
|
401
|
-
priceOps.push({ type: "archive", planKey: key, existing: sp, configKey: pk });
|
|
402
|
-
}
|
|
403
|
-
}
|
|
427
|
+
const priceDiff = diffPrices(key, plan.prices, existing.prices);
|
|
428
|
+
priceOps.push(...priceDiff.creates, ...priceDiff.archives);
|
|
404
429
|
serverByKey.delete(existing.configKey);
|
|
405
430
|
serverByKey.delete(key);
|
|
406
431
|
continue;
|
|
@@ -425,30 +450,12 @@ function computeDiff(config, server, subscriberCounts) {
|
|
|
425
450
|
const existingFeatures = ((_f = existing.features) != null ? _f : []).sort().join(",");
|
|
426
451
|
const configFeatures = ((_g = plan.features) != null ? _g : []).sort().join(",");
|
|
427
452
|
if (existingFeatures !== configFeatures) changes.push(`features changed`);
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
if (existingEntitlements !== configEntitlements) changes.push(`entitlements changed`);
|
|
431
|
-
const serverPricesByKey = /* @__PURE__ */ new Map();
|
|
432
|
-
for (const sp of existing.prices) {
|
|
433
|
-
if (sp.configKey && sp.isActive) {
|
|
434
|
-
serverPricesByKey.set(sp.configKey, sp);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
const configPriceKeys = /* @__PURE__ */ new Set();
|
|
438
|
-
for (const price of plan.prices) {
|
|
439
|
-
const pk = priceConfigKey(key, price);
|
|
440
|
-
configPriceKeys.add(pk);
|
|
441
|
-
if (!serverPricesByKey.has(pk)) {
|
|
442
|
-
priceOps.push({ type: "create", planKey: key, price, configKey: pk });
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
let hasPriceArchives = false;
|
|
446
|
-
for (const [pk, sp] of serverPricesByKey) {
|
|
447
|
-
if (!configPriceKeys.has(pk)) {
|
|
448
|
-
priceOps.push({ type: "archive", planKey: key, existing: sp, configKey: pk });
|
|
449
|
-
hasPriceArchives = true;
|
|
450
|
-
}
|
|
453
|
+
if (!deepEqual((_h = existing.entitlements) != null ? _h : null, (_i = plan.entitlements) != null ? _i : null)) {
|
|
454
|
+
changes.push(`entitlements changed`);
|
|
451
455
|
}
|
|
456
|
+
const priceDiff = diffPrices(key, plan.prices, existing.prices);
|
|
457
|
+
priceOps.push(...priceDiff.creates, ...priceDiff.archives);
|
|
458
|
+
const hasPriceArchives = priceDiff.archives.length > 0;
|
|
452
459
|
if (changes.length > 0 || hasPriceArchives) {
|
|
453
460
|
const versionBump = hasPriceArchives;
|
|
454
461
|
if (versionBump) changes.push("prices changed");
|
|
@@ -608,6 +615,11 @@ var SyncClient = class {
|
|
|
608
615
|
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
609
616
|
this.apiKey = config.apiKey;
|
|
610
617
|
this.environment = config.environment;
|
|
618
|
+
if (!this.baseUrl.startsWith("https://") && !this.baseUrl.startsWith("http://localhost") && !this.baseUrl.startsWith("http://127.0.0.1")) {
|
|
619
|
+
console.warn(
|
|
620
|
+
"WARNING: AUTHGATE_BASE_URL does not use HTTPS. API key may be transmitted insecurely."
|
|
621
|
+
);
|
|
622
|
+
}
|
|
611
623
|
}
|
|
612
624
|
async request(method, path, body) {
|
|
613
625
|
var _a, _b;
|
|
@@ -629,9 +641,12 @@ var SyncClient = class {
|
|
|
629
641
|
let message;
|
|
630
642
|
try {
|
|
631
643
|
const json = JSON.parse(text);
|
|
632
|
-
message = (_b = (_a = json.error) != null ? _a : json.message) != null ? _b :
|
|
644
|
+
message = (_b = (_a = json.error) != null ? _a : json.message) != null ? _b : "Unknown error";
|
|
633
645
|
} catch (e) {
|
|
634
|
-
message = text;
|
|
646
|
+
message = text.length > 500 ? text.slice(0, 500) + "..." : text;
|
|
647
|
+
}
|
|
648
|
+
if (this.apiKey) {
|
|
649
|
+
message = message.replaceAll(this.apiKey, "[REDACTED]");
|
|
635
650
|
}
|
|
636
651
|
throw new Error(`API error (${res.status}): ${message}`);
|
|
637
652
|
}
|
|
@@ -646,6 +661,7 @@ var SyncClient = class {
|
|
|
646
661
|
async apply(config, force) {
|
|
647
662
|
return this.request("POST", "/api/v1/billing/sync/apply", {
|
|
648
663
|
plans: config.plans,
|
|
664
|
+
features: config.features,
|
|
649
665
|
migrations: config.migrations,
|
|
650
666
|
force
|
|
651
667
|
});
|
|
@@ -653,13 +669,13 @@ var SyncClient = class {
|
|
|
653
669
|
async getMigration(migrationId) {
|
|
654
670
|
return this.request(
|
|
655
671
|
"GET",
|
|
656
|
-
`/api/v1/billing/migrations/${migrationId}`
|
|
672
|
+
`/api/v1/billing/migrations/${encodeURIComponent(migrationId)}`
|
|
657
673
|
);
|
|
658
674
|
}
|
|
659
675
|
async executeMigration(migrationId, opts) {
|
|
660
676
|
return this.request(
|
|
661
677
|
"POST",
|
|
662
|
-
`/api/v1/billing/migrations/${migrationId}/execute`,
|
|
678
|
+
`/api/v1/billing/migrations/${encodeURIComponent(migrationId)}/execute`,
|
|
663
679
|
(opts == null ? void 0 : opts.batchSize) ? { batchSize: opts.batchSize } : void 0
|
|
664
680
|
);
|
|
665
681
|
}
|
|
@@ -781,14 +797,21 @@ function calculateRevenueImpact(diff, subscriberCounts) {
|
|
|
781
797
|
var _a, _b;
|
|
782
798
|
const breakdown = [];
|
|
783
799
|
const archivedPrices = diff.priceOps.filter((op) => op.type === "archive");
|
|
800
|
+
const planKeyToProductId = /* @__PURE__ */ new Map();
|
|
801
|
+
for (const op of diff.planOps) {
|
|
802
|
+
if (op.type === "update" || op.type === "archive" || op.type === "rename") {
|
|
803
|
+
planKeyToProductId.set(op.key, op.existing.id);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
784
806
|
for (const archived of archivedPrices) {
|
|
785
807
|
if (archived.type !== "archive") continue;
|
|
786
808
|
const matching = diff.priceOps.find(
|
|
787
809
|
(op) => op.type === "create" && op.planKey === archived.planKey && op.price.interval === archived.existing.interval && op.price.currency === archived.existing.currency
|
|
788
810
|
);
|
|
789
|
-
const
|
|
811
|
+
const productId = planKeyToProductId.get(archived.planKey);
|
|
812
|
+
const subs = (_a = subscriberCounts[productId != null ? productId : archived.planKey]) != null ? _a : 0;
|
|
790
813
|
const oldAmount = (_b = archived.existing.amount) != null ? _b : 0;
|
|
791
|
-
const newAmount = (matching == null ? void 0 : matching.type) === "create" ? matching.price.amount : 0;
|
|
814
|
+
const newAmount = (matching == null ? void 0 : matching.type) === "create" && "amount" in matching.price ? matching.price.amount : 0;
|
|
792
815
|
const normalizer = archived.existing.interval === "yearly" ? 12 : 1;
|
|
793
816
|
const delta = Math.round((newAmount - oldAmount) / normalizer * subs);
|
|
794
817
|
if (delta !== 0) {
|
|
@@ -837,7 +860,7 @@ function mapPriceOp(op) {
|
|
|
837
860
|
configKey: op.configKey
|
|
838
861
|
};
|
|
839
862
|
if (op.type === "create") {
|
|
840
|
-
base.amount = op.price.amount;
|
|
863
|
+
base.amount = "amount" in op.price ? op.price.amount : void 0;
|
|
841
864
|
base.currency = op.price.currency;
|
|
842
865
|
base.interval = op.price.interval;
|
|
843
866
|
} else {
|
|
@@ -998,7 +1021,13 @@ Environment:
|
|
|
998
1021
|
`;
|
|
999
1022
|
function parseEnvFlag(args) {
|
|
1000
1023
|
const idx = args.indexOf("--env");
|
|
1001
|
-
|
|
1024
|
+
if (idx === -1) return void 0;
|
|
1025
|
+
const value = args[idx + 1];
|
|
1026
|
+
if (!value || value.startsWith("--")) {
|
|
1027
|
+
console.error(import_chalk2.default.red("--env requires a value (e.g., --env staging)."));
|
|
1028
|
+
process.exit(1);
|
|
1029
|
+
}
|
|
1030
|
+
return value;
|
|
1002
1031
|
}
|
|
1003
1032
|
async function main() {
|
|
1004
1033
|
const args = process.argv.slice(2);
|
|
@@ -1054,7 +1083,16 @@ async function main() {
|
|
|
1054
1083
|
const batchIndex = args.indexOf("--batch-size");
|
|
1055
1084
|
const batchSize = batchIndex !== -1 ? parseInt(args[batchIndex + 1], 10) : void 0;
|
|
1056
1085
|
const dryRun = args.includes("--dry-run");
|
|
1057
|
-
const positionalArgs =
|
|
1086
|
+
const positionalArgs = [];
|
|
1087
|
+
const migrateArgs = args.slice(1);
|
|
1088
|
+
for (let i = 0; i < migrateArgs.length; i++) {
|
|
1089
|
+
const a = migrateArgs[i];
|
|
1090
|
+
if (a.startsWith("--")) {
|
|
1091
|
+
if (a === "--id" || a === "--batch-size") i++;
|
|
1092
|
+
continue;
|
|
1093
|
+
}
|
|
1094
|
+
positionalArgs.push(a);
|
|
1095
|
+
}
|
|
1058
1096
|
const fromKey = positionalArgs[0];
|
|
1059
1097
|
const toKey = positionalArgs[1];
|
|
1060
1098
|
await runMigrate({ migrationId, fromKey, toKey, batchSize, dryRun });
|
package/dist/cli.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
renderConfigAsTypeScript,
|
|
5
5
|
serverStateToBillingConfig,
|
|
6
6
|
validateConfig
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-VLL3I4K4.mjs";
|
|
8
8
|
import "./chunk-I4E63NIC.mjs";
|
|
9
9
|
|
|
10
10
|
// src/config-loader.ts
|
|
@@ -168,6 +168,11 @@ var SyncClient = class {
|
|
|
168
168
|
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
169
169
|
this.apiKey = config.apiKey;
|
|
170
170
|
this.environment = config.environment;
|
|
171
|
+
if (!this.baseUrl.startsWith("https://") && !this.baseUrl.startsWith("http://localhost") && !this.baseUrl.startsWith("http://127.0.0.1")) {
|
|
172
|
+
console.warn(
|
|
173
|
+
"WARNING: AUTHGATE_BASE_URL does not use HTTPS. API key may be transmitted insecurely."
|
|
174
|
+
);
|
|
175
|
+
}
|
|
171
176
|
}
|
|
172
177
|
async request(method, path, body) {
|
|
173
178
|
var _a, _b;
|
|
@@ -189,9 +194,12 @@ var SyncClient = class {
|
|
|
189
194
|
let message;
|
|
190
195
|
try {
|
|
191
196
|
const json = JSON.parse(text);
|
|
192
|
-
message = (_b = (_a = json.error) != null ? _a : json.message) != null ? _b :
|
|
197
|
+
message = (_b = (_a = json.error) != null ? _a : json.message) != null ? _b : "Unknown error";
|
|
193
198
|
} catch (e) {
|
|
194
|
-
message = text;
|
|
199
|
+
message = text.length > 500 ? text.slice(0, 500) + "..." : text;
|
|
200
|
+
}
|
|
201
|
+
if (this.apiKey) {
|
|
202
|
+
message = message.replaceAll(this.apiKey, "[REDACTED]");
|
|
195
203
|
}
|
|
196
204
|
throw new Error(`API error (${res.status}): ${message}`);
|
|
197
205
|
}
|
|
@@ -206,6 +214,7 @@ var SyncClient = class {
|
|
|
206
214
|
async apply(config, force) {
|
|
207
215
|
return this.request("POST", "/api/v1/billing/sync/apply", {
|
|
208
216
|
plans: config.plans,
|
|
217
|
+
features: config.features,
|
|
209
218
|
migrations: config.migrations,
|
|
210
219
|
force
|
|
211
220
|
});
|
|
@@ -213,13 +222,13 @@ var SyncClient = class {
|
|
|
213
222
|
async getMigration(migrationId) {
|
|
214
223
|
return this.request(
|
|
215
224
|
"GET",
|
|
216
|
-
`/api/v1/billing/migrations/${migrationId}`
|
|
225
|
+
`/api/v1/billing/migrations/${encodeURIComponent(migrationId)}`
|
|
217
226
|
);
|
|
218
227
|
}
|
|
219
228
|
async executeMigration(migrationId, opts) {
|
|
220
229
|
return this.request(
|
|
221
230
|
"POST",
|
|
222
|
-
`/api/v1/billing/migrations/${migrationId}/execute`,
|
|
231
|
+
`/api/v1/billing/migrations/${encodeURIComponent(migrationId)}/execute`,
|
|
223
232
|
(opts == null ? void 0 : opts.batchSize) ? { batchSize: opts.batchSize } : void 0
|
|
224
233
|
);
|
|
225
234
|
}
|
|
@@ -237,14 +246,21 @@ function calculateRevenueImpact(diff, subscriberCounts) {
|
|
|
237
246
|
var _a, _b;
|
|
238
247
|
const breakdown = [];
|
|
239
248
|
const archivedPrices = diff.priceOps.filter((op) => op.type === "archive");
|
|
249
|
+
const planKeyToProductId = /* @__PURE__ */ new Map();
|
|
250
|
+
for (const op of diff.planOps) {
|
|
251
|
+
if (op.type === "update" || op.type === "archive" || op.type === "rename") {
|
|
252
|
+
planKeyToProductId.set(op.key, op.existing.id);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
240
255
|
for (const archived of archivedPrices) {
|
|
241
256
|
if (archived.type !== "archive") continue;
|
|
242
257
|
const matching = diff.priceOps.find(
|
|
243
258
|
(op) => op.type === "create" && op.planKey === archived.planKey && op.price.interval === archived.existing.interval && op.price.currency === archived.existing.currency
|
|
244
259
|
);
|
|
245
|
-
const
|
|
260
|
+
const productId = planKeyToProductId.get(archived.planKey);
|
|
261
|
+
const subs = (_a = subscriberCounts[productId != null ? productId : archived.planKey]) != null ? _a : 0;
|
|
246
262
|
const oldAmount = (_b = archived.existing.amount) != null ? _b : 0;
|
|
247
|
-
const newAmount = (matching == null ? void 0 : matching.type) === "create" ? matching.price.amount : 0;
|
|
263
|
+
const newAmount = (matching == null ? void 0 : matching.type) === "create" && "amount" in matching.price ? matching.price.amount : 0;
|
|
248
264
|
const normalizer = archived.existing.interval === "yearly" ? 12 : 1;
|
|
249
265
|
const delta = Math.round((newAmount - oldAmount) / normalizer * subs);
|
|
250
266
|
if (delta !== 0) {
|
|
@@ -293,7 +309,7 @@ function mapPriceOp(op) {
|
|
|
293
309
|
configKey: op.configKey
|
|
294
310
|
};
|
|
295
311
|
if (op.type === "create") {
|
|
296
|
-
base.amount = op.price.amount;
|
|
312
|
+
base.amount = "amount" in op.price ? op.price.amount : void 0;
|
|
297
313
|
base.currency = op.price.currency;
|
|
298
314
|
base.interval = op.price.interval;
|
|
299
315
|
} else {
|
|
@@ -454,7 +470,13 @@ Environment:
|
|
|
454
470
|
`;
|
|
455
471
|
function parseEnvFlag(args) {
|
|
456
472
|
const idx = args.indexOf("--env");
|
|
457
|
-
|
|
473
|
+
if (idx === -1) return void 0;
|
|
474
|
+
const value = args[idx + 1];
|
|
475
|
+
if (!value || value.startsWith("--")) {
|
|
476
|
+
console.error(chalk2.red("--env requires a value (e.g., --env staging)."));
|
|
477
|
+
process.exit(1);
|
|
478
|
+
}
|
|
479
|
+
return value;
|
|
458
480
|
}
|
|
459
481
|
async function main() {
|
|
460
482
|
const args = process.argv.slice(2);
|
|
@@ -510,7 +532,16 @@ async function main() {
|
|
|
510
532
|
const batchIndex = args.indexOf("--batch-size");
|
|
511
533
|
const batchSize = batchIndex !== -1 ? parseInt(args[batchIndex + 1], 10) : void 0;
|
|
512
534
|
const dryRun = args.includes("--dry-run");
|
|
513
|
-
const positionalArgs =
|
|
535
|
+
const positionalArgs = [];
|
|
536
|
+
const migrateArgs = args.slice(1);
|
|
537
|
+
for (let i = 0; i < migrateArgs.length; i++) {
|
|
538
|
+
const a = migrateArgs[i];
|
|
539
|
+
if (a.startsWith("--")) {
|
|
540
|
+
if (a === "--id" || a === "--batch-size") i++;
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
positionalArgs.push(a);
|
|
544
|
+
}
|
|
514
545
|
const fromKey = positionalArgs[0];
|
|
515
546
|
const toKey = positionalArgs[1];
|
|
516
547
|
await runMigrate({ migrationId, fromKey, toKey, batchSize, dryRun });
|
|
@@ -551,7 +582,7 @@ async function runPull(opts) {
|
|
|
551
582
|
);
|
|
552
583
|
process.exit(1);
|
|
553
584
|
}
|
|
554
|
-
const { fetchStripeState } = await import("./pull-from-stripe-
|
|
585
|
+
const { fetchStripeState } = await import("./pull-from-stripe-AAY6MWMX.mjs");
|
|
555
586
|
state = await fetchStripeState(stripeKey);
|
|
556
587
|
} else {
|
|
557
588
|
const apiKey = process.env.AUTHGATE_API_KEY;
|
package/dist/index.cjs
CHANGED
|
@@ -58,8 +58,11 @@ function validateTiers(price, prefix) {
|
|
|
58
58
|
}
|
|
59
59
|
for (let j = 0; j < price.tiers.length; j++) {
|
|
60
60
|
const tier = price.tiers[j];
|
|
61
|
-
if (typeof tier.unitAmount !== "number" || tier.unitAmount < 0) {
|
|
62
|
-
throw new Error(`${prefix}.tiers[${j}].unitAmount must be a non-negative number.`);
|
|
61
|
+
if (typeof tier.unitAmount !== "number" || !Number.isFinite(tier.unitAmount) || tier.unitAmount < 0) {
|
|
62
|
+
throw new Error(`${prefix}.tiers[${j}].unitAmount must be a finite non-negative number.`);
|
|
63
|
+
}
|
|
64
|
+
if (tier.flatAmount !== void 0 && (typeof tier.flatAmount !== "number" || !Number.isFinite(tier.flatAmount) || tier.flatAmount < 0)) {
|
|
65
|
+
throw new Error(`${prefix}.tiers[${j}].flatAmount must be a finite non-negative number.`);
|
|
63
66
|
}
|
|
64
67
|
}
|
|
65
68
|
}
|
|
@@ -251,7 +254,7 @@ function normalizeConfig(config) {
|
|
|
251
254
|
const plans = {};
|
|
252
255
|
for (const [key, plan] of Object.entries(config.plans)) {
|
|
253
256
|
if (plan.entitlements) {
|
|
254
|
-
plans[key] = plan;
|
|
257
|
+
plans[key] = __spreadValues({}, plan);
|
|
255
258
|
} else if (plan.features && plan.features.length > 0) {
|
|
256
259
|
const entitlements = {};
|
|
257
260
|
for (const f of plan.features) {
|
|
@@ -259,13 +262,28 @@ function normalizeConfig(config) {
|
|
|
259
262
|
}
|
|
260
263
|
plans[key] = __spreadProps(__spreadValues({}, plan), { entitlements });
|
|
261
264
|
} else {
|
|
262
|
-
plans[key] = plan;
|
|
265
|
+
plans[key] = __spreadValues({}, plan);
|
|
263
266
|
}
|
|
264
267
|
}
|
|
265
268
|
return __spreadProps(__spreadValues({}, config), { plans });
|
|
266
269
|
}
|
|
267
270
|
|
|
268
271
|
// src/diff.ts
|
|
272
|
+
function deepEqual(a, b) {
|
|
273
|
+
if (a === b) return true;
|
|
274
|
+
if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
|
|
275
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
276
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
277
|
+
if (a.length !== b.length) return false;
|
|
278
|
+
return a.every((val, i) => deepEqual(val, b[i]));
|
|
279
|
+
}
|
|
280
|
+
const keysA = Object.keys(a).sort();
|
|
281
|
+
const keysB = Object.keys(b).sort();
|
|
282
|
+
if (keysA.length !== keysB.length) return false;
|
|
283
|
+
return keysA.every(
|
|
284
|
+
(key, i) => key === keysB[i] && deepEqual(a[key], b[key])
|
|
285
|
+
);
|
|
286
|
+
}
|
|
269
287
|
function priceConfigKey(planKey, price) {
|
|
270
288
|
var _a, _b;
|
|
271
289
|
const interval = price.interval;
|
|
@@ -287,6 +305,30 @@ function priceConfigKey(planKey, price) {
|
|
|
287
305
|
const p = price;
|
|
288
306
|
return `${planKey}_${interval}${suffix}_${p.amount}_${p.currency}`;
|
|
289
307
|
}
|
|
308
|
+
function diffPrices(planKey, configPrices, serverPrices) {
|
|
309
|
+
const serverPricesByKey = /* @__PURE__ */ new Map();
|
|
310
|
+
for (const sp of serverPrices) {
|
|
311
|
+
if (sp.configKey && sp.isActive) {
|
|
312
|
+
serverPricesByKey.set(sp.configKey, sp);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const creates = [];
|
|
316
|
+
const archives = [];
|
|
317
|
+
const configPriceKeys = /* @__PURE__ */ new Set();
|
|
318
|
+
for (const price of configPrices) {
|
|
319
|
+
const pk = priceConfigKey(planKey, price);
|
|
320
|
+
configPriceKeys.add(pk);
|
|
321
|
+
if (!serverPricesByKey.has(pk)) {
|
|
322
|
+
creates.push({ type: "create", planKey, price, configKey: pk });
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
for (const [pk, sp] of serverPricesByKey) {
|
|
326
|
+
if (!configPriceKeys.has(pk)) {
|
|
327
|
+
archives.push({ type: "archive", planKey, existing: sp, configKey: pk });
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return { creates, archives };
|
|
331
|
+
}
|
|
290
332
|
function computeDiff(config, server, subscriberCounts) {
|
|
291
333
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
292
334
|
const planOps = [];
|
|
@@ -315,25 +357,8 @@ function computeDiff(config, server, subscriberCounts) {
|
|
|
315
357
|
if (existing) {
|
|
316
358
|
const subs = (_c = subscriberCounts[existing.id]) != null ? _c : 0;
|
|
317
359
|
planOps.push({ type: "rename", key, oldKey: plan.renamedFrom, plan, existing, activeSubscribers: subs });
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
if (sp.configKey && sp.isActive) {
|
|
321
|
-
serverPricesByKey.set(sp.configKey, sp);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
const configPriceKeys = /* @__PURE__ */ new Set();
|
|
325
|
-
for (const price of plan.prices) {
|
|
326
|
-
const pk = priceConfigKey(key, price);
|
|
327
|
-
configPriceKeys.add(pk);
|
|
328
|
-
if (!serverPricesByKey.has(pk)) {
|
|
329
|
-
priceOps.push({ type: "create", planKey: key, price, configKey: pk });
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
for (const [pk, sp] of serverPricesByKey) {
|
|
333
|
-
if (!configPriceKeys.has(pk)) {
|
|
334
|
-
priceOps.push({ type: "archive", planKey: key, existing: sp, configKey: pk });
|
|
335
|
-
}
|
|
336
|
-
}
|
|
360
|
+
const priceDiff = diffPrices(key, plan.prices, existing.prices);
|
|
361
|
+
priceOps.push(...priceDiff.creates, ...priceDiff.archives);
|
|
337
362
|
serverByKey.delete(existing.configKey);
|
|
338
363
|
serverByKey.delete(key);
|
|
339
364
|
continue;
|
|
@@ -358,30 +383,12 @@ function computeDiff(config, server, subscriberCounts) {
|
|
|
358
383
|
const existingFeatures = ((_f = existing.features) != null ? _f : []).sort().join(",");
|
|
359
384
|
const configFeatures = ((_g = plan.features) != null ? _g : []).sort().join(",");
|
|
360
385
|
if (existingFeatures !== configFeatures) changes.push(`features changed`);
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
if (existingEntitlements !== configEntitlements) changes.push(`entitlements changed`);
|
|
364
|
-
const serverPricesByKey = /* @__PURE__ */ new Map();
|
|
365
|
-
for (const sp of existing.prices) {
|
|
366
|
-
if (sp.configKey && sp.isActive) {
|
|
367
|
-
serverPricesByKey.set(sp.configKey, sp);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
const configPriceKeys = /* @__PURE__ */ new Set();
|
|
371
|
-
for (const price of plan.prices) {
|
|
372
|
-
const pk = priceConfigKey(key, price);
|
|
373
|
-
configPriceKeys.add(pk);
|
|
374
|
-
if (!serverPricesByKey.has(pk)) {
|
|
375
|
-
priceOps.push({ type: "create", planKey: key, price, configKey: pk });
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
let hasPriceArchives = false;
|
|
379
|
-
for (const [pk, sp] of serverPricesByKey) {
|
|
380
|
-
if (!configPriceKeys.has(pk)) {
|
|
381
|
-
priceOps.push({ type: "archive", planKey: key, existing: sp, configKey: pk });
|
|
382
|
-
hasPriceArchives = true;
|
|
383
|
-
}
|
|
386
|
+
if (!deepEqual((_h = existing.entitlements) != null ? _h : null, (_i = plan.entitlements) != null ? _i : null)) {
|
|
387
|
+
changes.push(`entitlements changed`);
|
|
384
388
|
}
|
|
389
|
+
const priceDiff = diffPrices(key, plan.prices, existing.prices);
|
|
390
|
+
priceOps.push(...priceDiff.creates, ...priceDiff.archives);
|
|
391
|
+
const hasPriceArchives = priceDiff.archives.length > 0;
|
|
385
392
|
if (changes.length > 0 || hasPriceArchives) {
|
|
386
393
|
const versionBump = hasPriceArchives;
|
|
387
394
|
if (versionBump) changes.push("prices changed");
|
package/dist/index.mjs
CHANGED
|
@@ -6,11 +6,11 @@ async function fetchStripeState(stripeKey) {
|
|
|
6
6
|
const Stripe = (await import("stripe")).default;
|
|
7
7
|
const stripe = new Stripe(stripeKey);
|
|
8
8
|
const [products, prices] = await Promise.all([
|
|
9
|
-
stripe.products.list({ active: true, limit: 100 }),
|
|
10
|
-
stripe.prices.list({ active: true, limit: 100 })
|
|
9
|
+
stripe.products.list({ active: true, limit: 100 }).autoPagingToArray({ limit: 1e4 }),
|
|
10
|
+
stripe.prices.list({ active: true, limit: 100 }).autoPagingToArray({ limit: 1e4 })
|
|
11
11
|
]);
|
|
12
12
|
const pricesByProduct = /* @__PURE__ */ new Map();
|
|
13
|
-
for (const sp of prices
|
|
13
|
+
for (const sp of prices) {
|
|
14
14
|
const productId = typeof sp.product === "string" ? sp.product : sp.product.id;
|
|
15
15
|
const mapped = {
|
|
16
16
|
id: sp.id,
|
|
@@ -27,7 +27,7 @@ async function fetchStripeState(stripeKey) {
|
|
|
27
27
|
mapped
|
|
28
28
|
]);
|
|
29
29
|
}
|
|
30
|
-
const serverProducts = products.
|
|
30
|
+
const serverProducts = products.map((p) => {
|
|
31
31
|
var _a2;
|
|
32
32
|
return {
|
|
33
33
|
id: p.id,
|