mcp-scraper 0.3.20 → 0.3.21

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.
Files changed (33) hide show
  1. package/dist/bin/api-server.cjs +108 -155
  2. package/dist/bin/api-server.cjs.map +1 -1
  3. package/dist/bin/api-server.js +3 -3
  4. package/dist/bin/mcp-scraper-cli.cjs +1 -1
  5. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  6. package/dist/bin/mcp-scraper-cli.js +1 -1
  7. package/dist/bin/mcp-scraper-install.cjs +1 -1
  8. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  9. package/dist/bin/mcp-scraper-install.js +1 -1
  10. package/dist/bin/mcp-stdio-server.cjs +1 -1
  11. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  12. package/dist/bin/mcp-stdio-server.js +2 -2
  13. package/dist/{chunk-IORA5BFI.js → chunk-3KYRG7O7.js} +24 -2
  14. package/dist/chunk-3KYRG7O7.js.map +1 -0
  15. package/dist/{chunk-YZAYOFJV.js → chunk-7PQFU7TV.js} +13 -20
  16. package/dist/chunk-7PQFU7TV.js.map +1 -0
  17. package/dist/chunk-J4PSMJNZ.js +7 -0
  18. package/dist/chunk-J4PSMJNZ.js.map +1 -0
  19. package/dist/{chunk-6N6USFMH.js → chunk-UO252634.js} +2 -2
  20. package/dist/{db-ZWV4Y43C.js → db-EG5ETPTY.js} +4 -2
  21. package/dist/{server-NKZW2SEV.js → server-K4UIE2R3.js} +83 -141
  22. package/dist/server-K4UIE2R3.js.map +1 -0
  23. package/dist/{worker-PR7GODIV.js → worker-AITQTXHM.js} +3 -3
  24. package/docs/mcp-tool-manifest.generated.json +1 -1
  25. package/package.json +1 -1
  26. package/dist/chunk-IORA5BFI.js.map +0 -1
  27. package/dist/chunk-XLHBBA3U.js +0 -7
  28. package/dist/chunk-XLHBBA3U.js.map +0 -1
  29. package/dist/chunk-YZAYOFJV.js.map +0 -1
  30. package/dist/server-NKZW2SEV.js.map +0 -1
  31. /package/dist/{chunk-6N6USFMH.js.map → chunk-UO252634.js.map} +0 -0
  32. /package/dist/{db-ZWV4Y43C.js.map → db-EG5ETPTY.js.map} +0 -0
  33. /package/dist/{worker-PR7GODIV.js.map → worker-AITQTXHM.js.map} +0 -0
