@solvapay/server 1.0.1-preview.2 → 1.0.1-preview.4

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/edge.js CHANGED
@@ -162,6 +162,21 @@ function createSolvaPayClient(opts) {
162
162
  }
163
163
  return await res.json();
164
164
  },
165
+ // PUT: /v1/sdk/products/{productRef}/mcp/plans
166
+ async configureMcpPlans(productRef, params) {
167
+ const url = `${base}/v1/sdk/products/${productRef}/mcp/plans`;
168
+ const res = await fetch(url, {
169
+ method: "PUT",
170
+ headers,
171
+ body: JSON.stringify(params)
172
+ });
173
+ if (!res.ok) {
174
+ const error = await res.text();
175
+ log(`\u274C API Error: ${res.status} - ${error}`);
176
+ throw new SolvaPayError(`Configure MCP plans failed (${res.status}): ${error}`);
177
+ }
178
+ return await res.json();
179
+ },
165
180
  // DELETE: /v1/sdk/products/{productRef}
166
181
  async deleteProduct(productRef) {
167
182
  const url = `${base}/v1/sdk/products/${productRef}`;
@@ -560,6 +575,24 @@ var PaywallError = class extends Error {
560
575
  this.name = "PaywallError";
561
576
  }
562
577
  };
578
+ function paywallErrorToClientPayload(error) {
579
+ const sc = error.structuredContent;
580
+ const base = {
581
+ success: false,
582
+ error: sc.kind === "activation_required" ? "Activation required" : "Payment required",
583
+ product: sc.product,
584
+ checkoutUrl: sc.checkoutUrl,
585
+ message: sc.message
586
+ };
587
+ if (sc.kind === "activation_required") {
588
+ base.kind = "activation_required";
589
+ if (sc.plans !== void 0) base.plans = sc.plans;
590
+ if (sc.balance !== void 0) base.balance = sc.balance;
591
+ if (sc.productDetails !== void 0) base.productDetails = sc.productDetails;
592
+ if (sc.confirmationUrl !== void 0) base.confirmationUrl = sc.confirmationUrl;
593
+ }
594
+ return base;
595
+ }
563
596
  var sharedCustomerLookupDeduplicator = createRequestDeduplicator({
564
597
  cacheTTL: 6e4,
565
598
  // Cache results for 60 seconds (reduces API calls significantly)
@@ -619,6 +652,7 @@ var SolvaPayPaywall = class {
619
652
  let withinLimits;
620
653
  let remaining;
621
654
  let checkoutUrl;
655
+ let lastLimitsCheck;
622
656
  const hasFreshCachedLimits = cachedLimits && now - cachedLimits.timestamp < this.limitsCacheTTL;
623
657
  if (hasFreshCachedLimits) {
624
658
  checkoutUrl = cachedLimits.checkoutUrl;
@@ -645,6 +679,7 @@ var SolvaPayPaywall = class {
645
679
  ...configuredPlanRef ? { planRef: configuredPlanRef } : {},
646
680
  meterName: usageType
647
681
  });
682
+ lastLimitsCheck = limitsCheck;
648
683
  withinLimits = limitsCheck.withinLimits;
649
684
  remaining = limitsCheck.remaining;
650
685
  checkoutUrl = limitsCheck.checkoutUrl;
@@ -673,6 +708,20 @@ var SolvaPayPaywall = class {
673
708
  requestId,
674
709
  latencyMs2
675
710
  );
711
+ if (lastLimitsCheck?.activationRequired) {
712
+ const confirmationUrl = lastLimitsCheck.confirmationUrl;
713
+ const payCheckoutUrl = confirmationUrl || lastLimitsCheck.checkoutUrl || checkoutUrl || "";
714
+ throw new PaywallError("Activation required", {
715
+ kind: "activation_required",
716
+ product,
717
+ message: "Product activation is required before this tool can be used.",
718
+ checkoutUrl: payCheckoutUrl,
719
+ confirmationUrl,
720
+ plans: lastLimitsCheck.plans,
721
+ balance: lastLimitsCheck.balance,
722
+ productDetails: lastLimitsCheck.product
723
+ });
724
+ }
676
725
  throw new PaywallError("Payment required", {
677
726
  kind: "payment_required",
678
727
  product,
@@ -775,7 +824,8 @@ var SolvaPayPaywall = class {
775
824
  this.customerCreationAttempts.add(customerRef);
776
825
  try {
777
826
  const createParams = {
778
- email: options?.email || `${customerRef}-${Date.now()}@auto-created.local`
827
+ email: options?.email || `${customerRef}-${Date.now()}@auto-created.local`,
828
+ metadata: {}
779
829
  };
780
830
  if (options?.name) {
781
831
  createParams.name = options.name;
@@ -799,7 +849,10 @@ var SolvaPayPaywall = class {
799
849
  return searchResult.customerRef;
800
850
  }
801
851
  } catch (lookupError) {
802
- this.log(`\u26A0\uFE0F Failed to lookup existing customer by externalRef after 409:`, lookupError instanceof Error ? lookupError.message : lookupError);
852
+ this.log(
853
+ `\u26A0\uFE0F Failed to lookup existing customer by externalRef after 409:`,
854
+ lookupError instanceof Error ? lookupError.message : lookupError
855
+ );
803
856
  }
804
857
  }
805
858
  const isEmailConflict = errorMessage.includes("email") || errorMessage.includes("identifier email");
@@ -822,7 +875,8 @@ var SolvaPayPaywall = class {
822
875
  try {
823
876
  const retryParams = {
824
877
  email: `${customerRef}-${Date.now()}@auto-created.local`,
825
- externalRef
878
+ externalRef,
879
+ metadata: {}
826
880
  };
827
881
  if (options?.name) {
828
882
  retryParams.name = options.name;
@@ -919,10 +973,10 @@ async function createAdapterHandler(adapter, paywall, metadata, businessLogic) {
919
973
  const backendRefCache = /* @__PURE__ */ new Map();
920
974
  const getCustomerRef = (args) => args.auth?.customer_ref || "anonymous";
921
975
  const protectedHandler = await paywall.protect(businessLogic, metadata, getCustomerRef);
922
- return async (context) => {
976
+ return async (context, extra) => {
923
977
  try {
924
978
  const args = await adapter.extractArgs(context);
925
- const customerRef = await adapter.getCustomerRef(context);
979
+ const customerRef = await adapter.getCustomerRef(context, extra);
926
980
  let backendRef = backendRefCache.get(customerRef);
927
981
  if (!backendRef) {
928
982
  backendRef = await paywall.ensureCustomer(customerRef, customerRef);
@@ -1113,18 +1167,18 @@ var McpAdapter = class {
1113
1167
  extractArgs(args) {
1114
1168
  return args;
1115
1169
  }
1116
- async getCustomerRef(args) {
1170
+ async getCustomerRef(args, extra) {
1117
1171
  if (this.options.getCustomerRef) {
1118
- const ref = await this.options.getCustomerRef(args);
1172
+ const ref = await this.options.getCustomerRef(args, extra);
1119
1173
  return AdapterUtils.ensureCustomerRef(ref);
1120
1174
  }
1121
- const auth = args?.auth;
1122
- const customerRef = auth?.customer_ref || "anonymous";
1175
+ const customerRefFromExtra = extra?.authInfo?.extra?.customer_ref;
1176
+ const customerRef = typeof customerRefFromExtra === "string" && customerRefFromExtra.trim() ? customerRefFromExtra.trim() : "anonymous";
1123
1177
  return AdapterUtils.ensureCustomerRef(customerRef);
1124
1178
  }
1125
1179
  formatResponse(result, _context) {
1126
1180
  const transformed = this.options.transformResponse ? this.options.transformResponse(result) : result;
1127
- return {
1181
+ const response = {
1128
1182
  content: [
1129
1183
  {
1130
1184
  type: "text",
@@ -1132,6 +1186,10 @@ var McpAdapter = class {
1132
1186
  }
1133
1187
  ]
1134
1188
  };
1189
+ if (transformed && typeof transformed === "object" && !Array.isArray(transformed)) {
1190
+ response.structuredContent = transformed;
1191
+ }
1192
+ return response;
1135
1193
  }
1136
1194
  formatError(error, _context) {
1137
1195
  if (error instanceof PaywallError) {
@@ -1139,17 +1197,7 @@ var McpAdapter = class {
1139
1197
  content: [
1140
1198
  {
1141
1199
  type: "text",
1142
- text: JSON.stringify(
1143
- {
1144
- success: false,
1145
- error: "Payment required",
1146
- product: error.structuredContent.product,
1147
- checkoutUrl: error.structuredContent.checkoutUrl,
1148
- message: error.structuredContent.message
1149
- },
1150
- null,
1151
- 2
1152
- )
1200
+ text: JSON.stringify(paywallErrorToClientPayload(error), null, 2)
1153
1201
  }
1154
1202
  ],
1155
1203
  isError: true,
@@ -1218,8 +1266,8 @@ function mcpErrorResult(message) {
1218
1266
  return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
1219
1267
  }
1220
1268
  function createGetUserInfoHandler(apiClient, productRef, getCustomerRef) {
1221
- return async (args) => {
1222
- const customerRef = getCustomerRef(args);
1269
+ return async (args, extra) => {
1270
+ const customerRef = getCustomerRef(args, extra);
1223
1271
  try {
1224
1272
  if (!apiClient.getUserInfo) {
1225
1273
  return mcpErrorResult("getUserInfo is not available on this API client");
@@ -1234,12 +1282,12 @@ function createGetUserInfoHandler(apiClient, productRef, getCustomerRef) {
1234
1282
  };
1235
1283
  }
1236
1284
  function createUpgradeHandler(apiClient, productRef, getCustomerRef) {
1237
- return async (args) => {
1238
- const customerRef = getCustomerRef(args);
1285
+ return async (args, extra) => {
1286
+ const customerRef = getCustomerRef(args, extra);
1239
1287
  const planRef = typeof args.planRef === "string" ? args.planRef : void 0;
1240
1288
  try {
1241
1289
  const result = await apiClient.createCheckoutSession({
1242
- customerReference: customerRef,
1290
+ customerRef,
1243
1291
  productRef,
1244
1292
  ...planRef && { planRef }
1245
1293
  });
@@ -1266,8 +1314,8 @@ You'll be able to compare options and select the one that's right for you.`;
1266
1314
  };
1267
1315
  }
1268
1316
  function createManageAccountHandler(apiClient, productRef, getCustomerRef) {
1269
- return async (args) => {
1270
- const customerRef = getCustomerRef(args);
1317
+ return async (args, extra) => {
1318
+ const customerRef = getCustomerRef(args, extra);
1271
1319
  try {
1272
1320
  const session = await apiClient.createCustomerSession({ customerRef, productRef });
1273
1321
  const portalUrl = session.customerUrl;
@@ -1310,6 +1358,98 @@ function createVirtualTools(apiClient, options) {
1310
1358
  return allTools.filter((t) => !excludeSet.has(t.name));
1311
1359
  }
1312
1360
 
1361
+ // src/register-virtual-tools-mcp.ts
1362
+ import { createRequire } from "module";
1363
+ function getNodeRequire() {
1364
+ try {
1365
+ return (0, eval)("require");
1366
+ } catch {
1367
+ return createRequire(`${process.cwd()}/package.json`);
1368
+ }
1369
+ }
1370
+ var nodeRequire = getNodeRequire();
1371
+ function getZod() {
1372
+ try {
1373
+ const zodModule = nodeRequire("zod");
1374
+ return zodModule.z ?? zodModule;
1375
+ } catch {
1376
+ throw new Error(
1377
+ "zod is required to use registerVirtualToolsMcp(). Install it as a dependency in your MCP server project."
1378
+ );
1379
+ }
1380
+ }
1381
+ function isJsonSchemaObject(value) {
1382
+ return Boolean(value && typeof value === "object");
1383
+ }
1384
+ function toZodSchema(property) {
1385
+ const z = getZod();
1386
+ if (Array.isArray(property.enum) && property.enum.length > 0) {
1387
+ if (property.enum.every((value) => typeof value === "string")) {
1388
+ const [first, ...rest] = property.enum;
1389
+ return z.enum([first, ...rest]);
1390
+ }
1391
+ if (property.enum.length === 1) {
1392
+ return z.literal(property.enum[0]);
1393
+ }
1394
+ return z.union(property.enum.map((value) => z.literal(value)));
1395
+ }
1396
+ switch (property.type) {
1397
+ case "string":
1398
+ return z.string();
1399
+ case "number":
1400
+ return z.number();
1401
+ case "integer":
1402
+ return z.number().int();
1403
+ case "boolean":
1404
+ return z.boolean();
1405
+ case "array":
1406
+ return z.array(
1407
+ isJsonSchemaObject(property.items) ? toZodSchema(property.items) : z.any()
1408
+ );
1409
+ case "object":
1410
+ return z.object(jsonSchemaToZodRawShape(property.properties || {}));
1411
+ default:
1412
+ return z.any();
1413
+ }
1414
+ }
1415
+ function jsonSchemaToZodRawShape(properties, required = []) {
1416
+ const requiredSet = new Set(required);
1417
+ return Object.fromEntries(
1418
+ Object.entries(properties).map(([name, property]) => {
1419
+ const schema = toZodSchema(property);
1420
+ const finalSchema = requiredSet.has(name) ? schema : schema.optional();
1421
+ return [name, finalSchema];
1422
+ })
1423
+ );
1424
+ }
1425
+ function defaultGetCustomerRef(_args, extra) {
1426
+ const fromExtra = extra?.authInfo?.extra?.customer_ref;
1427
+ return typeof fromExtra === "string" && fromExtra.trim() ? fromExtra.trim() : "anonymous";
1428
+ }
1429
+ function registerVirtualToolsMcpImpl(server, apiClient, options) {
1430
+ const { filter, mapDefinition, wrapHandler, getCustomerRef, ...virtualToolsOptions } = options;
1431
+ const virtualTools = createVirtualTools(apiClient, {
1432
+ ...virtualToolsOptions,
1433
+ getCustomerRef: getCustomerRef || defaultGetCustomerRef
1434
+ });
1435
+ for (const toolDefinition of virtualTools) {
1436
+ if (filter && !filter(toolDefinition)) continue;
1437
+ const mappedDefinition = mapDefinition ? mapDefinition(toolDefinition) : toolDefinition;
1438
+ const wrappedHandler = wrapHandler ? wrapHandler(mappedDefinition.handler, mappedDefinition) : mappedDefinition.handler;
1439
+ server.registerTool(
1440
+ mappedDefinition.name,
1441
+ {
1442
+ description: mappedDefinition.description,
1443
+ inputSchema: jsonSchemaToZodRawShape(
1444
+ mappedDefinition.inputSchema.properties,
1445
+ mappedDefinition.inputSchema.required || []
1446
+ )
1447
+ },
1448
+ wrappedHandler
1449
+ );
1450
+ }
1451
+ }
1452
+
1313
1453
  // src/factory.ts
1314
1454
  function createSolvaPay(config) {
1315
1455
  let resolvedConfig;
@@ -1359,14 +1499,14 @@ function createSolvaPay(config) {
1359
1499
  if (!apiClient.createCustomer) {
1360
1500
  throw new SolvaPayError2("createCustomer is not available on this API client");
1361
1501
  }
1362
- return apiClient.createCustomer(params);
1502
+ return apiClient.createCustomer({ ...params, metadata: params.metadata ?? {} });
1363
1503
  },
1364
1504
  getCustomer(params) {
1365
1505
  return apiClient.getCustomer(params);
1366
1506
  },
1367
1507
  createCheckoutSession(params) {
1368
1508
  return apiClient.createCheckoutSession({
1369
- customerReference: params.customerRef,
1509
+ customerRef: params.customerRef,
1370
1510
  productRef: params.productRef,
1371
1511
  planRef: params.planRef
1372
1512
  });
@@ -1380,9 +1520,18 @@ function createSolvaPay(config) {
1380
1520
  }
1381
1521
  return apiClient.bootstrapMcpProduct(params);
1382
1522
  },
1523
+ configureMcpPlans(productRef, params) {
1524
+ if (!apiClient.configureMcpPlans) {
1525
+ throw new SolvaPayError2("configureMcpPlans is not available on this API client");
1526
+ }
1527
+ return apiClient.configureMcpPlans(productRef, params);
1528
+ },
1383
1529
  getVirtualTools(options) {
1384
1530
  return createVirtualTools(apiClient, options);
1385
1531
  },
1532
+ async registerVirtualToolsMcp(server, options) {
1533
+ await registerVirtualToolsMcpImpl(server, apiClient, options);
1534
+ },
1386
1535
  // Payable API for framework-specific handlers
1387
1536
  payable(options = {}) {
1388
1537
  const product = options.productRef || options.product || process.env.SOLVAPAY_PRODUCT || "default-product";
@@ -1421,9 +1570,9 @@ function createSolvaPay(config) {
1421
1570
  getCustomerRef: adapterOptions?.getCustomerRef || options.getCustomerRef
1422
1571
  });
1423
1572
  const handlerPromise = createAdapterHandler(adapter, paywall, metadata, businessLogic);
1424
- return async (args) => {
1573
+ return async (args, extra) => {
1425
1574
  const handler = await handlerPromise;
1426
- return handler(args);
1575
+ return handler(args, extra);
1427
1576
  };
1428
1577
  },
1429
1578
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -1818,6 +1967,7 @@ export {
1818
1967
  handleRouteError,
1819
1968
  isErrorResult,
1820
1969
  listPlansCore,
1970
+ paywallErrorToClientPayload,
1821
1971
  processPaymentIntentCore,
1822
1972
  syncCustomerCore,
1823
1973
  verifyWebhook,