@solvapay/server 2.7.0 → 2.8.0
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.d.ts +40 -15
- package/dist/edge.js +77 -35
- package/dist/fetch/index.cjs +84 -42
- package/dist/fetch/index.d.cts +10 -1
- package/dist/fetch/index.d.ts +10 -1
- package/dist/fetch/index.js +75 -33
- package/dist/index.cjs +93 -51
- package/dist/index.d.cts +41 -16
- package/dist/index.d.ts +41 -16
- package/dist/index.js +81 -39
- package/package.json +3 -3
package/dist/fetch/index.js
CHANGED
|
@@ -825,6 +825,9 @@ function classifyPaywallState(limits) {
|
|
|
825
825
|
if (limits.activationRequired === true || limits.paywallReason === "activation_required") {
|
|
826
826
|
return { kind: "activation_required" };
|
|
827
827
|
}
|
|
828
|
+
if (limits.paywallReason === "limit_reached") {
|
|
829
|
+
return { kind: "limit_reached" };
|
|
830
|
+
}
|
|
828
831
|
if (limits.paywallReason === "topup_required") {
|
|
829
832
|
return { kind: "topup_required" };
|
|
830
833
|
}
|
|
@@ -888,9 +891,12 @@ function formatCreditsWithMoney(credits, gate) {
|
|
|
888
891
|
}
|
|
889
892
|
return `${amount} credits`;
|
|
890
893
|
}
|
|
894
|
+
function isFreeMeter(name) {
|
|
895
|
+
return typeof name === "string" && name.startsWith("free-");
|
|
896
|
+
}
|
|
891
897
|
function meterLabel(gate) {
|
|
892
898
|
if (!gate.meterName) return "units";
|
|
893
|
-
return gate.meterName.replace(/_/g, " ");
|
|
899
|
+
return gate.meterName.replace(/[_-]/g, " ");
|
|
894
900
|
}
|
|
895
901
|
function namedCheckoutMarkdown(url, label = "Open checkout") {
|
|
896
902
|
return `[${label}](${url})`;
|
|
@@ -926,6 +932,12 @@ function buildGateMessage(state, gate) {
|
|
|
926
932
|
switch (state.kind) {
|
|
927
933
|
case "limit_reached": {
|
|
928
934
|
const included = gate.included;
|
|
935
|
+
if (isFreeMeter(gate.meterName)) {
|
|
936
|
+
const total = included?.total;
|
|
937
|
+
const usedLine2 = total !== void 0 ? `You've used all ${total} ${meterLabel(gate)} for this period.` : `You've used all ${meterLabel(gate)} for this period.`;
|
|
938
|
+
const switchLine2 = ladder ? ` Pick a plan to keep going: ${ladder}. ${callViewer("account").replace(/^c/, "C")} for usage and recovery.` : recoverClause(url, "keep going", "checkout");
|
|
939
|
+
return `${usedLine2}${switchLine2}`;
|
|
940
|
+
}
|
|
929
941
|
const price = gate.unitPriceMinor != null && gate.currency ? formatMinor(gate.unitPriceMinor, gate.currency) : null;
|
|
930
942
|
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.`;
|
|
931
943
|
const nextLine = price ? ` The next call is ${price}.` : "";
|
|
@@ -1228,6 +1240,7 @@ function createRequestDeduplicator(options = {}) {
|
|
|
1228
1240
|
}
|
|
1229
1241
|
|
|
1230
1242
|
// src/paywall.ts
|
|
1243
|
+
import { SolvaPayError as SolvaPayError4 } from "@solvapay/core";
|
|
1231
1244
|
var PaywallError = class extends Error {
|
|
1232
1245
|
/**
|
|
1233
1246
|
* Creates a new PaywallError instance.
|
|
@@ -1278,6 +1291,15 @@ var sharedLimitsFetchDeduplicator = createRequestDeduplicator({
|
|
|
1278
1291
|
});
|
|
1279
1292
|
var sharedLimitsFetchClaims = /* @__PURE__ */ new Map();
|
|
1280
1293
|
var EXTRA_FORWARD_KEY = "__solvapayExtra";
|
|
1294
|
+
function trackUsageExtra(metadata, outcome, consequence) {
|
|
1295
|
+
if (metadata.freeLimit) {
|
|
1296
|
+
return { meterName: metadata.freeLimit.meter };
|
|
1297
|
+
}
|
|
1298
|
+
if (outcome === "success") {
|
|
1299
|
+
return { usageClass: consequence === "overage" ? "overage" : "included" };
|
|
1300
|
+
}
|
|
1301
|
+
return void 0;
|
|
1302
|
+
}
|
|
1281
1303
|
var SolvaPayPaywall = class {
|
|
1282
1304
|
constructor(apiClient, options = {}) {
|
|
1283
1305
|
this.apiClient = apiClient;
|
|
@@ -1337,10 +1359,19 @@ var SolvaPayPaywall = class {
|
|
|
1337
1359
|
*/
|
|
1338
1360
|
async decide(args, metadata = {}, getCustomerRef) {
|
|
1339
1361
|
const product = this.resolveProduct(metadata);
|
|
1340
|
-
const usageType = metadata.meterName || metadata.usageType || "requests";
|
|
1362
|
+
const usageType = metadata.freeLimit?.meter || metadata.meterName || metadata.usageType || "requests";
|
|
1341
1363
|
const requestId = this.generateRequestId();
|
|
1342
1364
|
const startTime = Date.now();
|
|
1343
1365
|
const inputCustomerRef = getCustomerRef ? getCustomerRef(args) : args.auth?.customer_ref || "anonymous";
|
|
1366
|
+
if (metadata.freeLimit) {
|
|
1367
|
+
const resolved = typeof inputCustomerRef === "string" ? inputCustomerRef.trim() : "";
|
|
1368
|
+
if (!resolved || resolved === "anonymous") {
|
|
1369
|
+
throw new SolvaPayError4("identity required", {
|
|
1370
|
+
status: 401,
|
|
1371
|
+
code: "identity_required"
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1344
1375
|
let backendCustomerRef;
|
|
1345
1376
|
if (inputCustomerRef.startsWith("cus_")) {
|
|
1346
1377
|
backendCustomerRef = inputCustomerRef;
|
|
@@ -1391,7 +1422,8 @@ var SolvaPayPaywall = class {
|
|
|
1391
1422
|
// `checkLimitsCore`, which powers the React `useLimits` hook)
|
|
1392
1423
|
// leave this unset and the backend skips the session-creation
|
|
1393
1424
|
// side effect.
|
|
1394
|
-
includeCheckoutSession: true
|
|
1425
|
+
includeCheckoutSession: true,
|
|
1426
|
+
...metadata.freeLimit ? { freeAllowance: metadata.freeLimit } : {}
|
|
1395
1427
|
});
|
|
1396
1428
|
}
|
|
1397
1429
|
);
|
|
@@ -1435,7 +1467,8 @@ var SolvaPayPaywall = class {
|
|
|
1435
1467
|
"paywall",
|
|
1436
1468
|
requestId,
|
|
1437
1469
|
latencyMs,
|
|
1438
|
-
metadata.toolName
|
|
1470
|
+
metadata.toolName,
|
|
1471
|
+
metadata.freeLimit ? { meterName: metadata.freeLimit.meter } : void 0
|
|
1439
1472
|
).catch(() => void 0);
|
|
1440
1473
|
const gate = buildPaywallGate(
|
|
1441
1474
|
product,
|
|
@@ -1482,7 +1515,7 @@ var SolvaPayPaywall = class {
|
|
|
1482
1515
|
*/
|
|
1483
1516
|
async runAllow(decision, handler, metadata, args) {
|
|
1484
1517
|
const product = this.resolveProduct(metadata);
|
|
1485
|
-
const usageType = metadata.meterName || metadata.usageType || "requests";
|
|
1518
|
+
const usageType = metadata.freeLimit?.meter || metadata.meterName || metadata.usageType || "requests";
|
|
1486
1519
|
const requestId = decision.requestId;
|
|
1487
1520
|
const startTime = Date.now();
|
|
1488
1521
|
const forwardedExtra = args[EXTRA_FORWARD_KEY];
|
|
@@ -1501,7 +1534,8 @@ var SolvaPayPaywall = class {
|
|
|
1501
1534
|
"success",
|
|
1502
1535
|
requestId,
|
|
1503
1536
|
latencyMs,
|
|
1504
|
-
metadata.toolName
|
|
1537
|
+
metadata.toolName,
|
|
1538
|
+
trackUsageExtra(metadata, "success", decision.consequence)
|
|
1505
1539
|
).catch(() => void 0);
|
|
1506
1540
|
return result;
|
|
1507
1541
|
} catch (error) {
|
|
@@ -1520,7 +1554,8 @@ var SolvaPayPaywall = class {
|
|
|
1520
1554
|
"fail",
|
|
1521
1555
|
requestId,
|
|
1522
1556
|
latencyMs,
|
|
1523
|
-
metadata.toolName
|
|
1557
|
+
metadata.toolName,
|
|
1558
|
+
trackUsageExtra(metadata, "fail")
|
|
1524
1559
|
).catch(() => void 0);
|
|
1525
1560
|
}
|
|
1526
1561
|
throw error;
|
|
@@ -1711,7 +1746,7 @@ var SolvaPayPaywall = class {
|
|
|
1711
1746
|
}
|
|
1712
1747
|
return backendRef;
|
|
1713
1748
|
}
|
|
1714
|
-
async trackUsage(customerRef, productRef, action, outcome, requestId, actionDuration, toolName) {
|
|
1749
|
+
async trackUsage(customerRef, productRef, action, outcome, requestId, actionDuration, toolName, extra) {
|
|
1715
1750
|
await withRetry(
|
|
1716
1751
|
() => this.apiClient.trackUsage({
|
|
1717
1752
|
customerRef,
|
|
@@ -1724,7 +1759,9 @@ var SolvaPayPaywall = class {
|
|
|
1724
1759
|
metadata: {
|
|
1725
1760
|
action: action || "api_requests",
|
|
1726
1761
|
requestId,
|
|
1727
|
-
...toolName ? { toolName } : {}
|
|
1762
|
+
...toolName ? { toolName } : {},
|
|
1763
|
+
...extra?.meterName ? { meterName: extra.meterName } : {},
|
|
1764
|
+
...extra?.usageClass ? { usageClass: extra.usageClass } : {}
|
|
1728
1765
|
},
|
|
1729
1766
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1730
1767
|
}),
|
|
@@ -2064,7 +2101,7 @@ var McpAdapter = class {
|
|
|
2064
2101
|
};
|
|
2065
2102
|
|
|
2066
2103
|
// src/factory.ts
|
|
2067
|
-
import { SolvaPayError as
|
|
2104
|
+
import { SolvaPayError as SolvaPayError5, getSolvaPayConfig } from "@solvapay/core";
|
|
2068
2105
|
|
|
2069
2106
|
// src/virtual-tools.ts
|
|
2070
2107
|
var TOOL_GET_USER_INFO = {
|
|
@@ -2341,25 +2378,25 @@ function createSolvaPay(config) {
|
|
|
2341
2378
|
},
|
|
2342
2379
|
createPaymentIntent(params) {
|
|
2343
2380
|
if (!apiClient.createPaymentIntent) {
|
|
2344
|
-
throw new
|
|
2381
|
+
throw new SolvaPayError5("createPaymentIntent is not available on this API client");
|
|
2345
2382
|
}
|
|
2346
2383
|
return apiClient.createPaymentIntent(params);
|
|
2347
2384
|
},
|
|
2348
2385
|
createTopupPaymentIntent(params) {
|
|
2349
2386
|
if (!apiClient.createTopupPaymentIntent) {
|
|
2350
|
-
throw new
|
|
2387
|
+
throw new SolvaPayError5("createTopupPaymentIntent is not available on this API client");
|
|
2351
2388
|
}
|
|
2352
2389
|
return apiClient.createTopupPaymentIntent(params);
|
|
2353
2390
|
},
|
|
2354
2391
|
processPaymentIntent(params) {
|
|
2355
2392
|
if (!apiClient.processPaymentIntent) {
|
|
2356
|
-
throw new
|
|
2393
|
+
throw new SolvaPayError5("processPaymentIntent is not available on this API client");
|
|
2357
2394
|
}
|
|
2358
2395
|
return apiClient.processPaymentIntent(params);
|
|
2359
2396
|
},
|
|
2360
2397
|
attachBusinessDetails(params) {
|
|
2361
2398
|
if (!apiClient.attachBusinessDetails) {
|
|
2362
|
-
throw new
|
|
2399
|
+
throw new SolvaPayError5("attachBusinessDetails is not available on this API client");
|
|
2363
2400
|
}
|
|
2364
2401
|
return apiClient.attachBusinessDetails(params);
|
|
2365
2402
|
},
|
|
@@ -2371,13 +2408,13 @@ function createSolvaPay(config) {
|
|
|
2371
2408
|
},
|
|
2372
2409
|
trackUsageBulk(params) {
|
|
2373
2410
|
if (!apiClient.trackUsageBulk) {
|
|
2374
|
-
throw new
|
|
2411
|
+
throw new SolvaPayError5("trackUsageBulk is not available on this API client");
|
|
2375
2412
|
}
|
|
2376
2413
|
return apiClient.trackUsageBulk(params);
|
|
2377
2414
|
},
|
|
2378
2415
|
createCustomer(params) {
|
|
2379
2416
|
if (!apiClient.createCustomer) {
|
|
2380
|
-
throw new
|
|
2417
|
+
throw new SolvaPayError5("createCustomer is not available on this API client");
|
|
2381
2418
|
}
|
|
2382
2419
|
return apiClient.createCustomer({ ...params, metadata: params.metadata ?? {} });
|
|
2383
2420
|
},
|
|
@@ -2386,13 +2423,13 @@ function createSolvaPay(config) {
|
|
|
2386
2423
|
},
|
|
2387
2424
|
assignCredits(params) {
|
|
2388
2425
|
if (!apiClient.assignCredits) {
|
|
2389
|
-
throw new
|
|
2426
|
+
throw new SolvaPayError5("assignCredits is not available on this API client");
|
|
2390
2427
|
}
|
|
2391
2428
|
return apiClient.assignCredits(params);
|
|
2392
2429
|
},
|
|
2393
2430
|
getCustomerBalance(params) {
|
|
2394
2431
|
if (!apiClient.getCustomerBalance) {
|
|
2395
|
-
throw new
|
|
2432
|
+
throw new SolvaPayError5("getCustomerBalance is not available on this API client");
|
|
2396
2433
|
}
|
|
2397
2434
|
return apiClient.getCustomerBalance(params);
|
|
2398
2435
|
},
|
|
@@ -2410,19 +2447,19 @@ function createSolvaPay(config) {
|
|
|
2410
2447
|
},
|
|
2411
2448
|
activatePlan(params) {
|
|
2412
2449
|
if (!apiClient.activatePlan) {
|
|
2413
|
-
throw new
|
|
2450
|
+
throw new SolvaPayError5("activatePlan is not available on this API client");
|
|
2414
2451
|
}
|
|
2415
2452
|
return apiClient.activatePlan(params);
|
|
2416
2453
|
},
|
|
2417
2454
|
bootstrapMcpProduct(params) {
|
|
2418
2455
|
if (!apiClient.bootstrapMcpProduct) {
|
|
2419
|
-
throw new
|
|
2456
|
+
throw new SolvaPayError5("bootstrapMcpProduct is not available on this API client");
|
|
2420
2457
|
}
|
|
2421
2458
|
return apiClient.bootstrapMcpProduct(params);
|
|
2422
2459
|
},
|
|
2423
2460
|
configureMcpPlans(productRef, params) {
|
|
2424
2461
|
if (!apiClient.configureMcpPlans) {
|
|
2425
|
-
throw new
|
|
2462
|
+
throw new SolvaPayError5("configureMcpPlans is not available on this API client");
|
|
2426
2463
|
}
|
|
2427
2464
|
return apiClient.configureMcpPlans(productRef, params);
|
|
2428
2465
|
},
|
|
@@ -2435,12 +2472,13 @@ function createSolvaPay(config) {
|
|
|
2435
2472
|
// Payable API for framework-specific handlers
|
|
2436
2473
|
payable(options = {}) {
|
|
2437
2474
|
const product = resolveProductRef(options.productRef || options.product);
|
|
2438
|
-
const usageType = options.meterName || options.usageType || "requests";
|
|
2475
|
+
const usageType = options.freeLimit?.meter || options.meterName || options.usageType || "requests";
|
|
2439
2476
|
const metadata = {
|
|
2440
2477
|
product,
|
|
2441
2478
|
meterName: usageType,
|
|
2442
2479
|
usageType,
|
|
2443
|
-
...options.toolName ? { toolName: options.toolName } : {}
|
|
2480
|
+
...options.toolName ? { toolName: options.toolName } : {},
|
|
2481
|
+
...options.freeLimit ? { freeLimit: options.freeLimit } : {}
|
|
2444
2482
|
};
|
|
2445
2483
|
return {
|
|
2446
2484
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -2529,6 +2567,8 @@ function createSolvaPay(config) {
|
|
|
2529
2567
|
const errMeta = opts?.error !== void 0 ? {
|
|
2530
2568
|
error: opts.error instanceof Error ? opts.error.message : String(opts.error)
|
|
2531
2569
|
} : {};
|
|
2570
|
+
const isFree = Boolean(decideMetadata.freeLimit);
|
|
2571
|
+
const usageClass = !isFree && outcome === "success" ? decision.consequence === "overage" ? "overage" : "included" : void 0;
|
|
2532
2572
|
const trackPromise = apiClient.trackUsage({
|
|
2533
2573
|
customerRef,
|
|
2534
2574
|
productRef,
|
|
@@ -2541,6 +2581,8 @@ function createSolvaPay(config) {
|
|
|
2541
2581
|
action: meterName,
|
|
2542
2582
|
requestId,
|
|
2543
2583
|
...decideMetadata.toolName ? { toolName: decideMetadata.toolName } : {},
|
|
2584
|
+
...isFree && decideMetadata.freeLimit ? { meterName: decideMetadata.freeLimit.meter } : {},
|
|
2585
|
+
...usageClass ? { usageClass } : {},
|
|
2544
2586
|
...errMeta,
|
|
2545
2587
|
...opts?.metadata ?? {}
|
|
2546
2588
|
},
|
|
@@ -2798,7 +2840,7 @@ async function createCustomerSessionCore(request, options = {}) {
|
|
|
2798
2840
|
}
|
|
2799
2841
|
|
|
2800
2842
|
// src/helpers/renewal.ts
|
|
2801
|
-
import { SolvaPayError as
|
|
2843
|
+
import { SolvaPayError as SolvaPayError6 } from "@solvapay/core";
|
|
2802
2844
|
async function cancelPurchaseCore(request, body, options = {}) {
|
|
2803
2845
|
try {
|
|
2804
2846
|
if (!body.purchaseRef) {
|
|
@@ -2844,7 +2886,7 @@ async function cancelPurchaseCore(request, body, options = {}) {
|
|
|
2844
2886
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2845
2887
|
return cancelledPurchase;
|
|
2846
2888
|
} catch (error) {
|
|
2847
|
-
if (error instanceof
|
|
2889
|
+
if (error instanceof SolvaPayError6) {
|
|
2848
2890
|
const errorMessage = error.message;
|
|
2849
2891
|
if (errorMessage.includes("not found")) {
|
|
2850
2892
|
return {
|
|
@@ -2912,7 +2954,7 @@ async function reactivatePurchaseCore(request, body, options = {}) {
|
|
|
2912
2954
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2913
2955
|
return reactivatedPurchase;
|
|
2914
2956
|
} catch (error) {
|
|
2915
|
-
if (error instanceof
|
|
2957
|
+
if (error instanceof SolvaPayError6) {
|
|
2916
2958
|
const errorMessage = error.message;
|
|
2917
2959
|
if (errorMessage.includes("not found")) {
|
|
2918
2960
|
return {
|
|
@@ -3231,7 +3273,7 @@ async function trackUsageCore(request, body, options = {}) {
|
|
|
3231
3273
|
}
|
|
3232
3274
|
|
|
3233
3275
|
// src/edge.ts
|
|
3234
|
-
import { SolvaPayError as
|
|
3276
|
+
import { SolvaPayError as SolvaPayError7 } from "@solvapay/core";
|
|
3235
3277
|
function timingSafeEqual(a, b) {
|
|
3236
3278
|
if (a.length !== b.length) return false;
|
|
3237
3279
|
let mismatch = 0;
|
|
@@ -3246,22 +3288,22 @@ async function verifyWebhook({
|
|
|
3246
3288
|
secret
|
|
3247
3289
|
}) {
|
|
3248
3290
|
const toleranceSec = 300;
|
|
3249
|
-
if (!signature) throw new
|
|
3291
|
+
if (!signature) throw new SolvaPayError7("Missing webhook signature");
|
|
3250
3292
|
const parts = signature.split(",");
|
|
3251
3293
|
const tPart = parts.find((p) => p.startsWith("t="));
|
|
3252
3294
|
const v1Part = parts.find((p) => p.startsWith("v1="));
|
|
3253
3295
|
if (!tPart || !v1Part) {
|
|
3254
|
-
throw new
|
|
3296
|
+
throw new SolvaPayError7("Malformed webhook signature");
|
|
3255
3297
|
}
|
|
3256
3298
|
const timestamp = parseInt(tPart.slice(2), 10);
|
|
3257
3299
|
const receivedHmac = v1Part.slice(3);
|
|
3258
3300
|
if (Number.isNaN(timestamp) || !receivedHmac) {
|
|
3259
|
-
throw new
|
|
3301
|
+
throw new SolvaPayError7("Malformed webhook signature");
|
|
3260
3302
|
}
|
|
3261
3303
|
if (toleranceSec > 0) {
|
|
3262
3304
|
const age = Math.abs(Math.floor(Date.now() / 1e3) - timestamp);
|
|
3263
3305
|
if (age > toleranceSec) {
|
|
3264
|
-
throw new
|
|
3306
|
+
throw new SolvaPayError7("Webhook signature timestamp too old");
|
|
3265
3307
|
}
|
|
3266
3308
|
}
|
|
3267
3309
|
const enc = new TextEncoder();
|
|
@@ -3275,12 +3317,12 @@ async function verifyWebhook({
|
|
|
3275
3317
|
const sigBuf = await crypto.subtle.sign("HMAC", key, enc.encode(`${timestamp}.${body}`));
|
|
3276
3318
|
const expectedHmac = Array.from(new Uint8Array(sigBuf)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3277
3319
|
if (!timingSafeEqual(expectedHmac, receivedHmac)) {
|
|
3278
|
-
throw new
|
|
3320
|
+
throw new SolvaPayError7("Invalid webhook signature");
|
|
3279
3321
|
}
|
|
3280
3322
|
try {
|
|
3281
3323
|
return JSON.parse(body);
|
|
3282
3324
|
} catch {
|
|
3283
|
-
throw new
|
|
3325
|
+
throw new SolvaPayError7("Invalid webhook payload: body is not valid JSON");
|
|
3284
3326
|
}
|
|
3285
3327
|
}
|
|
3286
3328
|
|