@solvapay/server 1.0.8-preview.9 → 1.0.8

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
@@ -1,5 +1,3 @@
1
- import "./chunk-MLKGABMK.js";
2
-
3
1
  // src/index.ts
4
2
  import crypto from "crypto";
5
3
  import { SolvaPayError as SolvaPayError5 } from "@solvapay/core";
@@ -64,7 +62,27 @@ function createSolvaPayClient(opts) {
64
62
  throw new SolvaPayError(`Create customer failed (${res.status}): ${error}`);
65
63
  }
66
64
  const result = await res.json();
67
- return result;
65
+ return {
66
+ customerRef: result.reference || result.customerRef
67
+ };
68
+ },
69
+ // PATCH: /v1/sdk/customers/{customerRef}
70
+ async updateCustomer(customerRef, params) {
71
+ const url = `${base}/v1/sdk/customers/${encodeURIComponent(customerRef)}`;
72
+ const res = await fetch(url, {
73
+ method: "PATCH",
74
+ headers,
75
+ body: JSON.stringify(params)
76
+ });
77
+ if (!res.ok) {
78
+ const error = await res.text();
79
+ log(`\u274C API Error: ${res.status} - ${error}`);
80
+ throw new SolvaPayError(`Update customer failed (${res.status}): ${error}`);
81
+ }
82
+ const result = await res.json();
83
+ return {
84
+ customerRef: result.reference || result.customerRef || customerRef
85
+ };
68
86
  },
69
87
  // GET: /v1/sdk/customers/{reference} or /v1/sdk/customers?externalRef={externalRef}|email={email}
70
88
  async getCustomer(params) {
@@ -124,6 +142,20 @@ function createSolvaPayClient(opts) {
124
142
  }
125
143
  return res.json();
126
144
  },
145
+ // GET: /v1/sdk/platform-config
146
+ async getPlatformConfig() {
147
+ const url = `${base}/v1/sdk/platform-config`;
148
+ const res = await fetch(url, {
149
+ method: "GET",
150
+ headers
151
+ });
152
+ if (!res.ok) {
153
+ const error = await res.text();
154
+ log(`\u274C API Error: ${res.status} - ${error}`);
155
+ throw new SolvaPayError(`Get platform config failed (${res.status}): ${error}`);
156
+ }
157
+ return res.json();
158
+ },
127
159
  // GET: /v1/sdk/products/{productRef}
128
160
  async getProduct(productRef) {
129
161
  const url = `${base}/v1/sdk/products/${encodeURIComponent(productRef)}`;
@@ -550,10 +582,67 @@ function createSolvaPayClient(opts) {
550
582
  throw new SolvaPayError(`Activate plan failed (${res.status}): ${error}`);
551
583
  }
552
584
  return await res.json();
585
+ },
586
+ async getPaymentMethod(params) {
587
+ const url = new URL(`${base}/v1/sdk/payment-method`);
588
+ url.searchParams.set("customerRef", params.customerRef);
589
+ const res = await fetch(url.toString(), { method: "GET", headers });
590
+ if (!res.ok) {
591
+ const error = await res.text();
592
+ log(`\u274C API Error: ${res.status} - ${error}`);
593
+ throw new SolvaPayError(`Get payment method failed (${res.status}): ${error}`);
594
+ }
595
+ return await res.json();
553
596
  }
554
597
  };
555
598
  }
556
599
 
600
+ // src/paywall-state.ts
601
+ function classifyPaywallState(limits) {
602
+ if (!limits) return { kind: "upgrade_required" };
603
+ if (limits.activationRequired === true) {
604
+ return { kind: "activation_required" };
605
+ }
606
+ const activePlan = limits.plans?.find((p) => p.reference === limits.plan);
607
+ const isUsageBased = activePlan?.type === "usage-based" || limits.balance !== void 0;
608
+ const creditBalance = limits.balance?.creditBalance ?? limits.creditBalance;
609
+ if (isUsageBased) {
610
+ if (creditBalance === 0) return { kind: "topup_required" };
611
+ if (creditBalance === void 0 && limits.remaining === 0) {
612
+ return { kind: "topup_required" };
613
+ }
614
+ }
615
+ return { kind: "upgrade_required" };
616
+ }
617
+ function buildGateMessage(state, gate) {
618
+ const url = gate.checkoutUrl && gate.checkoutUrl.length > 0 ? gate.checkoutUrl : null;
619
+ const openClause = url ? `, or open ${url} in a browser` : "";
620
+ switch (state.kind) {
621
+ case "activation_required":
622
+ return `Your plan needs activation before you can use this tool. Call the \`activate_plan\` tool to activate it${openClause}.`;
623
+ case "topup_required":
624
+ return `You're out of credits. Call the \`topup\` tool to add more${openClause}.`;
625
+ case "upgrade_required":
626
+ return `You don't have an active plan for this tool. Call the \`upgrade\` tool to pick a plan${openClause}.`;
627
+ case "reactivation_required":
628
+ 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.`;
629
+ }
630
+ }
631
+ function buildNudgeMessage(state, limits) {
632
+ const url = limits?.checkoutUrl && limits.checkoutUrl.length > 0 ? limits.checkoutUrl : null;
633
+ const visitClause = url ? `, or visit ${url}` : "";
634
+ switch (state.kind) {
635
+ case "topup_required":
636
+ return `Heads up \u2014 running low on credits. Call the \`topup\` tool to add more${visitClause}.`;
637
+ case "upgrade_required":
638
+ return `Heads up \u2014 approaching your plan's limit this period. Call the \`upgrade\` tool for more headroom${visitClause}.`;
639
+ case "activation_required":
640
+ return `Heads up \u2014 this plan still needs activation. Call the \`activate_plan\` tool${visitClause}.`;
641
+ case "reactivation_required":
642
+ return `Heads up \u2014 your plan is no longer active. Call the \`manage_account\` tool to reactivate it${visitClause}.`;
643
+ }
644
+ }
645
+
557
646
  // src/utils.ts
