@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/README.md +1 -1
- package/dist/edge.d.ts +1722 -1194
- package/dist/edge.js +182 -32
- package/dist/index.cjs +356 -32
- package/dist/index.d.cts +1780 -1187
- package/dist/index.d.ts +1780 -1187
- package/dist/index.js +349 -32
- package/package.json +14 -4
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(
|
|
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
|
|
1122
|
-
const customerRef =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|