@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.js CHANGED
@@ -762,6 +762,8 @@ function createSolvaPayClient(opts) {
762
762
  }
763
763
 
764
764
  // src/paywall-state.ts
765
+ var CHECKOUT_SESSION_TTL_MINUTES = 15;
766
+ var DOCS_HINT = "See docs://solvapay/overview.md.";
765
767
  function classifyPaywallState(limits) {
766
768
  if (!limits) return { kind: "upgrade_required" };
767
769
  if (limits.activationRequired === true) {
@@ -782,34 +784,77 @@ function classifyPaywallState(limits) {
782
784
  return { kind: "topup_required" };
783
785
  }
784
786
  }
787
+ if (limits.plan.length > 0 && limits.remaining <= 0) {
788
+ return { kind: "limit_reached" };
789
+ }
785
790
  return { kind: "upgrade_required" };
786
791
  }
792
+ function formatMinor(amountMinor, currency) {
793
+ const major = amountMinor / 100;
794
+ try {
795
+ return new Intl.NumberFormat("en-US", {
796
+ style: "currency",
797
+ currency: currency.toUpperCase(),
798
+ minimumFractionDigits: 2,
799
+ maximumFractionDigits: 2
800
+ }).format(major);
801
+ } catch {
802
+ return `${currency.toUpperCase()} ${major.toFixed(2)}`;
803
+ }
804
+ }
805
+ function meterLabel(gate) {
806
+ if (!gate.meterName) return "units";
807
+ return gate.meterName.replace(/_/g, " ");
808
+ }
809
+ function namedCheckoutMarkdown(url) {
810
+ return `[Open checkout](${url})`;
811
+ }
812
+ var VIEWER_TOOL = "account";
813
+ function callViewer(view) {
814
+ return view ? `call the \`${VIEWER_TOOL}\` tool with view: '${view}'` : `call the \`${VIEWER_TOOL}\` tool`;
815
+ }
816
+ function recoverClause(url, verb, view) {
817
+ if (url) {
818
+ return ` ${namedCheckoutMarkdown(url)} to ${verb} (expires in ${CHECKOUT_SESSION_TTL_MINUTES} minutes), or ${callViewer(view)}.`;
819
+ }
820
+ return ` ${callViewer(view).replace(/^c/, "C")}.`;
821
+ }
787
822
  function buildGateMessage(state, gate) {
788
823
  const url = gate.checkoutUrl && gate.checkoutUrl.length > 0 ? gate.checkoutUrl : null;
789
- const openClause = url ? `, or open ${url} in a browser` : "";
790
824
  switch (state.kind) {
825
+ case "limit_reached": {
826
+ const included = gate.included;
827
+ const price = gate.unitPriceMinor != null && gate.currency ? formatMinor(gate.unitPriceMinor, gate.currency) : null;
828
+ 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.`;
829
+ const nextLine = price ? ` The next call is ${price}.` : "";
830
+ return `${usedLine}${nextLine}${recoverClause(url, "continue", "checkout")} ${DOCS_HINT}`;
831
+ }
791
832
  case "activation_required":
792
- return `Your plan needs activation before you can use this tool. Call the \`activate_plan\` tool to activate it${openClause}.`;
793
- case "topup_required":
794
- return `You're out of credits. Call the \`topup\` tool to add more${openClause}.`;
833
+ return `Your plan needs activation.${recoverClause(url, "activate", "checkout")} Or call \`activate_plan\` with a \`planRef\`. ${DOCS_HINT}`;
834
+ case "topup_required": {
835
+ const currency = gate.currency ?? gate.balance?.currency ?? "USD";
836
+ const presets = [1e3, 2500, 5e3, 1e4].map((m) => formatMinor(m, currency)).join(" \xB7 ");
837
+ return `You're out of credits. Top up first (${presets}).${recoverClause(url, "add credits", "topup")} ${DOCS_HINT}`;
838
+ }
795
839
  case "upgrade_required":
796
- return `You don't have an active plan for this tool. Call the \`upgrade\` tool to pick a plan${openClause}.`;
840
+ return `You don't have an active plan for this tool.${recoverClause(url, "pick a plan", "checkout")} ${DOCS_HINT}`;
797
841
  case "reactivation_required":
798
- 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.`;
842
+ 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}`;
799
843
  }
800
844
  }
801
845
  function buildNudgeMessage(state, limits) {
802
846
  const url = limits?.checkoutUrl && limits.checkoutUrl.length > 0 ? limits.checkoutUrl : null;
803
- const visitClause = url ? `, or visit ${url}` : "";
847
+ const visitClause = url ? `, or ${namedCheckoutMarkdown(url)}` : "";
804
848
  switch (state.kind) {
805
849
  case "topup_required":
806
- return `Heads up \u2014 running low on credits. Call the \`topup\` tool to add more${visitClause}.`;
850
+ return `Heads up \u2014 running low on credits. ${callViewer("topup").replace(/^c/, "C")} to add more${visitClause}.`;
807
851
  case "upgrade_required":
808
- return `Heads up \u2014 approaching your plan's limit this period. Call the \`upgrade\` tool for more headroom${visitClause}.`;
852
+ case "limit_reached":
853
+ return `Heads up \u2014 approaching your plan's limit this period. ${callViewer("checkout").replace(/^c/, "C")} for more headroom${visitClause}.`;
809
854
  case "activation_required":
810
- return `Heads up \u2014 this plan still needs activation. Call the \`activate_plan\` tool${visitClause}.`;
855
+ return `Heads up \u2014 this plan still needs activation. Call the \`activate_plan\` tool with a \`planRef\`${visitClause}.`;
811
856
  case "reactivation_required":
812
- return `Heads up \u2014 your plan is no longer active. Call the \`manage_account\` tool to reactivate it${visitClause}.`;
857
+ return `Heads up \u2014 your plan is no longer active. ${callViewer("account").replace(/^c/, "C")} to reactivate it${visitClause}.`;
813
858
  }
814
859
  }
815
860
 
@@ -820,26 +865,55 @@ function allPaidPlansArePayg(plans) {
820
865
  if (paidPlans.length === 0) return false;
821
866
  return paidPlans.every((p) => p.type === "usage-based" || p.type === "hybrid");
822
867
  }
868
+ function activePlanOf(limits) {
869
+ if (!limits.plan) return void 0;
870
+ return limits.plans?.find((p) => p.reference === limits.plan);
871
+ }
872
+ function includedFromLimits(limits) {
873
+ const total = activePlanOf(limits)?.freeUnits;
874
+ if (total == null || total === 0) return void 0;
875
+ const remaining = limits.remaining;
876
+ return {
877
+ total,
878
+ used: Math.max(0, total - remaining),
879
+ remaining
880
+ };
881
+ }
882
+ function recoveryFields(limits) {
883
+ const plan = activePlanOf(limits);
884
+ const included = includedFromLimits(limits);
885
+ const creditBalance = limits.balance?.creditBalance ?? limits.creditBalance;
886
+ return {
887
+ ...limits.plan ? { planRef: limits.plan } : {},
888
+ ...limits.plans !== void 0 ? { plans: limits.plans } : {},
889
+ ...limits.meterName !== void 0 ? { meterName: limits.meterName } : {},
890
+ ...plan?.perUnitChargeMinor != null ? { unitPriceMinor: plan.perUnitChargeMinor, currency: plan.currency } : limits.currency ? { currency: limits.currency } : {},
891
+ ...included ? { included } : {},
892
+ ...creditBalance !== void 0 ? { creditBalance } : {}
893
+ };
894
+ }
823
895
  function buildPaywallGate(productRef, limits) {
824
896
  const checkoutUrl = limits.checkoutUrl;
825
897
  const state = classifyPaywallState({ ...limits, plan: limits.plan ?? "" });
826
898
  const useActivationForTopup = !limits.activationRequired && state.kind === "topup_required" && allPaidPlansArePayg(limits.plans);
899
+ const recovery = recoveryFields(limits);
827
900
  const preMessage = limits.activationRequired || useActivationForTopup ? {
828
901
  kind: "activation_required",
829
902
  product: productRef,
830
903
  message: "",
831
904
  checkoutUrl: limits.confirmationUrl || checkoutUrl || "",
832
905
  ...limits.confirmationUrl !== void 0 ? { confirmationUrl: limits.confirmationUrl } : {},
833
- ...limits.plans !== void 0 ? { plans: limits.plans } : {},
834
906
  ...limits.balance !== void 0 ? { balance: limits.balance } : {},
835
- ...limits.product !== void 0 ? { productDetails: limits.product } : {}
907
+ ...limits.product !== void 0 ? { productDetails: limits.product } : {},
908
+ ...recovery
836
909
  } : {
837
910
  kind: "payment_required",
838
911
  product: productRef,
839
912
  checkoutUrl: checkoutUrl || "",
840
913
  message: "",
841
914
  ...limits.balance !== void 0 ? { balance: limits.balance } : {},
842
- ...limits.product !== void 0 ? { productDetails: limits.product } : {}
915
+ ...limits.product !== void 0 ? { productDetails: limits.product } : {},
916
+ ...recovery
843
917
  };
844
918
  return { ...preMessage, message: buildGateMessage(state, preMessage) };
845
919
  }
@@ -1947,34 +2021,34 @@ function isJsonSchemaObject(value) {
1947
2021
  return Boolean(value && typeof value === "object");
1948
2022
  }
1949
2023
  function toZodSchema(property) {
1950
- const z = getZod();
2024
+ const z2 = getZod();
1951
2025
  if (Array.isArray(property.enum) && property.enum.length > 0) {
1952
2026
  if (property.enum.every((value) => typeof value === "string")) {
1953
2027
  const [first, ...rest] = property.enum;
1954
- return z.enum([first, ...rest]);
2028
+ return z2.enum([first, ...rest]);
1955
2029
  }
1956
2030
  if (property.enum.length === 1) {
1957
- return z.literal(property.enum[0]);
2031
+ return z2.literal(property.enum[0]);
1958
2032
  }
1959
- return z.union(property.enum.map((value) => z.literal(value)));
2033
+ return z2.union(property.enum.map((value) => z2.literal(value)));
1960
2034
  }
1961
2035
  switch (property.type) {
1962
2036
  case "string":
1963
- return z.string();
2037
+ return z2.string();
1964
2038
  case "number":
1965
- return z.number();
2039
+ return z2.number();
1966
2040
  case "integer":
1967
- return z.number().int();
2041
+ return z2.number().int();
1968
2042
  case "boolean":
1969
- return z.boolean();
2043
+ return z2.boolean();
1970
2044
  case "array":
1971
- return z.array(
1972
- isJsonSchemaObject(property.items) ? toZodSchema(property.items) : z.any()
2045
+ return z2.array(
2046
+ isJsonSchemaObject(property.items) ? toZodSchema(property.items) : z2.any()
1973
2047
  );
1974
2048
  case "object":
1975
- return z.object(jsonSchemaToZodRawShape(property.properties || {}));
2049
+ return z2.object(jsonSchemaToZodRawShape(property.properties || {}));
1976
2050
  default:
1977
- return z.any();
2051
+ return z2.any();
1978
2052
  }
1979
2053
  }
1980
2054
  function jsonSchemaToZodRawShape(properties, required = []) {
@@ -2009,12 +2083,12 @@ function registerVirtualToolsMcpImpl(server, apiClient, options) {
2009
2083
  if (filter && !filter(toolDefinition)) continue;
2010
2084
  const mappedDefinition = mapDefinition ? mapDefinition(toolDefinition) : toolDefinition;
2011
2085
  const wrappedHandler = wrapHandler ? wrapHandler(mappedDefinition.handler, mappedDefinition) : mappedDefinition.handler;
2012
- const z = getZod();
2086
+ const z2 = getZod();
2013
2087
  server.registerTool(
2014
2088
  mappedDefinition.name,
2015
2089
  {
2016
2090
  description: mappedDefinition.description,
2017
- inputSchema: z.object(
2091
+ inputSchema: z2.object(
2018
2092
  jsonSchemaToZodRawShape(
2019
2093
  mappedDefinition.inputSchema.properties,
2020
2094
  mappedDefinition.inputSchema.required || []
@@ -2337,6 +2411,42 @@ function isPaywallStructuredContent(value) {
2337
2411
  return kind === "payment_required" || kind === "activation_required";
2338
2412
  }
2339
2413
 
2414
+ // src/types/paywall-schema.ts
2415
+ import { z } from "zod";
2416
+ var includedSchema = z.object({
2417
+ total: z.number(),
2418
+ used: z.number(),
2419
+ remaining: z.number()
2420
+ });
2421
+ var recoveryFields2 = {
2422
+ planRef: z.string().optional(),
2423
+ plans: z.array(z.record(z.string(), z.unknown())).optional(),
2424
+ meterName: z.string().optional(),
2425
+ unitPriceMinor: z.number().optional(),
2426
+ currency: z.string().optional(),
2427
+ included: includedSchema.optional(),
2428
+ creditBalance: z.number().optional(),
2429
+ balance: z.record(z.string(), z.unknown()).optional(),
2430
+ productDetails: z.record(z.string(), z.unknown()).optional()
2431
+ };
2432
+ var PaywallStructuredContentSchema = z.discriminatedUnion("kind", [
2433
+ z.object({
2434
+ kind: z.literal("payment_required"),
2435
+ product: z.string(),
2436
+ checkoutUrl: z.string(),
2437
+ message: z.string(),
2438
+ ...recoveryFields2
2439
+ }),
2440
+ z.object({
2441
+ kind: z.literal("activation_required"),
2442
+ product: z.string(),
2443
+ checkoutUrl: z.string(),
2444
+ message: z.string(),
2445
+ confirmationUrl: z.string().optional(),
2446
+ ...recoveryFields2
2447
+ })
2448
+ ]);
2449
+
2340
2450
  // src/helpers/error.ts
2341
2451
  import { SolvaPayError as SolvaPayError5 } from "@solvapay/core";
2342
2452
  function isErrorResult(result) {
@@ -3399,6 +3509,7 @@ function verifyWebhook({
3399
3509
  export {
3400
3510
  BALANCE_RECONCILE_DELAYS_MS,
3401
3511
  PaywallError,
3512
+ PaywallStructuredContentSchema,
3402
3513
  TOPUP_BALANCE_POLL_DELAYS_MS,
3403
3514
  VIRTUAL_TOOL_DEFINITIONS,
3404
3515
  activatePlanCore,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solvapay/server",
3
- "version": "2.3.1",
3
+ "version": "2.4.0-preview-11dbbe603cd1b9a44cc41b5eee3686bc4000f97f",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "sideEffects": false,
44
44
  "dependencies": {
45
- "@solvapay/core": "1.6.0"
45
+ "@solvapay/core": "1.6.0-preview-11dbbe603cd1b9a44cc41b5eee3686bc4000f97f"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "zod": "^4.2.0",