@@ -7400,6 +7400,7 @@ __export(db_exports, {
7400
7400
  setExtraConcurrencySlots: () => setExtraConcurrencySlots,
7401
7401
  setPassword: () => setPassword,
7402
7402
  setStripeCustomerId: () => setStripeCustomerId,
7403
+ setSubscriptionTier: () => setSubscriptionTier,
7403
7404
  startHarvestAttempt: () => startHarvestAttempt,
7404
7405
  stripeEventAlreadyProcessed: () => stripeEventAlreadyProcessed,
7405
7406
  updateKpoJobState: () => updateKpoJobState,
@@ -7527,6 +7528,18 @@ async function migrate() {
7527
7528
  await db.execute(`ALTER TABLE users ADD COLUMN concurrency_stripe_sub_id TEXT`);
7528
7529
  } catch {
7529
7530
  }
7531
+ try {
7532
+ await db.execute(`ALTER TABLE users ADD COLUMN subscription_concurrency INTEGER NOT NULL DEFAULT 0`);
7533
+ } catch {
7534
+ }
7535
+ try {
7536
+ await db.execute(`ALTER TABLE users ADD COLUMN subscription_tier TEXT`);
7537
+ } catch {
7538
+ }
7539
+ try {
7540
+ await db.execute(`ALTER TABLE users ADD COLUMN subscription_id TEXT`);
7541
+ } catch {
7542
+ }
7530
7543
  await db.execute(`
7531
7544
  CREATE TABLE IF NOT EXISTS concurrency_locks (
7532
7545
  id TEXT PRIMARY KEY,
@@ -7809,7 +7822,10 @@ function rowToUser(row) {
7809
7822
  stripe_customer_id: row.stripe_customer_id != null ? String(row.stripe_customer_id) : null,
7810
7823
  balance_mc: Number(row.balance_mc ?? 0),
7811
7824
  extra_concurrency_slots: Number(row.extra_concurrency_slots ?? 0),
7812
- concurrency_stripe_sub_id: row.concurrency_stripe_sub_id != null ? String(row.concurrency_stripe_sub_id) : null
7825
+ concurrency_stripe_sub_id: row.concurrency_stripe_sub_id != null ? String(row.concurrency_stripe_sub_id) : null,
7826
+ subscription_concurrency: Number(row.subscription_concurrency ?? 0),
7827
+ subscription_tier: row.subscription_tier != null ? String(row.subscription_tier) : null,
7828
+ subscription_id: row.subscription_id != null ? String(row.subscription_id) : null
7813
7829
  };
7814
7830
  }
7815
7831
  async function getUserByEmail(email) {
@@ -8594,6 +8610,12 @@ async function setConcurrencySubId(userId, subId) {
8594
8610
  args: [subId, userId]
8595
8611
  });
8596
8612
  }
8613
+ async function setSubscriptionTier(userId, tier, concurrency, subId) {
8614
+ await getDb().execute({
8615
+ sql: "UPDATE users SET subscription_tier = ?, subscription_concurrency = ?, subscription_id = ? WHERE id = ?",
8616
+ args: [tier, Math.max(0, Math.round(concurrency)), subId, userId]
8617
+ });
8618
+ }
8597
8619
  async function ensureMigrated(userId) {
8598
8620
  const db = getDb();
8599
8621
  const hasLots = await db.execute({ sql: "SELECT 1 FROM credit_lots WHERE user_id = ? LIMIT 1", args: [userId] });
@@ -9613,7 +9635,7 @@ function insufficientBalanceResponse(balanceMc, requiredMc) {
9613
9635
  topup_url: topupUrl
9614
9636
  };
9615
9637
  }
9616
- var MC_COSTS, MC_PER_BROWSER_MS, BROWSER_OPEN_MIN_BALANCE_MC, MC_PER_CREDIT, CREDIT_COST_CATALOG, CONCURRENCY_PRICE_ID, CONCURRENCY_SLOT_PRICE_USD, CONCURRENCY_SLOT_PRICE_CURRENCY, CONCURRENCY_SLOT_PRICE_INTERVAL, CONCURRENCY_SLOT_PRICE_LABEL, CONCURRENCY_SLOT_TERMINAL_COMMAND, FREE_SIGNUP_MC, FREE_MONTHLY_REFRESH_MC, BALANCE_PRICE_IDS, BALANCE_PACK_LABELS, LedgerOperation;
9638
+ var MC_COSTS, MC_PER_BROWSER_MS, BROWSER_OPEN_MIN_BALANCE_MC, MC_PER_CREDIT, CREDIT_COST_CATALOG, CONCURRENCY_PRICE_ID, SUBSCRIPTION_TIERS, SUBSCRIPTION_TIER_BY_KEY, CONCURRENCY_SLOT_PRICE_USD, CONCURRENCY_SLOT_PRICE_CURRENCY, CONCURRENCY_SLOT_PRICE_INTERVAL, CONCURRENCY_SLOT_PRICE_LABEL, CONCURRENCY_SLOT_TERMINAL_COMMAND, LedgerOperation;
9617
9639
  var init_rates = __esm({
9618
9640
  "src/api/rates.ts"() {
9619
9641
  "use strict";
@@ -9767,27 +9789,22 @@ var init_rates = __esm({
9767
9789
  }
9768
9790
  ];
9769
9791
  CONCURRENCY_PRICE_ID = "price_1Ta1NRS8aAcsk3TGwsRnYbix";
9792
+ SUBSCRIPTION_TIERS = {
9793
+ "price_1TmiHRS8aAcsk3TGwmSNfNIa": { tier: "starter", label: "Starter", price_id: "price_1TmiHRS8aAcsk3TGwmSNfNIa", monthly_usd: 12, credits_mc: 10312500, concurrency: 3, intro_coupon: "mcp-starter-1dollar-intro-12" },
9794
+ "price_1Tmg1nS8aAcsk3TGe8zcnGTM": { tier: "growth", label: "Growth", price_id: "price_1Tmg1nS8aAcsk3TGe8zcnGTM", monthly_usd: 100, credits_mc: 9075e4, concurrency: 10, intro_coupon: null },
9795
+ "price_1Tmg1nS8aAcsk3TGsZw34iXS": { tier: "scale", label: "Scale", price_id: "price_1Tmg1nS8aAcsk3TGsZw34iXS", monthly_usd: 250, credits_mc: 226875e3, concurrency: 20, intro_coupon: null }
9796
+ };
9797
+ SUBSCRIPTION_TIER_BY_KEY = Object.fromEntries(
9798
+ Object.values(SUBSCRIPTION_TIERS).map((t) => [t.tier, t])
9799
+ );
9770
9800
  CONCURRENCY_SLOT_PRICE_USD = 5;
9771
9801
  CONCURRENCY_SLOT_PRICE_CURRENCY = "usd";
9772
9802
  CONCURRENCY_SLOT_PRICE_INTERVAL = "month";
9773
9803
  CONCURRENCY_SLOT_PRICE_LABEL = "$5/month";
9774
9804
  CONCURRENCY_SLOT_TERMINAL_COMMAND = "npx -y -p mcp-scraper@latest mcp-scraper-cli billing concurrency checkout";
9775
- FREE_SIGNUP_MC = 5e5;
9776
- FREE_MONTHLY_REFRESH_MC = 25e4;
9777
- BALANCE_PRICE_IDS = {
9778
- "price_1TZx6rS8aAcsk3TGNMc1Vgpo": 11e6,
9779
- "price_1TZx6sS8aAcsk3TGxgqB7khO": 275e5,
9780
- "price_1TZx6tS8aAcsk3TG8PnJqHlG": 605e5,
9781
- "price_1TZx6tS8aAcsk3TGNgRMpy0e": 121e6
9782
- };
9783
- BALANCE_PACK_LABELS = {
9784
- "price_1TZx6rS8aAcsk3TGNMc1Vgpo": "$10",
9785
- "price_1TZx6sS8aAcsk3TGxgqB7khO": "$25",
9786
- "price_1TZx6tS8aAcsk3TG8PnJqHlG": "$50",
9787
- "price_1TZx6tS8aAcsk3TGNgRMpy0e": "$100"
9788
- };
9789
9805
  LedgerOperation = {
9790
9806
  TOPUP: "topup",
9807
+ SUBSCRIPTION: "subscription",
9791
9808
  SIGNUP_GRANT: "signup_grant",
9792
9809
  MONTHLY_REFRESH: "monthly_free_refresh",
9793
9810
  PAA: "paa",
@@ -12212,7 +12229,8 @@ var init_server_schemas = __esm({
12212
12229
 
12213
12230
  // src/api/concurrency-gates.ts
12214
12231
  function concurrencyLimitForUser(user) {
12215
- return 1 + Math.max(0, Number(user.extra_concurrency_slots ?? 0));
12232
+ const tier = Math.max(0, Number(user.subscription_concurrency ?? 0));
12233
+ return Math.max(1, tier) + Math.max(0, Number(user.extra_concurrency_slots ?? 0));
12216
12234
  }
12217
12235
  function concurrencyLimitExceededResponse(gate) {
12218
12236
  const upgrade = concurrencySlotBillingInfo();
@@ -23863,7 +23881,7 @@ var PACKAGE_VERSION;
23863
23881
  var init_version = __esm({
23864
23882
  "src/version.ts"() {
23865
23883
  "use strict";
23866
- PACKAGE_VERSION = "0.3.20";
23884
+ PACKAGE_VERSION = "0.3.21";
23867
23885
  }
23868
23886
  });
23869
23887
 
@@ -27997,6 +28015,18 @@ var init_browser_agent_console = __esm({
27997
28015
  });
27998
28016
 
27999
28017
  // src/api/stripe-routes.ts
28018
+ function linePriceId(line) {
28019
+ const l = line;
28020
+ return l?.price?.id ?? l?.pricing?.price_details?.price ?? l?.plan?.id;
28021
+ }
28022
+ async function resolveUser(customerId, emailFallback) {
28023
+ let user = await getUserByStripeCustomerId(customerId);
28024
+ if (user) return user;
28025
+ if (!emailFallback) return void 0;
28026
+ user = await getUserByEmail(emailFallback);
28027
+ if (user) await setStripeCustomerId(user.id, customerId);
28028
+ return user;
28029
+ }
28000
28030
  var import_stripe, import_hono12, stripe, stripeApp;
28001
28031
  var init_stripe_routes = __esm({
28002
28032
  "src/api/stripe-routes.ts"() {
@@ -28018,46 +28048,43 @@ var init_stripe_routes = __esm({
28018
28048
  }
28019
28049
  const isNew = await recordStripeEvent(event.id);
28020
28050
  if (!isNew) return c.json({ received: true });
28021
- if (event.type === "checkout.session.completed") {
28022
- const session = event.data.object;
28023
- if (session.payment_status !== "paid") return c.json({ received: true });
28024
- const customerId = session.customer;
28025
- const lineItems = await stripe.checkout.sessions.listLineItems(session.id);
28026
- const priceId = lineItems.data[0]?.price?.id;
28027
- if (!priceId) return c.json({ received: true });
28028
- if (priceId in BALANCE_PRICE_IDS) {
28029
- const mc = BALANCE_PRICE_IDS[priceId];
28030
- const label = BALANCE_PACK_LABELS[priceId] ?? "unknown";
28031
- let user = await getUserByStripeCustomerId(customerId);
28032
- if (!user) {
28033
- const email = session.customer_details?.email;
28034
- if (!email) return c.json({ received: true });
28035
- user = await getUserByEmail(email);
28036
- if (!user) return c.json({ received: true });
28037
- await setStripeCustomerId(user.id, customerId);
28051
+ if (event.type === "invoice.paid" || event.type === "invoice.payment_succeeded") {
28052
+ const invoice = event.data.object;
28053
+ const lineTierId = invoice.lines.data.map(linePriceId).find((id) => id && id in SUBSCRIPTION_TIERS);
28054
+ if (lineTierId) {
28055
+ const tier = SUBSCRIPTION_TIERS[lineTierId];
28056
+ const user = await resolveUser(invoice.customer, invoice.customer_email ?? void 0);
28057
+ if (user && invoice.id && !await ledgerExistsForStripePI(invoice.id)) {
28058
+ await creditMc(user.id, tier.credits_mc, LedgerOperation.SUBSCRIPTION, `${tier.label} subscription credits`, invoice.id);
28059
+ await setSubscriptionTier(user.id, tier.tier, tier.concurrency, invoice.subscription ?? user.subscription_id);
28038
28060
  }
28039
- const pi = session.payment_intent;
28040
- if (await ledgerExistsForStripePI(pi)) return c.json({ received: true });
28041
- await creditMc(user.id, mc, LedgerOperation.TOPUP, `${label} balance top-up`, pi);
28042
28061
  }
28043
28062
  }
28044
- if (event.type === "customer.subscription.created") {
28063
+ if (event.type === "customer.subscription.created" || event.type === "customer.subscription.updated") {
28045
28064
  const sub = event.data.object;
28046
28065
  const priceId = sub.items.data[0]?.price?.id;
28047
- if (priceId !== CONCURRENCY_PRICE_ID) return c.json({ received: true });
28048
28066
  const user = await getUserByStripeCustomerId(sub.customer);
28049
28067
  if (!user) return c.json({ received: true });
28050
- await setExtraConcurrencySlots(user.id, user.extra_concurrency_slots + 1);
28051
- await setConcurrencySubId(user.id, sub.id);
28068
+ if (priceId && priceId in SUBSCRIPTION_TIERS) {
28069
+ const tier = SUBSCRIPTION_TIERS[priceId];
28070
+ const live = sub.status === "active" || sub.status === "trialing";
28071
+ await setSubscriptionTier(user.id, live ? tier.tier : null, live ? tier.concurrency : 0, live ? sub.id : null);
28072
+ } else if (priceId === CONCURRENCY_PRICE_ID && event.type === "customer.subscription.created") {
28073
+ await setExtraConcurrencySlots(user.id, user.extra_concurrency_slots + 1);
28074
+ await setConcurrencySubId(user.id, sub.id);
28075
+ }
28052
28076
  }
28053
28077
  if (event.type === "customer.subscription.deleted") {
28054
28078
  const sub = event.data.object;
28055
28079
  const priceId = sub.items.data[0]?.price?.id;
28056
- if (priceId !== CONCURRENCY_PRICE_ID) return c.json({ received: true });
28057
28080
  const user = await getUserByStripeCustomerId(sub.customer);
28058
28081
  if (!user) return c.json({ received: true });
28059
- await setExtraConcurrencySlots(user.id, Math.max(0, user.extra_concurrency_slots - 1));
28060
- await setConcurrencySubId(user.id, null);
28082
+ if (priceId && priceId in SUBSCRIPTION_TIERS) {
28083
+ await setSubscriptionTier(user.id, null, 0, null);
28084
+ } else if (priceId === CONCURRENCY_PRICE_ID) {
28085
+ await setExtraConcurrencySlots(user.id, Math.max(0, user.extra_concurrency_slots - 1));
28086
+ await setConcurrencySubId(user.id, null);
28087
+ }
28061
28088
  }
28062
28089
  return c.json({ received: true });
28063
28090
  });
@@ -28106,102 +28133,15 @@ var init_site_audit_worker = __esm({
28106
28133
  }
28107
28134
  });
28108
28135
 
28109
- // src/api/billing-schemas.ts
28110
- var import_zod28, BillingCheckoutBodySchema, FreeCreditBreakdownSchema, BillingBalanceResponseSchema, MonthlyRefreshSweepResultSchema;
28111
- var init_billing_schemas = __esm({
28112
- "src/api/billing-schemas.ts"() {
28113
- "use strict";
28114
- import_zod28 = require("zod");
28115
- BillingCheckoutBodySchema = import_zod28.z.object({
28116
- priceId: import_zod28.z.string().min(1)
28117
- });
28118
- FreeCreditBreakdownSchema = import_zod28.z.object({
28119
- signup_grant_mc: import_zod28.z.number().int().nonnegative(),
28120
- monthly_refresh_mc: import_zod28.z.number().int().nonnegative(),
28121
- total_free_mc: import_zod28.z.number().int().nonnegative(),
28122
- signup_grant_credits: import_zod28.z.number().nonnegative(),
28123
- monthly_refresh_credits: import_zod28.z.number().nonnegative(),
28124
- total_free_credits: import_zod28.z.number().nonnegative()
28125
- });
28126
- BillingBalanceResponseSchema = import_zod28.z.object({
28127
- balance_mc: import_zod28.z.number().int().nonnegative(),
28128
- balance_credits: import_zod28.z.number().nonnegative(),
28129
- free_credits: FreeCreditBreakdownSchema,
28130
- ledger: import_zod28.z.array(import_zod28.z.any())
28131
- });
28132
- MonthlyRefreshSweepResultSchema = import_zod28.z.object({
28133
- usersRefreshed: import_zod28.z.number().int().nonnegative(),
28134
- totalMcGranted: import_zod28.z.number().int().nonnegative()
28135
- });
28136
- }
28137
- });
28138
-
28139
28136
  // src/api/credit-operations.ts
28140
- function monthKey(value = /* @__PURE__ */ new Date()) {
28141
- return value.toISOString().slice(0, 7);
28142
- }
28143
- function userCreatedBeforeThisMonth(user) {
28144
- return user.created_at.slice(0, 7) < monthKey();
28145
- }
28146
- async function grantSignupCredit(userId) {
28147
- if (await ledgerExistsForOperation(userId, LedgerOperation.SIGNUP_GRANT)) return;
28148
- await creditMc(userId, FREE_SIGNUP_MC, LedgerOperation.SIGNUP_GRANT, "Welcome to MCP Scraper");
28137
+ async function grantSignupCredit(_userId) {
28138
+ return;
28149
28139
  }
28150
28140
  async function applyMonthlyFreeRefresh(user) {
28151
- if (!userCreatedBeforeThisMonth(user)) return user;
28152
- const claimed = await claimMonthlyFreeRefresh(user.id, monthKey());
28153
- if (!claimed) return user;
28154
- const balance = await creditMc(
28155
- user.id,
28156
- FREE_MONTHLY_REFRESH_MC,
28157
- LedgerOperation.MONTHLY_REFRESH,
28158
- "Monthly free credits"
28159
- );
28160
- return { ...user, balance_mc: balance };
28141
+ return user;
28161
28142
  }
28162
28143
  async function runMonthlyRefreshSweep() {
28163
- const db = getDb();
28164
- const monthStart = /* @__PURE__ */ new Date();
28165
- monthStart.setUTCDate(1);
28166
- monthStart.setUTCHours(0, 0, 0, 0);
28167
- const monthStartIso = monthStart.toISOString();
28168
- const currentMonth = monthKey();
28169
- const res = await db.execute({
28170
- sql: `
28171
- SELECT * FROM users
28172
- WHERE created_at < ?
28173
- AND active = 1
28174
- AND id NOT IN (
28175
- SELECT user_id FROM free_credit_refreshes WHERE month = ?
28176
- )
28177
- `,
28178
- args: [monthStartIso, currentMonth]
28179
- });
28180
- const users = res.rows.map((r) => ({
28181
- id: Number(r.id),
28182
- email: String(r.email),
28183
- name: r.name != null ? String(r.name) : null,
28184
- api_key: String(r.api_key ?? ""),
28185
- key_active: Number(r.key_active ?? 1),
28186
- password_hash: r.password_hash != null ? String(r.password_hash) : null,
28187
- created_at: String(r.created_at),
28188
- active: Number(r.active),
28189
- stripe_customer_id: r.stripe_customer_id != null ? String(r.stripe_customer_id) : null,
28190
- balance_mc: Number(r.balance_mc ?? 0),
28191
- extra_concurrency_slots: Number(r.extra_concurrency_slots ?? 0),
28192
- concurrency_stripe_sub_id: r.concurrency_stripe_sub_id != null ? String(r.concurrency_stripe_sub_id) : null
28193
- }));
28194
- let usersRefreshed = 0;
28195
- let totalMcGranted = 0;
28196
- for (const user of users) {
28197
- const before = user.balance_mc;
28198
- const after = await applyMonthlyFreeRefresh(user);
28199
- if (after.balance_mc !== before) {
28200
- usersRefreshed++;
28201
- totalMcGranted += FREE_MONTHLY_REFRESH_MC;
28202
- }
28203
- }
28204
- return { usersRefreshed, totalMcGranted };
28144
+ return { usersRefreshed: 0, totalMcGranted: 0 };
28205
28145
  }
28206
28146
  async function getFreeCreditBreakdown(userId) {
28207
28147
  const res = await getDb().execute({
@@ -28237,7 +28177,6 @@ var init_credit_operations = __esm({
28237
28177
  "use strict";
28238
28178
  init_db();
28239
28179
  init_rates();
28240
- init_db();
28241
28180
  }
28242
28181
  });
28243
28182
 
@@ -28587,7 +28526,6 @@ var init_server = __esm({
28587
28526
  init_db();
28588
28527
  import_stripe2 = __toESM(require("stripe"), 1);
28589
28528
  init_rates();
28590
- init_billing_schemas();
28591
28529
  init_server_schemas();
28592
28530
  init_schemas3();
28593
28531
  init_credit_operations();
@@ -28781,8 +28719,10 @@ var init_server = __esm({
28781
28719
  balance_mc: user.balance_mc,
28782
28720
  balance_credits: user.balance_mc / 1e3,
28783
28721
  extra_concurrency_slots: user.extra_concurrency_slots,
28784
- concurrency_limit: 1 + user.extra_concurrency_slots,
28722
+ concurrency_limit: concurrencyLimitForUser(user),
28785
28723
  has_concurrency_sub: !!user.concurrency_stripe_sub_id,
28724
+ subscription_tier: user.subscription_tier,
28725
+ subscription_concurrency: user.subscription_concurrency,
28786
28726
  ...stats
28787
28727
  });
28788
28728
  });
@@ -29208,13 +29148,12 @@ var init_server = __esm({
29208
29148
  });
29209
29149
  });
29210
29150
  app.post("/billing/checkout", requireAllowedOrigin, sessionAuth, async (c) => {
29151
+ return c.json({ error: "One-time credit packs are no longer available. Subscribe to a plan instead \u2014 use /billing/subscribe.", error_code: "packs_removed" }, 410);
29152
+ });
29153
+ app.post("/billing/concurrency/checkout", requireAllowedOrigin, sessionAuth, async (c) => {
29211
29154
  try {
29212
29155
  const user = c.get("sessionUser");
29213
- const raw = await c.req.json();
29214
- const parsed = BillingCheckoutBodySchema.safeParse(raw);
29215
- if (!parsed.success) return c.json({ error: "Invalid request" }, 400);
29216
- const { priceId } = parsed.data;
29217
- if (!(priceId in BALANCE_PRICE_IDS)) return c.json({ error: "Invalid price" }, 400);
29156
+ if (user.concurrency_stripe_sub_id) return c.json({ error: "Already subscribed \u2014 cancel existing slot first to change." }, 409);
29218
29157
  const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
29219
29158
  if (!secret2) return c.json({ error: "Stripe is not configured." }, 503);
29220
29159
  const stripeClient = new import_stripe2.default(secret2, { apiVersion: STRIPE_API_VERSION });
@@ -29226,26 +29165,28 @@ var init_server = __esm({
29226
29165
  }
29227
29166
  const session = await stripeClient.checkout.sessions.create({
29228
29167
  customer: customerId,
29229
- mode: "payment",
29230
- line_items: [{ price: priceId, quantity: 1 }],
29168
+ mode: "subscription",
29169
+ line_items: [{ price: CONCURRENCY_PRICE_ID, quantity: 1 }],
29231
29170
  ui_mode: "embedded",
29232
29171
  automatic_tax: { enabled: true },
29233
29172
  billing_address_collection: "required",
29234
29173
  customer_update: { address: "auto", name: "auto" },
29235
29174
  tax_id_collection: { enabled: true },
29236
- return_url: `${appOrigin()}/billing?session_id={CHECKOUT_SESSION_ID}`
29175
+ return_url: `${appOrigin()}/billing?slot_added=1`
29237
29176
  });
29238
29177
  return c.json({ clientSecret: session.client_secret });
29239
29178
  } catch (err) {
29240
29179
  const message = err instanceof Error ? err.message : "Unable to start checkout.";
29241
- console.error("[billing/checkout]", message);
29180
+ console.error("[billing/concurrency/checkout]", message);
29242
29181
  return c.json({ error: message }, 500);
29243
29182
  }
29244
29183
  });
29245
- app.post("/billing/concurrency/checkout", requireAllowedOrigin, sessionAuth, async (c) => {
29184
+ app.post("/billing/subscribe", requireAllowedOrigin, sessionAuth, async (c) => {
29246
29185
  try {
29247
29186
  const user = c.get("sessionUser");
29248
- if (user.concurrency_stripe_sub_id) return c.json({ error: "Already subscribed \u2014 cancel existing slot first to change." }, 409);
29187
+ const raw = await c.req.json().catch(() => ({}));
29188
+ const tier = SUBSCRIPTION_TIER_BY_KEY[String(raw.tier ?? "")];
29189
+ if (!tier) return c.json({ error: "Invalid tier. Choose starter, growth, or scale." }, 400);
29249
29190
  const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
29250
29191
  if (!secret2) return c.json({ error: "Stripe is not configured." }, 503);
29251
29192
  const stripeClient = new import_stripe2.default(secret2, { apiVersion: STRIPE_API_VERSION });
@@ -29255,21 +29196,33 @@ var init_server = __esm({
29255
29196
  customerId = customer.id;
29256
29197
  await setStripeCustomerId(user.id, customerId);
29257
29198
  }
29199
+ if (user.subscription_id) {
29200
+ const sub = await stripeClient.subscriptions.retrieve(user.subscription_id);
29201
+ const itemId = sub.items.data[0]?.id;
29202
+ if (itemId) {
29203
+ await stripeClient.subscriptions.update(user.subscription_id, {
29204
+ items: [{ id: itemId, price: tier.price_id }],
29205
+ proration_behavior: "create_prorations"
29206
+ });
29207
+ return c.json({ updated: true, tier: tier.tier });
29208
+ }
29209
+ }
29258
29210
  const session = await stripeClient.checkout.sessions.create({
29259
29211
  customer: customerId,
29260
29212
  mode: "subscription",
29261
- line_items: [{ price: CONCURRENCY_PRICE_ID, quantity: 1 }],
29213
+ line_items: [{ price: tier.price_id, quantity: 1 }],
29214
+ ...tier.intro_coupon ? { discounts: [{ coupon: tier.intro_coupon }] } : {},
29262
29215
  ui_mode: "embedded",
29263
29216
  automatic_tax: { enabled: true },
29264
29217
  billing_address_collection: "required",
29265
29218
  customer_update: { address: "auto", name: "auto" },
29266
29219
  tax_id_collection: { enabled: true },
29267
- return_url: `${appOrigin()}/billing?slot_added=1`
29220
+ return_url: `${appOrigin()}/billing?subscribed=${tier.tier}`
29268
29221
  });
29269
29222
  return c.json({ clientSecret: session.client_secret });
29270
29223
  } catch (err) {
29271
- const message = err instanceof Error ? err.message : "Unable to start checkout.";
29272
- console.error("[billing/concurrency/checkout]", message);
29224
+ const message = err instanceof Error ? err.message : "Unable to start subscription.";
29225
+ console.error("[billing/subscribe]", message);
29273
29226
  return c.json({ error: message }, 500);
29274
29227
  }
29275
29228
  });
@@ -29317,7 +29270,7 @@ var init_server = __esm({
29317
29270
  current_extra_slots: user.extra_concurrency_slots,
29318
29271
  current_limit: concurrencyLimitForUser(user),
29319
29272
  after_checkout_extra_slots: user.extra_concurrency_slots + 1,
29320
- after_checkout_limit: concurrencyLimitForUser({ extra_concurrency_slots: user.extra_concurrency_slots + 1 })
29273
+ after_checkout_limit: concurrencyLimitForUser({ extra_concurrency_slots: user.extra_concurrency_slots + 1, subscription_concurrency: user.subscription_concurrency })
29321
29274
  },
29322
29275
  next_step: "Open checkout_url in a browser, complete checkout, then restart or retry the MCP request."
29323
29276
  });