@solvapay/server 2.7.0 → 2.8.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/index.cjs CHANGED
@@ -85,7 +85,7 @@ __export(index_exports, {
85
85
  });
86
86
  module.exports = __toCommonJS(index_exports);
87
87
  var import_node_crypto = __toESM(require("crypto"), 1);
88
- var import_core13 = require("@solvapay/core");
88
+ var import_core14 = require("@solvapay/core");
89
89
 
90
90
  // src/client.ts
91
91
  var import_core = require("@solvapay/core");
@@ -760,6 +760,9 @@ function classifyPaywallState(limits) {
760
760
  if (limits.activationRequired === true || limits.paywallReason === "activation_required") {
761
761
  return { kind: "activation_required" };
762
762
  }
763
+ if (limits.paywallReason === "limit_reached") {
764
+ return { kind: "limit_reached" };
765
+ }
763
766
  if (limits.paywallReason === "topup_required") {
764
767
  return { kind: "topup_required" };
765
768
  }
@@ -823,9 +826,12 @@ function formatCreditsWithMoney(credits, gate) {
823
826
  }
824
827
  return `${amount} credits`;
825
828
  }
829
+ function isFreeMeter(name) {
830
+ return typeof name === "string" && name.startsWith("free-");
831
+ }
826
832
  function meterLabel(gate) {
827
833
  if (!gate.meterName) return "units";
828
- return gate.meterName.replace(/_/g, " ");
834
+ return gate.meterName.replace(/[_-]/g, " ");
829
835
  }