558
647
  async function withRetry(fn, options = {}) {
559
648
  const {
@@ -720,6 +809,10 @@ function paywallErrorToClientPayload(error) {
720
809
  if (sc.balance !== void 0) base.balance = sc.balance;
721
810
  if (sc.productDetails !== void 0) base.productDetails = sc.productDetails;
722
811
  if (sc.confirmationUrl !== void 0) base.confirmationUrl = sc.confirmationUrl;
812
+ } else {
813
+ base.kind = "payment_required";
814
+ if (sc.balance !== void 0) base.balance = sc.balance;
815
+ if (sc.productDetails !== void 0) base.productDetails = sc.productDetails;
723
816
  }
724
817
  return base;
725
818
  }
@@ -731,6 +824,7 @@ var sharedCustomerLookupDeduplicator = createRequestDeduplicator({
731
824
  cacheErrors: false
732
825
  // Don't cache errors - retry on next request
733
826
  });
827
+ var EXTRA_FORWARD_KEY = "__solvapayExtra";
734
828
  var SolvaPayPaywall = class {
735
829
  constructor(apiClient, options = {}) {
736
830
  this.apiClient = apiClient;
@@ -756,136 +850,216 @@ var SolvaPayPaywall = class {
756
850
  return `solvapay_${timestamp}_${random}`;
757
851
  }
758
852
  /**
759
- * Core protection method - works for both MCP and HTTP
853
+ * Pure decision routine performs customer resolution, limits cache
854
+ * lookup / fresh `checkLimits` fetch, and returns a `PaywallDecision`
855
+ * describing whether the handler should run.
856
+ *
857
+ * Side effects kept in lockstep with the legacy `protect()` path:
858
+ * - creates the backend customer on first use (`ensureCustomer`),
859
+ * - updates the limits cache (consume-one-unit bookkeeping), and
860
+ * - emits a `paywall` usage event on gate outcomes.
861
+ *
862
+ * `trackUsage` for the `success` / `fail` outcome is emitted by the
863
+ * caller (adapter or `protect()`) once it has actually invoked the
864
+ * handler — `decide()` never counts handler execution as usage.
865
+ *
866
+ * @since 1.1.0
760
867
  */
761
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
762
- async protect(handler, metadata = {}, getCustomerRef) {
868
+ async decide(args, metadata = {}, getCustomerRef) {
763
869
  const product = this.resolveProduct(metadata);
764
870
  const usageType = metadata.usageType || "requests";
765
- return async (args) => {
766
- const startTime = Date.now();
767
- const requestId = this.generateRequestId();
768
- const inputCustomerRef = getCustomerRef ? getCustomerRef(args) : args.auth?.customer_ref || "anonymous";
769
- let backendCustomerRef;
770
- if (inputCustomerRef.startsWith("cus_")) {
771
- backendCustomerRef = inputCustomerRef;
871
+ const requestId = this.generateRequestId();
872
+ const startTime = Date.now();
873
+ const inputCustomerRef = getCustomerRef ? getCustomerRef(args) : args.auth?.customer_ref || "anonymous";
874
+ let backendCustomerRef;
875
+ if (inputCustomerRef.startsWith("cus_")) {
876
+ backendCustomerRef = inputCustomerRef;
877
+ } else {
878
+ backendCustomerRef = await this.ensureCustomer(inputCustomerRef, inputCustomerRef);
879
+ }
880
+ const limitsCacheKey = `${backendCustomerRef}:${product}:${usageType}`;
881
+ const cachedLimits = this.limitsCache.get(limitsCacheKey);
882
+ const now = Date.now();
883
+ let withinLimits;
884
+ let remaining;
885
+ let checkoutUrl;
886
+ let resolvedMeterName;
887
+ let lastLimitsCheck;
888
+ const hasFreshCachedLimits = cachedLimits && now - cachedLimits.timestamp < this.limitsCacheTTL;
889
+ if (hasFreshCachedLimits) {
890
+ checkoutUrl = cachedLimits.checkoutUrl;
891
+ resolvedMeterName = cachedLimits.meterName;
892
+ lastLimitsCheck = cachedLimits.limits;
893
+ if (cachedLimits.remaining > 0) {
894
+ cachedLimits.remaining--;
895
+ if (cachedLimits.remaining <= 0) {
896
+ this.limitsCache.delete(limitsCacheKey);
897
+ }
898
+ withinLimits = true;
899
+ remaining = cachedLimits.remaining;
900
+ } else {
901
+ withinLimits = false;
902
+ remaining = 0;
903
+ this.limitsCache.delete(limitsCacheKey);
904
+ }
905
+ } else {
906
+ if (cachedLimits) {
907
+ this.limitsCache.delete(limitsCacheKey);
908
+ }
909
+ const limitsCheck = await this.apiClient.checkLimits({
910
+ customerRef: backendCustomerRef,
911
+ productRef: product,
912
+ meterName: usageType
913
+ });
914
+ lastLimitsCheck = limitsCheck;
915
+ withinLimits = limitsCheck.withinLimits;
916
+ remaining = limitsCheck.remaining;
917
+ checkoutUrl = limitsCheck.checkoutUrl;
918
+ resolvedMeterName = limitsCheck.meterName;
919
+ const consumedAllowance = withinLimits && remaining > 0;
920
+ if (consumedAllowance) {
921
+ remaining = Math.max(0, remaining - 1);
922
+ }
923
+ if (consumedAllowance) {
924
+ this.limitsCache.set(limitsCacheKey, {
925
+ remaining,
926
+ checkoutUrl,
927
+ meterName: resolvedMeterName,
928
+ timestamp: now,
929
+ limits: limitsCheck
930
+ });
931
+ }
932
+ }
933
+ if (!withinLimits) {
934
+ const latencyMs = Date.now() - startTime;
935
+ this.trackUsage(
936
+ backendCustomerRef,
937
+ product,
938
+ resolvedMeterName || usageType,
939
+ "paywall",
940
+ requestId,
941
+ latencyMs
942
+ );
943
+ const state = classifyPaywallState(lastLimitsCheck ?? null);
944
+ const preMessageGate = lastLimitsCheck?.activationRequired ? {
945
+ kind: "activation_required",
946
+ product,
947
+ message: "",
948
+ checkoutUrl: lastLimitsCheck.confirmationUrl || lastLimitsCheck.checkoutUrl || checkoutUrl || "",
949
+ ...lastLimitsCheck.confirmationUrl !== void 0 ? { confirmationUrl: lastLimitsCheck.confirmationUrl } : {},
950
+ ...lastLimitsCheck.plans !== void 0 ? { plans: lastLimitsCheck.plans } : {},
951
+ ...lastLimitsCheck.balance !== void 0 ? { balance: lastLimitsCheck.balance } : {},
952
+ ...lastLimitsCheck.product !== void 0 ? { productDetails: lastLimitsCheck.product } : {}
953
+ } : {
954
+ kind: "payment_required",
955
+ product,
956
+ checkoutUrl: checkoutUrl || "",
957
+ message: "",
958
+ ...lastLimitsCheck?.balance !== void 0 ? { balance: lastLimitsCheck.balance } : {},
959
+ ...lastLimitsCheck?.product !== void 0 ? { productDetails: lastLimitsCheck.product } : {}
960
+ };
961
+ const gate = {
962
+ ...preMessageGate,
963
+ message: buildGateMessage(state, preMessageGate)
964
+ };
965
+ return {
966
+ outcome: "gate",
967
+ gate,
968
+ limits: lastLimitsCheck ?? null,
969
+ customerRef: backendCustomerRef
970
+ };
971
+ }
972
+ return {
973
+ outcome: "allow",
974
+ args,
975
+ limits: lastLimitsCheck,
976
+ customerRef: backendCustomerRef
977
+ };
978
+ }
979
+ /**
980
+ * Execute the handler for an already-obtained `allow` decision and
981
+ * emit the post-handler `trackUsage('success' | 'fail', ...)` event.
982
+ *
983
+ * Exposed for adapter integration — the adapter layer drives the
984
+ * paywall through `decide()` + `runAllow()` so `formatGate` can own
985
+ * gate outcomes without routing through `PaywallError`. `protect()`
986
+ * continues to offer the self-contained throw-based surface for
987
+ * legacy consumers.
988
+ *
989
+ * `runAllow` intentionally does NOT re-throw `PaywallError` — if a
990
+ * handler calls `ctx.gate(reason)` and throws from deep code, the
991
+ * adapter catches that at the `formatGate` boundary instead.
992
+ *
993
+ * @since 1.1.0
994
+ */
995
+ async runAllow(decision, handler, metadata, args) {
996
+ const product = this.resolveProduct(metadata);
997
+ const usageType = metadata.usageType || "requests";
998
+ const requestId = this.generateRequestId();
999
+ const startTime = Date.now();
1000
+ const forwardedExtra = args[EXTRA_FORWARD_KEY];
1001
+ const handlerContext = {
1002
+ customerRef: decision.customerRef,
1003
+ limits: decision.limits,
1004
+ ...forwardedExtra !== void 0 ? { extra: forwardedExtra } : {}
1005
+ };
1006
+ try {
1007
+ const result = await handler(args, handlerContext);
1008
+ const latencyMs = Date.now() - startTime;
1009
+ this.trackUsage(
1010
+ decision.customerRef,
1011
+ product,
1012
+ decision.limits.meterName || usageType,
1013
+ "success",
1014
+ requestId,
1015
+ latencyMs
1016
+ );
1017
+ return result;
1018
+ } catch (error) {
1019
+ if (error instanceof Error) {
1020
+ const errorType = error instanceof PaywallError ? "PaywallError" : "API Error";
1021
+ this.log(`\u274C Error in paywall [${errorType}]: ${error.message}`);
772
1022
  } else {
773
- backendCustomerRef = await this.ensureCustomer(inputCustomerRef, inputCustomerRef);
1023
+ this.log(`\u274C Error in paywall:`, error);
774
1024
  }
775
- let resolvedMeterName;
776
- try {
777
- const limitsCacheKey = `${backendCustomerRef}:${product}:${usageType}`;
778
- const cachedLimits = this.limitsCache.get(limitsCacheKey);
779
- const now = Date.now();
780
- let withinLimits;
781
- let remaining;
782
- let checkoutUrl;
783
- let lastLimitsCheck;
784
- const hasFreshCachedLimits = cachedLimits && now - cachedLimits.timestamp < this.limitsCacheTTL;
785
- if (hasFreshCachedLimits) {
786
- checkoutUrl = cachedLimits.checkoutUrl;
787
- resolvedMeterName = cachedLimits.meterName;
788
- if (cachedLimits.remaining > 0) {
789
- cachedLimits.remaining--;
790
- if (cachedLimits.remaining <= 0) {
791
- this.limitsCache.delete(limitsCacheKey);
792
- }
793
- withinLimits = true;
794
- remaining = cachedLimits.remaining;
795
- } else {
796
- withinLimits = false;
797
- remaining = 0;
798
- this.limitsCache.delete(limitsCacheKey);
799
- }
800
- } else {
801
- if (cachedLimits) {
802
- this.limitsCache.delete(limitsCacheKey);
803
- }
804
- const limitsCheck = await this.apiClient.checkLimits({
805
- customerRef: backendCustomerRef,
806
- productRef: product,
807
- meterName: usageType
808
- });
809
- lastLimitsCheck = limitsCheck;
810
- withinLimits = limitsCheck.withinLimits;
811
- remaining = limitsCheck.remaining;
812
- checkoutUrl = limitsCheck.checkoutUrl;
813
- resolvedMeterName = limitsCheck.meterName;
814
- const consumedAllowance = withinLimits && remaining > 0;
815
- if (consumedAllowance) {
816
- remaining = Math.max(0, remaining - 1);
817
- }
818
- if (consumedAllowance) {
819
- this.limitsCache.set(limitsCacheKey, {
820
- remaining,
821
- checkoutUrl,
822
- meterName: resolvedMeterName,
823
- timestamp: now
824
- });
825
- }
826
- }
827
- if (!withinLimits) {
828
- const latencyMs2 = Date.now() - startTime;
829
- this.trackUsage(
830
- backendCustomerRef,
831
- product,
832
- resolvedMeterName || usageType,
833
- "paywall",
834
- requestId,
835
- latencyMs2
836
- );
837
- if (lastLimitsCheck?.activationRequired) {
838
- const confirmationUrl = lastLimitsCheck.confirmationUrl;
839
- const payCheckoutUrl = confirmationUrl || lastLimitsCheck.checkoutUrl || checkoutUrl || "";
840
- throw new PaywallError("Activation required", {
841
- kind: "activation_required",
842
- product,
843
- message: "Product activation is required before this tool can be used.",
844
- checkoutUrl: payCheckoutUrl,
845
- confirmationUrl,
846
- plans: lastLimitsCheck.plans,
847
- balance: lastLimitsCheck.balance,
848
- productDetails: lastLimitsCheck.product
849
- });
850
- }
851
- throw new PaywallError("Payment required", {
852
- kind: "payment_required",
853
- product,
854
- checkoutUrl: checkoutUrl || "",
855
- message: `Purchase required. Remaining: ${remaining}`
856
- });
857
- }
858
- const result = await handler(args);
1025
+ if (!(error instanceof PaywallError)) {
859
1026
  const latencyMs = Date.now() - startTime;
860
1027
  this.trackUsage(
861
- backendCustomerRef,
1028
+ decision.customerRef,
862
1029
  product,
863
- resolvedMeterName || usageType,
864
- "success",
1030
+ decision.limits.meterName || usageType,
1031
+ "fail",
865
1032
  requestId,
866
1033
  latencyMs
867
1034
  );
868
- return result;
869
- } catch (error) {
870
- if (error instanceof Error) {
871
- const errorType = error instanceof PaywallError ? "PaywallError" : "API Error";
872
- this.log(`\u274C Error in paywall [${errorType}]: ${error.message}`);
873
- } else {
874
- this.log(`\u274C Error in paywall:`, error);
875
- }
876
- if (!(error instanceof PaywallError)) {
877
- const latencyMs = Date.now() - startTime;
878
- this.trackUsage(
879
- backendCustomerRef,
880
- product,
881
- resolvedMeterName || usageType,
882
- "fail",
883
- requestId,
884
- latencyMs
885
- );
886
- }
887
- throw error;
888
1035
  }
1036
+ throw error;
1037
+ }
1038
+ }
1039
+ /**
1040
+ * Core protection method - works for both MCP and HTTP
1041
+ *
1042
+ * The `handler` may optionally declare a second positional argument
1043
+ * of type `ProtectHandlerContext` to receive the resolved customer
1044
+ * ref, the pre-check `LimitResponseWithPlan`, and an opaque `extra`
1045
+ * bag threaded through from the adapter layer. One-arg handlers
1046
+ * ignore the second argument and continue to work unchanged.
1047
+ *
1048
+ * Implemented on top of `decide()`: pre-check runs through the same
1049
+ * decision routine, and gate outcomes are raised as a `PaywallError`
1050
+ * to preserve the legacy throw-based signal for consumers that
1051
+ * haven't migrated to adapter-level `formatGate` routing.
1052
+ */
1053
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1054
+ async protect(handler, metadata = {}, getCustomerRef) {
1055
+ return async (args) => {
1056
+ const decision = await this.decide(args, metadata, getCustomerRef);
1057
+ if (decision.outcome === "gate") {
1058
+ const message = decision.gate.kind === "activation_required" ? "Activation required" : "Payment required";
1059
+ this.log(`\u274C Error in paywall [PaywallError]: ${message}`);
1060
+ throw new PaywallError(message, decision.gate);
1061
+ }
1062
+ return this.runAllow(decision, handler, metadata, args);
889
1063
  };
890
1064
  }
891
1065
  /**
@@ -988,6 +1162,16 @@ var SolvaPayPaywall = class {
988
1162
  this.log(
989
1163
  `\u26A0\uFE0F Resolved customer ${customerRef} by email after conflict; using existing customer ${byEmail.customerRef}`
990
1164
  );
1165
+ if (!byEmail.externalRef && this.apiClient.updateCustomer) {
1166
+ try {
1167
+ await this.apiClient.updateCustomer(byEmail.customerRef, { externalRef });
1168
+ } catch (backfillError) {
1169
+ this.log(
1170
+ `\u26A0\uFE0F Failed to backfill externalRef on ${byEmail.customerRef}:`,
1171
+ backfillError instanceof Error ? backfillError.message : backfillError
1172
+ );
1173
+ }
1174
+ }
991
1175
  return byEmail.customerRef;
992
1176
  }
993
1177
  } catch (emailLookupError) {
@@ -1064,6 +1248,7 @@ var SolvaPayPaywall = class {
1064
1248
  };
1065
1249
 
1066
1250
  // src/adapters/base.ts
1251
+ var EXTRA_FORWARD_KEY2 = "__solvapayExtra";
1067
1252
  var AdapterUtils = class {
1068
1253
  /**
1069
1254
  * Ensure customer reference is properly formatted
@@ -1079,7 +1264,7 @@ var AdapterUtils = class {
1079
1264
  */
1080
1265
  static async extractFromJWT(token, options) {
1081
1266
  try {
1082
- const { jwtVerify } = await import("./webapi-K5XBCEO6.js");
1267
+ const { jwtVerify } = await import("jose");
1083
1268
  const jwtSecret = new TextEncoder().encode(
1084
1269
  options?.secret || process.env.OAUTH_JWKS_SECRET || "test-jwt-secret"
1085
1270
  );
@@ -1095,23 +1280,45 @@ var AdapterUtils = class {
1095
1280
  };
1096
1281
  async function createAdapterHandler(adapter, paywall, metadata, businessLogic) {
1097
1282
  const backendRefCache = /* @__PURE__ */ new Map();
1098
- const getCustomerRef = (args) => args.auth?.customer_ref || "anonymous";
1099
- const protectedHandler = await paywall.protect(businessLogic, metadata, getCustomerRef);
1100
1283
  return async (context, extra) => {
1284
+ let args;
1285
+ let customerRef;
1101
1286
  try {
1102
- const args = await adapter.extractArgs(context);
1103
- const customerRef = await adapter.getCustomerRef(context, extra);
1287
+ args = await adapter.extractArgs(context);
1288
+ customerRef = await adapter.getCustomerRef(context, extra);
1104
1289
  let backendRef = backendRefCache.get(customerRef);
1105
1290
  if (!backendRef) {
1106
1291
  backendRef = await paywall.ensureCustomer(customerRef, customerRef);
1107
1292
  backendRefCache.set(customerRef, backendRef);
1108
1293
  }
1109
1294
  args.auth = { customer_ref: backendRef };
1110
- const result = await protectedHandler(args);
1111
- return adapter.formatResponse(result, context);
1112
1295
  } catch (error) {
1113
1296
  return adapter.formatError(error, context);
1114
1297
  }
1298
+ const decideGetCustomerRef = (args2) => args2.auth?.customer_ref || "anonymous";
1299
+ try {
1300
+ const decision = await paywall.decide(args, metadata, decideGetCustomerRef);
1301
+ if (decision.outcome === "gate") {
1302
+ return adapter.formatGate(decision.gate, context);
1303
+ }
1304
+ if (extra !== void 0) {
1305
+ ;
1306
+ args[EXTRA_FORWARD_KEY2] = extra;
1307
+ }
1308
+ try {
1309
+ const result = await paywall.runAllow(decision, businessLogic, metadata, args);
1310
+ return adapter.formatResponse(result, context);
1311
+ } finally {
1312
+ if (EXTRA_FORWARD_KEY2 in args) {
1313
+ delete args[EXTRA_FORWARD_KEY2];
1314
+ }
1315
+ }
1316
+ } catch (error) {
1317
+ if (error instanceof PaywallError) {
1318
+ return adapter.formatGate(error.structuredContent, context);
1319
+ }
1320
+ return adapter.formatError(error, context);
1321
+ }
1115
1322
  };
1116
1323
  }
1117
1324
 
@@ -1159,24 +1366,25 @@ var HttpAdapter = class {
1159
1366
  }
1160
1367
  return result;
1161
1368
  }
1162
- formatError(error, [_req, reply]) {
1163
- if (error instanceof PaywallError) {
1164
- const errorResponse2 = {
1165
- success: false,
1166
- error: "Payment required",
1167
- product: error.structuredContent.product,
1168
- checkoutUrl: error.structuredContent.checkoutUrl,
1169
- message: error.structuredContent.message
1170
- };
1171
- if (reply && reply.status && typeof reply.json === "function") {
1172
- reply.status(402).json(errorResponse2);
1173
- return;
1174
- }
1175
- if (reply && reply.code) {
1176
- reply.code(402);
1177
- }
1178
- return errorResponse2;
1369
+ /**
1370
+ * Emit a 402 Payment Required response with the same JSON body shape
1371
+ * REST consumers have always received (`{success:false, error, product,
1372
+ * checkoutUrl, message, ...}`). The shape is reused via
1373
+ * `paywallErrorToClientPayload` so HTTP / Next / hosted-proxy clients
1374
+ * don't have to branch on an SDK version.
1375
+ */
1376
+ formatGate(gate, [_req, reply]) {
1377
+ const errorResponse = paywallErrorToClientPayload(new PaywallError(gate.message, gate));
1378
+ if (reply && reply.status && typeof reply.json === "function") {
1379
+ reply.status(402).json(errorResponse);
1380
+ return;
1179
1381
  }
1382
+ if (reply && reply.code) {
1383
+ reply.code(402);
1384
+ }
1385
+ return errorResponse;
1386
+ }
1387
+ formatError(error, [_req, reply]) {
1180
1388
  const errorResponse = {
1181
1389
  success: false,
1182
1390
  error: error instanceof Error ? error.message : "Internal server error"
@@ -1254,22 +1462,22 @@ var NextAdapter = class {
1254
1462
  headers: { "Content-Type": "application/json" }
1255
1463
  });
1256
1464
  }
1465
+ /**
1466
+ * Emit a 402 Payment Required `Response` with the same JSON body
1467
+ * REST consumers have always received. Reuses
1468
+ * `paywallErrorToClientPayload` so HTTP / Next / hosted-proxy
1469
+ * clients don't have to branch on an SDK version.
1470
+ */
1471
+ formatGate(gate, _context) {
1472
+ return new Response(
1473
+ JSON.stringify(paywallErrorToClientPayload(new PaywallError(gate.message, gate))),
1474
+ {
1475
+ status: 402,
1476
+ headers: { "Content-Type": "application/json" }
1477
+ }
1478
+ );
1479
+ }
1257
1480
  formatError(error, _context) {
1258
- if (error instanceof PaywallError) {
1259
- return new Response(
1260
- JSON.stringify({
1261
- success: false,
1262
- error: "Payment required",
1263
- product: error.structuredContent.product,
1264
- checkoutUrl: error.structuredContent.checkoutUrl,
1265
- message: error.structuredContent.message
1266
- }),
1267
- {
1268
- status: 402,
1269
- headers: { "Content-Type": "application/json" }
1270
- }
1271
- );
1272
- }
1273
1481
  return new Response(
1274
1482
  JSON.stringify({
1275
1483
  success: false,
@@ -1317,19 +1525,27 @@ var McpAdapter = class {
1317
1525
  }
1318
1526
  return response;
1319
1527
  }
1528
+ /**
1529
+ * Emit a plain-narration paywall response — `content[0].text` carries
1530
+ * the gate's human message (LLM-actionable), `structuredContent`
1531
+ * carries the machine-readable gate payload, and `isError` stays
1532
+ * `false` per the MCP spec's own `isError` definition (paywall is
1533
+ * not a self-correctable tool execution error; it is a user-facing
1534
+ * control transfer to the UI).
1535
+ *
1536
+ * Hosts that read widget metadata from `tools/list` or tool-result
1537
+ * `_meta.ui` open the paywall iframe on top of this response;
1538
+ * `buildPayableHandler` stamps the `_meta.ui.resourceUri` envelope
1539
+ * before returning.
1540
+ */
1541
+ formatGate(gate, _context) {
1542
+ return {
1543
+ content: [{ type: "text", text: gate.message }],
1544
+ isError: false,
1545
+ structuredContent: gate
1546
+ };
1547
+ }
1320
1548
  formatError(error, _context) {
1321
- if (error instanceof PaywallError) {
1322
- return {
1323
- content: [
1324
- {
1325
- type: "text",
1326
- text: JSON.stringify(paywallErrorToClientPayload(error), null, 2)
1327
- }
1328
- ],
1329
- isError: true,
1330
- structuredContent: error.structuredContent
1331
- };
1332
- }
1333
1549
  return {
1334
1550
  content: [
1335
1551
  {
@@ -1735,217 +1951,13 @@ function createSolvaPay(config) {
1735
1951
  };
1736
1952
  }
1737
1953
 
1738
- // src/mcp-auth.ts
1739
- var McpBearerAuthError = class extends Error {
1740
- constructor(message) {
1741
- super(message);
1742
- this.name = "McpBearerAuthError";
1743
- }
1744
- };
1745
- function base64UrlDecode(input) {
1746
- const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
1747
- const padded = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
1748
- const bytes = Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
1749
- return new TextDecoder().decode(bytes);
1750
- }
1751
- function extractBearerToken(authorization) {
1752
- if (!authorization) return null;
1753
- if (!authorization.startsWith("Bearer ")) return null;
1754
- return authorization.slice(7).trim() || null;
1755
- }
1756
- function decodeJwtPayload(token) {
1757
- const parts = token.split(".");
1758
- if (parts.length < 2) {
1759
- throw new McpBearerAuthError("Invalid JWT format");
1760
- }
1761
- try {
1762
- const payloadText = base64UrlDecode(parts[1]);
1763
- const payload = JSON.parse(payloadText);
1764
- return payload;
1765
- } catch {
1766
- throw new McpBearerAuthError("Invalid JWT payload");
1767
- }
1768
- }
1769
- function getCustomerRefFromJwtPayload(payload, options = {}) {
1770
- const claimPriority = options.claimPriority || ["customerRef", "customer_ref", "sub"];
1771
- for (const claim of claimPriority) {
1772
- const value = payload[claim];
1773
- if (typeof value === "string" && value.trim()) {
1774
- return value.trim();
1775
- }
1776
- }
1777
- throw new McpBearerAuthError(
1778
- `No customer reference claim found (checked: ${claimPriority.join(", ")})`
1779
- );
1780
- }
1781
- function getCustomerRefFromBearerAuthHeader(authorization, options = {}) {
1782
- const token = extractBearerToken(authorization);
1783
- if (!token) {
1784
- throw new McpBearerAuthError("Missing bearer token");
1785
- }
1786
- const payload = decodeJwtPayload(token);
1787
- return getCustomerRefFromJwtPayload(payload, options);
1788
- }
1789
-
1790
- // src/mcp/auth-bridge.ts
1791
- function getClientId(payload, explicitClientId) {
1792
- if (explicitClientId) return explicitClientId;
1793
- const payloadClientId = typeof payload.client_id === "string" && payload.client_id || typeof payload.azp === "string" && payload.azp || typeof payload.aud === "string" && payload.aud || null;
1794
- return payloadClientId || "solvapay-mcp-client";
1795
- }
1796
- function getScopes(payload, defaultScopes) {
1797
- if (Array.isArray(payload.scp)) {
1798
- return payload.scp.filter((scope) => typeof scope === "string");
1799
- }
1800
- if (typeof payload.scope === "string" && payload.scope.trim()) {
1801
- return payload.scope.split(/\s+/).map((scope) => scope.trim()).filter(Boolean);
1802
- }
1803
- return defaultScopes;
1804
- }
1805
- function getExpiresAt(payload) {
1806
- return typeof payload.exp === "number" ? payload.exp : void 0;
1807
- }
1808
- function buildAuthInfoFromBearer(authorization, options = {}) {
1809
- const token = extractBearerToken(authorization);
1810
- if (!token) return null;
1811
- const payload = decodeJwtPayload(token);
1812
- const customerRef = getCustomerRefFromJwtPayload(payload, options);
1813
- const clientId = getClientId(payload, options.clientId);
1814
- const scopes = getScopes(payload, options.defaultScopes || []);
1815
- const expiresAt = getExpiresAt(payload);
1816
- return {
1817
- token,
1818
- clientId,
1819
- scopes,
1820
- expiresAt,
1821
- extra: {
1822
- customer_ref: customerRef,
1823
- ...options.includePayload ? { payload } : {}
1824
- }
1825
- };
1826
- }
1827
-
1828
- // src/mcp/oauth-bridge.ts
1829
- function withoutTrailingSlash(value) {
1830
- return value.replace(/\/$/, "");
1831
- }
1832
- function getRequestAuthHeader(req) {
1833
- const header = req.headers?.authorization;
1834
- if (typeof header === "string") return header;
1835
- if (Array.isArray(header)) return header[0] || null;
1836
- return null;
1837
- }
1838
- function getRequestJsonRpcId(body) {
1839
- if (body && typeof body === "object" && "id" in body) {
1840
- const id = body.id;
1841
- return id ?? null;
1954
+ // src/types/paywall.ts
1955
+ function isPaywallStructuredContent(value) {
1956
+ if (typeof value !== "object" || value === null || !("kind" in value)) {
1957
+ return false;
1842
1958
  }
1843
- return null;
1844
- }
1845
- function makeUnauthorizedJsonRpc(id) {
1846
- return {
1847
- jsonrpc: "2.0",
1848
- id,
1849
- error: {
1850
- code: -32001,
1851
- message: "Unauthorized"
1852
- }
1853
- };
1854
- }
1855
- function setMcpChallengeHeader(res, publicBaseUrl, protectedResourcePath) {
1856
- res.setHeader(
1857
- "WWW-Authenticate",
1858
- `Bearer resource_metadata="${withoutTrailingSlash(publicBaseUrl)}${protectedResourcePath}"`
1859
- );
1860
- }
1861
- function getOAuthProtectedResourceResponse(publicBaseUrl) {
1862
- const resource = withoutTrailingSlash(publicBaseUrl);
1863
- return {
1864
- resource,
1865
- authorization_servers: [resource],
1866
- scopes_supported: ["openid", "profile", "email"]
1867
- };
1868
- }
1869
- function getOAuthAuthorizationServerResponse({
1870
- apiBaseUrl,
1871
- productRef
1872
- }) {
1873
- const normalizedApiBaseUrl = withoutTrailingSlash(apiBaseUrl);
1874
- const registrationEndpoint = `${normalizedApiBaseUrl}/v1/customer/auth/register?product_ref=${encodeURIComponent(productRef)}`;
1875
- return {
1876
- issuer: normalizedApiBaseUrl,
1877
- authorization_endpoint: `${normalizedApiBaseUrl}/v1/customer/auth/authorize`,
1878
- token_endpoint: `${normalizedApiBaseUrl}/v1/customer/auth/token`,
1879
- registration_endpoint: registrationEndpoint,
1880
- token_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
1881
- response_types_supported: ["code"],
1882
- grant_types_supported: ["authorization_code", "refresh_token"],
1883
- scopes_supported: ["openid", "profile", "email"],
1884
- code_challenge_methods_supported: ["S256"]
1885
- };
1886
- }
1887
- function createMcpOAuthBridge(options) {
1888
- const {
1889
- publicBaseUrl,
1890
- apiBaseUrl,
1891
- productRef,
1892
- mcpPath = "/mcp",
1893
- requireAuth = true,
1894
- authInfo,
1895
- protectedResourcePath = "/.well-known/oauth-protected-resource",
1896
- authorizationServerPath = "/.well-known/oauth-authorization-server"
1897
- } = options;
1898
- const protectedResourceMiddleware = (req, res, next) => {
1899
- if (req.method !== "GET" || req.path !== protectedResourcePath) {
1900
- next();
1901
- return;
1902
- }
1903
- res.json(getOAuthProtectedResourceResponse(publicBaseUrl));
1904
- };
1905
- const authorizationServerMiddleware = (req, res, next) => {
1906
- if (req.method !== "GET" || req.path !== authorizationServerPath) {
1907
- next();
1908
- return;
1909
- }
1910
- if (!productRef) {
1911
- res.status(500).json({ error: "SOLVAPAY_PRODUCT_REF missing" });
1912
- return;
1913
- }
1914
- res.json(
1915
- getOAuthAuthorizationServerResponse({
1916
- apiBaseUrl,
1917
- productRef
1918
- })
1919
- );
1920
- };
1921
- const mcpAuthMiddleware = (req, res, next) => {
1922
- if (req.path !== mcpPath) {
1923
- next();
1924
- return;
1925
- }
1926
- const authHeader = getRequestAuthHeader(req);
1927
- const id = getRequestJsonRpcId(req.body);
1928
- if (!authHeader && !requireAuth) {
1929
- next();
1930
- return;
1931
- }
1932
- try {
1933
- const auth = buildAuthInfoFromBearer(authHeader, authInfo);
1934
- if (!auth) {
1935
- throw new McpBearerAuthError("Missing bearer token");
1936
- }
1937
- req.auth = auth;
1938
- next();
1939
- } catch {
1940
- setMcpChallengeHeader(res, publicBaseUrl, protectedResourcePath);
1941
- if (req.method === "POST") {
1942
- res.status(401).json(makeUnauthorizedJsonRpc(id));
1943
- return;
1944
- }
1945
- res.status(401).json({ error: "Unauthorized" });
1946
- }
1947
- };
1948
- return [protectedResourceMiddleware, authorizationServerMiddleware, mcpAuthMiddleware];
1959
+ const kind = value.kind;
1960
+ return kind === "payment_required" || kind === "activation_required";
1949
1961
  }
1950
1962
 
1951
1963
  // src/helpers/error.ts
@@ -1973,26 +1985,128 @@ function handleRouteError(error, operationName, defaultMessage) {
1973
1985
  }
1974
1986
 
1975
1987
  // src/helpers/auth.ts
1988
+ function base64UrlDecode(input) {
1989
+ const padded = input.replace(/-/g, "+").replace(/_/g, "/");
1990
+ const padding = padded.length % 4 === 0 ? "" : "=".repeat(4 - padded.length % 4);
1991
+ const base64 = padded + padding;
1992
+ if (typeof atob === "function") {
1993
+ const binary = atob(base64);
1994
+ let result = "";
1995
+ for (let i = 0; i < binary.length; i++) {
1996
+ result += String.fromCharCode(binary.charCodeAt(i));
1997
+ }
1998
+ try {
1999
+ return decodeURIComponent(escape(result));
2000
+ } catch {
2001
+ return result;
2002
+ }
2003
+ }
2004
+ const BufferCtor = globalThis.Buffer;
2005
+ if (BufferCtor) {
2006
+ return BufferCtor.from(base64, "base64").toString("utf-8");
2007
+ }
2008
+ throw new Error("No base64 decoder available in this runtime");
2009
+ }
2010
+ function decodeJwtUnverified(token) {
2011
+ const parts = token.split(".");
2012
+ if (parts.length !== 3) return null;
2013
+ try {
2014
+ const json = base64UrlDecode(parts[1]);
2015
+ const payload = JSON.parse(json);
2016
+ if (typeof payload !== "object" || payload === null) return null;
2017
+ return payload;
2018
+ } catch {
2019
+ return null;
2020
+ }
2021
+ }
2022
+ function extractBearerToken(request) {
2023
+ const authHeader = request.headers.get("authorization");
2024
+ if (!authHeader || !authHeader.toLowerCase().startsWith("bearer ")) return null;
2025
+ const token = authHeader.slice(7).trim();
2026
+ return token.length > 0 ? token : null;
2027
+ }
2028
+ function readEnv(name) {
2029
+ const proc = globalThis.process;
2030
+ return proc?.env?.[name];
2031
+ }
2032
+ function isStrictMode() {
2033
+ return readEnv("SOLVAPAY_AUTH_STRICT") === "true";
2034
+ }
2035
+ function getConfiguredSecret() {
2036
+ return readEnv("SOLVAPAY_JWT_SECRET") || readEnv("SUPABASE_JWT_SECRET");
2037
+ }
2038
+ async function verifyHs256(token, secret) {
2039
+ try {
2040
+ const { jwtVerify } = await import("jose");
2041
+ const key = new TextEncoder().encode(secret);
2042
+ const { payload } = await jwtVerify(token, key, { algorithms: ["HS256"] });
2043
+ return payload;
2044
+ } catch {
2045
+ return null;
2046
+ }
2047
+ }
2048
+ function pickName(payload) {
2049
+ const metadataFullName = typeof payload.user_metadata?.full_name === "string" ? payload.user_metadata.full_name : null;
2050
+ const metadataName = typeof payload.user_metadata?.name === "string" ? payload.user_metadata.name : null;
2051
+ const claimName = typeof payload.name === "string" ? payload.name : null;
2052
+ return metadataFullName || metadataName || claimName || null;
2053
+ }
2054
+ function pickEmail(payload) {
2055
+ return typeof payload.email === "string" ? payload.email : null;
2056
+ }
2057
+ function unauthorized(details) {
2058
+ return { error: "Unauthorized", status: 401, details };
2059
+ }
1976
2060
  async function getAuthenticatedUserCore(request, options = {}) {
1977
2061
  try {
1978
- const { requireUserId, getUserEmailFromRequest, getUserNameFromRequest } = await import("@solvapay/auth");
1979
- const userIdOrError = requireUserId(request);
1980
- if (userIdOrError instanceof Response) {
1981
- const clonedResponse = userIdOrError.clone();
1982
- const body = await clonedResponse.json().catch(() => ({ error: "Unauthorized" }));
1983
- return {
1984
- error: body.error || "Unauthorized",
1985
- status: userIdOrError.status,
1986
- details: body.error || "Unauthorized"
1987
- };
2062
+ const includeEmail = options.includeEmail !== false;
2063
+ const includeName = options.includeName !== false;
2064
+ const headerUserId = request.headers.get("x-user-id");
2065
+ if (headerUserId) {
2066
+ let email = null;
2067
+ let name = null;
2068
+ if (includeEmail || includeName) {
2069
+ const token2 = extractBearerToken(request);
2070
+ if (token2) {
2071
+ const secret2 = getConfiguredSecret();
2072
+ const payload2 = secret2 ? await verifyHs256(token2, secret2) : isStrictMode() ? null : decodeJwtUnverified(token2);
2073
+ if (payload2) {
2074
+ if (includeEmail) email = pickEmail(payload2);
2075
+ if (includeName) name = pickName(payload2);
2076
+ }
2077
+ }
2078
+ }
2079
+ return { userId: headerUserId, email, name };
2080
+ }
2081
+ const token = extractBearerToken(request);
2082
+ if (!token) {
2083
+ return unauthorized("User ID not found. Ensure middleware is configured.");
2084
+ }
2085
+ const secret = getConfiguredSecret();
2086
+ let payload = null;
2087
+ if (secret) {
2088
+ payload = await verifyHs256(token, secret);
2089
+ if (!payload) {
2090
+ return unauthorized("Invalid or expired authentication token");
2091
+ }
2092
+ } else if (isStrictMode()) {
2093
+ return unauthorized(
2094
+ "Strict auth mode is enabled but no JWT secret is configured. Set SOLVAPAY_JWT_SECRET or SUPABASE_JWT_SECRET."
2095
+ );
2096
+ } else {
2097
+ payload = decodeJwtUnverified(token);
2098
+ if (!payload) {
2099
+ return unauthorized("Malformed authentication token");
2100
+ }
2101
+ }
2102
+ const userId = typeof payload.sub === "string" ? payload.sub : null;
2103
+ if (!userId) {
2104
+ return unauthorized("Authentication token missing subject (sub) claim");
1988
2105
  }
1989
- const userId = userIdOrError;
1990
- const email = options.includeEmail !== false ? await getUserEmailFromRequest(request) : null;
1991
- const name = options.includeName !== false ? await getUserNameFromRequest(request) : null;
1992
2106
  return {
1993
2107
  userId,
1994
- email,
1995
- name
2108
+ email: includeEmail ? pickEmail(payload) : null,
2109
+ name: includeName ? pickName(payload) : null
1996
2110
  };
1997
2111
  } catch (error) {
1998
2112
  return handleRouteError(error, "Get authenticated user", "Authentication failed");
@@ -2385,6 +2499,31 @@ async function activatePlanCore(request, body, options = {}) {
2385
2499
  }
2386
2500
  }
2387
2501
 
2502
+ // src/helpers/payment-method.ts
2503
+ async function getPaymentMethodCore(request, options = {}) {
2504
+ try {
2505
+ const customerResult = await syncCustomerCore(request, {
2506
+ solvaPay: options.solvaPay,
2507
+ includeEmail: options.includeEmail,
2508
+ includeName: options.includeName
2509
+ });
2510
+ if (isErrorResult(customerResult)) {
2511
+ return customerResult;
2512
+ }
2513
+ const customerRef = customerResult;
2514
+ const solvaPay = options.solvaPay || createSolvaPay();
2515
+ if (!solvaPay.apiClient.getPaymentMethod) {
2516
+ return {
2517
+ error: "getPaymentMethod is not implemented on this API client",
2518
+ status: 500
2519
+ };
2520
+ }
2521
+ return await solvaPay.apiClient.getPaymentMethod({ customerRef });
2522
+ } catch (error) {
2523
+ return handleRouteError(error, "Get payment method", "Failed to load payment method");
2524
+ }
2525
+ }
2526
+
2388
2527
  // src/helpers/plans.ts
2389
2528
  import { getSolvaPayConfig as getSolvaPayConfig2 } from "@solvapay/core";
2390
2529
  async function listPlansCore(request, options = {}) {
@@ -2554,6 +2693,37 @@ async function checkPurchaseCore(request, options = {}) {
2554
2693
  }
2555
2694
 
2556
2695
  // src/helpers/usage.ts
2696
+ async function getUsageCore(request, options = {}) {
2697
+ const purchaseResult = await checkPurchaseCore(request, options);
2698
+ if (isErrorResult(purchaseResult)) return purchaseResult;
2699
+ const activePurchase = (purchaseResult.purchases ?? []).find((p) => p.status === "active");
2700
+ if (!activePurchase) {
2701
+ return {
2702
+ meterRef: null,
2703
+ total: null,
2704
+ used: 0,
2705
+ remaining: null,
2706
+ percentUsed: null
2707
+ };
2708
+ }
2709
+ const snap = activePurchase.planSnapshot;
2710
+ const usage = activePurchase.usage;
2711
+ const meterRef = snap?.meterRef ?? snap?.meterId ?? null;
2712
+ const total = typeof snap?.limit === "number" ? snap.limit : null;
2713
+ const used = typeof usage?.used === "number" ? usage.used : 0;
2714
+ const remaining = total !== null ? Math.max(0, total - used) : null;
2715
+ const percentUsed = total !== null && total > 0 ? Math.min(100, Math.round(used / total * 1e4) / 100) : null;
2716
+ return {
2717
+ meterRef,
2718
+ total,
2719
+ used,
2720
+ remaining,
2721
+ percentUsed,
2722
+ ...usage?.periodStart ? { periodStart: usage.periodStart } : {},
2723
+ ...usage?.periodEnd ? { periodEnd: usage.periodEnd } : {},
2724
+ purchaseRef: activePurchase.reference
2725
+ };
2726
+ }
2557
2727
  async function trackUsageCore(request, body, options = {}) {
2558
2728
  try {
2559
2729
  const userResult = await getAuthenticatedUserCore(request);
@@ -2618,33 +2788,30 @@ function verifyWebhook({
2618
2788
  }
2619
2789
  }
2620
2790
  export {
2621
- McpBearerAuthError,
2622
2791
  PaywallError,
2623
2792
  VIRTUAL_TOOL_DEFINITIONS,
2624
2793
  activatePlanCore,
2625
- buildAuthInfoFromBearer,
2794
+ buildGateMessage,
2795
+ buildNudgeMessage,
2626
2796
  cancelPurchaseCore,
2627
2797
  checkPurchaseCore,
2798
+ classifyPaywallState,
2628
2799
  createCheckoutSessionCore,
2629
2800
  createCustomerSessionCore,
2630
- createMcpOAuthBridge,
2631
2801
  createPaymentIntentCore,
2632
2802
  createSolvaPay,
2633
2803
  createSolvaPayClient,
2634
2804
  createTopupPaymentIntentCore,
2635
2805
  createVirtualTools,
2636
- decodeJwtPayload,
2637
- extractBearerToken,
2638
2806
  getAuthenticatedUserCore,
2639
2807
  getCustomerBalanceCore,
2640
- getCustomerRefFromBearerAuthHeader,
2641
- getCustomerRefFromJwtPayload,
2642
2808
  getMerchantCore,
2643
- getOAuthAuthorizationServerResponse,
2644
- getOAuthProtectedResourceResponse,
2809
+ getPaymentMethodCore,
2645
2810
  getProductCore,
2811
+ getUsageCore,
2646
2812
  handleRouteError,
2647
2813
  isErrorResult,
2814
+ isPaywallStructuredContent,
2648
2815
  jsonSchemaToZodRawShape,
2649
2816
  listPlansCore,
2650
2817
  paywallErrorToClientPayload,