@solvapay/server 2.3.1 → 2.4.0-preview-11dbbe603cd1b9a44cc41b5eee3686bc4000f97f

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
@@ -32,6 +32,7 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  BALANCE_RECONCILE_DELAYS_MS: () => BALANCE_RECONCILE_DELAYS_MS,
34
34
  PaywallError: () => PaywallError,
35
+ PaywallStructuredContentSchema: () => PaywallStructuredContentSchema,
35
36
  TOPUP_BALANCE_POLL_DELAYS_MS: () => TOPUP_BALANCE_POLL_DELAYS_MS,
36
37
  VIRTUAL_TOOL_DEFINITIONS: () => VIRTUAL_TOOL_DEFINITIONS,
37
38
  activatePlanCore: () => activatePlanCore,
@@ -840,6 +841,8 @@ function createSolvaPayClient(opts) {
840
841
  }
841
842
 
842
843
  // src/paywall-state.ts
844
+ var CHECKOUT_SESSION_TTL_MINUTES = 15;
845
+ var DOCS_HINT = "See docs://solvapay/overview.md.";
843
846
  function classifyPaywallState(limits) {
844
847
  if (!limits) return { kind: "upgrade_required" };
845
848
  if (limits.activationRequired === true) {
@@ -860,34 +863,77 @@ function classifyPaywallState(limits) {
860
863
  return { kind: "topup_required" };
861
864
  }
862
865
  }
866
+ if (limits.plan.length > 0 && limits.remaining <= 0) {
867
+ return { kind: "limit_reached" };
868
+ }
863
869
  return { kind: "upgrade_required" };
864
870
  }
