@solvapay/server 1.0.6 → 1.0.7
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 +945 -1746
- package/dist/edge.js +229 -35
- package/dist/index.cjs +352 -35
- package/dist/index.d.cts +980 -1734
- package/dist/index.d.ts +980 -1734
- package/dist/index.js +347 -35
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -39,12 +39,10 @@ function createSolvaPayClient(opts) {
|
|
|
39
39
|
// POST: /v1/sdk/usages
|
|
40
40
|
async trackUsage(params) {
|
|
41
41
|
const url = `${base}/v1/sdk/usages`;
|
|
42
|
-
const { customerRef, ...rest } = params;
|
|
43
|
-
const body = { ...rest, customerId: customerRef };
|
|
44
42
|
const res = await fetch(url, {
|
|
45
43
|
method: "POST",
|
|
46
44
|
headers,
|
|
47
|
-
body: JSON.stringify(
|
|
45
|
+
body: JSON.stringify(params)
|
|
48
46
|
});
|
|
49
47
|
if (!res.ok) {
|
|
50
48
|
const error = await res.text();
|
|
@@ -289,7 +287,7 @@ function createSolvaPayClient(opts) {
|
|
|
289
287
|
body: JSON.stringify({
|
|
290
288
|
productRef: params.productRef,
|
|
291
289
|
planRef: params.planRef,
|
|
292
|
-
|
|
290
|
+
customerRef: params.customerRef
|
|
293
291
|
})
|
|
294
292
|
});
|
|
295
293
|
if (!res.ok) {
|
|
@@ -297,8 +295,32 @@ function createSolvaPayClient(opts) {
|
|
|
297
295
|
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
298
296
|
throw new SolvaPayError(`Create payment intent failed (${res.status}): ${error}`);
|
|
299
297
|
}
|
|
300
|
-
|
|
301
|
-
|
|
298
|
+
return await res.json();
|
|
299
|
+
},
|
|
300
|
+
// POST: /v1/sdk/payment-intents (purpose: credit_topup)
|
|
301
|
+
async createTopupPaymentIntent(params) {
|
|
302
|
+
const idempotencyKey = params.idempotencyKey || `topup-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
303
|
+
const url = `${base}/v1/sdk/payment-intents`;
|
|
304
|
+
const res = await fetch(url, {
|
|
305
|
+
method: "POST",
|
|
306
|
+
headers: {
|
|
307
|
+
...headers,
|
|
308
|
+
"Idempotency-Key": idempotencyKey
|
|
309
|
+
},
|
|
310
|
+
body: JSON.stringify({
|
|
311
|
+
customerRef: params.customerRef,
|
|
312
|
+
purpose: "credit_topup",
|
|
313
|
+
amount: params.amount,
|
|
314
|
+
currency: params.currency,
|
|
315
|
+
description: params.description
|
|
316
|
+
})
|
|
317
|
+
});
|
|
318
|
+
if (!res.ok) {
|
|
319
|
+
const error = await res.text();
|
|
320
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
321
|
+
throw new SolvaPayError(`Create topup payment intent failed (${res.status}): ${error}`);
|
|
322
|
+
}
|
|
323
|
+
return await res.json();
|
|
302
324
|
},
|
|
303
325
|
// POST: /v1/sdk/payment-intents/{paymentIntentId}/process
|
|
304
326
|
async processPaymentIntent(params) {
|
|
@@ -375,6 +397,54 @@ function createSolvaPayClient(opts) {
|
|
|
375
397
|
}
|
|
376
398
|
return result;
|
|
377
399
|
},
|
|
400
|
+
// POST: /v1/sdk/purchases/{purchaseRef}/reactivate
|
|
401
|
+
async reactivatePurchase(params) {
|
|
402
|
+
const url = `${base}/v1/sdk/purchases/${params.purchaseRef}/reactivate`;
|
|
403
|
+
const res = await fetch(url, {
|
|
404
|
+
method: "POST",
|
|
405
|
+
headers
|
|
406
|
+
});
|
|
407
|
+
if (!res.ok) {
|
|
408
|
+
const error = await res.text();
|
|
409
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
410
|
+
if (res.status === 404) {
|
|
411
|
+
throw new SolvaPayError(`Purchase not found: ${error}`);
|
|
412
|
+
}
|
|
413
|
+
if (res.status === 400) {
|
|
414
|
+
throw new SolvaPayError(
|
|
415
|
+
`Purchase cannot be reactivated: ${error}`
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
throw new SolvaPayError(`Reactivate purchase failed (${res.status}): ${error}`);
|
|
419
|
+
}
|
|
420
|
+
const responseText = await res.text();
|
|
421
|
+
let responseData;
|
|
422
|
+
try {
|
|
423
|
+
responseData = JSON.parse(responseText);
|
|
424
|
+
} catch (parseError) {
|
|
425
|
+
log(`\u274C Failed to parse response as JSON: ${parseError}`);
|
|
426
|
+
throw new SolvaPayError(
|
|
427
|
+
`Invalid JSON response from reactivate purchase endpoint: ${responseText.substring(0, 200)}`
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
if (!responseData || typeof responseData !== "object") {
|
|
431
|
+
log(`\u274C Invalid response structure: ${JSON.stringify(responseData)}`);
|
|
432
|
+
throw new SolvaPayError(`Invalid response structure from reactivate purchase endpoint`);
|
|
433
|
+
}
|
|
434
|
+
let result;
|
|
435
|
+
if (responseData.purchase && typeof responseData.purchase === "object") {
|
|
436
|
+
result = responseData.purchase;
|
|
437
|
+
} else if (responseData.reference) {
|
|
438
|
+
result = responseData;
|
|
439
|
+
} else {
|
|
440
|
+
result = responseData.purchase || responseData;
|
|
441
|
+
}
|
|
442
|
+
if (!result || typeof result !== "object") {
|
|
443
|
+
log(`\u274C Invalid purchase data in response. Full response:`, responseData);
|
|
444
|
+
throw new SolvaPayError(`Invalid purchase data in reactivate purchase response`);
|
|
445
|
+
}
|
|
446
|
+
return result;
|
|
447
|
+
},
|
|
378
448
|
// POST: /v1/sdk/user-info
|
|
379
449
|
async getUserInfo(params) {
|
|
380
450
|
const url = `${base}/v1/sdk/user-info`;
|
|
@@ -390,6 +460,20 @@ function createSolvaPayClient(opts) {
|
|
|
390
460
|
}
|
|
391
461
|
return await res.json();
|
|
392
462
|
},
|
|
463
|
+
// GET: /v1/sdk/customers/:customerRef/balance
|
|
464
|
+
async getCustomerBalance(params) {
|
|
465
|
+
const url = `${base}/v1/sdk/customers/${params.customerRef}/balance`;
|
|
466
|
+
const res = await fetch(url, {
|
|
467
|
+
method: "GET",
|
|
468
|
+
headers
|
|
469
|
+
});
|
|
470
|
+
if (!res.ok) {
|
|
471
|
+
const error = await res.text();
|
|
472
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
473
|
+
throw new SolvaPayError(`Get customer balance failed (${res.status}): ${error}`);
|
|
474
|
+
}
|
|
475
|
+
return await res.json();
|
|
476
|
+
},
|
|
393
477
|
// POST: /v1/sdk/checkout-sessions
|
|
394
478
|
async createCheckoutSession(params) {
|
|
395
479
|
const url = `${base}/v1/sdk/checkout-sessions`;
|
|
@@ -421,6 +505,21 @@ function createSolvaPayClient(opts) {
|
|
|
421
505
|
}
|
|
422
506
|
const result = await res.json();
|
|
423
507
|
return result;
|
|
508
|
+
},
|
|
509
|
+
// POST: /v1/sdk/activate
|
|
510
|
+
async activatePlan(params) {
|
|
511
|
+
const url = `${base}/v1/sdk/activate`;
|
|
512
|
+
const res = await fetch(url, {
|
|
513
|
+
method: "POST",
|
|
514
|
+
headers,
|
|
515
|
+
body: JSON.stringify(params)
|
|
516
|
+
});
|
|
517
|
+
if (!res.ok) {
|
|
518
|
+
const error = await res.text();
|
|
519
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
520
|
+
throw new SolvaPayError(`Activate plan failed (${res.status}): ${error}`);
|
|
521
|
+
}
|
|
522
|
+
return await res.json();
|
|
424
523
|
}
|
|
425
524
|
};
|
|
426
525
|
}
|
|
@@ -576,6 +675,24 @@ var PaywallError = class extends Error {
|
|
|
576
675
|
this.name = "PaywallError";
|
|
577
676
|
}
|
|
578
677
|
};
|
|
678
|
+
function paywallErrorToClientPayload(error) {
|
|
679
|
+
const sc = error.structuredContent;
|
|
680
|
+
const base = {
|
|
681
|
+
success: false,
|
|
682
|
+
error: sc.kind === "activation_required" ? "Activation required" : "Payment required",
|
|
683
|
+
product: sc.product,
|
|
684
|
+
checkoutUrl: sc.checkoutUrl,
|
|
685
|
+
message: sc.message
|
|
686
|
+
};
|
|
687
|
+
if (sc.kind === "activation_required") {
|
|
688
|
+
base.kind = "activation_required";
|
|
689
|
+
if (sc.plans !== void 0) base.plans = sc.plans;
|
|
690
|
+
if (sc.balance !== void 0) base.balance = sc.balance;
|
|
691
|
+
if (sc.productDetails !== void 0) base.productDetails = sc.productDetails;
|
|
692
|
+
if (sc.confirmationUrl !== void 0) base.confirmationUrl = sc.confirmationUrl;
|
|
693
|
+
}
|
|
694
|
+
return base;
|
|
695
|
+
}
|
|
579
696
|
var sharedCustomerLookupDeduplicator = createRequestDeduplicator({
|
|
580
697
|
cacheTTL: 6e4,
|
|
581
698
|
// Cache results for 60 seconds (reduces API calls significantly)
|
|
@@ -614,8 +731,6 @@ var SolvaPayPaywall = class {
|
|
|
614
731
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
615
732
|
async protect(handler, metadata = {}, getCustomerRef) {
|
|
616
733
|
const product = this.resolveProduct(metadata);
|
|
617
|
-
const configuredPlanRef = metadata.plan?.trim();
|
|
618
|
-
const usagePlanRef = configuredPlanRef || "unspecified";
|
|
619
734
|
const usageType = metadata.usageType || "requests";
|
|
620
735
|
return async (args) => {
|
|
621
736
|
const startTime = Date.now();
|
|
@@ -629,12 +744,13 @@ var SolvaPayPaywall = class {
|
|
|
629
744
|
}
|
|
630
745
|
let resolvedMeterName;
|
|
631
746
|
try {
|
|
632
|
-
const limitsCacheKey = `${backendCustomerRef}:${product}:${
|
|
747
|
+
const limitsCacheKey = `${backendCustomerRef}:${product}:${usageType}`;
|
|
633
748
|
const cachedLimits = this.limitsCache.get(limitsCacheKey);
|
|
634
749
|
const now = Date.now();
|
|
635
750
|
let withinLimits;
|
|
636
751
|
let remaining;
|
|
637
752
|
let checkoutUrl;
|
|
753
|
+
let lastLimitsCheck;
|
|
638
754
|
const hasFreshCachedLimits = cachedLimits && now - cachedLimits.timestamp < this.limitsCacheTTL;
|
|
639
755
|
if (hasFreshCachedLimits) {
|
|
640
756
|
checkoutUrl = cachedLimits.checkoutUrl;
|
|
@@ -658,9 +774,9 @@ var SolvaPayPaywall = class {
|
|
|
658
774
|
const limitsCheck = await this.apiClient.checkLimits({
|
|
659
775
|
customerRef: backendCustomerRef,
|
|
660
776
|
productRef: product,
|
|
661
|
-
...configuredPlanRef ? { planRef: configuredPlanRef } : {},
|
|
662
777
|
meterName: usageType
|
|
663
778
|
});
|
|
779
|
+
lastLimitsCheck = limitsCheck;
|
|
664
780
|
withinLimits = limitsCheck.withinLimits;
|
|
665
781
|
remaining = limitsCheck.remaining;
|
|
666
782
|
checkoutUrl = limitsCheck.checkoutUrl;
|
|
@@ -683,12 +799,25 @@ var SolvaPayPaywall = class {
|
|
|
683
799
|
this.trackUsage(
|
|
684
800
|
backendCustomerRef,
|
|
685
801
|
product,
|
|
686
|
-
usagePlanRef,
|
|
687
802
|
resolvedMeterName || usageType,
|
|
688
803
|
"paywall",
|
|
689
804
|
requestId,
|
|
690
805
|
latencyMs2
|
|
691
806
|
);
|
|
807
|
+
if (lastLimitsCheck?.activationRequired) {
|
|
808
|
+
const confirmationUrl = lastLimitsCheck.confirmationUrl;
|
|
809
|
+
const payCheckoutUrl = confirmationUrl || lastLimitsCheck.checkoutUrl || checkoutUrl || "";
|
|
810
|
+
throw new PaywallError("Activation required", {
|
|
811
|
+
kind: "activation_required",
|
|
812
|
+
product,
|
|
813
|
+
message: "Product activation is required before this tool can be used.",
|
|
814
|
+
checkoutUrl: payCheckoutUrl,
|
|
815
|
+
confirmationUrl,
|
|
816
|
+
plans: lastLimitsCheck.plans,
|
|
817
|
+
balance: lastLimitsCheck.balance,
|
|
818
|
+
productDetails: lastLimitsCheck.product
|
|
819
|
+
});
|
|
820
|
+
}
|
|
692
821
|
throw new PaywallError("Payment required", {
|
|
693
822
|
kind: "payment_required",
|
|
694
823
|
product,
|
|
@@ -701,7 +830,6 @@ var SolvaPayPaywall = class {
|
|
|
701
830
|
this.trackUsage(
|
|
702
831
|
backendCustomerRef,
|
|
703
832
|
product,
|
|
704
|
-
usagePlanRef,
|
|
705
833
|
resolvedMeterName || usageType,
|
|
706
834
|
"success",
|
|
707
835
|
requestId,
|
|
@@ -720,7 +848,6 @@ var SolvaPayPaywall = class {
|
|
|
720
848
|
this.trackUsage(
|
|
721
849
|
backendCustomerRef,
|
|
722
850
|
product,
|
|
723
|
-
usagePlanRef,
|
|
724
851
|
resolvedMeterName || usageType,
|
|
725
852
|
"fail",
|
|
726
853
|
requestId,
|
|
@@ -791,7 +918,8 @@ var SolvaPayPaywall = class {
|
|
|
791
918
|
this.customerCreationAttempts.add(customerRef);
|
|
792
919
|
try {
|
|
793
920
|
const createParams = {
|
|
794
|
-
email: options?.email || `${customerRef}-${Date.now()}@auto-created.local
|
|
921
|
+
email: options?.email || `${customerRef}-${Date.now()}@auto-created.local`,
|
|
922
|
+
metadata: {}
|
|
795
923
|
};
|
|
796
924
|
if (options?.name) {
|
|
797
925
|
createParams.name = options.name;
|
|
@@ -815,7 +943,10 @@ var SolvaPayPaywall = class {
|
|
|
815
943
|
return searchResult.customerRef;
|
|
816
944
|
}
|
|
817
945
|
} catch (lookupError) {
|
|
818
|
-
this.log(
|
|
946
|
+
this.log(
|
|
947
|
+
`\u26A0\uFE0F Failed to lookup existing customer by externalRef after 409:`,
|
|
948
|
+
lookupError instanceof Error ? lookupError.message : lookupError
|
|
949
|
+
);
|
|
819
950
|
}
|
|
820
951
|
}
|
|
821
952
|
const isEmailConflict = errorMessage.includes("email") || errorMessage.includes("identifier email");
|
|
@@ -838,7 +969,8 @@ var SolvaPayPaywall = class {
|
|
|
838
969
|
try {
|
|
839
970
|
const retryParams = {
|
|
840
971
|
email: `${customerRef}-${Date.now()}@auto-created.local`,
|
|
841
|
-
externalRef
|
|
972
|
+
externalRef,
|
|
973
|
+
metadata: {}
|
|
842
974
|
};
|
|
843
975
|
if (options?.name) {
|
|
844
976
|
retryParams.name = options.name;
|
|
@@ -875,14 +1007,14 @@ var SolvaPayPaywall = class {
|
|
|
875
1007
|
}
|
|
876
1008
|
return backendRef;
|
|
877
1009
|
}
|
|
878
|
-
async trackUsage(customerRef,
|
|
1010
|
+
async trackUsage(customerRef, productRef, action, outcome, requestId, actionDuration) {
|
|
879
1011
|
await withRetry(
|
|
880
1012
|
() => this.apiClient.trackUsage({
|
|
881
1013
|
customerRef,
|
|
882
1014
|
actionType: "api_call",
|
|
883
1015
|
units: 1,
|
|
884
1016
|
outcome,
|
|
885
|
-
|
|
1017
|
+
productRef,
|
|
886
1018
|
duration: actionDuration,
|
|
887
1019
|
metadata: { action: action || "api_requests", requestId },
|
|
888
1020
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -1134,8 +1266,10 @@ var McpAdapter = class {
|
|
|
1134
1266
|
const ref = await this.options.getCustomerRef(args, extra);
|
|
1135
1267
|
return AdapterUtils.ensureCustomerRef(ref);
|
|
1136
1268
|
}
|
|
1137
|
-
const customerRefFromExtra = extra?.authInfo?.extra?.customer_ref;
|
|
1138
|
-
const
|
|
1269
|
+
const customerRefFromExtra = typeof extra?.authInfo?.extra?.customer_ref === "string" ? String(extra.authInfo.extra.customer_ref) : void 0;
|
|
1270
|
+
const customerRefFromArgs = typeof args.auth === "object" && args.auth !== null && typeof args.auth.customer_ref === "string" ? String(args.auth.customer_ref) : void 0;
|
|
1271
|
+
const directCustomerRef = typeof args.customer_ref === "string" ? args.customer_ref : void 0;
|
|
1272
|
+
const customerRef = (customerRefFromExtra || customerRefFromArgs || directCustomerRef || "anonymous").trim();
|
|
1139
1273
|
return AdapterUtils.ensureCustomerRef(customerRef);
|
|
1140
1274
|
}
|
|
1141
1275
|
formatResponse(result, _context) {
|
|
@@ -1159,17 +1293,7 @@ var McpAdapter = class {
|
|
|
1159
1293
|
content: [
|
|
1160
1294
|
{
|
|
1161
1295
|
type: "text",
|
|
1162
|
-
text: JSON.stringify(
|
|
1163
|
-
{
|
|
1164
|
-
success: false,
|
|
1165
|
-
error: "Payment required",
|
|
1166
|
-
product: error.structuredContent.product,
|
|
1167
|
-
checkoutUrl: error.structuredContent.checkoutUrl,
|
|
1168
|
-
message: error.structuredContent.message
|
|
1169
|
-
},
|
|
1170
|
-
null,
|
|
1171
|
-
2
|
|
1172
|
-
)
|
|
1296
|
+
text: JSON.stringify(paywallErrorToClientPayload(error), null, 2)
|
|
1173
1297
|
}
|
|
1174
1298
|
],
|
|
1175
1299
|
isError: true,
|
|
@@ -1456,6 +1580,12 @@ function createSolvaPay(config) {
|
|
|
1456
1580
|
}
|
|
1457
1581
|
return apiClient.createPaymentIntent(params);
|
|
1458
1582
|
},
|
|
1583
|
+
createTopupPaymentIntent(params) {
|
|
1584
|
+
if (!apiClient.createTopupPaymentIntent) {
|
|
1585
|
+
throw new SolvaPayError2("createTopupPaymentIntent is not available on this API client");
|
|
1586
|
+
}
|
|
1587
|
+
return apiClient.createTopupPaymentIntent(params);
|
|
1588
|
+
},
|
|
1459
1589
|
processPaymentIntent(params) {
|
|
1460
1590
|
if (!apiClient.processPaymentIntent) {
|
|
1461
1591
|
throw new SolvaPayError2("processPaymentIntent is not available on this API client");
|
|
@@ -1472,11 +1602,17 @@ function createSolvaPay(config) {
|
|
|
1472
1602
|
if (!apiClient.createCustomer) {
|
|
1473
1603
|
throw new SolvaPayError2("createCustomer is not available on this API client");
|
|
1474
1604
|
}
|
|
1475
|
-
return apiClient.createCustomer(params);
|
|
1605
|
+
return apiClient.createCustomer({ ...params, metadata: params.metadata ?? {} });
|
|
1476
1606
|
},
|
|
1477
1607
|
getCustomer(params) {
|
|
1478
1608
|
return apiClient.getCustomer(params);
|
|
1479
1609
|
},
|
|
1610
|
+
getCustomerBalance(params) {
|
|
1611
|
+
if (!apiClient.getCustomerBalance) {
|
|
1612
|
+
throw new SolvaPayError2("getCustomerBalance is not available on this API client");
|
|
1613
|
+
}
|
|
1614
|
+
return apiClient.getCustomerBalance(params);
|
|
1615
|
+
},
|
|
1480
1616
|
createCheckoutSession(params) {
|
|
1481
1617
|
return apiClient.createCheckoutSession({
|
|
1482
1618
|
customerRef: params.customerRef,
|
|
@@ -1487,6 +1623,12 @@ function createSolvaPay(config) {
|
|
|
1487
1623
|
createCustomerSession(params) {
|
|
1488
1624
|
return apiClient.createCustomerSession(params);
|
|
1489
1625
|
},
|
|
1626
|
+
activatePlan(params) {
|
|
1627
|
+
if (!apiClient.activatePlan) {
|
|
1628
|
+
throw new SolvaPayError2("activatePlan is not available on this API client");
|
|
1629
|
+
}
|
|
1630
|
+
return apiClient.activatePlan(params);
|
|
1631
|
+
},
|
|
1490
1632
|
bootstrapMcpProduct(params) {
|
|
1491
1633
|
if (!apiClient.bootstrapMcpProduct) {
|
|
1492
1634
|
throw new SolvaPayError2("bootstrapMcpProduct is not available on this API client");
|
|
@@ -1508,9 +1650,8 @@ function createSolvaPay(config) {
|
|
|
1508
1650
|
// Payable API for framework-specific handlers
|
|
1509
1651
|
payable(options = {}) {
|
|
1510
1652
|
const product = options.productRef || options.product || process.env.SOLVAPAY_PRODUCT || "default-product";
|
|
1511
|
-
const plan = options.planRef || options.plan;
|
|
1512
1653
|
const usageType = options.usageType || "requests";
|
|
1513
|
-
const metadata = { product,
|
|
1654
|
+
const metadata = { product, usageType };
|
|
1514
1655
|
return {
|
|
1515
1656
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1516
1657
|
http(businessLogic, adapterOptions) {
|
|
@@ -1848,6 +1989,24 @@ async function syncCustomerCore(request, options = {}) {
|
|
|
1848
1989
|
return handleRouteError(error, "Sync customer", "Failed to sync customer");
|
|
1849
1990
|
}
|
|
1850
1991
|
}
|
|
1992
|
+
async function getCustomerBalanceCore(request, options = {}) {
|
|
1993
|
+
try {
|
|
1994
|
+
const userResult = await getAuthenticatedUserCore(request);
|
|
1995
|
+
if (isErrorResult(userResult)) {
|
|
1996
|
+
return userResult;
|
|
1997
|
+
}
|
|
1998
|
+
const { userId, email, name } = userResult;
|
|
1999
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
2000
|
+
const customerRef = await solvaPay.ensureCustomer(userId, userId, {
|
|
2001
|
+
email: email || void 0,
|
|
2002
|
+
name: name || void 0
|
|
2003
|
+
});
|
|
2004
|
+
const result = await solvaPay.getCustomerBalance({ customerRef });
|
|
2005
|
+
return result;
|
|
2006
|
+
} catch (error) {
|
|
2007
|
+
return handleRouteError(error, "Get customer credits", "Failed to get customer credits");
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
1851
2010
|
|
|
1852
2011
|
// src/helpers/payment.ts
|
|
1853
2012
|
async function createPaymentIntentCore(request, body, options = {}) {
|
|
@@ -1874,7 +2033,7 @@ async function createPaymentIntentCore(request, body, options = {}) {
|
|
|
1874
2033
|
customerRef
|
|
1875
2034
|
});
|
|
1876
2035
|
return {
|
|
1877
|
-
|
|
2036
|
+
processorPaymentId: paymentIntent.processorPaymentId,
|
|
1878
2037
|
clientSecret: paymentIntent.clientSecret,
|
|
1879
2038
|
publishableKey: paymentIntent.publishableKey,
|
|
1880
2039
|
accountId: paymentIntent.accountId,
|
|
@@ -1884,6 +2043,57 @@ async function createPaymentIntentCore(request, body, options = {}) {
|
|
|
1884
2043
|
return handleRouteError(error, "Create payment intent", "Payment intent creation failed");
|
|
1885
2044
|
}
|
|
1886
2045
|
}
|
|
2046
|
+
async function createTopupPaymentIntentCore(request, body, options = {}) {
|
|
2047
|
+
try {
|
|
2048
|
+
if (!body.amount || body.amount <= 0) {
|
|
2049
|
+
return {
|
|
2050
|
+
error: "Missing or invalid amount: must be a positive number",
|
|
2051
|
+
status: 400
|
|
2052
|
+
};
|
|
2053
|
+
}
|
|
2054
|
+
if (!body.currency) {
|
|
2055
|
+
return {
|
|
2056
|
+
error: "Missing required parameter: currency",
|
|
2057
|
+
status: 400
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
if (body.currency !== body.currency.toUpperCase()) {
|
|
2061
|
+
return {
|
|
2062
|
+
error: `Invalid currency "${body.currency}": must be an uppercase ISO 4217 code (e.g. "USD", "EUR")`,
|
|
2063
|
+
status: 400
|
|
2064
|
+
};
|
|
2065
|
+
}
|
|
2066
|
+
const customerResult = await syncCustomerCore(request, {
|
|
2067
|
+
solvaPay: options.solvaPay,
|
|
2068
|
+
includeEmail: options.includeEmail,
|
|
2069
|
+
includeName: options.includeName
|
|
2070
|
+
});
|
|
2071
|
+
if (isErrorResult(customerResult)) {
|
|
2072
|
+
return customerResult;
|
|
2073
|
+
}
|
|
2074
|
+
const customerRef = customerResult;
|
|
2075
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
2076
|
+
const paymentIntent = await solvaPay.createTopupPaymentIntent({
|
|
2077
|
+
customerRef,
|
|
2078
|
+
amount: body.amount,
|
|
2079
|
+
currency: body.currency,
|
|
2080
|
+
description: body.description
|
|
2081
|
+
});
|
|
2082
|
+
return {
|
|
2083
|
+
processorPaymentId: paymentIntent.processorPaymentId,
|
|
2084
|
+
clientSecret: paymentIntent.clientSecret,
|
|
2085
|
+
publishableKey: paymentIntent.publishableKey,
|
|
2086
|
+
accountId: paymentIntent.accountId,
|
|
2087
|
+
customerRef
|
|
2088
|
+
};
|
|
2089
|
+
} catch (error) {
|
|
2090
|
+
return handleRouteError(
|
|
2091
|
+
error,
|
|
2092
|
+
"Create topup payment intent",
|
|
2093
|
+
"Topup payment intent creation failed"
|
|
2094
|
+
);
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
1887
2097
|
async function processPaymentIntentCore(request, body, options = {}) {
|
|
1888
2098
|
try {
|
|
1889
2099
|
if (!body.paymentIntentId || !body.productRef) {
|
|
@@ -2046,6 +2256,103 @@ async function cancelPurchaseCore(request, body, options = {}) {
|
|
|
2046
2256
|
return handleRouteError(error, "Cancel purchase", "Failed to cancel purchase");
|
|
2047
2257
|
}
|
|
2048
2258
|
}
|
|
2259
|
+
async function reactivatePurchaseCore(request, body, options = {}) {
|
|
2260
|
+
try {
|
|
2261
|
+
if (!body.purchaseRef) {
|
|
2262
|
+
return {
|
|
2263
|
+
error: "Missing required parameter: purchaseRef is required",
|
|
2264
|
+
status: 400
|
|
2265
|
+
};
|
|
2266
|
+
}
|
|
2267
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
2268
|
+
if (!solvaPay.apiClient.reactivatePurchase) {
|
|
2269
|
+
return {
|
|
2270
|
+
error: "Reactivate purchase method not available on SDK client",
|
|
2271
|
+
status: 500
|
|
2272
|
+
};
|
|
2273
|
+
}
|
|
2274
|
+
let reactivatedPurchase = await solvaPay.apiClient.reactivatePurchase({
|
|
2275
|
+
purchaseRef: body.purchaseRef
|
|
2276
|
+
});
|
|
2277
|
+
if (!reactivatedPurchase || typeof reactivatedPurchase !== "object") {
|
|
2278
|
+
return {
|
|
2279
|
+
error: "Invalid response from reactivate purchase endpoint",
|
|
2280
|
+
status: 500
|
|
2281
|
+
};
|
|
2282
|
+
}
|
|
2283
|
+
const responseObj = reactivatedPurchase;
|
|
2284
|
+
if (responseObj.purchase && typeof responseObj.purchase === "object") {
|
|
2285
|
+
reactivatedPurchase = responseObj.purchase;
|
|
2286
|
+
}
|
|
2287
|
+
if (!reactivatedPurchase.reference) {
|
|
2288
|
+
return {
|
|
2289
|
+
error: "Reactivate purchase response missing required fields",
|
|
2290
|
+
status: 500
|
|
2291
|
+
};
|
|
2292
|
+
}
|
|
2293
|
+
if (reactivatedPurchase.cancelledAt) {
|
|
2294
|
+
return {
|
|
2295
|
+
error: `Purchase reactivation failed: cancelledAt is still set`,
|
|
2296
|
+
status: 500
|
|
2297
|
+
};
|
|
2298
|
+
}
|
|
2299
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2300
|
+
return reactivatedPurchase;
|
|
2301
|
+
} catch (error) {
|
|
2302
|
+
if (error instanceof SolvaPayError4) {
|
|
2303
|
+
const errorMessage = error.message;
|
|
2304
|
+
if (errorMessage.includes("not found")) {
|
|
2305
|
+
return {
|
|
2306
|
+
error: "Purchase not found",
|
|
2307
|
+
status: 404,
|
|
2308
|
+
details: errorMessage
|
|
2309
|
+
};
|
|
2310
|
+
}
|
|
2311
|
+
if (errorMessage.includes("cannot be reactivated") || errorMessage.includes("not pending cancellation") || errorMessage.includes("already been fully cancelled") || errorMessage.includes("already ended")) {
|
|
2312
|
+
return {
|
|
2313
|
+
error: "Purchase cannot be reactivated",
|
|
2314
|
+
status: 400,
|
|
2315
|
+
details: errorMessage
|
|
2316
|
+
};
|
|
2317
|
+
}
|
|
2318
|
+
return {
|
|
2319
|
+
error: errorMessage,
|
|
2320
|
+
status: 500,
|
|
2321
|
+
details: errorMessage
|
|
2322
|
+
};
|
|
2323
|
+
}
|
|
2324
|
+
return handleRouteError(error, "Reactivate purchase", "Failed to reactivate purchase");
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2328
|
+
// src/helpers/activation.ts
|
|
2329
|
+
async function activatePlanCore(request, body, options = {}) {
|
|
2330
|
+
try {
|
|
2331
|
+
if (!body.productRef || !body.planRef) {
|
|
2332
|
+
return {
|
|
2333
|
+
error: "Missing required parameters: productRef and planRef are required",
|
|
2334
|
+
status: 400
|
|
2335
|
+
};
|
|
2336
|
+
}
|
|
2337
|
+
const customerResult = await syncCustomerCore(request, {
|
|
2338
|
+
solvaPay: options.solvaPay,
|
|
2339
|
+
includeEmail: options.includeEmail,
|
|
2340
|
+
includeName: options.includeName
|
|
2341
|
+
});
|
|
2342
|
+
if (isErrorResult(customerResult)) {
|
|
2343
|
+
return customerResult;
|
|
2344
|
+
}
|
|
2345
|
+
const customerRef = customerResult;
|
|
2346
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
2347
|
+
return await solvaPay.activatePlan({
|
|
2348
|
+
customerRef,
|
|
2349
|
+
productRef: body.productRef,
|
|
2350
|
+
planRef: body.planRef
|
|
2351
|
+
});
|
|
2352
|
+
} catch (error) {
|
|
2353
|
+
return handleRouteError(error, "Activate plan", "Plan activation failed");
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2049
2356
|
|
|
2050
2357
|
// src/helpers/plans.ts
|
|
2051
2358
|
import { getSolvaPayConfig as getSolvaPayConfig2 } from "@solvapay/core";
|
|
@@ -2129,6 +2436,7 @@ export {
|
|
|
2129
2436
|
McpBearerAuthError,
|
|
2130
2437
|
PaywallError,
|
|
2131
2438
|
VIRTUAL_TOOL_DEFINITIONS,
|
|
2439
|
+
activatePlanCore,
|
|
2132
2440
|
buildAuthInfoFromBearer,
|
|
2133
2441
|
cancelPurchaseCore,
|
|
2134
2442
|
createCheckoutSessionCore,
|
|
@@ -2137,10 +2445,12 @@ export {
|
|
|
2137
2445
|
createPaymentIntentCore,
|
|
2138
2446
|
createSolvaPay,
|
|
2139
2447
|
createSolvaPayClient,
|
|
2448
|
+
createTopupPaymentIntentCore,
|
|
2140
2449
|
createVirtualTools,
|
|
2141
2450
|
decodeJwtPayload,
|
|
2142
2451
|
extractBearerToken,
|
|
2143
2452
|
getAuthenticatedUserCore,
|
|
2453
|
+
getCustomerBalanceCore,
|
|
2144
2454
|
getCustomerRefFromBearerAuthHeader,
|
|
2145
2455
|
getCustomerRefFromJwtPayload,
|
|
2146
2456
|
getOAuthAuthorizationServerResponse,
|
|
@@ -2149,7 +2459,9 @@ export {
|
|
|
2149
2459
|
isErrorResult,
|
|
2150
2460
|
jsonSchemaToZodRawShape,
|
|
2151
2461
|
listPlansCore,
|
|
2462
|
+
paywallErrorToClientPayload,
|
|
2152
2463
|
processPaymentIntentCore,
|
|
2464
|
+
reactivatePurchaseCore,
|
|
2153
2465
|
registerVirtualToolsMcpImpl,
|
|
2154
2466
|
syncCustomerCore,
|
|
2155
2467
|
verifyWebhook,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solvapay/server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -37,12 +37,12 @@
|
|
|
37
37
|
},
|
|
38
38
|
"sideEffects": false,
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@solvapay/core": "1.0.
|
|
40
|
+
"@solvapay/core": "1.0.7"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
44
44
|
"zod": "^3.25.0 || ^4.0.0",
|
|
45
|
-
"@solvapay/auth": "1.0.
|
|
45
|
+
"@solvapay/auth": "1.0.7"
|
|
46
46
|
},
|
|
47
47
|
"peerDependenciesMeta": {
|
|
48
48
|
"@modelcontextprotocol/sdk": {
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"tsx": "^4.21.0",
|
|
61
61
|
"typescript": "^5.9.3",
|
|
62
62
|
"vitest": "^4.1.2",
|
|
63
|
-
"@solvapay/auth": "1.0.
|
|
63
|
+
"@solvapay/auth": "1.0.7",
|
|
64
64
|
"@solvapay/demo-services": "0.0.0",
|
|
65
65
|
"@solvapay/test-utils": "^0.0.0"
|
|
66
66
|
},
|