@solvapay/server 1.0.6 → 1.0.9-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 +1035 -1749
- package/dist/edge.js +432 -35
- package/dist/index.cjs +441 -36
- package/dist/index.d.cts +1036 -1747
- package/dist/index.d.ts +1036 -1747
- package/dist/index.js +434 -36
- package/package.json +5 -5
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();
|
|
@@ -288,7 +286,7 @@ function createSolvaPayClient(opts) {
|
|
|
288
286
|
body: JSON.stringify({
|
|
289
287
|
productRef: params.productRef,
|
|
290
288
|
planRef: params.planRef,
|
|
291
|
-
|
|
289
|
+
customerRef: params.customerRef
|
|
292
290
|
})
|
|
293
291
|
});
|
|
294
292
|
if (!res.ok) {
|
|
@@ -296,8 +294,32 @@ function createSolvaPayClient(opts) {
|
|
|
296
294
|
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
297
295
|
throw new SolvaPayError(`Create payment intent failed (${res.status}): ${error}`);
|
|
298
296
|
}
|
|
299
|
-
|
|
300
|
-
|
|
297
|
+
return await res.json();
|
|
298
|
+
},
|
|
299
|
+
// POST: /v1/sdk/payment-intents (purpose: credit_topup)
|
|
300
|
+
async createTopupPaymentIntent(params) {
|
|
301
|
+
const idempotencyKey = params.idempotencyKey || `topup-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
302
|
+
const url = `${base}/v1/sdk/payment-intents`;
|
|
303
|
+
const res = await fetch(url, {
|
|
304
|
+
method: "POST",
|
|
305
|
+
headers: {
|
|
306
|
+
...headers,
|
|
307
|
+
"Idempotency-Key": idempotencyKey
|
|
308
|
+
},
|
|
309
|
+
body: JSON.stringify({
|
|
310
|
+
customerRef: params.customerRef,
|
|
311
|
+
purpose: "credit_topup",
|
|
312
|
+
amount: params.amount,
|
|
313
|
+
currency: params.currency,
|
|
314
|
+
description: params.description
|
|
315
|
+
})
|
|
316
|
+
});
|
|
317
|
+
if (!res.ok) {
|
|
318
|
+
const error = await res.text();
|
|
319
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
320
|
+
throw new SolvaPayError(`Create topup payment intent failed (${res.status}): ${error}`);
|
|
321
|
+
}
|
|
322
|
+
return await res.json();
|
|
301
323
|
},
|
|
302
324
|
// POST: /v1/sdk/payment-intents/{paymentIntentId}/process
|
|
303
325
|
async processPaymentIntent(params) {
|
|
@@ -374,6 +396,54 @@ function createSolvaPayClient(opts) {
|
|
|
374
396
|
}
|
|
375
397
|
return result;
|
|
376
398
|
},
|
|
399
|
+
// POST: /v1/sdk/purchases/{purchaseRef}/reactivate
|
|
400
|
+
async reactivatePurchase(params) {
|
|
401
|
+
const url = `${base}/v1/sdk/purchases/${params.purchaseRef}/reactivate`;
|
|
402
|
+
const res = await fetch(url, {
|
|
403
|
+
method: "POST",
|
|
404
|
+
headers
|
|
405
|
+
});
|
|
406
|
+
if (!res.ok) {
|
|
407
|
+
const error = await res.text();
|
|
408
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
409
|
+
if (res.status === 404) {
|
|
410
|
+
throw new SolvaPayError(`Purchase not found: ${error}`);
|
|
411
|
+
}
|
|
412
|
+
if (res.status === 400) {
|
|
413
|
+
throw new SolvaPayError(
|
|
414
|
+
`Purchase cannot be reactivated: ${error}`
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
throw new SolvaPayError(`Reactivate purchase failed (${res.status}): ${error}`);
|
|
418
|
+
}
|
|
419
|
+
const responseText = await res.text();
|
|
420
|
+
let responseData;
|
|
421
|
+
try {
|
|
422
|
+
responseData = JSON.parse(responseText);
|
|
423
|
+
} catch (parseError) {
|
|
424
|
+
log(`\u274C Failed to parse response as JSON: ${parseError}`);
|
|
425
|
+
throw new SolvaPayError(
|
|
426
|
+
`Invalid JSON response from reactivate purchase endpoint: ${responseText.substring(0, 200)}`
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
if (!responseData || typeof responseData !== "object") {
|
|
430
|
+
log(`\u274C Invalid response structure: ${JSON.stringify(responseData)}`);
|
|
431
|
+
throw new SolvaPayError(`Invalid response structure from reactivate purchase endpoint`);
|
|
432
|
+
}
|
|
433
|
+
let result;
|
|
434
|
+
if (responseData.purchase && typeof responseData.purchase === "object") {
|
|
435
|
+
result = responseData.purchase;
|
|
436
|
+
} else if (responseData.reference) {
|
|
437
|
+
result = responseData;
|
|
438
|
+
} else {
|
|
439
|
+
result = responseData.purchase || responseData;
|
|
440
|
+
}
|
|
441
|
+
if (!result || typeof result !== "object") {
|
|
442
|
+
log(`\u274C Invalid purchase data in response. Full response:`, responseData);
|
|
443
|
+
throw new SolvaPayError(`Invalid purchase data in reactivate purchase response`);
|
|
444
|
+
}
|
|
445
|
+
return result;
|
|
446
|
+
},
|
|
377
447
|
// POST: /v1/sdk/user-info
|
|
378
448
|
async getUserInfo(params) {
|
|
379
449
|
const url = `${base}/v1/sdk/user-info`;
|
|
@@ -389,6 +459,20 @@ function createSolvaPayClient(opts) {
|
|
|
389
459
|
}
|
|
390
460
|
return await res.json();
|
|
391
461
|
},
|
|
462
|
+
// GET: /v1/sdk/customers/:customerRef/balance
|
|
463
|
+
async getCustomerBalance(params) {
|
|
464
|
+
const url = `${base}/v1/sdk/customers/${params.customerRef}/balance`;
|
|
465
|
+
const res = await fetch(url, {
|
|
466
|
+
method: "GET",
|
|
467
|
+
headers
|
|
468
|
+
});
|
|
469
|
+
if (!res.ok) {
|
|
470
|
+
const error = await res.text();
|
|
471
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
472
|
+
throw new SolvaPayError(`Get customer balance failed (${res.status}): ${error}`);
|
|
473
|
+
}
|
|
474
|
+
return await res.json();
|
|
475
|
+
},
|
|
392
476
|
// POST: /v1/sdk/checkout-sessions
|
|
393
477
|
async createCheckoutSession(params) {
|
|
394
478
|
const url = `${base}/v1/sdk/checkout-sessions`;
|
|
@@ -420,6 +504,21 @@ function createSolvaPayClient(opts) {
|
|
|
420
504
|
}
|
|
421
505
|
const result = await res.json();
|
|
422
506
|
return result;
|
|
507
|
+
},
|
|
508
|
+
// POST: /v1/sdk/activate
|
|
509
|
+
async activatePlan(params) {
|
|
510
|
+
const url = `${base}/v1/sdk/activate`;
|
|
511
|
+
const res = await fetch(url, {
|
|
512
|
+
method: "POST",
|
|
513
|
+
headers,
|
|
514
|
+
body: JSON.stringify(params)
|
|
515
|
+
});
|
|
516
|
+
if (!res.ok) {
|
|
517
|
+
const error = await res.text();
|
|
518
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
519
|
+
throw new SolvaPayError(`Activate plan failed (${res.status}): ${error}`);
|
|
520
|
+
}
|
|
521
|
+
return await res.json();
|
|
423
522
|
}
|
|
424
523
|
};
|
|
425
524
|
}
|
|
@@ -575,6 +674,24 @@ var PaywallError = class extends Error {
|
|
|
575
674
|
this.name = "PaywallError";
|
|
576
675
|
}
|
|
577
676
|
};
|
|
677
|
+
function paywallErrorToClientPayload(error) {
|
|
678
|
+
const sc = error.structuredContent;
|
|
679
|
+
const base = {
|
|
680
|
+
success: false,
|
|
681
|
+
error: sc.kind === "activation_required" ? "Activation required" : "Payment required",
|
|
682
|
+
product: sc.product,
|
|
683
|
+
checkoutUrl: sc.checkoutUrl,
|
|
684
|
+
message: sc.message
|
|
685
|
+
};
|
|
686
|
+
if (sc.kind === "activation_required") {
|
|
687
|
+
base.kind = "activation_required";
|
|
688
|
+
if (sc.plans !== void 0) base.plans = sc.plans;
|
|
689
|
+
if (sc.balance !== void 0) base.balance = sc.balance;
|
|
690
|
+
if (sc.productDetails !== void 0) base.productDetails = sc.productDetails;
|
|
691
|
+
if (sc.confirmationUrl !== void 0) base.confirmationUrl = sc.confirmationUrl;
|
|
692
|
+
}
|
|
693
|
+
return base;
|
|
694
|
+
}
|
|
578
695
|
var sharedCustomerLookupDeduplicator = createRequestDeduplicator({
|
|
579
696
|
cacheTTL: 6e4,
|
|
580
697
|
// Cache results for 60 seconds (reduces API calls significantly)
|
|
@@ -613,8 +730,6 @@ var SolvaPayPaywall = class {
|
|
|
613
730
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
614
731
|
async protect(handler, metadata = {}, getCustomerRef) {
|
|
615
732
|
const product = this.resolveProduct(metadata);
|
|
616
|
-
const configuredPlanRef = metadata.plan?.trim();
|
|
617
|
-
const usagePlanRef = configuredPlanRef || "unspecified";
|
|
618
733
|
const usageType = metadata.usageType || "requests";
|
|
619
734
|
return async (args) => {
|
|
620
735
|
const startTime = Date.now();
|
|
@@ -628,12 +743,13 @@ var SolvaPayPaywall = class {
|
|
|
628
743
|
}
|
|
629
744
|
let resolvedMeterName;
|
|
630
745
|
try {
|
|
631
|
-
const limitsCacheKey = `${backendCustomerRef}:${product}:${
|
|
746
|
+
const limitsCacheKey = `${backendCustomerRef}:${product}:${usageType}`;
|
|
632
747
|
const cachedLimits = this.limitsCache.get(limitsCacheKey);
|
|
633
748
|
const now = Date.now();
|
|
634
749
|
let withinLimits;
|
|
635
750
|
let remaining;
|
|
636
751
|
let checkoutUrl;
|
|
752
|
+
let lastLimitsCheck;
|
|
637
753
|
const hasFreshCachedLimits = cachedLimits && now - cachedLimits.timestamp < this.limitsCacheTTL;
|
|
638
754
|
if (hasFreshCachedLimits) {
|
|
639
755
|
checkoutUrl = cachedLimits.checkoutUrl;
|
|
@@ -657,9 +773,9 @@ var SolvaPayPaywall = class {
|
|
|
657
773
|
const limitsCheck = await this.apiClient.checkLimits({
|
|
658
774
|
customerRef: backendCustomerRef,
|
|
659
775
|
productRef: product,
|
|
660
|
-
...configuredPlanRef ? { planRef: configuredPlanRef } : {},
|
|
661
776
|
meterName: usageType
|
|
662
777
|
});
|
|
778
|
+
lastLimitsCheck = limitsCheck;
|
|
663
779
|
withinLimits = limitsCheck.withinLimits;
|
|
664
780
|
remaining = limitsCheck.remaining;
|
|
665
781
|
checkoutUrl = limitsCheck.checkoutUrl;
|
|
@@ -682,12 +798,25 @@ var SolvaPayPaywall = class {
|
|
|
682
798
|
this.trackUsage(
|
|
683
799
|
backendCustomerRef,
|
|
684
800
|
product,
|
|
685
|
-
usagePlanRef,
|
|
686
801
|
resolvedMeterName || usageType,
|
|
687
802
|
"paywall",
|
|
688
803
|
requestId,
|
|
689
804
|
latencyMs2
|
|
690
805
|
);
|
|
806
|
+
if (lastLimitsCheck?.activationRequired) {
|
|
807
|
+
const confirmationUrl = lastLimitsCheck.confirmationUrl;
|
|
808
|
+
const payCheckoutUrl = confirmationUrl || lastLimitsCheck.checkoutUrl || checkoutUrl || "";
|
|
809
|
+
throw new PaywallError("Activation required", {
|
|
810
|
+
kind: "activation_required",
|
|
811
|
+
product,
|
|
812
|
+
message: "Product activation is required before this tool can be used.",
|
|
813
|
+
checkoutUrl: payCheckoutUrl,
|
|
814
|
+
confirmationUrl,
|
|
815
|
+
plans: lastLimitsCheck.plans,
|
|
816
|
+
balance: lastLimitsCheck.balance,
|
|
817
|
+
productDetails: lastLimitsCheck.product
|
|
818
|
+
});
|
|
819
|
+
}
|
|
691
820
|
throw new PaywallError("Payment required", {
|
|
692
821
|
kind: "payment_required",
|
|
693
822
|
product,
|
|
@@ -700,7 +829,6 @@ var SolvaPayPaywall = class {
|
|
|
700
829
|
this.trackUsage(
|
|
701
830
|
backendCustomerRef,
|
|
702
831
|
product,
|
|
703
|
-
usagePlanRef,
|
|
704
832
|
resolvedMeterName || usageType,
|
|
705
833
|
"success",
|
|
706
834
|
requestId,
|
|
@@ -719,7 +847,6 @@ var SolvaPayPaywall = class {
|
|
|
719
847
|
this.trackUsage(
|
|
720
848
|
backendCustomerRef,
|
|
721
849
|
product,
|
|
722
|
-
usagePlanRef,
|
|
723
850
|
resolvedMeterName || usageType,
|
|
724
851
|
"fail",
|
|
725
852
|
requestId,
|
|
@@ -790,7 +917,8 @@ var SolvaPayPaywall = class {
|
|
|
790
917
|
this.customerCreationAttempts.add(customerRef);
|
|
791
918
|
try {
|
|
792
919
|
const createParams = {
|
|
793
|
-
email: options?.email || `${customerRef}-${Date.now()}@auto-created.local
|
|
920
|
+
email: options?.email || `${customerRef}-${Date.now()}@auto-created.local`,
|
|
921
|
+
metadata: {}
|
|
794
922
|
};
|
|
795
923
|
if (options?.name) {
|
|
796
924
|
createParams.name = options.name;
|
|
@@ -814,7 +942,10 @@ var SolvaPayPaywall = class {
|
|
|
814
942
|
return searchResult.customerRef;
|
|
815
943
|
}
|
|
816
944
|
} catch (lookupError) {
|
|
817
|
-
this.log(
|
|
945
|
+
this.log(
|
|
946
|
+
`\u26A0\uFE0F Failed to lookup existing customer by externalRef after 409:`,
|
|
947
|
+
lookupError instanceof Error ? lookupError.message : lookupError
|
|
948
|
+
);
|
|
818
949
|
}
|
|
819
950
|
}
|
|
820
951
|
const isEmailConflict = errorMessage.includes("email") || errorMessage.includes("identifier email");
|
|
@@ -837,7 +968,8 @@ var SolvaPayPaywall = class {
|
|
|
837
968
|
try {
|
|
838
969
|
const retryParams = {
|
|
839
970
|
email: `${customerRef}-${Date.now()}@auto-created.local`,
|
|
840
|
-
externalRef
|
|
971
|
+
externalRef,
|
|
972
|
+
metadata: {}
|
|
841
973
|
};
|
|
842
974
|
if (options?.name) {
|
|
843
975
|
retryParams.name = options.name;
|
|
@@ -874,14 +1006,14 @@ var SolvaPayPaywall = class {
|
|
|
874
1006
|
}
|
|
875
1007
|
return backendRef;
|
|
876
1008
|
}
|
|
877
|
-
async trackUsage(customerRef,
|
|
1009
|
+
async trackUsage(customerRef, productRef, action, outcome, requestId, actionDuration) {
|
|
878
1010
|
await withRetry(
|
|
879
1011
|
() => this.apiClient.trackUsage({
|
|
880
1012
|
customerRef,
|
|
881
1013
|
actionType: "api_call",
|
|
882
1014
|
units: 1,
|
|
883
1015
|
outcome,
|
|
884
|
-
|
|
1016
|
+
productRef,
|
|
885
1017
|
duration: actionDuration,
|
|
886
1018
|
metadata: { action: action || "api_requests", requestId },
|
|
887
1019
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -1133,8 +1265,10 @@ var McpAdapter = class {
|
|
|
1133
1265
|
const ref = await this.options.getCustomerRef(args, extra);
|
|
1134
1266
|
return AdapterUtils.ensureCustomerRef(ref);
|
|
1135
1267
|
}
|
|
1136
|
-
const customerRefFromExtra = extra?.authInfo?.extra?.customer_ref;
|
|
1137
|
-
const
|
|
1268
|
+
const customerRefFromExtra = typeof extra?.authInfo?.extra?.customer_ref === "string" ? String(extra.authInfo.extra.customer_ref) : void 0;
|
|
1269
|
+
const customerRefFromArgs = typeof args.auth === "object" && args.auth !== null && typeof args.auth.customer_ref === "string" ? String(args.auth.customer_ref) : void 0;
|
|
1270
|
+
const directCustomerRef = typeof args.customer_ref === "string" ? args.customer_ref : void 0;
|
|
1271
|
+
const customerRef = (customerRefFromExtra || customerRefFromArgs || directCustomerRef || "anonymous").trim();
|
|
1138
1272
|
return AdapterUtils.ensureCustomerRef(customerRef);
|
|
1139
1273
|
}
|
|
1140
1274
|
formatResponse(result, _context) {
|
|
@@ -1158,17 +1292,7 @@ var McpAdapter = class {
|
|
|
1158
1292
|
content: [
|
|
1159
1293
|
{
|
|
1160
1294
|
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
|
-
)
|
|
1295
|
+
text: JSON.stringify(paywallErrorToClientPayload(error), null, 2)
|
|
1172
1296
|
}
|
|
1173
1297
|
],
|
|
1174
1298
|
isError: true,
|
|
@@ -1454,6 +1578,12 @@ function createSolvaPay(config) {
|
|
|
1454
1578
|
}
|
|
1455
1579
|
return apiClient.createPaymentIntent(params);
|
|
1456
1580
|
},
|
|
1581
|
+
createTopupPaymentIntent(params) {
|
|
1582
|
+
if (!apiClient.createTopupPaymentIntent) {
|
|
1583
|
+
throw new SolvaPayError2("createTopupPaymentIntent is not available on this API client");
|
|
1584
|
+
}
|
|
1585
|
+
return apiClient.createTopupPaymentIntent(params);
|
|
1586
|
+
},
|
|
1457
1587
|
processPaymentIntent(params) {
|
|
1458
1588
|
if (!apiClient.processPaymentIntent) {
|
|
1459
1589
|
throw new SolvaPayError2("processPaymentIntent is not available on this API client");
|
|
@@ -1470,11 +1600,17 @@ function createSolvaPay(config) {
|
|
|
1470
1600
|
if (!apiClient.createCustomer) {
|
|
1471
1601
|
throw new SolvaPayError2("createCustomer is not available on this API client");
|
|
1472
1602
|
}
|
|
1473
|
-
return apiClient.createCustomer(params);
|
|
1603
|
+
return apiClient.createCustomer({ ...params, metadata: params.metadata ?? {} });
|
|
1474
1604
|
},
|
|
1475
1605
|
getCustomer(params) {
|
|
1476
1606
|
return apiClient.getCustomer(params);
|
|
1477
1607
|
},
|
|
1608
|
+
getCustomerBalance(params) {
|
|
1609
|
+
if (!apiClient.getCustomerBalance) {
|
|
1610
|
+
throw new SolvaPayError2("getCustomerBalance is not available on this API client");
|
|
1611
|
+
}
|
|
1612
|
+
return apiClient.getCustomerBalance(params);
|
|
1613
|
+
},
|
|
1478
1614
|
createCheckoutSession(params) {
|
|
1479
1615
|
return apiClient.createCheckoutSession({
|
|
1480
1616
|
customerRef: params.customerRef,
|
|
@@ -1485,6 +1621,12 @@ function createSolvaPay(config) {
|
|
|
1485
1621
|
createCustomerSession(params) {
|
|
1486
1622
|
return apiClient.createCustomerSession(params);
|
|
1487
1623
|
},
|
|
1624
|
+
activatePlan(params) {
|
|
1625
|
+
if (!apiClient.activatePlan) {
|
|
1626
|
+
throw new SolvaPayError2("activatePlan is not available on this API client");
|
|
1627
|
+
}
|
|
1628
|
+
return apiClient.activatePlan(params);
|
|
1629
|
+
},
|
|
1488
1630
|
bootstrapMcpProduct(params) {
|
|
1489
1631
|
if (!apiClient.bootstrapMcpProduct) {
|
|
1490
1632
|
throw new SolvaPayError2("bootstrapMcpProduct is not available on this API client");
|
|
@@ -1506,9 +1648,8 @@ function createSolvaPay(config) {
|
|
|
1506
1648
|
// Payable API for framework-specific handlers
|
|
1507
1649
|
payable(options = {}) {
|
|
1508
1650
|
const product = options.productRef || options.product || process.env.SOLVAPAY_PRODUCT || "default-product";
|
|
1509
|
-
const plan = options.planRef || options.plan;
|
|
1510
1651
|
const usageType = options.usageType || "requests";
|
|
1511
|
-
const metadata = { product,
|
|
1652
|
+
const metadata = { product, usageType };
|
|
1512
1653
|
return {
|
|
1513
1654
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1514
1655
|
http(businessLogic, adapterOptions) {
|
|
@@ -1634,6 +1775,24 @@ async function syncCustomerCore(request, options = {}) {
|
|
|
1634
1775
|
return handleRouteError(error, "Sync customer", "Failed to sync customer");
|
|
1635
1776
|
}
|
|
1636
1777
|
}
|
|
1778
|
+
async function getCustomerBalanceCore(request, options = {}) {
|
|
1779
|
+
try {
|
|
1780
|
+
const userResult = await getAuthenticatedUserCore(request);
|
|
1781
|
+
if (isErrorResult(userResult)) {
|
|
1782
|
+
return userResult;
|
|
1783
|
+
}
|
|
1784
|
+
const { userId, email, name } = userResult;
|
|
1785
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
1786
|
+
const customerRef = await solvaPay.ensureCustomer(userId, userId, {
|
|
1787
|
+
email: email || void 0,
|
|
1788
|
+
name: name || void 0
|
|
1789
|
+
});
|
|
1790
|
+
const result = await solvaPay.getCustomerBalance({ customerRef });
|
|
1791
|
+
return result;
|
|
1792
|
+
} catch (error) {
|
|
1793
|
+
return handleRouteError(error, "Get customer credits", "Failed to get customer credits");
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1637
1796
|
|
|
1638
1797
|
// src/helpers/payment.ts
|
|
1639
1798
|
async function createPaymentIntentCore(request, body, options = {}) {
|
|
@@ -1660,7 +1819,7 @@ async function createPaymentIntentCore(request, body, options = {}) {
|
|
|
1660
1819
|
customerRef
|
|
1661
1820
|
});
|
|
1662
1821
|
return {
|
|
1663
|
-
|
|
1822
|
+
processorPaymentId: paymentIntent.processorPaymentId,
|
|
1664
1823
|
clientSecret: paymentIntent.clientSecret,
|
|
1665
1824
|
publishableKey: paymentIntent.publishableKey,
|
|
1666
1825
|
accountId: paymentIntent.accountId,
|
|
@@ -1670,6 +1829,57 @@ async function createPaymentIntentCore(request, body, options = {}) {
|
|
|
1670
1829
|
return handleRouteError(error, "Create payment intent", "Payment intent creation failed");
|
|
1671
1830
|
}
|
|
1672
1831
|
}
|
|
1832
|
+
async function createTopupPaymentIntentCore(request, body, options = {}) {
|
|
1833
|
+
try {
|
|
1834
|
+
if (!body.amount || body.amount <= 0) {
|
|
1835
|
+
return {
|
|
1836
|
+
error: "Missing or invalid amount: must be a positive number",
|
|
1837
|
+
status: 400
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1840
|
+
if (!body.currency) {
|
|
1841
|
+
return {
|
|
1842
|
+
error: "Missing required parameter: currency",
|
|
1843
|
+
status: 400
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
if (body.currency !== body.currency.toUpperCase()) {
|
|
1847
|
+
return {
|
|
1848
|
+
error: `Invalid currency "${body.currency}": must be an uppercase ISO 4217 code (e.g. "USD", "EUR")`,
|
|
1849
|
+
status: 400
|
|
1850
|
+
};
|
|
1851
|
+
}
|
|
1852
|
+
const customerResult = await syncCustomerCore(request, {
|
|
1853
|
+
solvaPay: options.solvaPay,
|
|
1854
|
+
includeEmail: options.includeEmail,
|
|
1855
|
+
includeName: options.includeName
|
|
1856
|
+
});
|
|
1857
|
+
if (isErrorResult(customerResult)) {
|
|
1858
|
+
return customerResult;
|
|
1859
|
+
}
|
|
1860
|
+
const customerRef = customerResult;
|
|
1861
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
1862
|
+
const paymentIntent = await solvaPay.createTopupPaymentIntent({
|
|
1863
|
+
customerRef,
|
|
1864
|
+
amount: body.amount,
|
|
1865
|
+
currency: body.currency,
|
|
1866
|
+
description: body.description
|
|
1867
|
+
});
|
|
1868
|
+
return {
|
|
1869
|
+
processorPaymentId: paymentIntent.processorPaymentId,
|
|
1870
|
+
clientSecret: paymentIntent.clientSecret,
|
|
1871
|
+
publishableKey: paymentIntent.publishableKey,
|
|
1872
|
+
accountId: paymentIntent.accountId,
|
|
1873
|
+
customerRef
|
|
1874
|
+
};
|
|
1875
|
+
} catch (error) {
|
|
1876
|
+
return handleRouteError(
|
|
1877
|
+
error,
|
|
1878
|
+
"Create topup payment intent",
|
|
1879
|
+
"Topup payment intent creation failed"
|
|
1880
|
+
);
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1673
1883
|
async function processPaymentIntentCore(request, body, options = {}) {
|
|
1674
1884
|
try {
|
|
1675
1885
|
if (!body.paymentIntentId || !body.productRef) {
|
|
@@ -1832,6 +2042,103 @@ async function cancelPurchaseCore(request, body, options = {}) {
|
|
|
1832
2042
|
return handleRouteError(error, "Cancel purchase", "Failed to cancel purchase");
|
|
1833
2043
|
}
|
|
1834
2044
|
}
|
|
2045
|
+
async function reactivatePurchaseCore(request, body, options = {}) {
|
|
2046
|
+
try {
|
|
2047
|
+
if (!body.purchaseRef) {
|
|
2048
|
+
return {
|
|
2049
|
+
error: "Missing required parameter: purchaseRef is required",
|
|
2050
|
+
status: 400
|
|
2051
|
+
};
|
|
2052
|
+
}
|
|
2053
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
2054
|
+
if (!solvaPay.apiClient.reactivatePurchase) {
|
|
2055
|
+
return {
|
|
2056
|
+
error: "Reactivate purchase method not available on SDK client",
|
|
2057
|
+
status: 500
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
let reactivatedPurchase = await solvaPay.apiClient.reactivatePurchase({
|
|
2061
|
+
purchaseRef: body.purchaseRef
|
|
2062
|
+
});
|
|
2063
|
+
if (!reactivatedPurchase || typeof reactivatedPurchase !== "object") {
|
|
2064
|
+
return {
|
|
2065
|
+
error: "Invalid response from reactivate purchase endpoint",
|
|
2066
|
+
status: 500
|
|
2067
|
+
};
|
|
2068
|
+
}
|
|
2069
|
+
const responseObj = reactivatedPurchase;
|
|
2070
|
+
if (responseObj.purchase && typeof responseObj.purchase === "object") {
|
|
2071
|
+
reactivatedPurchase = responseObj.purchase;
|
|
2072
|
+
}
|
|
2073
|
+
if (!reactivatedPurchase.reference) {
|
|
2074
|
+
return {
|
|
2075
|
+
error: "Reactivate purchase response missing required fields",
|
|
2076
|
+
status: 500
|
|
2077
|
+
};
|
|
2078
|
+
}
|
|
2079
|
+
if (reactivatedPurchase.cancelledAt) {
|
|
2080
|
+
return {
|
|
2081
|
+
error: `Purchase reactivation failed: cancelledAt is still set`,
|
|
2082
|
+
status: 500
|
|
2083
|
+
};
|
|
2084
|
+
}
|
|
2085
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2086
|
+
return reactivatedPurchase;
|
|
2087
|
+
} catch (error) {
|
|
2088
|
+
if (error instanceof SolvaPayError4) {
|
|
2089
|
+
const errorMessage = error.message;
|
|
2090
|
+
if (errorMessage.includes("not found")) {
|
|
2091
|
+
return {
|
|
2092
|
+
error: "Purchase not found",
|
|
2093
|
+
status: 404,
|
|
2094
|
+
details: errorMessage
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
if (errorMessage.includes("cannot be reactivated") || errorMessage.includes("not pending cancellation") || errorMessage.includes("already been fully cancelled") || errorMessage.includes("already ended")) {
|
|
2098
|
+
return {
|
|
2099
|
+
error: "Purchase cannot be reactivated",
|
|
2100
|
+
status: 400,
|
|
2101
|
+
details: errorMessage
|
|
2102
|
+
};
|
|
2103
|
+
}
|
|
2104
|
+
return {
|
|
2105
|
+
error: errorMessage,
|
|
2106
|
+
status: 500,
|
|
2107
|
+
details: errorMessage
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
return handleRouteError(error, "Reactivate purchase", "Failed to reactivate purchase");
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
// src/helpers/activation.ts
|
|
2115
|
+
async function activatePlanCore(request, body, options = {}) {
|
|
2116
|
+
try {
|
|
2117
|
+
if (!body.productRef || !body.planRef) {
|
|
2118
|
+
return {
|
|
2119
|
+
error: "Missing required parameters: productRef and planRef are required",
|
|
2120
|
+
status: 400
|
|
2121
|
+
};
|
|
2122
|
+
}
|
|
2123
|
+
const customerResult = await syncCustomerCore(request, {
|
|
2124
|
+
solvaPay: options.solvaPay,
|
|
2125
|
+
includeEmail: options.includeEmail,
|
|
2126
|
+
includeName: options.includeName
|
|
2127
|
+
});
|
|
2128
|
+
if (isErrorResult(customerResult)) {
|
|
2129
|
+
return customerResult;
|
|
2130
|
+
}
|
|
2131
|
+
const customerRef = customerResult;
|
|
2132
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
2133
|
+
return await solvaPay.activatePlan({
|
|
2134
|
+
customerRef,
|
|
2135
|
+
productRef: body.productRef,
|
|
2136
|
+
planRef: body.planRef
|
|
2137
|
+
});
|
|
2138
|
+
} catch (error) {
|
|
2139
|
+
return handleRouteError(error, "Activate plan", "Plan activation failed");
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
1835
2142
|
|
|
1836
2143
|
// src/helpers/plans.ts
|
|
1837
2144
|
import { getSolvaPayConfig as getSolvaPayConfig2 } from "@solvapay/core";
|
|
@@ -1874,6 +2181,89 @@ async function listPlansCore(request) {
|
|
|
1874
2181
|
}
|
|
1875
2182
|
}
|
|
1876
2183
|
|
|
2184
|
+
// src/helpers/purchase.ts
|
|
2185
|
+
async function checkPurchaseCore(request, options = {}) {
|
|
2186
|
+
try {
|
|
2187
|
+
const userResult = await getAuthenticatedUserCore(request, {
|
|
2188
|
+
includeEmail: options.includeEmail,
|
|
2189
|
+
includeName: options.includeName
|
|
2190
|
+
});
|
|
2191
|
+
if (isErrorResult(userResult)) {
|
|
2192
|
+
return userResult;
|
|
2193
|
+
}
|
|
2194
|
+
const { userId, email, name } = userResult;
|
|
2195
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
2196
|
+
const cachedCustomerRef = request.headers.get("x-solvapay-customer-ref");
|
|
2197
|
+
if (cachedCustomerRef) {
|
|
2198
|
+
try {
|
|
2199
|
+
const customer = await solvaPay.getCustomer({ customerRef: cachedCustomerRef });
|
|
2200
|
+
if (customer && customer.customerRef) {
|
|
2201
|
+
if (customer.externalRef && customer.externalRef === userId) {
|
|
2202
|
+
const filteredPurchases = (customer.purchases || []).filter(
|
|
2203
|
+
(p) => p.status === "active"
|
|
2204
|
+
);
|
|
2205
|
+
return {
|
|
2206
|
+
customerRef: customer.customerRef,
|
|
2207
|
+
email: customer.email,
|
|
2208
|
+
name: customer.name,
|
|
2209
|
+
purchases: filteredPurchases
|
|
2210
|
+
};
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
} catch {
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
try {
|
|
2217
|
+
const customerRef = await solvaPay.ensureCustomer(userId, userId, {
|
|
2218
|
+
email: email || void 0,
|
|
2219
|
+
name: name || void 0
|
|
2220
|
+
});
|
|
2221
|
+
const customer = await solvaPay.getCustomer({ customerRef });
|
|
2222
|
+
const filteredPurchases = (customer.purchases || []).filter((p) => p.status === "active");
|
|
2223
|
+
return {
|
|
2224
|
+
customerRef: customer.customerRef || userId,
|
|
2225
|
+
email: customer.email,
|
|
2226
|
+
name: customer.name,
|
|
2227
|
+
purchases: filteredPurchases
|
|
2228
|
+
};
|
|
2229
|
+
} catch {
|
|
2230
|
+
return {
|
|
2231
|
+
customerRef: userId,
|
|
2232
|
+
purchases: []
|
|
2233
|
+
};
|
|
2234
|
+
}
|
|
2235
|
+
} catch (error) {
|
|
2236
|
+
return handleRouteError(error, "Check purchase", "Failed to check purchase");
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
// src/helpers/usage.ts
|
|
2241
|
+
async function trackUsageCore(request, body, options = {}) {
|
|
2242
|
+
try {
|
|
2243
|
+
const userResult = await getAuthenticatedUserCore(request);
|
|
2244
|
+
if (isErrorResult(userResult)) {
|
|
2245
|
+
return userResult;
|
|
2246
|
+
}
|
|
2247
|
+
const { userId, email, name } = userResult;
|
|
2248
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
2249
|
+
const customerRef = await solvaPay.ensureCustomer(userId, userId, {
|
|
2250
|
+
email: email || void 0,
|
|
2251
|
+
name: name || void 0
|
|
2252
|
+
});
|
|
2253
|
+
await solvaPay.trackUsage({
|
|
2254
|
+
customerRef,
|
|
2255
|
+
actionType: body.actionType,
|
|
2256
|
+
units: body.units,
|
|
2257
|
+
productRef: body.productRef,
|
|
2258
|
+
description: body.description,
|
|
2259
|
+
metadata: body.metadata
|
|
2260
|
+
});
|
|
2261
|
+
return { success: true };
|
|
2262
|
+
} catch (error) {
|
|
2263
|
+
return handleRouteError(error, "Track usage", "Track usage failed");
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
|
|
1877
2267
|
// src/edge.ts
|
|
1878
2268
|
function timingSafeEqual(a, b) {
|
|
1879
2269
|
if (a.length !== b.length) return false;
|
|
@@ -1928,18 +2318,25 @@ async function verifyWebhook({
|
|
|
1928
2318
|
}
|
|
1929
2319
|
export {
|
|
1930
2320
|
PaywallError,
|
|
2321
|
+
activatePlanCore,
|
|
1931
2322
|
cancelPurchaseCore,
|
|
2323
|
+
checkPurchaseCore,
|
|
1932
2324
|
createCheckoutSessionCore,
|
|
1933
2325
|
createCustomerSessionCore,
|
|
1934
2326
|
createPaymentIntentCore,
|
|
1935
2327
|
createSolvaPay,
|
|
1936
2328
|
createSolvaPayClient,
|
|
2329
|
+
createTopupPaymentIntentCore,
|
|
1937
2330
|
getAuthenticatedUserCore,
|
|
2331
|
+
getCustomerBalanceCore,
|
|
1938
2332
|
handleRouteError,
|
|
1939
2333
|
isErrorResult,
|
|
1940
2334
|
listPlansCore,
|
|
2335
|
+
paywallErrorToClientPayload,
|
|
1941
2336
|
processPaymentIntentCore,
|
|
2337
|
+
reactivatePurchaseCore,
|
|
1942
2338
|
syncCustomerCore,
|
|
2339
|
+
trackUsageCore,
|
|
1943
2340
|
verifyWebhook,
|
|
1944
2341
|
withRetry
|
|
1945
2342
|
};
|