871
+ function formatMinor(amountMinor, currency) {
872
+ const major = amountMinor / 100;
873
+ try {
874
+ return new Intl.NumberFormat("en-US", {
875
+ style: "currency",
876
+ currency: currency.toUpperCase(),
877
+ minimumFractionDigits: 2,
878
+ maximumFractionDigits: 2
879
+ }).format(major);
880
+ } catch {
881
+ return `${currency.toUpperCase()} ${major.toFixed(2)}`;
882
+ }
883
+ }
884
+ function meterLabel(gate) {
885
+ if (!gate.meterName) return "units";
886
+ return gate.meterName.replace(/_/g, " ");
887
+ }
888
+ function namedCheckoutMarkdown(url) {
889
+ return `[Open checkout](${url})`;
890
+ }
891
+ var VIEWER_TOOL = "account";
892
+ function callViewer(view) {
893
+ return view ? `call the \`${VIEWER_TOOL}\` tool with view: '${view}'` : `call the \`${VIEWER_TOOL}\` tool`;
894
+ }
895
+ function recoverClause(url, verb, view) {
896
+ if (url) {
897
+ return ` ${namedCheckoutMarkdown(url)} to ${verb} (expires in ${CHECKOUT_SESSION_TTL_MINUTES} minutes), or ${callViewer(view)}.`;
898
+ }
899
+ return ` ${callViewer(view).replace(/^c/, "C")}.`;
900
+ }
865
901
  function buildGateMessage(state, gate) {
866
902
  const url = gate.checkoutUrl && gate.checkoutUrl.length > 0 ? gate.checkoutUrl : null;
867
- const openClause = url ? `, or open ${url} in a browser` : "";
868
903
  switch (state.kind) {
904
+ case "limit_reached": {
905
+ const included = gate.included;
906
+ const price = gate.unitPriceMinor != null && gate.currency ? formatMinor(gate.unitPriceMinor, gate.currency) : null;
907
+ 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.`;
908
+ const nextLine = price ? ` The next call is ${price}.` : "";
909
+ return `${usedLine}${nextLine}${recoverClause(url, "continue", "checkout")} ${DOCS_HINT}`;
910
+ }
869
911
  case "activation_required":
870
- return `Your plan needs activation before you can use this tool. Call the \`activate_plan\` tool to activate it${openClause}.`;
871
- case "topup_required":
872
- return `You're out of credits. Call the \`topup\` tool to add more${openClause}.`;
912
+ return `Your plan needs activation.${recoverClause(url, "activate", "checkout")} Or call \`activate_plan\` with a \`planRef\`. ${DOCS_HINT}`;
913
+ case "topup_required": {
914
+ const currency = gate.currency ?? gate.balance?.currency ?? "USD";
915
+ const presets = [1e3, 2500, 5e3, 1e4].map((m) => formatMinor(m, currency)).join(" \xB7 ");
916
+ return `You're out of credits. Top up first (${presets}).${recoverClause(url, "add credits", "topup")} ${DOCS_HINT}`;
917
+ }
873
918
  case "upgrade_required":
874
- return `You don't have an active plan for this tool. Call the \`upgrade\` tool to pick a plan${openClause}.`;
919
+ return `You don't have an active plan for this tool.${recoverClause(url, "pick a plan", "checkout")} ${DOCS_HINT}`;
875
920
  case "reactivation_required":
876
- return `Your previous plan is no longer active. Call the \`manage_account\` tool to reactivate it, or the \`upgrade\` tool to pick a new plan.`;
921
+ return `Your previous plan is no longer active. ${callViewer("account").replace(/^c/, "C")} to reactivate it, or ${callViewer("checkout")} to pick a new plan. ${DOCS_HINT}`;
877
922
  }
878
923
  }
879
924
  function buildNudgeMessage(state, limits) {
880
925
  const url = limits?.checkoutUrl && limits.checkoutUrl.length > 0 ? limits.checkoutUrl : null;
881
- const visitClause = url ? `, or visit ${url}` : "";
926
+ const visitClause = url ? `, or ${namedCheckoutMarkdown(url)}` : "";
882
927
  switch (state.kind) {
883
928
  case "topup_required":
884
- return `Heads up \u2014 running low on credits. Call the \`topup\` tool to add more${visitClause}.`;
929
+ return `Heads up \u2014 running low on credits. ${callViewer("topup").replace(/^c/, "C")} to add more${visitClause}.`;
885
930
  case "upgrade_required":
886
- return `Heads up \u2014 approaching your plan's limit this period. Call the \`upgrade\` tool for more headroom${visitClause}.`;
931
+ case "limit_reached":
932
+ return `Heads up \u2014 approaching your plan's limit this period. ${callViewer("checkout").replace(/^c/, "C")} for more headroom${visitClause}.`;
887
933
  case "activation_required":
888
- return `Heads up \u2014 this plan still needs activation. Call the \`activate_plan\` tool${visitClause}.`;
934
+ return `Heads up \u2014 this plan still needs activation. Call the \`activate_plan\` tool with a \`planRef\`${visitClause}.`;
889
935
  case "reactivation_required":
890
- return `Heads up \u2014 your plan is no longer active. Call the \`manage_account\` tool to reactivate it${visitClause}.`;
936
+ return `Heads up \u2014 your plan is no longer active. ${callViewer("account").replace(/^c/, "C")} to reactivate it${visitClause}.`;
891
937
  }
892
938
  }
893
939
 
@@ -898,26 +944,55 @@ function allPaidPlansArePayg(plans) {
898
944
  if (paidPlans.length === 0) return false;
899
945
  return paidPlans.every((p) => p.type === "usage-based" || p.type === "hybrid");
900
946
  }
947
+ function activePlanOf(limits) {
948
+ if (!limits.plan) return void 0;
949
+ return limits.plans?.find((p) => p.reference === limits.plan);
950
+ }
951
+ function includedFromLimits(limits) {
952
+ const total = activePlanOf(limits)?.freeUnits;
953
+ if (total == null || total === 0) return void 0;
954
+ const remaining = limits.remaining;
955
+ return {
956
+ total,
957
+ used: Math.max(0, total - remaining),
958
+ remaining
959
+ };
960
+ }
961
+ function recoveryFields(limits) {
962
+ const plan = activePlanOf(limits);
963
+ const included = includedFromLimits(limits);
964
+ const creditBalance = limits.balance?.creditBalance ?? limits.creditBalance;
965
+ return {
966
+ ...limits.plan ? { planRef: limits.plan } : {},
967
+ ...limits.plans !== void 0 ? { plans: limits.plans } : {},
968
+ ...limits.meterName !== void 0 ? { meterName: limits.meterName } : {},
969
+ ...plan?.perUnitChargeMinor != null ? { unitPriceMinor: plan.perUnitChargeMinor, currency: plan.currency } : limits.currency ? { currency: limits.currency } : {},
970
+ ...included ? { included } : {},
971
+ ...creditBalance !== void 0 ? { creditBalance } : {}
972
+ };
973
+ }
901
974
  function buildPaywallGate(productRef, limits) {
902
975
  const checkoutUrl = limits.checkoutUrl;
903
976
  const state = classifyPaywallState({ ...limits, plan: limits.plan ?? "" });
904
977
  const useActivationForTopup = !limits.activationRequired && state.kind === "topup_required" && allPaidPlansArePayg(limits.plans);
978
+ const recovery = recoveryFields(limits);
905
979
  const preMessage = limits.activationRequired || useActivationForTopup ? {
906
980
  kind: "activation_required",
907
981
  product: productRef,
908
982
  message: "",
909
983
  checkoutUrl: limits.confirmationUrl || checkoutUrl || "",
910
984
  ...limits.confirmationUrl !== void 0 ? { confirmationUrl: limits.confirmationUrl } : {},
911
- ...limits.plans !== void 0 ? { plans: limits.plans } : {},
912
985
  ...limits.balance !== void 0 ? { balance: limits.balance } : {},
913
- ...limits.product !== void 0 ? { productDetails: limits.product } : {}
986
+ ...limits.product !== void 0 ? { productDetails: limits.product } : {},
987
+ ...recovery
914
988
  } : {
915
989
  kind: "payment_required",
916
990
  product: productRef,
917
991
  checkoutUrl: checkoutUrl || "",
918
992
  message: "",
919
993
  ...limits.balance !== void 0 ? { balance: limits.balance } : {},
920
- ...limits.product !== void 0 ? { productDetails: limits.product } : {}
994
+ ...limits.product !== void 0 ? { productDetails: limits.product } : {},
995
+ ...recovery
921
996
  };
922
997
  return { ...preMessage, message: buildGateMessage(state, preMessage) };
923
998
  }
@@ -2025,34 +2100,34 @@ function isJsonSchemaObject(value) {
2025
2100
  return Boolean(value && typeof value === "object");
2026
2101
  }
2027
2102
  function toZodSchema(property) {
2028
- const z = getZod();
2103
+ const z2 = getZod();
2029
2104
  if (Array.isArray(property.enum) && property.enum.length > 0) {
2030
2105
  if (property.enum.every((value) => typeof value === "string")) {
2031
2106
  const [first, ...rest] = property.enum;
2032
- return z.enum([first, ...rest]);
2107
+ return z2.enum([first, ...rest]);
2033
2108
  }
2034
2109
  if (property.enum.length === 1) {
2035
- return z.literal(property.enum[0]);
2110
+ return z2.literal(property.enum[0]);
2036
2111
  }
2037
- return z.union(property.enum.map((value) => z.literal(value)));
2112
+ return z2.union(property.enum.map((value) => z2.literal(value)));
2038
2113
  }
2039
2114
  switch (property.type) {
2040
2115
  case "string":
2041
- return z.string();
2116
+ return z2.string();
2042
2117
  case "number":
2043
- return z.number();
2118
+ return z2.number();
2044
2119
  case "integer":
2045
- return z.number().int();
2120
+ return z2.number().int();
2046
2121
  case "boolean":
2047
- return z.boolean();
2122
+ return z2.boolean();
2048
2123
  case "array":
2049
- return z.array(
2050
- isJsonSchemaObject(property.items) ? toZodSchema(property.items) : z.any()
2124
+ return z2.array(
2125
+ isJsonSchemaObject(property.items) ? toZodSchema(property.items) : z2.any()
2051
2126
  );
2052
2127
  case "object":
2053
- return z.object(jsonSchemaToZodRawShape(property.properties || {}));
2128
+ return z2.object(jsonSchemaToZodRawShape(property.properties || {}));
2054
2129
  default:
2055
- return z.any();
2130
+ return z2.any();
2056
2131
  }
2057
2132
  }
2058
2133
  function jsonSchemaToZodRawShape(properties, required = []) {
@@ -2087,12 +2162,12 @@ function registerVirtualToolsMcpImpl(server, apiClient, options) {
2087
2162
  if (filter && !filter(toolDefinition)) continue;
2088
2163
  const mappedDefinition = mapDefinition ? mapDefinition(toolDefinition) : toolDefinition;
2089
2164
  const wrappedHandler = wrapHandler ? wrapHandler(mappedDefinition.handler, mappedDefinition) : mappedDefinition.handler;
2090
- const z = getZod();
2165
+ const z2 = getZod();
2091
2166
  server.registerTool(
2092
2167
  mappedDefinition.name,
2093
2168
  {
2094
2169
  description: mappedDefinition.description,
2095
- inputSchema: z.object(
2170
+ inputSchema: z2.object(
2096
2171
  jsonSchemaToZodRawShape(
2097
2172
  mappedDefinition.inputSchema.properties,
2098
2173
  mappedDefinition.inputSchema.required || []
@@ -2415,6 +2490,42 @@ function isPaywallStructuredContent(value) {
2415
2490
  return kind === "payment_required" || kind === "activation_required";
2416
2491
  }
2417
2492
 
2493
+ // src/types/paywall-schema.ts
2494
+ var import_zod = require("zod");
2495
+ var includedSchema = import_zod.z.object({
2496
+ total: import_zod.z.number(),
2497
+ used: import_zod.z.number(),
2498
+ remaining: import_zod.z.number()
2499
+ });
2500
+ var recoveryFields2 = {
2501
+ planRef: import_zod.z.string().optional(),
2502
+ plans: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())).optional(),
2503
+ meterName: import_zod.z.string().optional(),
2504
+ unitPriceMinor: import_zod.z.number().optional(),
2505
+ currency: import_zod.z.string().optional(),
2506
+ included: includedSchema.optional(),
2507
+ creditBalance: import_zod.z.number().optional(),
2508
+ balance: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
2509
+ productDetails: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
2510
+ };
2511
+ var PaywallStructuredContentSchema = import_zod.z.discriminatedUnion("kind", [
2512
+ import_zod.z.object({
2513
+ kind: import_zod.z.literal("payment_required"),
2514
+ product: import_zod.z.string(),
2515
+ checkoutUrl: import_zod.z.string(),
2516
+ message: import_zod.z.string(),
2517
+ ...recoveryFields2
2518
+ }),
2519
+ import_zod.z.object({
2520
+ kind: import_zod.z.literal("activation_required"),
2521
+ product: import_zod.z.string(),
2522
+ checkoutUrl: import_zod.z.string(),
2523
+ message: import_zod.z.string(),
2524
+ confirmationUrl: import_zod.z.string().optional(),
2525
+ ...recoveryFields2
2526
+ })
2527
+ ]);
2528
+
2418
2529
  // src/helpers/error.ts
2419
2530
  var import_core5 = require("@solvapay/core");
2420
2531
  function isErrorResult(result) {
@@ -3476,6 +3587,7 @@ function verifyWebhook({
3476
3587
  0 && (module.exports = {
3477
3588
  BALANCE_RECONCILE_DELAYS_MS,
3478
3589
  PaywallError,
3590
+ PaywallStructuredContentSchema,
3479
3591
  TOPUP_BALANCE_POLL_DELAYS_MS,
3480
3592
  VIRTUAL_TOOL_DEFINITIONS,
3481
3593
  activatePlanCore,
package/dist/index.d.cts CHANGED
@@ -1,3 +1,4 @@
1
+ import { z } from 'zod';
1
2
  import { BusinessDetailsInput, TaxBreakdown } from '@solvapay/core';
2
3
 
3
4
  interface components {
@@ -4097,7 +4098,36 @@ interface PaywallMetadata {
4097
4098
  /**
4098
4099
  * Structured content for paywall errors (MCP structuredContent and manual handling).
4099
4100
  */
4100
- type PaywallStructuredContent = {
4101
+ /**
4102
+ * Fields shared by every `PaywallStructuredContent` kind so a text-only
4103
+ * host can recover from a gate without a second tool call. Every field
4104
+ * other than `kind` / `product` / `checkoutUrl` / `message` is omitted
4105
+ * when the backend did not send it — never defaulted.
4106
+ */
4107
+ type PaywallGateRecoveryFields = {
4108
+ /** Active plan reference from `limits.plan`. */
4109
+ planRef?: string;
4110
+ /** Product plans from `checkLimits` (activation already had this). */
4111
+ plans?: LimitPlanSummary[];
4112
+ /** Meter the gated call was charged against. */
4113
+ meterName?: string;
4114
+ /** Per-unit charge of the active plan, in minor currency units. */
4115
+ unitPriceMinor?: number;
4116
+ /** ISO 4217 currency for `unitPriceMinor`. */
4117
+ currency?: string;
4118
+ /**
4119
+ * Included-usage counters. Omitted when `freeUnits` is absent or `0`
4120
+ * (the unlimited sentinel) so we never emit misleading zeros.
4121
+ */
4122
+ included?: {
4123
+ total: number;
4124
+ used: number;
4125
+ remaining: number;
4126
+ };
4127
+ /** Prepaid credit balance, coalesced from nested or top-level fields. */
4128
+ creditBalance?: number;
4129
+ };
4130
+ type PaywallStructuredContent = ({
4101
4131
  kind: 'payment_required';
4102
4132
  /** Product ref from paywall metadata (or env default) */
4103
4133
  product: string;
@@ -4112,7 +4142,7 @@ type PaywallStructuredContent = {
4112
4142
  balance?: LimitActivationBalance;
4113
4143
  /** Rich product context from checkLimits (name, ref, provider slug/id) */
4114
4144
  productDetails?: LimitActivationProduct;
4115
- } | {
4145
+ } & PaywallGateRecoveryFields) | ({
4116
4146
  kind: 'activation_required';
4117
4147
  /** Product ref from paywall metadata (or env default) */
4118
4148
  product: string;
@@ -4122,11 +4152,10 @@ type PaywallStructuredContent = {
4122
4152
  */
4123
4153
  checkoutUrl: string;
4124
4154
  confirmationUrl?: string;
4125
- plans?: LimitPlanSummary[];
4126
4155
  balance?: LimitActivationBalance;
4127
4156
  /** Rich product context from checkLimits (name, ref, provider slug/id) */
4128
4157
  productDetails?: LimitActivationProduct;
4129
- };
4158
+ } & PaywallGateRecoveryFields);
4130
4159
  /**
4131
4160
  * Runtime type guard for `PaywallStructuredContent`.
4132
4161
  *
@@ -5341,6 +5370,52 @@ type LimitsLike = Omit<LimitResponseWithPlan, 'plan'> & {
5341
5370
  };
5342
5371
  declare function buildPaywallGate(productRef: string, limits: LimitsLike): PaywallStructuredContent;
5343
5372
 
5373
+ /**
5374
+ * Zod schemas for `PaywallStructuredContent`. Declared so MCP hosts can
5375
+ * hydrate `structuredContent` and so we fail loud when a required field
5376
+ * the narrator claims to print is missing. Every recovery field is
5377
+ * optional — a default here would invent data the backend did not send.
5378
+ */
5379
+
5380
+ declare const PaywallStructuredContentSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
5381
+ planRef: z.ZodOptional<z.ZodString>;
5382
+ plans: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
5383
+ meterName: z.ZodOptional<z.ZodString>;
5384
+ unitPriceMinor: z.ZodOptional<z.ZodNumber>;
5385
+ currency: z.ZodOptional<z.ZodString>;
5386
+ included: z.ZodOptional<z.ZodObject<{
5387
+ total: z.ZodNumber;
5388
+ used: z.ZodNumber;
5389
+ remaining: z.ZodNumber;
5390
+ }, z.core.$strip>>;
5391
+ creditBalance: z.ZodOptional<z.ZodNumber>;
5392
+ balance: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
5393
+ productDetails: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
5394
+ kind: z.ZodLiteral<"payment_required">;
5395
+ product: z.ZodString;
5396
+ checkoutUrl: z.ZodString;
5397
+ message: z.ZodString;
5398
+ }, z.core.$strip>, z.ZodObject<{
5399
+ planRef: z.ZodOptional<z.ZodString>;
5400
+ plans: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
5401
+ meterName: z.ZodOptional<z.ZodString>;
5402
+ unitPriceMinor: z.ZodOptional<z.ZodNumber>;
5403
+ currency: z.ZodOptional<z.ZodString>;
5404
+ included: z.ZodOptional<z.ZodObject<{
5405
+ total: z.ZodNumber;
5406
+ used: z.ZodNumber;
5407
+ remaining: z.ZodNumber;
5408
+ }, z.core.$strip>>;
5409
+ creditBalance: z.ZodOptional<z.ZodNumber>;
5410
+ balance: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
5411
+ productDetails: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
5412
+ kind: z.ZodLiteral<"activation_required">;
5413
+ product: z.ZodString;
5414
+ checkoutUrl: z.ZodString;
5415
+ message: z.ZodString;
5416
+ confirmationUrl: z.ZodOptional<z.ZodString>;
5417
+ }, z.core.$strip>], "kind">;
5418
+
5344
5419
  /**
5345
5420
  * Pure state engine that classifies a `LimitResponseWithPlan` into a
5346
5421
  * recovery-tool-specific `PaywallState`, and produces the human-readable
@@ -5366,6 +5441,8 @@ type PaywallState = {
5366
5441
  kind: 'topup_required';
5367
5442
  } | {
5368
5443
  kind: 'upgrade_required';
5444
+ } | {
5445
+ kind: 'limit_reached';
5369
5446
  } | {
5370
5447
  kind: 'reactivation_required';
5371
5448
  };
@@ -5387,10 +5464,12 @@ type PaywallState = {
5387
5464
  * `creditBalance === 0` field, or `remaining === 0` as a
5388
5465
  * fallback for older backend responses that omit both credit
5389
5466
  * fields on usage-based plans.
5390
- * 4. Everything else → `upgrade_required`, including:
5467
+ * 4. Active plan at included cap (`plan` is a non-empty ref and
5468
+ * `remaining <= 0`) → `limit_reached`. This is the "on Free, used
5469
+ * 3 of 3" case — not "no plan".
5470
+ * 5. Everything else → `upgrade_required`, including:
5391
5471
  * - `limits === null` (defensive),
5392
- * - no active plan on the product,
5393
- * - recurring plan at period cap (`remaining <= 0`).
5472
+ * - no active plan on the product.
5394
5473
  *
5395
5474
  * `reactivation_required` is deferred — it needs a distinct backend
5396
5475
  * signal (future `LimitResponse.inactivePurchaseRef`) which isn't
@@ -5400,12 +5479,13 @@ type PaywallState = {
5400
5479
  */
5401
5480
  declare function classifyPaywallState(limits: LimitResponseWithPlan | null): PaywallState;
5402
5481
  /**
5403
- * Produce the terminal-friendly gate message. Names exactly one
5404
- * recovery tool (`upgrade` / `topup` / `activate_plan`), except for
5405
- * the rare `reactivation_required` path which names two alternatives
5406
- * (`manage_account` / `upgrade`). Inlines `gate.checkoutUrl` when
5407
- * present so terminal-only MCP hosts (Claude Code, CLI clients) can
5408
- * open a browser directly.
5482
+ * Produce the terminal-friendly gate message. Names the `account`
5483
+ * viewer (with a `view` hint when the landing screen matters) or
5484
+ * `activate_plan` when a specific plan needs activating. Inlines
5485
+ * `gate.checkoutUrl` when present so terminal-only MCP hosts (Claude
5486
+ * Code, CLI clients) can open a browser directly. States the
5487
+ * 15-minute session lifetime inline — the URL is a bearer credential,
5488
+ * not a durable link.
5409
5489
  *
5410
5490
  * Kept as a pure string so the adapter layer can concatenate it with
5411
5491
  * an optional narrator prefix without parsing structured copy.
@@ -5418,10 +5498,10 @@ declare function buildGateMessage(state: PaywallState, gate: PaywallStructuredCo
5418
5498
  * like a softer version of the same text-only nudge path.
5419
5499
  *
5420
5500
  * Receives the `PaywallState` the classifier would have produced if
5421
- * the customer had tripped the gate. `upgrade_required` and
5422
- * `topup_required` are the only kinds that currently produce nudge
5423
- * copy; the others are no-ops (shouldn't happen — nudges only fire
5424
- * on successful calls).
5501
+ * the customer had tripped the gate. `upgrade_required`,
5502
+ * `limit_reached`, and `topup_required` are the kinds that currently
5503
+ * produce nudge copy; the others are no-ops (shouldn't happen —
5504
+ * nudges only fire on successful calls).
5425
5505
  */
5426
5506
  declare function buildNudgeMessage(state: PaywallState, limits: LimitResponseWithPlan | null): string;
5427
5507
 
@@ -6154,4 +6234,4 @@ declare function verifyWebhook({ body, signature, secret, }: {
6154
6234
  secret: string;
6155
6235
  }): WebhookEvent;
6156
6236
 
6157
- 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 CreditDebitResult, type CreditDebitSkipReason, type CreditDisplayBlock, type CustomerBalanceResult, type CustomerResponseMapped, type CustomerWebhookObject, type ErrorResult, type GetUsageResult, type HttpAdapterOptions, type LimitActivationBalance, type LimitActivationProduct, 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 PaywallMetadata, type PaywallState, type PaywallStructuredContent, 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, 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, disableAutoRechargeCore, getAuthenticatedUserCore, getAutoRechargeCore, getCustomerBalanceCore, getMerchantCore, getPaymentMethodCore, getProductCore, getUsageCore, handleRouteError, isErrorResult, isPaywallStructuredContent, jsonSchemaToZodRawShape, listPlansCore, paywallErrorToClientPayload, pollBalanceUntilIncreased, processPaymentIntentCore, processTopupPaymentIntentCore, reactivatePurchaseCore, registerVirtualToolsMcpImpl, saveAutoRechargeCore, syncCustomerCore, trackUsageCore, verifyProductConfiguration, verifyWebhook, withRetry };
6237
+ 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 CreditDebitResult, type CreditDebitSkipReason, type CreditDisplayBlock, type CustomerBalanceResult, type CustomerResponseMapped, type CustomerWebhookObject, type ErrorResult, type GetUsageResult, type HttpAdapterOptions, type LimitActivationBalance, type LimitActivationProduct, 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 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, 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, disableAutoRechargeCore, getAuthenticatedUserCore, getAutoRechargeCore, getCustomerBalanceCore, getMerchantCore, getPaymentMethodCore, getProductCore, getUsageCore, handleRouteError, isErrorResult, isPaywallStructuredContent, jsonSchemaToZodRawShape, listPlansCore, paywallErrorToClientPayload, pollBalanceUntilIncreased, processPaymentIntentCore, processTopupPaymentIntentCore, reactivatePurchaseCore, registerVirtualToolsMcpImpl, saveAutoRechargeCore, syncCustomerCore, trackUsageCore, verifyProductConfiguration, verifyWebhook, withRetry };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { z } from 'zod';
1
2
  import { BusinessDetailsInput, TaxBreakdown } from '@solvapay/core';
2
3
 
3
4
  interface components {
@@ -4097,7 +4098,36 @@ interface PaywallMetadata {
4097
4098
  /**
4098
4099
  * Structured content for paywall errors (MCP structuredContent and manual handling).
4099
4100
  */
4100
- type PaywallStructuredContent = {
4101
+ /**
4102
+ * Fields shared by every `PaywallStructuredContent` kind so a text-only
4103
+ * host can recover from a gate without a second tool call. Every field
4104
+ * other than `kind` / `product` / `checkoutUrl` / `message` is omitted
4105
+ * when the backend did not send it — never defaulted.
4106
+ */
4107
+ type PaywallGateRecoveryFields = {
4108
+ /** Active plan reference from `limits.plan`. */
4109
+ planRef?: string;
4110
+ /** Product plans from `checkLimits` (activation already had this). */
4111
+ plans?: LimitPlanSummary[];
4112
+ /** Meter the gated call was charged against. */
4113
+ meterName?: string;
4114
+ /** Per-unit charge of the active plan, in minor currency units. */
4115
+ unitPriceMinor?: number;
4116
+ /** ISO 4217 currency for `unitPriceMinor`. */
4117
+ currency?: string;
4118
+ /**
4119
+ * Included-usage counters. Omitted when `freeUnits` is absent or `0`
4120
+ * (the unlimited sentinel) so we never emit misleading zeros.
4121
+ */
4122
+ included?: {
4123
+ total: number;
4124
+ used: number;
4125
+ remaining: number;
4126
+ };
4127
+ /** Prepaid credit balance, coalesced from nested or top-level fields. */
4128
+ creditBalance?: number;
4129
+ };
4130
+ type PaywallStructuredContent = ({
4101
4131
  kind: 'payment_required';
4102
4132
  /** Product ref from paywall metadata (or env default) */
4103
4133
  product: string;
@@ -4112,7 +4142,7 @@ type PaywallStructuredContent = {
4112
4142
  balance?: LimitActivationBalance;
4113
4143
  /** Rich product context from checkLimits (name, ref, provider slug/id) */
4114
4144
  productDetails?: LimitActivationProduct;
4115
- } | {
4145
+ } & PaywallGateRecoveryFields) | ({
4116
4146
  kind: 'activation_required';
4117
4147
  /** Product ref from paywall metadata (or env default) */
4118
4148
  product: string;
@@ -4122,11 +4152,10 @@ type PaywallStructuredContent = {
4122
4152
  */
4123
4153
  checkoutUrl: string;
4124
4154
  confirmationUrl?: string;
4125
- plans?: LimitPlanSummary[];
4126
4155
  balance?: LimitActivationBalance;
4127
4156
  /** Rich product context from checkLimits (name, ref, provider slug/id) */
4128
4157
  productDetails?: LimitActivationProduct;
4129
- };
4158
+ } & PaywallGateRecoveryFields);
4130
4159
  /**
4131
4160
  * Runtime type guard for `PaywallStructuredContent`.
4132
4161
  *
@@ -5341,6 +5370,52 @@ type LimitsLike = Omit<LimitResponseWithPlan, 'plan'> & {
5341
5370
  };
5342
5371
  declare function buildPaywallGate(productRef: string, limits: LimitsLike): PaywallStructuredContent;
5343
5372
 
5373
+ /**
5374
+ * Zod schemas for `PaywallStructuredContent`. Declared so MCP hosts can
5375
+ * hydrate `structuredContent` and so we fail loud when a required field
5376
+ * the narrator claims to print is missing. Every recovery field is
5377
+ * optional — a default here would invent data the backend did not send.
5378
+ */
5379
+
5380
+ declare const PaywallStructuredContentSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
5381
+ planRef: z.ZodOptional<z.ZodString>;
5382
+ plans: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
5383
+ meterName: z.ZodOptional<z.ZodString>;
5384
+ unitPriceMinor: z.ZodOptional<z.ZodNumber>;
5385
+ currency: z.ZodOptional<z.ZodString>;
5386
+ included: z.ZodOptional<z.ZodObject<{
5387
+ total: z.ZodNumber;
5388
+ used: z.ZodNumber;
5389
+ remaining: z.ZodNumber;
5390
+ }, z.core.$strip>>;
5391
+ creditBalance: z.ZodOptional<z.ZodNumber>;
5392
+ balance: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
5393
+ productDetails: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
5394
+ kind: z.ZodLiteral<"payment_required">;
5395
+ product: z.ZodString;
5396
+ checkoutUrl: z.ZodString;
5397
+ message: z.ZodString;
5398
+ }, z.core.$strip>, z.ZodObject<{
5399
+ planRef: z.ZodOptional<z.ZodString>;
5400
+ plans: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
5401
+ meterName: z.ZodOptional<z.ZodString>;
5402
+ unitPriceMinor: z.ZodOptional<z.ZodNumber>;
5403
+ currency: z.ZodOptional<z.ZodString>;
5404
+ included: z.ZodOptional<z.ZodObject<{
5405
+ total: z.ZodNumber;
5406
+ used: z.ZodNumber;
5407
+ remaining: z.ZodNumber;
5408
+ }, z.core.$strip>>;
5409
+ creditBalance: z.ZodOptional<z.ZodNumber>;
5410
+ balance: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
5411
+ productDetails: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
5412
+ kind: z.ZodLiteral<"activation_required">;
5413
+ product: z.ZodString;
5414
+ checkoutUrl: z.ZodString;
5415
+ message: z.ZodString;
5416
+ confirmationUrl: z.ZodOptional<z.ZodString>;
5417
+ }, z.core.$strip>], "kind">;
5418
+
5344
5419
  /**
5345
5420
  * Pure state engine that classifies a `LimitResponseWithPlan` into a
5346
5421
  * recovery-tool-specific `PaywallState`, and produces the human-readable
@@ -5366,6 +5441,8 @@ type PaywallState = {
5366
5441
  kind: 'topup_required';
5367
5442
  } | {
5368
5443
  kind: 'upgrade_required';
5444
+ } | {
5445
+ kind: 'limit_reached';
5369
5446
  } | {
5370
5447
  kind: 'reactivation_required';
5371
5448
  };
@@ -5387,10 +5464,12 @@ type PaywallState = {
5387
5464
  * `creditBalance === 0` field, or `remaining === 0` as a
5388
5465
  * fallback for older backend responses that omit both credit
5389
5466
  * fields on usage-based plans.
5390
- * 4. Everything else → `upgrade_required`, including:
5467
+ * 4. Active plan at included cap (`plan` is a non-empty ref and
5468
+ * `remaining <= 0`) → `limit_reached`. This is the "on Free, used
5469
+ * 3 of 3" case — not "no plan".
5470
+ * 5. Everything else → `upgrade_required`, including:
5391
5471
  * - `limits === null` (defensive),
5392
- * - no active plan on the product,
5393
- * - recurring plan at period cap (`remaining <= 0`).
5472
+ * - no active plan on the product.
5394
5473
  *
5395
5474
  * `reactivation_required` is deferred — it needs a distinct backend
5396
5475
  * signal (future `LimitResponse.inactivePurchaseRef`) which isn't
@@ -5400,12 +5479,13 @@ type PaywallState = {
5400
5479
  */
5401
5480
  declare function classifyPaywallState(limits: LimitResponseWithPlan | null): PaywallState;
5402
5481
  /**
5403
- * Produce the terminal-friendly gate message. Names exactly one
5404
- * recovery tool (`upgrade` / `topup` / `activate_plan`), except for
5405
- * the rare `reactivation_required` path which names two alternatives
5406
- * (`manage_account` / `upgrade`). Inlines `gate.checkoutUrl` when
5407
- * present so terminal-only MCP hosts (Claude Code, CLI clients) can
5408
- * open a browser directly.
5482
+ * Produce the terminal-friendly gate message. Names the `account`
5483
+ * viewer (with a `view` hint when the landing screen matters) or
5484
+ * `activate_plan` when a specific plan needs activating. Inlines
5485
+ * `gate.checkoutUrl` when present so terminal-only MCP hosts (Claude
5486
+ * Code, CLI clients) can open a browser directly. States the
5487
+ * 15-minute session lifetime inline — the URL is a bearer credential,
5488
+ * not a durable link.
5409
5489
  *
5410
5490
  * Kept as a pure string so the adapter layer can concatenate it with
5411
5491
  * an optional narrator prefix without parsing structured copy.
@@ -5418,10 +5498,10 @@ declare function buildGateMessage(state: PaywallState, gate: PaywallStructuredCo
5418
5498
  * like a softer version of the same text-only nudge path.
5419
5499
  *
5420
5500
  * Receives the `PaywallState` the classifier would have produced if
5421
- * the customer had tripped the gate. `upgrade_required` and
5422
- * `topup_required` are the only kinds that currently produce nudge
5423
- * copy; the others are no-ops (shouldn't happen — nudges only fire
5424
- * on successful calls).
5501
+ * the customer had tripped the gate. `upgrade_required`,
5502
+ * `limit_reached`, and `topup_required` are the kinds that currently
5503
+ * produce nudge copy; the others are no-ops (shouldn't happen —
5504
+ * nudges only fire on successful calls).
5425
5505
  */
5426
5506
  declare function buildNudgeMessage(state: PaywallState, limits: LimitResponseWithPlan | null): string;
5427
5507
 
@@ -6154,4 +6234,4 @@ declare function verifyWebhook({ body, signature, secret, }: {
6154
6234
  secret: string;
6155
6235
  }): WebhookEvent;
6156
6236
 
6157
- 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 CreditDebitResult, type CreditDebitSkipReason, type CreditDisplayBlock, type CustomerBalanceResult, type CustomerResponseMapped, type CustomerWebhookObject, type ErrorResult, type GetUsageResult, type HttpAdapterOptions, type LimitActivationBalance, type LimitActivationProduct, 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 PaywallMetadata, type PaywallState, type PaywallStructuredContent, 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, 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, disableAutoRechargeCore, getAuthenticatedUserCore, getAutoRechargeCore, getCustomerBalanceCore, getMerchantCore, getPaymentMethodCore, getProductCore, getUsageCore, handleRouteError, isErrorResult, isPaywallStructuredContent, jsonSchemaToZodRawShape, listPlansCore, paywallErrorToClientPayload, pollBalanceUntilIncreased, processPaymentIntentCore, processTopupPaymentIntentCore, reactivatePurchaseCore, registerVirtualToolsMcpImpl, saveAutoRechargeCore, syncCustomerCore, trackUsageCore, verifyProductConfiguration, verifyWebhook, withRetry };
6237
+ 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 CreditDebitResult, type CreditDebitSkipReason, type CreditDisplayBlock, type CustomerBalanceResult, type CustomerResponseMapped, type CustomerWebhookObject, type ErrorResult, type GetUsageResult, type HttpAdapterOptions, type LimitActivationBalance, type LimitActivationProduct, 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 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, 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, disableAutoRechargeCore, getAuthenticatedUserCore, getAutoRechargeCore, getCustomerBalanceCore, getMerchantCore, getPaymentMethodCore, getProductCore, getUsageCore, handleRouteError, isErrorResult, isPaywallStructuredContent, jsonSchemaToZodRawShape, listPlansCore, paywallErrorToClientPayload, pollBalanceUntilIncreased, processPaymentIntentCore, processTopupPaymentIntentCore, reactivatePurchaseCore, registerVirtualToolsMcpImpl, saveAutoRechargeCore, syncCustomerCore, trackUsageCore, verifyProductConfiguration, verifyWebhook, withRetry };