830
836
  function namedCheckoutMarkdown(url, label = "Open checkout") {
831
837
  return `[${label}](${url})`;
@@ -861,6 +867,12 @@ function buildGateMessage(state, gate) {
861
867
  switch (state.kind) {
862
868
  case "limit_reached": {
863
869
  const included = gate.included;
870
+ if (isFreeMeter(gate.meterName)) {
871
+ const total = included?.total;
872
+ const usedLine2 = total !== void 0 ? `You've used all ${total} ${meterLabel(gate)} for this period.` : `You've used all ${meterLabel(gate)} for this period.`;
873
+ const switchLine2 = ladder ? ` Pick a plan to keep going: ${ladder}. ${callViewer("account").replace(/^c/, "C")} for usage and recovery.` : recoverClause(url, "keep going", "checkout");
874
+ return `${usedLine2}${switchLine2}`;
875
+ }
864
876
  const price = gate.unitPriceMinor != null && gate.currency ? formatMinor(gate.unitPriceMinor, gate.currency) : null;
865
877
  const usedLine = included ? `You've used ${included.used} of ${included.total} included ${meterLabel(gate)} this period.` : `You've reached the included usage for this period.`;
866
878
  const nextLine = price ? ` The next call is ${price}.` : "";
@@ -1187,6 +1199,7 @@ function createRequestDeduplicator(options = {}) {
1187
1199
  }
1188
1200
 
1189
1201
  // src/paywall.ts
1202
+ var import_core4 = require("@solvapay/core");
1190
1203
  var PaywallError = class extends Error {
1191
1204
  /**
1192
1205
  * Creates a new PaywallError instance.
@@ -1237,6 +1250,15 @@ var sharedLimitsFetchDeduplicator = createRequestDeduplicator({
1237
1250
  });
1238
1251
  var sharedLimitsFetchClaims = /* @__PURE__ */ new Map();
1239
1252
  var EXTRA_FORWARD_KEY = "__solvapayExtra";
1253
+ function trackUsageExtra(metadata, outcome, consequence) {
1254
+ if (metadata.freeLimit) {
1255
+ return { meterName: metadata.freeLimit.meter };
1256
+ }
1257
+ if (outcome === "success") {
1258
+ return { usageClass: consequence === "overage" ? "overage" : "included" };
1259
+ }
1260
+ return void 0;
1261
+ }
1240
1262
  var SolvaPayPaywall = class {
1241
1263
  constructor(apiClient, options = {}) {
1242
1264
  this.apiClient = apiClient;
@@ -1296,10 +1318,19 @@ var SolvaPayPaywall = class {
1296
1318
  */
1297
1319
  async decide(args, metadata = {}, getCustomerRef) {
1298
1320
  const product = this.resolveProduct(metadata);
1299
- const usageType = metadata.meterName || metadata.usageType || "requests";
1321
+ const usageType = metadata.freeLimit?.meter || metadata.meterName || metadata.usageType || "requests";
1300
1322
  const requestId = this.generateRequestId();
1301
1323
  const startTime = Date.now();
1302
1324
  const inputCustomerRef = getCustomerRef ? getCustomerRef(args) : args.auth?.customer_ref || "anonymous";
1325
+ if (metadata.freeLimit) {
1326
+ const resolved = typeof inputCustomerRef === "string" ? inputCustomerRef.trim() : "";
1327
+ if (!resolved || resolved === "anonymous") {
1328
+ throw new import_core4.SolvaPayError("identity required", {
1329
+ status: 401,
1330
+ code: "identity_required"
1331
+ });
1332
+ }
1333
+ }
1303
1334
  let backendCustomerRef;
1304
1335
  if (inputCustomerRef.startsWith("cus_")) {
1305
1336
  backendCustomerRef = inputCustomerRef;
@@ -1350,7 +1381,8 @@ var SolvaPayPaywall = class {
1350
1381
  // `checkLimitsCore`, which powers the React `useLimits` hook)
1351
1382
  // leave this unset and the backend skips the session-creation
1352
1383
  // side effect.
1353
- includeCheckoutSession: true
1384
+ includeCheckoutSession: true,
1385
+ ...metadata.freeLimit ? { freeAllowance: metadata.freeLimit } : {}
1354
1386
  });
1355
1387
  }
1356
1388
  );
@@ -1394,7 +1426,8 @@ var SolvaPayPaywall = class {
1394
1426
  "paywall",
1395
1427
  requestId,
1396
1428
  latencyMs,
1397
- metadata.toolName
1429
+ metadata.toolName,
1430
+ metadata.freeLimit ? { meterName: metadata.freeLimit.meter } : void 0
1398
1431
  ).catch(() => void 0);
1399
1432
  const gate = buildPaywallGate(
1400
1433
  product,
@@ -1441,7 +1474,7 @@ var SolvaPayPaywall = class {
1441
1474
  */
1442
1475
  async runAllow(decision, handler, metadata, args) {
1443
1476
  const product = this.resolveProduct(metadata);
1444
- const usageType = metadata.meterName || metadata.usageType || "requests";
1477
+ const usageType = metadata.freeLimit?.meter || metadata.meterName || metadata.usageType || "requests";
1445
1478
  const requestId = decision.requestId;
1446
1479
  const startTime = Date.now();
1447
1480
  const forwardedExtra = args[EXTRA_FORWARD_KEY];
@@ -1460,7 +1493,8 @@ var SolvaPayPaywall = class {
1460
1493
  "success",
1461
1494
  requestId,
1462
1495
  latencyMs,
1463
- metadata.toolName
1496
+ metadata.toolName,
1497
+ trackUsageExtra(metadata, "success", decision.consequence)
1464
1498
  ).catch(() => void 0);
1465
1499
  return result;
1466
1500
  } catch (error) {
@@ -1479,7 +1513,8 @@ var SolvaPayPaywall = class {
1479
1513
  "fail",
1480
1514
  requestId,
1481
1515
  latencyMs,
1482
- metadata.toolName
1516
+ metadata.toolName,
1517
+ trackUsageExtra(metadata, "fail")
1483
1518
  ).catch(() => void 0);
1484
1519
  }
1485
1520
  throw error;
@@ -1670,7 +1705,7 @@ var SolvaPayPaywall = class {
1670
1705
  }
1671
1706
  return backendRef;
1672
1707
  }
1673
- async trackUsage(customerRef, productRef, action, outcome, requestId, actionDuration, toolName) {
1708
+ async trackUsage(customerRef, productRef, action, outcome, requestId, actionDuration, toolName, extra) {
1674
1709
  await withRetry(
1675
1710
  () => this.apiClient.trackUsage({
1676
1711
  customerRef,
@@ -1683,7 +1718,9 @@ var SolvaPayPaywall = class {
1683
1718
  metadata: {
1684
1719
  action: action || "api_requests",
1685
1720
  requestId,
1686
- ...toolName ? { toolName } : {}
1721
+ ...toolName ? { toolName } : {},
1722
+ ...extra?.meterName ? { meterName: extra.meterName } : {},
1723
+ ...extra?.usageClass ? { usageClass: extra.usageClass } : {}
1687
1724
  },
1688
1725
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1689
1726
  }),
@@ -2023,7 +2060,7 @@ var McpAdapter = class {
2023
2060
  };
2024
2061
 
2025
2062
  // src/factory.ts
2026
- var import_core4 = require("@solvapay/core");
2063
+ var import_core5 = require("@solvapay/core");
2027
2064
 
2028
2065
  // src/virtual-tools.ts
2029
2066
  var TOOL_GET_USER_INFO = {
@@ -2265,7 +2302,7 @@ function registerVirtualToolsMcpImpl(server, apiClient, options) {
2265
2302
  function createSolvaPay(config) {
2266
2303
  let resolvedConfig;
2267
2304
  if (!config) {
2268
- const envConfig = (0, import_core4.getSolvaPayConfig)();
2305
+ const envConfig = (0, import_core5.getSolvaPayConfig)();
2269
2306
  resolvedConfig = {
2270
2307
  apiKey: envConfig.apiKey,
2271
2308
  apiBaseUrl: envConfig.apiBaseUrl
@@ -2301,25 +2338,25 @@ function createSolvaPay(config) {
2301
2338
  },
2302
2339
  createPaymentIntent(params) {
2303
2340
  if (!apiClient.createPaymentIntent) {
2304
- throw new import_core4.SolvaPayError("createPaymentIntent is not available on this API client");
2341
+ throw new import_core5.SolvaPayError("createPaymentIntent is not available on this API client");
2305
2342
  }
2306
2343
  return apiClient.createPaymentIntent(params);
2307
2344
  },
2308
2345
  createTopupPaymentIntent(params) {
2309
2346
  if (!apiClient.createTopupPaymentIntent) {
2310
- throw new import_core4.SolvaPayError("createTopupPaymentIntent is not available on this API client");
2347
+ throw new import_core5.SolvaPayError("createTopupPaymentIntent is not available on this API client");
2311
2348
  }
2312
2349
  return apiClient.createTopupPaymentIntent(params);
2313
2350
  },
2314
2351
  processPaymentIntent(params) {
2315
2352
  if (!apiClient.processPaymentIntent) {
2316
- throw new import_core4.SolvaPayError("processPaymentIntent is not available on this API client");
2353
+ throw new import_core5.SolvaPayError("processPaymentIntent is not available on this API client");
2317
2354
  }
2318
2355
  return apiClient.processPaymentIntent(params);
2319
2356
  },
2320
2357
  attachBusinessDetails(params) {
2321
2358
  if (!apiClient.attachBusinessDetails) {
2322
- throw new import_core4.SolvaPayError("attachBusinessDetails is not available on this API client");
2359
+ throw new import_core5.SolvaPayError("attachBusinessDetails is not available on this API client");
2323
2360
  }
2324
2361
  return apiClient.attachBusinessDetails(params);
2325
2362
  },
@@ -2331,13 +2368,13 @@ function createSolvaPay(config) {
2331
2368
  },
2332
2369
  trackUsageBulk(params) {
2333
2370
  if (!apiClient.trackUsageBulk) {
2334
- throw new import_core4.SolvaPayError("trackUsageBulk is not available on this API client");
2371
+ throw new import_core5.SolvaPayError("trackUsageBulk is not available on this API client");
2335
2372
  }
2336
2373
  return apiClient.trackUsageBulk(params);
2337
2374
  },
2338
2375
  createCustomer(params) {
2339
2376
  if (!apiClient.createCustomer) {
2340
- throw new import_core4.SolvaPayError("createCustomer is not available on this API client");
2377
+ throw new import_core5.SolvaPayError("createCustomer is not available on this API client");
2341
2378
  }
2342
2379
  return apiClient.createCustomer({ ...params, metadata: params.metadata ?? {} });
2343
2380
  },
@@ -2346,13 +2383,13 @@ function createSolvaPay(config) {
2346
2383
  },
2347
2384
  assignCredits(params) {
2348
2385
  if (!apiClient.assignCredits) {
2349
- throw new import_core4.SolvaPayError("assignCredits is not available on this API client");
2386
+ throw new import_core5.SolvaPayError("assignCredits is not available on this API client");
2350
2387
  }
2351
2388
  return apiClient.assignCredits(params);
2352
2389
  },
2353
2390
  getCustomerBalance(params) {
2354
2391
  if (!apiClient.getCustomerBalance) {
2355
- throw new import_core4.SolvaPayError("getCustomerBalance is not available on this API client");
2392
+ throw new import_core5.SolvaPayError("getCustomerBalance is not available on this API client");
2356
2393
  }
2357
2394
  return apiClient.getCustomerBalance(params);
2358
2395
  },
@@ -2370,19 +2407,19 @@ function createSolvaPay(config) {
2370
2407
  },
2371
2408
  activatePlan(params) {
2372
2409
  if (!apiClient.activatePlan) {
2373
- throw new import_core4.SolvaPayError("activatePlan is not available on this API client");
2410
+ throw new import_core5.SolvaPayError("activatePlan is not available on this API client");
2374
2411
  }
2375
2412
  return apiClient.activatePlan(params);
2376
2413
  },
2377
2414
  bootstrapMcpProduct(params) {
2378
2415
  if (!apiClient.bootstrapMcpProduct) {
2379
- throw new import_core4.SolvaPayError("bootstrapMcpProduct is not available on this API client");
2416
+ throw new import_core5.SolvaPayError("bootstrapMcpProduct is not available on this API client");
2380
2417
  }
2381
2418
  return apiClient.bootstrapMcpProduct(params);
2382
2419
  },
2383
2420
  configureMcpPlans(productRef, params) {
2384
2421
  if (!apiClient.configureMcpPlans) {
2385
- throw new import_core4.SolvaPayError("configureMcpPlans is not available on this API client");
2422
+ throw new import_core5.SolvaPayError("configureMcpPlans is not available on this API client");
2386
2423
  }
2387
2424
  return apiClient.configureMcpPlans(productRef, params);
2388
2425
  },
@@ -2395,12 +2432,13 @@ function createSolvaPay(config) {
2395
2432
  // Payable API for framework-specific handlers
2396
2433
  payable(options = {}) {
2397
2434
  const product = resolveProductRef(options.productRef || options.product);
2398
- const usageType = options.meterName || options.usageType || "requests";
2435
+ const usageType = options.freeLimit?.meter || options.meterName || options.usageType || "requests";
2399
2436
  const metadata = {
2400
2437
  product,
2401
2438
  meterName: usageType,
2402
2439
  usageType,
2403
- ...options.toolName ? { toolName: options.toolName } : {}
2440
+ ...options.toolName ? { toolName: options.toolName } : {},
2441
+ ...options.freeLimit ? { freeLimit: options.freeLimit } : {}
2404
2442
  };
2405
2443
  return {
2406
2444
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -2489,6 +2527,8 @@ function createSolvaPay(config) {
2489
2527
  const errMeta = opts?.error !== void 0 ? {
2490
2528
  error: opts.error instanceof Error ? opts.error.message : String(opts.error)
2491
2529
  } : {};
2530
+ const isFree = Boolean(decideMetadata.freeLimit);
2531
+ const usageClass = !isFree && outcome === "success" ? decision.consequence === "overage" ? "overage" : "included" : void 0;
2492
2532
  const trackPromise = apiClient.trackUsage({
2493
2533
  customerRef,
2494
2534
  productRef,
@@ -2501,6 +2541,8 @@ function createSolvaPay(config) {
2501
2541
  action: meterName,
2502
2542
  requestId,
2503
2543
  ...decideMetadata.toolName ? { toolName: decideMetadata.toolName } : {},
2544
+ ...isFree && decideMetadata.freeLimit ? { meterName: decideMetadata.freeLimit.meter } : {},
2545
+ ...usageClass ? { usageClass } : {},
2504
2546
  ...errMeta,
2505
2547
  ...opts?.metadata ?? {}
2506
2548
  },
@@ -2533,7 +2575,7 @@ async function resolveCustomerRefFromRequest(req, options) {
2533
2575
  }
2534
2576
 
2535
2577
  // src/verify-product-configuration.ts
2536
- var import_core5 = require("@solvapay/core");
2578
+ var import_core6 = require("@solvapay/core");
2537
2579
  async function verifyProductConfiguration(options) {
2538
2580
  const { apiClient, productRef } = options;
2539
2581
  const apiBaseUrl = options.apiBaseUrl ?? "(unknown API base URL)";
@@ -2544,7 +2586,7 @@ async function verifyProductConfiguration(options) {
2544
2586
  }
2545
2587
  try {
2546
2588
  const product = await apiClient.getProduct(productRef);
2547
- const readiness = (0, import_core5.evaluateProductReadiness)({
2589
+ const readiness = (0, import_core6.evaluateProductReadiness)({
2548
2590
  status: product.status,
2549
2591
  plans: product.plans
2550
2592
  });
@@ -2557,8 +2599,8 @@ async function verifyProductConfiguration(options) {
2557
2599
  issues: readiness.issues
2558
2600
  };
2559
2601
  } catch (error) {
2560
- const status = error instanceof import_core5.SolvaPayError ? error.status : void 0;
2561
- const code = error instanceof import_core5.SolvaPayError ? error.code : void 0;
2602
+ const status = error instanceof import_core6.SolvaPayError ? error.status : void 0;
2603
+ const code = error instanceof import_core6.SolvaPayError ? error.code : void 0;
2562
2604
  if (status === 404 && code === "non_json_response") {
2563
2605
  throw new Error(
2564
2606
  `Could not reach ${apiBaseUrl} while verifying SOLVAPAY_PRODUCT_REF "${productRef}": the server returned a non-JSON response (often an offline ngrok tunnel or proxy error page). Check SOLVAPAY_API_BASE_URL and that the API tunnel is running.`
@@ -2655,13 +2697,13 @@ var PaywallStructuredContentSchema = import_zod.z.discriminatedUnion("kind", [
2655
2697
  ]);
2656
2698
 
2657
2699
  // src/helpers/error.ts
2658
- var import_core6 = require("@solvapay/core");
2700
+ var import_core7 = require("@solvapay/core");
2659
2701
  function isErrorResult(result) {
2660
2702
  return typeof result === "object" && result !== null && "error" in result && "status" in result;
2661
2703
  }
2662
2704
  function handleRouteError(error, operationName, defaultMessage) {
2663
2705
  console.error(`[${operationName}] Error:`, error);
2664
- if (error instanceof import_core6.SolvaPayError) {
2706
+ if (error instanceof import_core7.SolvaPayError) {
2665
2707
  const errorMessage2 = error.message;
2666
2708
  return {
2667
2709
  error: errorMessage2,
@@ -2849,7 +2891,7 @@ async function getCustomerBalanceCore(request, options = {}) {
2849
2891
  }
2850
2892
 
2851
2893
  // src/helpers/payment.ts
2852
- var import_core7 = require("@solvapay/core");
2894
+ var import_core8 = require("@solvapay/core");
2853
2895
 
2854
2896
  // src/helpers/balance-poll.ts
2855
2897
  var TOPUP_BALANCE_POLL_DELAYS_MS = [500, 1e3, 2e3, 4e3];
@@ -2991,7 +3033,7 @@ async function attachBusinessDetailsCore(request, body, options = {}) {
2991
3033
  status: 400
2992
3034
  };
2993
3035
  }
2994
- const validation = (0, import_core7.validateBusinessDetails)({
3036
+ const validation = (0, import_core8.validateBusinessDetails)({
2995
3037
  isBusiness: body.isBusiness,
2996
3038
  businessName: body.businessName,
2997
3039
  country: body.country,
@@ -3158,7 +3200,7 @@ async function createCustomerSessionCore(request, options = {}) {
3158
3200
  }
3159
3201
 
3160
3202
  // src/helpers/renewal.ts
3161
- var import_core8 = require("@solvapay/core");
3203
+ var import_core9 = require("@solvapay/core");
3162
3204
  async function cancelPurchaseCore(request, body, options = {}) {
3163
3205
  try {
3164
3206
  if (!body.purchaseRef) {
@@ -3204,7 +3246,7 @@ async function cancelPurchaseCore(request, body, options = {}) {
3204
3246
  await new Promise((resolve) => setTimeout(resolve, 500));
3205
3247
  return cancelledPurchase;
3206
3248
  } catch (error) {
3207
- if (error instanceof import_core8.SolvaPayError) {
3249
+ if (error instanceof import_core9.SolvaPayError) {
3208
3250
  const errorMessage = error.message;
3209
3251
  if (errorMessage.includes("not found")) {
3210
3252
  return {
@@ -3272,7 +3314,7 @@ async function reactivatePurchaseCore(request, body, options = {}) {
3272
3314
  await new Promise((resolve) => setTimeout(resolve, 500));
3273
3315
  return reactivatedPurchase;
3274
3316
  } catch (error) {
3275
- if (error instanceof import_core8.SolvaPayError) {
3317
+ if (error instanceof import_core9.SolvaPayError) {
3276
3318
  const errorMessage = error.message;
3277
3319
  if (errorMessage.includes("not found")) {
3278
3320
  return {
@@ -3442,7 +3484,7 @@ async function getHistoryCore(request, input, options = {}) {
3442
3484
  }
3443
3485
 
3444
3486
  // src/helpers/plans.ts
3445
- var import_core9 = require("@solvapay/core");
3487
+ var import_core10 = require("@solvapay/core");
3446
3488
  async function listPlansCore(request, options = {}) {
3447
3489
  try {
3448
3490
  const url = new URL(request.url);
@@ -3454,7 +3496,7 @@ async function listPlansCore(request, options = {}) {
3454
3496
  };
3455
3497
  }
3456
3498
  const apiClient = options.solvaPay?.apiClient ?? (() => {
3457
- const config = (0, import_core9.getSolvaPayConfig)();
3499
+ const config = (0, import_core10.getSolvaPayConfig)();
3458
3500
  if (!config.apiKey) return null;
3459
3501
  return createSolvaPayClient({
3460
3502
  apiKey: config.apiKey,
@@ -3517,11 +3559,11 @@ async function checkLimitsCore(request, options = {}) {
3517
3559
  }
3518
3560
 
3519
3561
  // src/helpers/merchant.ts
3520
- var import_core10 = require("@solvapay/core");
3562
+ var import_core11 = require("@solvapay/core");
3521
3563
  async function getMerchantCore(_request, options = {}) {
3522
3564
  try {
3523
3565
  const apiClient = options.solvaPay?.apiClient ?? (() => {
3524
- const config = (0, import_core10.getSolvaPayConfig)();
3566
+ const config = (0, import_core11.getSolvaPayConfig)();
3525
3567
  if (!config.apiKey) return null;
3526
3568
  return createSolvaPayClient({
3527
3569
  apiKey: config.apiKey,
@@ -3548,7 +3590,7 @@ async function getMerchantCore(_request, options = {}) {
3548
3590
  }
3549
3591
 
3550
3592
  // src/helpers/product.ts
3551
- var import_core11 = require("@solvapay/core");
3593
+ var import_core12 = require("@solvapay/core");
3552
3594
  async function getProductCore(request, options = {}) {
3553
3595
  try {
3554
3596
  const url = new URL(request.url);
@@ -3560,7 +3602,7 @@ async function getProductCore(request, options = {}) {
3560
3602
  };
3561
3603
  }
3562
3604
  const apiClient = options.solvaPay?.apiClient ?? (() => {
3563
- const config = (0, import_core11.getSolvaPayConfig)();
3605
+ const config = (0, import_core12.getSolvaPayConfig)();
3564
3606
  if (!config.apiKey) return null;
3565
3607
  return createSolvaPayClient({
3566
3608
  apiKey: config.apiKey,
@@ -3643,7 +3685,7 @@ async function checkPurchaseCore(request, options = {}) {
3643
3685
  }
3644
3686
 
3645
3687
  // src/helpers/usage.ts
3646
- var import_core12 = require("@solvapay/core");
3688
+ var import_core13 = require("@solvapay/core");
3647
3689
  function deriveUsageSnapshot(input) {
3648
3690
  const period = {
3649
3691
  ...input.periodStart ? { periodStart: input.periodStart } : {},
@@ -3693,7 +3735,7 @@ async function getUsageCore(request, options = {}) {
3693
3735
  if ("limits" in options) {
3694
3736
  return deriveUsageSnapshot({ used, ...period, limits: options.limits ?? null });
3695
3737
  }
3696
- const usageCounted = (0, import_core12.countsUsage)(activePurchase.planSnapshot) || activePurchase.planSnapshot?.isMetered === true;
3738
+ const usageCounted = (0, import_core13.countsUsage)(activePurchase.planSnapshot) || activePurchase.planSnapshot?.isMetered === true;
3697
3739
  if (!usageCounted || !activePurchase.productRef) {
3698
3740
  return deriveUsageSnapshot({ used, ...period, limits: null });
3699
3741
  }
@@ -3738,34 +3780,34 @@ function verifyWebhook({
3738
3780
  secret
3739
3781
  }) {
3740
3782
  const toleranceSec = 300;
3741
- if (!signature) throw new import_core13.SolvaPayError("Missing webhook signature");
3783
+ if (!signature) throw new import_core14.SolvaPayError("Missing webhook signature");
3742
3784
  const parts = signature.split(",");
3743
3785
  const tPart = parts.find((p) => p.startsWith("t="));
3744
3786
  const v1Part = parts.find((p) => p.startsWith("v1="));
3745
3787
  if (!tPart || !v1Part) {
3746
- throw new import_core13.SolvaPayError("Malformed webhook signature");
3788
+ throw new import_core14.SolvaPayError("Malformed webhook signature");
3747
3789
  }
3748
3790
  const timestamp = parseInt(tPart.slice(2), 10);
3749
3791
  const receivedHmac = v1Part.slice(3);
3750
3792
  if (Number.isNaN(timestamp) || !receivedHmac) {
3751
- throw new import_core13.SolvaPayError("Malformed webhook signature");
3793
+ throw new import_core14.SolvaPayError("Malformed webhook signature");
3752
3794
  }
3753
3795
  if (toleranceSec > 0) {
3754
3796
  const age = Math.abs(Math.floor(Date.now() / 1e3) - timestamp);
3755
3797
  if (age > toleranceSec) {
3756
- throw new import_core13.SolvaPayError("Webhook signature timestamp too old");
3798
+ throw new import_core14.SolvaPayError("Webhook signature timestamp too old");
3757
3799
  }
3758
3800
  }
3759
3801
  const expectedHmac = import_node_crypto.default.createHmac("sha256", secret).update(`${timestamp}.${body}`).digest("hex");
3760
3802
  if (receivedHmac.length !== expectedHmac.length) {
3761
- throw new import_core13.SolvaPayError("Invalid webhook signature");
3803
+ throw new import_core14.SolvaPayError("Invalid webhook signature");
3762
3804
  }
3763
3805
  const ok = import_node_crypto.default.timingSafeEqual(Buffer.from(expectedHmac), Buffer.from(receivedHmac));
3764
- if (!ok) throw new import_core13.SolvaPayError("Invalid webhook signature");
3806
+ if (!ok) throw new import_core14.SolvaPayError("Invalid webhook signature");
3765
3807
  try {
3766
3808
  return JSON.parse(body);
3767
3809
  } catch {
3768
- throw new import_core13.SolvaPayError("Invalid webhook payload: body is not valid JSON");
3810
+ throw new import_core14.SolvaPayError("Invalid webhook payload: body is not valid JSON");
3769
3811
  }
3770
3812
  }
3771
3813
  // Annotate the CommonJS export names for ESM import in node:
package/dist/index.d.cts CHANGED
@@ -254,6 +254,15 @@ interface components {
254
254
  };
255
255
  CheckLimitRequest: {
256
256
  customerRef: string;
257
+ /** @description SDK-only free-tool allowance. Authenticated by the provider secret key, so `cap` is the provider's own declaration, not customer input. */
258
+ freeAllowance?: {
259
+ cap?: number;
260
+ /** @description Free meter name. Must match `/^free-[a-z0-9-]+$/`. */
261
+ meter?: string;
262
+ /** @enum {string} */
263
+ scope?: 'rolling_window' | 'lifetime';
264
+ windowDays?: number;
265
+ };
257
266
  includeCheckoutSession?: boolean;
258
267
  meterName?: string;
259
268
  productRef: string;
@@ -909,7 +918,7 @@ interface components {
909
918
  * Authoritative paywall classification shared with Managed MCP. Present on denial responses only.
910
919
  * @enum {string}
911
920
  */
912
- paywallReason?: 'activation_required' | 'topup_required' | 'payment_required';
921
+ paywallReason?: 'activation_required' | 'topup_required' | 'payment_required' | 'limit_reached';
913
922
  /** @description Display name of the active or default plan */
914
923
  planName?: string;
915
924
  /** @description Active plan reference when the customer already holds a purchase */
@@ -3919,6 +3928,16 @@ type AttachBusinessDetailsParams = {
3919
3928
  } & components['schemas']['BusinessDetailsDto'];
3920
3929
  type AttachBusinessDetailsResult = components['schemas']['AttachBusinessDetailsResponse'];
3921
3930
  type CheckLimitsRequest = components['schemas']['CheckLimitRequest'];
3931
+ /**
3932
+ * Per-customer cap declared in code on a `registerFree` tool. Not a
3933
+ * registered `Meter` — the name must match `/^free-[a-z0-9-]+$/`.
3934
+ *
3935
+ * Derived from `CheckLimitRequest.freeAllowance`. nestjs-zod omits
3936
+ * nested `required`, so OpenAPI marks every property optional; the
3937
+ * object schema only treats `windowDays` as optional.
3938
+ */
3939
+ type GeneratedFreeAllowance = NonNullable<CheckLimitsRequest['freeAllowance']>;
3940
+ type FreeLimit = Required<Omit<GeneratedFreeAllowance, 'windowDays'>> & Pick<GeneratedFreeAllowance, 'windowDays'>;
3922
3941
  /**
3923
3942
  * `LimitResponse` plus a deprecated SDK-only `plan` alias.
3924
3943
  *
@@ -4242,6 +4261,11 @@ interface PaywallMetadata {
4242
4261
  * attributable. Set by `registerPayable` / `buildPayableHandler`.
4243
4262
  */
4244
4263
  toolName?: string;
4264
+ /**
4265
+ * SDK-only free-tool allowance. When set, `decide()` sends this as
4266
+ * `freeAllowance` and the free meter wins over `meterName`.
4267
+ */
4268
+ freeLimit?: FreeLimit;
4245
4269
  }
4246
4270
  /**
4247
4271
  * Structured content for paywall errors (MCP structuredContent and manual handling).
@@ -4278,9 +4302,9 @@ type PaywallGateRecoveryFields = {
4278
4302
  creditBalance?: number;
4279
4303
  /**
4280
4304
  * `PaywallState.kind` — same vocabulary as the classifier, not a third enum.
4281
- * `upgrade_required` / `limit_reached` / `reactivation_required` are
4282
- * SDK-only; the backend's `paywallReason` is a smaller set
4283
- * (`activation_required` / `topup_required` / `payment_required`).
4305
+ * `upgrade_required` / `reactivation_required` are SDK-only. The
4306
+ * backend's `paywallReason` covers `activation_required` /
4307
+ * `topup_required` / `payment_required` / `limit_reached`.
4284
4308
  */
4285
4309
  reason?: PaywallReason;
4286
4310
  /** Single primary recovery the agent should take. */
@@ -4404,6 +4428,7 @@ type PaywallDecision<T> = {
4404
4428
  * Types for configuring various aspects of the SDK including retry behavior,
4405
4429
  * payable protection, and framework adapters.
4406
4430
  */
4431
+
4407
4432
  /**
4408
4433
  * Retry configuration options
4409
4434
  */
@@ -4482,6 +4507,11 @@ interface PayableOptions {
4482
4507
  * Product reference (alias for product, preferred for consistency with backend API)
4483
4508
  */
4484
4509
  productRef?: string;
4510
+ /**
4511
+ * SDK-only free-tool allowance. When set, this meter's name wins over
4512
+ * `meterName` / `usageType`, and `checkLimits` sends a `freeAllowance` block.
4513
+ */
4514
+ freeLimit?: FreeLimit;
4485
4515
  /**
4486
4516
  * Meter to charge against (defaults to `requests`).
4487
4517
  */
@@ -5042,14 +5072,7 @@ interface SolvaPay {
5042
5072
  * }
5043
5073
  * ```
5044
5074
  */
5045
- checkLimits(params: {
5046
- customerRef: string;
5047
- productRef: string;
5048
- planRef?: string;
5049
- meterName?: string;
5050
- /** @deprecated Use `meterName`. */
5051
- usageType?: string;
5052
- }): Promise<LimitResponseWithPlan>;
5075
+ checkLimits(params: CheckLimitsRequest): Promise<LimitResponseWithPlan>;
5053
5076
  /**
5054
5077
  * Track usage for a customer action.
5055
5078
  *
@@ -5597,8 +5620,8 @@ declare const PaywallStructuredContentSchema: z.ZodDiscriminatedUnion<[z.ZodObje
5597
5620
  topup_required: "topup_required";
5598
5621
  payment_required: "payment_required";
5599
5622
  activation_required: "activation_required";
5600
- upgrade_required: "upgrade_required";
5601
5623
  limit_reached: "limit_reached";
5624
+ upgrade_required: "upgrade_required";
5602
5625
  reactivation_required: "reactivation_required";
5603
5626
  }>>;
5604
5627
  nextAction: z.ZodOptional<z.ZodEnum<{
@@ -5646,8 +5669,8 @@ declare const PaywallStructuredContentSchema: z.ZodDiscriminatedUnion<[z.ZodObje
5646
5669
  topup_required: "topup_required";
5647
5670
  payment_required: "payment_required";
5648
5671
  activation_required: "activation_required";
5649
- upgrade_required: "upgrade_required";
5650
5672
  limit_reached: "limit_reached";
5673
+ upgrade_required: "upgrade_required";
5651
5674
  reactivation_required: "reactivation_required";
5652
5675
  }>>;
5653
5676
  nextAction: z.ZodOptional<z.ZodEnum<{
@@ -5728,7 +5751,9 @@ declare function nextActionFor(state: PaywallState): PaywallNextAction;
5728
5751
  *
5729
5752
  * Precedence:
5730
5753
  * 1. `activationRequired` / `paywallReason === 'activation_required'`.
5731
- * 2. `paywallReason === 'topup_required'` — backend stays authoritative
5754
+ * 2. `paywallReason === 'limit_reached'` — free-allowance exhaustion
5755
+ * (no purchaseRef / planRef) must not fall through to upgrade_required.
5756
+ * 3. `paywallReason === 'topup_required'` — backend stays authoritative
5732
5757
  * for credit-based denials (same rule as Managed MCP).
5733
5758
  * 3. Authoritative `needsTopUp` / `needsUpgrade` flags from `decideLimit`.
5734
5759
  * 4. Credit-field presence + a real shortfall (`balance < cost`).
@@ -6555,4 +6580,4 @@ declare function verifyWebhook({ body, signature, secret, }: {
6555
6580
  secret: string;
6556
6581
  }): WebhookEvent;
6557
6582
 
6558
- export { type ActivatePlanResult, type AssignCreditsRequest, type AssignCreditsResponse, type AttachBusinessDetailsParams, type AttachBusinessDetailsResult, type AuthenticatedUser, type AutoRechargeConfig, type AutoRechargeDisplayBlock, type AutoRechargeInput, type AutoRechargeResponse, BALANCE_RECONCILE_DELAYS_MS, type ConfigureMcpPlansRequest, type ConfigureMcpPlansResponse, type CreateSolvaPayConfig, type CreditActivityEntry, type CreditActivityResult, type CreditActivityType, type CreditDebitResult, type CreditDebitSkipReason, type CreditDisplayBlock, type CreditSignals, type CustomerBalanceResult, type CustomerResponseMapped, type CustomerWebhookObject, type ErrorResult, type GetHistoryResult, type GetUsageResult, type HttpAdapterOptions, type LimitActivationBalance, type LimitActivationProduct, type LimitAutoRechargeDto, type LimitPlanSummary, type LimitResponseWithPlan, type McpBootstrapPlanInput, type McpBootstrapRequest, type McpBootstrapResponse, type McpServerLike, type McpToolPlanMappingInput, type NextAdapterOptions, type OneTimePurchaseInfo, type PayableAllowResult, type PayableFunction, type PayableGateOptions, type PayableGateResult, type PayableOptions, type PayablePaywallResult, type PaymentMethodInfo, type PaywallArgs, type PaywallDecision, PaywallError, type PaywallGateRecoveryFields, type PaywallMetadata, type PaywallNextAction, type PaywallReason, type PaywallState, type PaywallStructuredContent, PaywallStructuredContentSchema, type ProcessPaymentResult, type ProductConfigurationStatus, type ProtectHandlerContext, type PurchaseCheckResult, type PurchaseInfo, type RegisterVirtualToolsMcpOptions, type RetryOptions, type SaveAutoRechargeInput, type SaveAutoRechargeResponse, type SdkMerchantResponse, type SdkProductResponse, type ServerClientOptions, type SolvaPay, type SolvaPayClient, TOPUP_BALANCE_POLL_DELAYS_MS, type ToolPlanMappingInput, type TopupProcessResult, type TrackUsageBulkRequest, type TrackUsageBulkResponse, type TrackUsageRequest, type TrackUsageResponse, type UsageLimitsInput, VIRTUAL_TOOL_DEFINITIONS, type VerifyProductConfigurationOptions, type VirtualToolDefinition, type VirtualToolsOptions, type WebhookEvent, type WebhookEventForType, type WebhookEventObjectMap, type WebhookEventType, type WebhookProduct, activatePlanCore, attachBusinessDetailsCore, buildGateMessage, buildNudgeMessage, buildPaywallGate, cancelPurchaseCore, checkLimitsCore, checkPurchaseCore, classifyPaywallState, type components, createCheckoutSessionCore, createCustomerSessionCore, createPaymentIntentCore, createSolvaPay, createSolvaPayClient, createTopupPaymentIntentCore, createVirtualTools, creditSignals, deriveUsageSnapshot, disableAutoRechargeCore, getAuthenticatedUserCore, getAutoRechargeCore, getCustomerBalanceCore, getHistoryCore, getMerchantCore, getPaymentMethodCore, getProductCore, getUsageCore, handleRouteError, isErrorResult, isPaywallStructuredContent, jsonSchemaToZodRawShape, linkLabel, listPlansCore, nextActionFor, paywallErrorToClientPayload, planLadder, pollBalanceUntilIncreased, processPaymentIntentCore, processTopupPaymentIntentCore, reactivatePurchaseCore, registerVirtualToolsMcpImpl, saveAutoRechargeCore, syncCustomerCore, trackUsageCore, verifyProductConfiguration, verifyWebhook, withRetry };
6583
+ export { type ActivatePlanResult, type AssignCreditsRequest, type AssignCreditsResponse, type AttachBusinessDetailsParams, type AttachBusinessDetailsResult, type AuthenticatedUser, type AutoRechargeConfig, type AutoRechargeDisplayBlock, type AutoRechargeInput, type AutoRechargeResponse, BALANCE_RECONCILE_DELAYS_MS, type ConfigureMcpPlansRequest, type ConfigureMcpPlansResponse, type CreateSolvaPayConfig, type CreditActivityEntry, type CreditActivityResult, type CreditActivityType, type CreditDebitResult, type CreditDebitSkipReason, type CreditDisplayBlock, type CreditSignals, type CustomerBalanceResult, type CustomerResponseMapped, type CustomerWebhookObject, type ErrorResult, type FreeLimit, type GetHistoryResult, type GetUsageResult, type HttpAdapterOptions, type LimitActivationBalance, type LimitActivationProduct, type LimitAutoRechargeDto, type LimitPlanSummary, type LimitResponseWithPlan, type McpBootstrapPlanInput, type McpBootstrapRequest, type McpBootstrapResponse, type McpServerLike, type McpToolPlanMappingInput, type NextAdapterOptions, type OneTimePurchaseInfo, type PayableAllowResult, type PayableFunction, type PayableGateOptions, type PayableGateResult, type PayableOptions, type PayablePaywallResult, type PaymentMethodInfo, type PaywallArgs, type PaywallDecision, PaywallError, type PaywallGateRecoveryFields, type PaywallMetadata, type PaywallNextAction, type PaywallReason, type PaywallState, type PaywallStructuredContent, PaywallStructuredContentSchema, type ProcessPaymentResult, type ProductConfigurationStatus, type ProtectHandlerContext, type PurchaseCheckResult, type PurchaseInfo, type RegisterVirtualToolsMcpOptions, type RetryOptions, type SaveAutoRechargeInput, type SaveAutoRechargeResponse, type SdkMerchantResponse, type SdkProductResponse, type ServerClientOptions, type SolvaPay, type SolvaPayClient, TOPUP_BALANCE_POLL_DELAYS_MS, type ToolPlanMappingInput, type TopupProcessResult, type TrackUsageBulkRequest, type TrackUsageBulkResponse, type TrackUsageRequest, type TrackUsageResponse, type UsageLimitsInput, VIRTUAL_TOOL_DEFINITIONS, type VerifyProductConfigurationOptions, type VirtualToolDefinition, type VirtualToolsOptions, type WebhookEvent, type WebhookEventForType, type WebhookEventObjectMap, type WebhookEventType, type WebhookProduct, activatePlanCore, attachBusinessDetailsCore, buildGateMessage, buildNudgeMessage, buildPaywallGate, cancelPurchaseCore, checkLimitsCore, checkPurchaseCore, classifyPaywallState, type components, createCheckoutSessionCore, createCustomerSessionCore, createPaymentIntentCore, createSolvaPay, createSolvaPayClient, createTopupPaymentIntentCore, createVirtualTools, creditSignals, deriveUsageSnapshot, disableAutoRechargeCore, getAuthenticatedUserCore, getAutoRechargeCore, getCustomerBalanceCore, getHistoryCore, getMerchantCore, getPaymentMethodCore, getProductCore, getUsageCore, handleRouteError, isErrorResult, isPaywallStructuredContent, jsonSchemaToZodRawShape, linkLabel, listPlansCore, nextActionFor, paywallErrorToClientPayload, planLadder, pollBalanceUntilIncreased, processPaymentIntentCore, processTopupPaymentIntentCore, reactivatePurchaseCore, registerVirtualToolsMcpImpl, saveAutoRechargeCore, syncCustomerCore, trackUsageCore, verifyProductConfiguration, verifyWebhook, withRetry };