@solvapay/server 1.0.6 → 1.0.8-preview.1
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 +1065 -1746
- package/dist/edge.js +472 -44
- package/dist/index.cjs +563 -53
- package/dist/index.d.cts +1137 -1792
- package/dist/index.d.ts +1137 -1792
- package/dist/index.js +546 -45
- package/package.json +4 -4
package/dist/edge.js
CHANGED
|
@@ -38,12 +38,10 @@ function createSolvaPayClient(opts) {
|
|
|
38
38
|
// POST: /v1/sdk/usages
|
|
39
39
|
async trackUsage(params) {
|
|
40
40
|
const url = `${base}/v1/sdk/usages`;
|
|
41
|
-
const { customerRef, ...rest } = params;
|
|
42
|
-
const body = { ...rest, customerId: customerRef };
|
|
43
41
|
const res = await fetch(url, {
|
|
44
42
|
method: "POST",
|
|
45
43
|
headers,
|
|
46
|
-
body: JSON.stringify(
|
|
44
|
+
body: JSON.stringify(params)
|
|
47
45
|
});
|
|
48
46
|
if (!res.ok) {
|
|
49
47
|
const error = await res.text();
|
|
@@ -111,6 +109,36 @@ function createSolvaPayClient(opts) {
|
|
|
111
109
|
purchases: customer.purchases || []
|
|
112
110
|
};
|
|
113
111
|
},
|
|
112
|
+
// GET: /v1/sdk/merchant
|
|
113
|
+
async getMerchant() {
|
|
114
|
+
const url = `${base}/v1/sdk/merchant`;
|
|
115
|
+
const res = await fetch(url, {
|
|
116
|
+
method: "GET",
|
|
117
|
+
headers
|
|
118
|
+
});
|
|
119
|
+
if (!res.ok) {
|
|
120
|
+
const error = await res.text();
|
|
121
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
122
|
+
throw new SolvaPayError(`Get merchant failed (${res.status}): ${error}`);
|
|
123
|
+
}
|
|
124
|
+
return res.json();
|
|
125
|
+
},
|
|
126
|
+
// GET: /v1/sdk/products/{productRef}
|
|
127
|
+
async getProduct(productRef) {
|
|
128
|
+
const url = `${base}/v1/sdk/products/${encodeURIComponent(productRef)}`;
|
|
129
|
+
const res = await fetch(url, {
|
|
130
|
+
method: "GET",
|
|
131
|
+
headers
|
|
132
|
+
});
|
|
133
|
+
if (!res.ok) {
|
|
134
|
+
const error = await res.text();
|
|
135
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
136
|
+
throw new SolvaPayError(`Get product failed (${res.status}): ${error}`);
|
|
137
|
+
}
|
|
138
|
+
const result = await res.json();
|
|
139
|
+
const data = result.data || {};
|
|
140
|
+
return { ...data, ...result };
|
|
141
|
+
},
|
|
114
142
|
// Product management methods (primarily for integration tests)
|
|
115
143
|
// GET: /v1/sdk/products
|
|
116
144
|
async listProducts() {
|
|
@@ -288,7 +316,7 @@ function createSolvaPayClient(opts) {
|
|
|
288
316
|
body: JSON.stringify({
|
|
289
317
|
productRef: params.productRef,
|
|
290
318
|
planRef: params.planRef,
|
|
291
|
-
|
|
319
|
+
customerRef: params.customerRef
|
|
292
320
|
})
|
|
293
321
|
});
|
|
294
322
|
if (!res.ok) {
|
|
@@ -296,8 +324,32 @@ function createSolvaPayClient(opts) {
|
|
|
296
324
|
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
297
325
|
throw new SolvaPayError(`Create payment intent failed (${res.status}): ${error}`);
|
|
298
326
|
}
|
|
299
|
-
|
|
300
|
-
|
|
327
|
+
return await res.json();
|
|
328
|
+
},
|
|
329
|
+
// POST: /v1/sdk/payment-intents (purpose: credit_topup)
|
|
330
|
+
async createTopupPaymentIntent(params) {
|
|
331
|
+
const idempotencyKey = params.idempotencyKey || `topup-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
332
|
+
const url = `${base}/v1/sdk/payment-intents`;
|
|
333
|
+
const res = await fetch(url, {
|
|
334
|
+
method: "POST",
|
|
335
|
+
headers: {
|
|
336
|
+
...headers,
|
|
337
|
+
"Idempotency-Key": idempotencyKey
|
|
338
|
+
},
|
|
339
|
+
body: JSON.stringify({
|
|
340
|
+
customerRef: params.customerRef,
|
|
341
|
+
purpose: "credit_topup",
|
|
342
|
+
amount: params.amount,
|
|
343
|
+
currency: params.currency,
|
|
344
|
+
description: params.description
|
|
345
|
+
})
|
|
346
|
+
});
|
|
347
|
+
if (!res.ok) {
|
|
348
|
+
const error = await res.text();
|
|
349
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
350
|
+
throw new SolvaPayError(`Create topup payment intent failed (${res.status}): ${error}`);
|
|
351
|
+
}
|
|
352
|
+
return await res.json();
|
|
301
353
|
},
|
|
302
354
|
// POST: /v1/sdk/payment-intents/{paymentIntentId}/process
|
|
303
355
|
async processPaymentIntent(params) {
|
|
@@ -374,6 +426,54 @@ function createSolvaPayClient(opts) {
|
|
|
374
426
|
}
|
|
375
427
|
return result;
|
|
376
428
|
},
|
|
429
|
+
// POST: /v1/sdk/purchases/{purchaseRef}/reactivate
|
|
430
|
+
async reactivatePurchase(params) {
|
|
431
|
+
const url = `${base}/v1/sdk/purchases/${params.purchaseRef}/reactivate`;
|
|
432
|
+
const res = await fetch(url, {
|
|
433
|
+
method: "POST",
|
|
434
|
+
headers
|
|
435
|
+
});
|
|
436
|
+
if (!res.ok) {
|
|
437
|
+
const error = await res.text();
|
|
438
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
439
|
+
if (res.status === 404) {
|
|
440
|
+
throw new SolvaPayError(`Purchase not found: ${error}`);
|
|
441
|
+
}
|
|
442
|
+
if (res.status === 400) {
|
|
443
|
+
throw new SolvaPayError(
|
|
444
|
+
`Purchase cannot be reactivated: ${error}`
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
throw new SolvaPayError(`Reactivate purchase failed (${res.status}): ${error}`);
|
|
448
|
+
}
|
|
449
|
+
const responseText = await res.text();
|
|
450
|
+
let responseData;
|
|
451
|
+
try {
|
|
452
|
+
responseData = JSON.parse(responseText);
|
|
453
|
+
} catch (parseError) {
|
|
454
|
+
log(`\u274C Failed to parse response as JSON: ${parseError}`);
|
|
455
|
+
throw new SolvaPayError(
|
|
456
|
+
`Invalid JSON response from reactivate purchase endpoint: ${responseText.substring(0, 200)}`
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
if (!responseData || typeof responseData !== "object") {
|
|
460
|
+
log(`\u274C Invalid response structure: ${JSON.stringify(responseData)}`);
|
|
461
|
+
throw new SolvaPayError(`Invalid response structure from reactivate purchase endpoint`);
|
|
462
|
+
}
|
|
463
|
+
let result;
|
|
464
|
+
if (responseData.purchase && typeof responseData.purchase === "object") {
|
|
465
|
+
result = responseData.purchase;
|
|
466
|
+
} else if (responseData.reference) {
|
|
467
|
+
result = responseData;
|
|
468
|
+
} else {
|
|
469
|
+
result = responseData.purchase || responseData;
|
|
470
|
+
}
|
|
471
|
+
if (!result || typeof result !== "object") {
|
|
472
|
+
log(`\u274C Invalid purchase data in response. Full response:`, responseData);
|
|
473
|
+
throw new SolvaPayError(`Invalid purchase data in reactivate purchase response`);
|
|
474
|
+
}
|
|
475
|
+
return result;
|
|
476
|
+
},
|
|
377
477
|
// POST: /v1/sdk/user-info
|
|
378
478
|
async getUserInfo(params) {
|
|
379
479
|
const url = `${base}/v1/sdk/user-info`;
|
|
@@ -389,6 +489,20 @@ function createSolvaPayClient(opts) {
|
|
|
389
489
|
}
|
|
390
490
|
return await res.json();
|
|
391
491
|
},
|
|
492
|
+
// GET: /v1/sdk/customers/:customerRef/balance
|
|
493
|
+
async getCustomerBalance(params) {
|
|
494
|
+
const url = `${base}/v1/sdk/customers/${params.customerRef}/balance`;
|
|
495
|
+
const res = await fetch(url, {
|
|
496
|
+
method: "GET",
|
|
497
|
+
headers
|
|
498
|
+
});
|
|
499
|
+
if (!res.ok) {
|
|
500
|
+
const error = await res.text();
|
|
501
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
502
|
+
throw new SolvaPayError(`Get customer balance failed (${res.status}): ${error}`);
|
|
503
|
+
}
|
|
504
|
+
return await res.json();
|
|
505
|
+
},
|
|
392
506
|
// POST: /v1/sdk/checkout-sessions
|
|
393
507
|
async createCheckoutSession(params) {
|
|
394
508
|
const url = `${base}/v1/sdk/checkout-sessions`;
|
|
@@ -420,6 +534,21 @@ function createSolvaPayClient(opts) {
|
|
|
420
534
|
}
|
|
421
535
|
const result = await res.json();
|
|
422
536
|
return result;
|
|
537
|
+
},
|
|
538
|
+
// POST: /v1/sdk/activate
|
|
539
|
+
async activatePlan(params) {
|
|
540
|
+
const url = `${base}/v1/sdk/activate`;
|
|
541
|
+
const res = await fetch(url, {
|
|
542
|
+
method: "POST",
|
|
543
|
+
headers,
|
|
544
|
+
body: JSON.stringify(params)
|
|
545
|
+
});
|
|
546
|
+
if (!res.ok) {
|
|
547
|
+
const error = await res.text();
|
|
548
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
549
|
+
throw new SolvaPayError(`Activate plan failed (${res.status}): ${error}`);
|
|
550
|
+
}
|
|
551
|
+
return await res.json();
|
|
423
552
|
}
|
|
424
553
|
};
|
|
425
554
|
}
|
|
@@ -575,6 +704,24 @@ var PaywallError = class extends Error {
|
|
|
575
704
|
this.name = "PaywallError";
|
|
576
705
|
}
|
|
577
706
|
};
|
|
707
|
+
function paywallErrorToClientPayload(error) {
|
|
708
|
+
const sc = error.structuredContent;
|
|
709
|
+
const base = {
|
|
710
|
+
success: false,
|
|
711
|
+
error: sc.kind === "activation_required" ? "Activation required" : "Payment required",
|
|
712
|
+
product: sc.product,
|
|
713
|
+
checkoutUrl: sc.checkoutUrl,
|
|
714
|
+
message: sc.message
|
|
715
|
+
};
|
|
716
|
+
if (sc.kind === "activation_required") {
|
|
717
|
+
base.kind = "activation_required";
|
|
718
|
+
if (sc.plans !== void 0) base.plans = sc.plans;
|
|
719
|
+
if (sc.balance !== void 0) base.balance = sc.balance;
|
|
720
|
+
if (sc.productDetails !== void 0) base.productDetails = sc.productDetails;
|
|
721
|
+
if (sc.confirmationUrl !== void 0) base.confirmationUrl = sc.confirmationUrl;
|
|
722
|
+
}
|
|
723
|
+
return base;
|
|
724
|
+
}
|
|
578
725
|
var sharedCustomerLookupDeduplicator = createRequestDeduplicator({
|
|
579
726
|
cacheTTL: 6e4,
|
|
580
727
|
// Cache results for 60 seconds (reduces API calls significantly)
|
|
@@ -613,8 +760,6 @@ var SolvaPayPaywall = class {
|
|
|
613
760
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
614
761
|
async protect(handler, metadata = {}, getCustomerRef) {
|
|
615
762
|
const product = this.resolveProduct(metadata);
|
|
616
|
-
const configuredPlanRef = metadata.plan?.trim();
|
|
617
|
-
const usagePlanRef = configuredPlanRef || "unspecified";
|
|
618
763
|
const usageType = metadata.usageType || "requests";
|
|
619
764
|
return async (args) => {
|
|
620
765
|
const startTime = Date.now();
|
|
@@ -628,12 +773,13 @@ var SolvaPayPaywall = class {
|
|
|
628
773
|
}
|
|
629
774
|
let resolvedMeterName;
|
|
630
775
|
try {
|
|
631
|
-
const limitsCacheKey = `${backendCustomerRef}:${product}:${
|
|
776
|
+
const limitsCacheKey = `${backendCustomerRef}:${product}:${usageType}`;
|
|
632
777
|
const cachedLimits = this.limitsCache.get(limitsCacheKey);
|
|
633
778
|
const now = Date.now();
|
|
634
779
|
let withinLimits;
|
|
635
780
|
let remaining;
|
|
636
781
|
let checkoutUrl;
|
|
782
|
+
let lastLimitsCheck;
|
|
637
783
|
const hasFreshCachedLimits = cachedLimits && now - cachedLimits.timestamp < this.limitsCacheTTL;
|
|
638
784
|
if (hasFreshCachedLimits) {
|
|
639
785
|
checkoutUrl = cachedLimits.checkoutUrl;
|
|
@@ -657,9 +803,9 @@ var SolvaPayPaywall = class {
|
|
|
657
803
|
const limitsCheck = await this.apiClient.checkLimits({
|
|
658
804
|
customerRef: backendCustomerRef,
|
|
659
805
|
productRef: product,
|
|
660
|
-
...configuredPlanRef ? { planRef: configuredPlanRef } : {},
|
|
661
806
|
meterName: usageType
|
|
662
807
|
});
|
|
808
|
+
lastLimitsCheck = limitsCheck;
|
|
663
809
|
withinLimits = limitsCheck.withinLimits;
|
|
664
810
|
remaining = limitsCheck.remaining;
|
|
665
811
|
checkoutUrl = limitsCheck.checkoutUrl;
|
|
@@ -682,12 +828,25 @@ var SolvaPayPaywall = class {
|
|
|
682
828
|
this.trackUsage(
|
|
683
829
|
backendCustomerRef,
|
|
684
830
|
product,
|
|
685
|
-
usagePlanRef,
|
|
686
831
|
resolvedMeterName || usageType,
|
|
687
832
|
"paywall",
|
|
688
833
|
requestId,
|
|
689
834
|
latencyMs2
|
|
690
835
|
);
|
|
836
|
+
if (lastLimitsCheck?.activationRequired) {
|
|
837
|
+
const confirmationUrl = lastLimitsCheck.confirmationUrl;
|
|
838
|
+
const payCheckoutUrl = confirmationUrl || lastLimitsCheck.checkoutUrl || checkoutUrl || "";
|
|
839
|
+
throw new PaywallError("Activation required", {
|
|
840
|
+
kind: "activation_required",
|
|
841
|
+
product,
|
|
842
|
+
message: "Product activation is required before this tool can be used.",
|
|
843
|
+
checkoutUrl: payCheckoutUrl,
|
|
844
|
+
confirmationUrl,
|
|
845
|
+
plans: lastLimitsCheck.plans,
|
|
846
|
+
balance: lastLimitsCheck.balance,
|
|
847
|
+
productDetails: lastLimitsCheck.product
|
|
848
|
+
});
|
|
849
|
+
}
|
|
691
850
|
throw new PaywallError("Payment required", {
|
|
692
851
|
kind: "payment_required",
|
|
693
852
|
product,
|
|
@@ -700,7 +859,6 @@ var SolvaPayPaywall = class {
|
|
|
700
859
|
this.trackUsage(
|
|
701
860
|
backendCustomerRef,
|
|
702
861
|
product,
|
|
703
|
-
usagePlanRef,
|
|
704
862
|
resolvedMeterName || usageType,
|
|
705
863
|
"success",
|
|
706
864
|
requestId,
|
|
@@ -719,7 +877,6 @@ var SolvaPayPaywall = class {
|
|
|
719
877
|
this.trackUsage(
|
|
720
878
|
backendCustomerRef,
|
|
721
879
|
product,
|
|
722
|
-
usagePlanRef,
|
|
723
880
|
resolvedMeterName || usageType,
|
|
724
881
|
"fail",
|
|
725
882
|
requestId,
|
|
@@ -790,7 +947,8 @@ var SolvaPayPaywall = class {
|
|
|
790
947
|
this.customerCreationAttempts.add(customerRef);
|
|
791
948
|
try {
|
|
792
949
|
const createParams = {
|
|
793
|
-
email: options?.email || `${customerRef}-${Date.now()}@auto-created.local
|
|
950
|
+
email: options?.email || `${customerRef}-${Date.now()}@auto-created.local`,
|
|
951
|
+
metadata: {}
|
|
794
952
|
};
|
|
795
953
|
if (options?.name) {
|
|
796
954
|
createParams.name = options.name;
|
|
@@ -814,7 +972,10 @@ var SolvaPayPaywall = class {
|
|
|
814
972
|
return searchResult.customerRef;
|
|
815
973
|
}
|
|
816
974
|
} catch (lookupError) {
|
|
817
|
-
this.log(
|
|
975
|
+
this.log(
|
|
976
|
+
`\u26A0\uFE0F Failed to lookup existing customer by externalRef after 409:`,
|
|
977
|
+
lookupError instanceof Error ? lookupError.message : lookupError
|
|
978
|
+
);
|
|
818
979
|
}
|
|
819
980
|
}
|
|
820
981
|
const isEmailConflict = errorMessage.includes("email") || errorMessage.includes("identifier email");
|
|
@@ -837,7 +998,8 @@ var SolvaPayPaywall = class {
|
|
|
837
998
|
try {
|
|
838
999
|
const retryParams = {
|
|
839
1000
|
email: `${customerRef}-${Date.now()}@auto-created.local`,
|
|
840
|
-
externalRef
|
|
1001
|
+
externalRef,
|
|
1002
|
+
metadata: {}
|
|
841
1003
|
};
|
|
842
1004
|
if (options?.name) {
|
|
843
1005
|
retryParams.name = options.name;
|
|
@@ -874,14 +1036,14 @@ var SolvaPayPaywall = class {
|
|
|
874
1036
|
}
|
|
875
1037
|
return backendRef;
|
|
876
1038
|
}
|
|
877
|
-
async trackUsage(customerRef,
|
|
1039
|
+
async trackUsage(customerRef, productRef, action, outcome, requestId, actionDuration) {
|
|
878
1040
|
await withRetry(
|
|
879
1041
|
() => this.apiClient.trackUsage({
|
|
880
1042
|
customerRef,
|
|
881
1043
|
actionType: "api_call",
|
|
882
1044
|
units: 1,
|
|
883
1045
|
outcome,
|
|
884
|
-
|
|
1046
|
+
productRef,
|
|
885
1047
|
duration: actionDuration,
|
|
886
1048
|
metadata: { action: action || "api_requests", requestId },
|
|
887
1049
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -1133,8 +1295,10 @@ var McpAdapter = class {
|
|
|
1133
1295
|
const ref = await this.options.getCustomerRef(args, extra);
|
|
1134
1296
|
return AdapterUtils.ensureCustomerRef(ref);
|
|
1135
1297
|
}
|
|
1136
|
-
const customerRefFromExtra = extra?.authInfo?.extra?.customer_ref;
|
|
1137
|
-
const
|
|
1298
|
+
const customerRefFromExtra = typeof extra?.authInfo?.extra?.customer_ref === "string" ? String(extra.authInfo.extra.customer_ref) : void 0;
|
|
1299
|
+
const customerRefFromArgs = typeof args.auth === "object" && args.auth !== null && typeof args.auth.customer_ref === "string" ? String(args.auth.customer_ref) : void 0;
|
|
1300
|
+
const directCustomerRef = typeof args.customer_ref === "string" ? args.customer_ref : void 0;
|
|
1301
|
+
const customerRef = (customerRefFromExtra || customerRefFromArgs || directCustomerRef || "anonymous").trim();
|
|
1138
1302
|
return AdapterUtils.ensureCustomerRef(customerRef);
|
|
1139
1303
|
}
|
|
1140
1304
|
formatResponse(result, _context) {
|
|
@@ -1158,17 +1322,7 @@ var McpAdapter = class {
|
|
|
1158
1322
|
content: [
|
|
1159
1323
|
{
|
|
1160
1324
|
type: "text",
|
|
1161
|
-
text: JSON.stringify(
|
|
1162
|
-
{
|
|
1163
|
-
success: false,
|
|
1164
|
-
error: "Payment required",
|
|
1165
|
-
product: error.structuredContent.product,
|
|
1166
|
-
checkoutUrl: error.structuredContent.checkoutUrl,
|
|
1167
|
-
message: error.structuredContent.message
|
|
1168
|
-
},
|
|
1169
|
-
null,
|
|
1170
|
-
2
|
|
1171
|
-
)
|
|
1325
|
+
text: JSON.stringify(paywallErrorToClientPayload(error), null, 2)
|
|
1172
1326
|
}
|
|
1173
1327
|
],
|
|
1174
1328
|
isError: true,
|
|
@@ -1454,6 +1608,12 @@ function createSolvaPay(config) {
|
|
|
1454
1608
|
}
|
|
1455
1609
|
return apiClient.createPaymentIntent(params);
|
|
1456
1610
|
},
|
|
1611
|
+
createTopupPaymentIntent(params) {
|
|
1612
|
+
if (!apiClient.createTopupPaymentIntent) {
|
|
1613
|
+
throw new SolvaPayError2("createTopupPaymentIntent is not available on this API client");
|
|
1614
|
+
}
|
|
1615
|
+
return apiClient.createTopupPaymentIntent(params);
|
|
1616
|
+
},
|
|
1457
1617
|
processPaymentIntent(params) {
|
|
1458
1618
|
if (!apiClient.processPaymentIntent) {
|
|
1459
1619
|
throw new SolvaPayError2("processPaymentIntent is not available on this API client");
|
|
@@ -1470,11 +1630,17 @@ function createSolvaPay(config) {
|
|
|
1470
1630
|
if (!apiClient.createCustomer) {
|
|
1471
1631
|
throw new SolvaPayError2("createCustomer is not available on this API client");
|
|
1472
1632
|
}
|
|
1473
|
-
return apiClient.createCustomer(params);
|
|
1633
|
+
return apiClient.createCustomer({ ...params, metadata: params.metadata ?? {} });
|
|
1474
1634
|
},
|
|
1475
1635
|
getCustomer(params) {
|
|
1476
1636
|
return apiClient.getCustomer(params);
|
|
1477
1637
|
},
|
|
1638
|
+
getCustomerBalance(params) {
|
|
1639
|
+
if (!apiClient.getCustomerBalance) {
|
|
1640
|
+
throw new SolvaPayError2("getCustomerBalance is not available on this API client");
|
|
1641
|
+
}
|
|
1642
|
+
return apiClient.getCustomerBalance(params);
|
|
1643
|
+
},
|
|
1478
1644
|
createCheckoutSession(params) {
|
|
1479
1645
|
return apiClient.createCheckoutSession({
|
|
1480
1646
|
customerRef: params.customerRef,
|
|
@@ -1485,6 +1651,12 @@ function createSolvaPay(config) {
|
|
|
1485
1651
|
createCustomerSession(params) {
|
|
1486
1652
|
return apiClient.createCustomerSession(params);
|
|
1487
1653
|
},
|
|
1654
|
+
activatePlan(params) {
|
|
1655
|
+
if (!apiClient.activatePlan) {
|
|
1656
|
+
throw new SolvaPayError2("activatePlan is not available on this API client");
|
|
1657
|
+
}
|
|
1658
|
+
return apiClient.activatePlan(params);
|
|
1659
|
+
},
|
|
1488
1660
|
bootstrapMcpProduct(params) {
|
|
1489
1661
|
if (!apiClient.bootstrapMcpProduct) {
|
|
1490
1662
|
throw new SolvaPayError2("bootstrapMcpProduct is not available on this API client");
|
|
@@ -1506,9 +1678,8 @@ function createSolvaPay(config) {
|
|
|
1506
1678
|
// Payable API for framework-specific handlers
|
|
1507
1679
|
payable(options = {}) {
|
|
1508
1680
|
const product = options.productRef || options.product || process.env.SOLVAPAY_PRODUCT || "default-product";
|
|
1509
|
-
const plan = options.planRef || options.plan;
|
|
1510
1681
|
const usageType = options.usageType || "requests";
|
|
1511
|
-
const metadata = { product,
|
|
1682
|
+
const metadata = { product, usageType };
|
|
1512
1683
|
return {
|
|
1513
1684
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1514
1685
|
http(businessLogic, adapterOptions) {
|
|
@@ -1634,6 +1805,24 @@ async function syncCustomerCore(request, options = {}) {
|
|
|
1634
1805
|
return handleRouteError(error, "Sync customer", "Failed to sync customer");
|
|
1635
1806
|
}
|
|
1636
1807
|
}
|
|
1808
|
+
async function getCustomerBalanceCore(request, options = {}) {
|
|
1809
|
+
try {
|
|
1810
|
+
const userResult = await getAuthenticatedUserCore(request);
|
|
1811
|
+
if (isErrorResult(userResult)) {
|
|
1812
|
+
return userResult;
|
|
1813
|
+
}
|
|
1814
|
+
const { userId, email, name } = userResult;
|
|
1815
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
1816
|
+
const customerRef = await solvaPay.ensureCustomer(userId, userId, {
|
|
1817
|
+
email: email || void 0,
|
|
1818
|
+
name: name || void 0
|
|
1819
|
+
});
|
|
1820
|
+
const result = await solvaPay.getCustomerBalance({ customerRef });
|
|
1821
|
+
return result;
|
|
1822
|
+
} catch (error) {
|
|
1823
|
+
return handleRouteError(error, "Get customer credits", "Failed to get customer credits");
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1637
1826
|
|
|
1638
1827
|
// src/helpers/payment.ts
|
|
1639
1828
|
async function createPaymentIntentCore(request, body, options = {}) {
|
|
@@ -1660,7 +1849,7 @@ async function createPaymentIntentCore(request, body, options = {}) {
|
|
|
1660
1849
|
customerRef
|
|
1661
1850
|
});
|
|
1662
1851
|
return {
|
|
1663
|
-
|
|
1852
|
+
processorPaymentId: paymentIntent.processorPaymentId,
|
|
1664
1853
|
clientSecret: paymentIntent.clientSecret,
|
|
1665
1854
|
publishableKey: paymentIntent.publishableKey,
|
|
1666
1855
|
accountId: paymentIntent.accountId,
|
|
@@ -1670,6 +1859,57 @@ async function createPaymentIntentCore(request, body, options = {}) {
|
|
|
1670
1859
|
return handleRouteError(error, "Create payment intent", "Payment intent creation failed");
|
|
1671
1860
|
}
|
|
1672
1861
|
}
|
|
1862
|
+
async function createTopupPaymentIntentCore(request, body, options = {}) {
|
|
1863
|
+
try {
|
|
1864
|
+
if (!body.amount || body.amount <= 0) {
|
|
1865
|
+
return {
|
|
1866
|
+
error: "Missing or invalid amount: must be a positive number",
|
|
1867
|
+
status: 400
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1870
|
+
if (!body.currency) {
|
|
1871
|
+
return {
|
|
1872
|
+
error: "Missing required parameter: currency",
|
|
1873
|
+
status: 400
|
|
1874
|
+
};
|
|
1875
|
+
}
|
|
1876
|
+
if (body.currency !== body.currency.toUpperCase()) {
|
|
1877
|
+
return {
|
|
1878
|
+
error: `Invalid currency "${body.currency}": must be an uppercase ISO 4217 code (e.g. "USD", "EUR")`,
|
|
1879
|
+
status: 400
|
|
1880
|
+
};
|
|
1881
|
+
}
|
|
1882
|
+
const customerResult = await syncCustomerCore(request, {
|
|
1883
|
+
solvaPay: options.solvaPay,
|
|
1884
|
+
includeEmail: options.includeEmail,
|
|
1885
|
+
includeName: options.includeName
|
|
1886
|
+
});
|
|
1887
|
+
if (isErrorResult(customerResult)) {
|
|
1888
|
+
return customerResult;
|
|
1889
|
+
}
|
|
1890
|
+
const customerRef = customerResult;
|
|
1891
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
1892
|
+
const paymentIntent = await solvaPay.createTopupPaymentIntent({
|
|
1893
|
+
customerRef,
|
|
1894
|
+
amount: body.amount,
|
|
1895
|
+
currency: body.currency,
|
|
1896
|
+
description: body.description
|
|
1897
|
+
});
|
|
1898
|
+
return {
|
|
1899
|
+
processorPaymentId: paymentIntent.processorPaymentId,
|
|
1900
|
+
clientSecret: paymentIntent.clientSecret,
|
|
1901
|
+
publishableKey: paymentIntent.publishableKey,
|
|
1902
|
+
accountId: paymentIntent.accountId,
|
|
1903
|
+
customerRef
|
|
1904
|
+
};
|
|
1905
|
+
} catch (error) {
|
|
1906
|
+
return handleRouteError(
|
|
1907
|
+
error,
|
|
1908
|
+
"Create topup payment intent",
|
|
1909
|
+
"Topup payment intent creation failed"
|
|
1910
|
+
);
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1673
1913
|
async function processPaymentIntentCore(request, body, options = {}) {
|
|
1674
1914
|
try {
|
|
1675
1915
|
if (!body.paymentIntentId || !body.productRef) {
|
|
@@ -1832,10 +2072,107 @@ async function cancelPurchaseCore(request, body, options = {}) {
|
|
|
1832
2072
|
return handleRouteError(error, "Cancel purchase", "Failed to cancel purchase");
|
|
1833
2073
|
}
|
|
1834
2074
|
}
|
|
2075
|
+
async function reactivatePurchaseCore(request, body, options = {}) {
|
|
2076
|
+
try {
|
|
2077
|
+
if (!body.purchaseRef) {
|
|
2078
|
+
return {
|
|
2079
|
+
error: "Missing required parameter: purchaseRef is required",
|
|
2080
|
+
status: 400
|
|
2081
|
+
};
|
|
2082
|
+
}
|
|
2083
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
2084
|
+
if (!solvaPay.apiClient.reactivatePurchase) {
|
|
2085
|
+
return {
|
|
2086
|
+
error: "Reactivate purchase method not available on SDK client",
|
|
2087
|
+
status: 500
|
|
2088
|
+
};
|
|
2089
|
+
}
|
|
2090
|
+
let reactivatedPurchase = await solvaPay.apiClient.reactivatePurchase({
|
|
2091
|
+
purchaseRef: body.purchaseRef
|
|
2092
|
+
});
|
|
2093
|
+
if (!reactivatedPurchase || typeof reactivatedPurchase !== "object") {
|
|
2094
|
+
return {
|
|
2095
|
+
error: "Invalid response from reactivate purchase endpoint",
|
|
2096
|
+
status: 500
|
|
2097
|
+
};
|
|
2098
|
+
}
|
|
2099
|
+
const responseObj = reactivatedPurchase;
|
|
2100
|
+
if (responseObj.purchase && typeof responseObj.purchase === "object") {
|
|
2101
|
+
reactivatedPurchase = responseObj.purchase;
|
|
2102
|
+
}
|
|
2103
|
+
if (!reactivatedPurchase.reference) {
|
|
2104
|
+
return {
|
|
2105
|
+
error: "Reactivate purchase response missing required fields",
|
|
2106
|
+
status: 500
|
|
2107
|
+
};
|
|
2108
|
+
}
|
|
2109
|
+
if (reactivatedPurchase.cancelledAt) {
|
|
2110
|
+
return {
|
|
2111
|
+
error: `Purchase reactivation failed: cancelledAt is still set`,
|
|
2112
|
+
status: 500
|
|
2113
|
+
};
|
|
2114
|
+
}
|
|
2115
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2116
|
+
return reactivatedPurchase;
|
|
2117
|
+
} catch (error) {
|
|
2118
|
+
if (error instanceof SolvaPayError4) {
|
|
2119
|
+
const errorMessage = error.message;
|
|
2120
|
+
if (errorMessage.includes("not found")) {
|
|
2121
|
+
return {
|
|
2122
|
+
error: "Purchase not found",
|
|
2123
|
+
status: 404,
|
|
2124
|
+
details: errorMessage
|
|
2125
|
+
};
|
|
2126
|
+
}
|
|
2127
|
+
if (errorMessage.includes("cannot be reactivated") || errorMessage.includes("not pending cancellation") || errorMessage.includes("already been fully cancelled") || errorMessage.includes("already ended")) {
|
|
2128
|
+
return {
|
|
2129
|
+
error: "Purchase cannot be reactivated",
|
|
2130
|
+
status: 400,
|
|
2131
|
+
details: errorMessage
|
|
2132
|
+
};
|
|
2133
|
+
}
|
|
2134
|
+
return {
|
|
2135
|
+
error: errorMessage,
|
|
2136
|
+
status: 500,
|
|
2137
|
+
details: errorMessage
|
|
2138
|
+
};
|
|
2139
|
+
}
|
|
2140
|
+
return handleRouteError(error, "Reactivate purchase", "Failed to reactivate purchase");
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
// src/helpers/activation.ts
|
|
2145
|
+
async function activatePlanCore(request, body, options = {}) {
|
|
2146
|
+
try {
|
|
2147
|
+
if (!body.productRef || !body.planRef) {
|
|
2148
|
+
return {
|
|
2149
|
+
error: "Missing required parameters: productRef and planRef are required",
|
|
2150
|
+
status: 400
|
|
2151
|
+
};
|
|
2152
|
+
}
|
|
2153
|
+
const customerResult = await syncCustomerCore(request, {
|
|
2154
|
+
solvaPay: options.solvaPay,
|
|
2155
|
+
includeEmail: options.includeEmail,
|
|
2156
|
+
includeName: options.includeName
|
|
2157
|
+
});
|
|
2158
|
+
if (isErrorResult(customerResult)) {
|
|
2159
|
+
return customerResult;
|
|
2160
|
+
}
|
|
2161
|
+
const customerRef = customerResult;
|
|
2162
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
2163
|
+
return await solvaPay.activatePlan({
|
|
2164
|
+
customerRef,
|
|
2165
|
+
productRef: body.productRef,
|
|
2166
|
+
planRef: body.planRef
|
|
2167
|
+
});
|
|
2168
|
+
} catch (error) {
|
|
2169
|
+
return handleRouteError(error, "Activate plan", "Plan activation failed");
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
1835
2172
|
|
|
1836
2173
|
// src/helpers/plans.ts
|
|
1837
2174
|
import { getSolvaPayConfig as getSolvaPayConfig2 } from "@solvapay/core";
|
|
1838
|
-
async function listPlansCore(request) {
|
|
2175
|
+
async function listPlansCore(request, options = {}) {
|
|
1839
2176
|
try {
|
|
1840
2177
|
const url = new URL(request.url);
|
|
1841
2178
|
const productRef = url.searchParams.get("productRef");
|
|
@@ -1845,19 +2182,20 @@ async function listPlansCore(request) {
|
|
|
1845
2182
|
status: 400
|
|
1846
2183
|
};
|
|
1847
2184
|
}
|
|
1848
|
-
const
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
2185
|
+
const apiClient = options.solvaPay?.apiClient ?? (() => {
|
|
2186
|
+
const config = getSolvaPayConfig2();
|
|
2187
|
+
if (!config.apiKey) return null;
|
|
2188
|
+
return createSolvaPayClient({
|
|
2189
|
+
apiKey: config.apiKey,
|
|
2190
|
+
apiBaseUrl: config.apiBaseUrl
|
|
2191
|
+
});
|
|
2192
|
+
})();
|
|
2193
|
+
if (!apiClient) {
|
|
1852
2194
|
return {
|
|
1853
2195
|
error: "Server configuration error: SolvaPay secret key not configured",
|
|
1854
2196
|
status: 500
|
|
1855
2197
|
};
|
|
1856
2198
|
}
|
|
1857
|
-
const apiClient = createSolvaPayClient({
|
|
1858
|
-
apiKey: solvapaySecretKey,
|
|
1859
|
-
apiBaseUrl: solvapayApiBaseUrl
|
|
1860
|
-
});
|
|
1861
2199
|
if (!apiClient.listPlans) {
|
|
1862
2200
|
return {
|
|
1863
2201
|
error: "List plans method not available",
|
|
@@ -1874,6 +2212,89 @@ async function listPlansCore(request) {
|
|
|
1874
2212
|
}
|
|
1875
2213
|
}
|
|
1876
2214
|
|
|
2215
|
+
// src/helpers/purchase.ts
|
|
2216
|
+
async function checkPurchaseCore(request, options = {}) {
|
|
2217
|
+
try {
|
|
2218
|
+
const userResult = await getAuthenticatedUserCore(request, {
|
|
2219
|
+
includeEmail: options.includeEmail,
|
|
2220
|
+
includeName: options.includeName
|
|
2221
|
+
});
|
|
2222
|
+
if (isErrorResult(userResult)) {
|
|
2223
|
+
return userResult;
|
|
2224
|
+
}
|
|
2225
|
+
const { userId, email, name } = userResult;
|
|
2226
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
2227
|
+
const cachedCustomerRef = request.headers.get("x-solvapay-customer-ref");
|
|
2228
|
+
if (cachedCustomerRef) {
|
|
2229
|
+
try {
|
|
2230
|
+
const customer = await solvaPay.getCustomer({ customerRef: cachedCustomerRef });
|
|
2231
|
+
if (customer && customer.customerRef) {
|
|
2232
|
+
if (customer.externalRef && customer.externalRef === userId) {
|
|
2233
|
+
const filteredPurchases = (customer.purchases || []).filter(
|
|
2234
|
+
(p) => p.status === "active"
|
|
2235
|
+
);
|
|
2236
|
+
return {
|
|
2237
|
+
customerRef: customer.customerRef,
|
|
2238
|
+
email: customer.email,
|
|
2239
|
+
name: customer.name,
|
|
2240
|
+
purchases: filteredPurchases
|
|
2241
|
+
};
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
} catch {
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
try {
|
|
2248
|
+
const customerRef = await solvaPay.ensureCustomer(userId, userId, {
|
|
2249
|
+
email: email || void 0,
|
|
2250
|
+
name: name || void 0
|
|
2251
|
+
});
|
|
2252
|
+
const customer = await solvaPay.getCustomer({ customerRef });
|
|
2253
|
+
const filteredPurchases = (customer.purchases || []).filter((p) => p.status === "active");
|
|
2254
|
+
return {
|
|
2255
|
+
customerRef: customer.customerRef || userId,
|
|
2256
|
+
email: customer.email,
|
|
2257
|
+
name: customer.name,
|
|
2258
|
+
purchases: filteredPurchases
|
|
2259
|
+
};
|
|
2260
|
+
} catch {
|
|
2261
|
+
return {
|
|
2262
|
+
customerRef: userId,
|
|
2263
|
+
purchases: []
|
|
2264
|
+
};
|
|
2265
|
+
}
|
|
2266
|
+
} catch (error) {
|
|
2267
|
+
return handleRouteError(error, "Check purchase", "Failed to check purchase");
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
// src/helpers/usage.ts
|
|
2272
|
+
async function trackUsageCore(request, body, options = {}) {
|
|
2273
|
+
try {
|
|
2274
|
+
const userResult = await getAuthenticatedUserCore(request);
|
|
2275
|
+
if (isErrorResult(userResult)) {
|
|
2276
|
+
return userResult;
|
|
2277
|
+
}
|
|
2278
|
+
const { userId, email, name } = userResult;
|
|
2279
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
2280
|
+
const customerRef = await solvaPay.ensureCustomer(userId, userId, {
|
|
2281
|
+
email: email || void 0,
|
|
2282
|
+
name: name || void 0
|
|
2283
|
+
});
|
|
2284
|
+
await solvaPay.trackUsage({
|
|
2285
|
+
customerRef,
|
|
2286
|
+
actionType: body.actionType,
|
|
2287
|
+
units: body.units,
|
|
2288
|
+
productRef: body.productRef,
|
|
2289
|
+
description: body.description,
|
|
2290
|
+
metadata: body.metadata
|
|
2291
|
+
});
|
|
2292
|
+
return { success: true };
|
|
2293
|
+
} catch (error) {
|
|
2294
|
+
return handleRouteError(error, "Track usage", "Track usage failed");
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
|
|
1877
2298
|
// src/edge.ts
|
|
1878
2299
|
function timingSafeEqual(a, b) {
|
|
1879
2300
|
if (a.length !== b.length) return false;
|
|
@@ -1928,18 +2349,25 @@ async function verifyWebhook({
|
|
|
1928
2349
|
}
|
|
1929
2350
|
export {
|
|
1930
2351
|
PaywallError,
|
|
2352
|
+
activatePlanCore,
|
|
1931
2353
|
cancelPurchaseCore,
|
|
2354
|
+
checkPurchaseCore,
|
|
1932
2355
|
createCheckoutSessionCore,
|
|
1933
2356
|
createCustomerSessionCore,
|
|
1934
2357
|
createPaymentIntentCore,
|
|
1935
2358
|
createSolvaPay,
|
|
1936
2359
|
createSolvaPayClient,
|
|
2360
|
+
createTopupPaymentIntentCore,
|
|
1937
2361
|
getAuthenticatedUserCore,
|
|
2362
|
+
getCustomerBalanceCore,
|
|
1938
2363
|
handleRouteError,
|
|
1939
2364
|
isErrorResult,
|
|
1940
2365
|
listPlansCore,
|
|
2366
|
+
paywallErrorToClientPayload,
|
|
1941
2367
|
processPaymentIntentCore,
|
|
2368
|
+
reactivatePurchaseCore,
|
|
1942
2369
|
syncCustomerCore,
|
|
2370
|
+
trackUsageCore,
|
|
1943
2371
|
verifyWebhook,
|
|
1944
2372
|
withRetry
|
|
1945
2373
|
};
|