@solvapay/server 1.0.5 → 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/README.md +1 -1
- 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/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) {
|
|
@@ -1660,7 +1801,7 @@ async function createPaymentIntentCore(request, body, options = {}) {
|
|
|
1660
1801
|
customerRef
|
|
1661
1802
|
});
|
|
1662
1803
|
return {
|
|
1663
|
-
|
|
1804
|
+
processorPaymentId: paymentIntent.processorPaymentId,
|
|
1664
1805
|
clientSecret: paymentIntent.clientSecret,
|
|
1665
1806
|
publishableKey: paymentIntent.publishableKey,
|
|
1666
1807
|
accountId: paymentIntent.accountId,
|
|
@@ -1670,6 +1811,57 @@ async function createPaymentIntentCore(request, body, options = {}) {
|
|
|
1670
1811
|
return handleRouteError(error, "Create payment intent", "Payment intent creation failed");
|
|
1671
1812
|
}
|
|
1672
1813
|
}
|
|
1814
|
+
async function createTopupPaymentIntentCore(request, body, options = {}) {
|
|
1815
|
+
try {
|
|
1816
|
+
if (!body.amount || body.amount <= 0) {
|
|
1817
|
+
return {
|
|
1818
|
+
error: "Missing or invalid amount: must be a positive number",
|
|
1819
|
+
status: 400
|
|
1820
|
+
};
|
|
1821
|
+
}
|
|
1822
|
+
if (!body.currency) {
|
|
1823
|
+
return {
|
|
1824
|
+
error: "Missing required parameter: currency",
|
|
1825
|
+
status: 400
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1828
|
+
if (body.currency !== body.currency.toUpperCase()) {
|
|
1829
|
+
return {
|
|
1830
|
+
error: `Invalid currency "${body.currency}": must be an uppercase ISO 4217 code (e.g. "USD", "EUR")`,
|
|
1831
|
+
status: 400
|
|
1832
|
+
};
|
|
1833
|
+
}
|
|
1834
|
+
const customerResult = await syncCustomerCore(request, {
|
|
1835
|
+
solvaPay: options.solvaPay,
|
|
1836
|
+
includeEmail: options.includeEmail,
|
|
1837
|
+
includeName: options.includeName
|
|
1838
|
+
});
|
|
1839
|
+
if (isErrorResult(customerResult)) {
|
|
1840
|
+
return customerResult;
|
|
1841
|
+
}
|
|
1842
|
+
const customerRef = customerResult;
|
|
1843
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
1844
|
+
const paymentIntent = await solvaPay.createTopupPaymentIntent({
|
|
1845
|
+
customerRef,
|
|
1846
|
+
amount: body.amount,
|
|
1847
|
+
currency: body.currency,
|
|
1848
|
+
description: body.description
|
|
1849
|
+
});
|
|
1850
|
+
return {
|
|
1851
|
+
processorPaymentId: paymentIntent.processorPaymentId,
|
|
1852
|
+
clientSecret: paymentIntent.clientSecret,
|
|
1853
|
+
publishableKey: paymentIntent.publishableKey,
|
|
1854
|
+
accountId: paymentIntent.accountId,
|
|
1855
|
+
customerRef
|
|
1856
|
+
};
|
|
1857
|
+
} catch (error) {
|
|
1858
|
+
return handleRouteError(
|
|
1859
|
+
error,
|
|
1860
|
+
"Create topup payment intent",
|
|
1861
|
+
"Topup payment intent creation failed"
|
|
1862
|
+
);
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1673
1865
|
async function processPaymentIntentCore(request, body, options = {}) {
|
|
1674
1866
|
try {
|
|
1675
1867
|
if (!body.paymentIntentId || !body.productRef) {
|
|
@@ -1934,10 +2126,12 @@ export {
|
|
|
1934
2126
|
createPaymentIntentCore,
|
|
1935
2127
|
createSolvaPay,
|
|
1936
2128
|
createSolvaPayClient,
|
|
2129
|
+
createTopupPaymentIntentCore,
|
|
1937
2130
|
getAuthenticatedUserCore,
|
|
1938
2131
|
handleRouteError,
|
|
1939
2132
|
isErrorResult,
|
|
1940
2133
|
listPlansCore,
|
|
2134
|
+
paywallErrorToClientPayload,
|
|
1941
2135
|
processPaymentIntentCore,
|
|
1942
2136
|
syncCustomerCore,
|
|
1943
2137
|
verifyWebhook,
|