@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/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();
|
|
@@ -112,6 +110,36 @@ function createSolvaPayClient(opts) {
|
|
|
112
110
|
purchases: customer.purchases || []
|
|
113
111
|
};
|
|
114
112
|
},
|
|
113
|
+
// GET: /v1/sdk/merchant
|
|
114
|
+
async getMerchant() {
|
|
115
|
+
const url = `${base}/v1/sdk/merchant`;
|
|
116
|
+
const res = await fetch(url, {
|
|
117
|
+
method: "GET",
|
|
118
|
+
headers
|
|
119
|
+
});
|
|
120
|
+
if (!res.ok) {
|
|
121
|
+
const error = await res.text();
|
|
122
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
123
|
+
throw new SolvaPayError(`Get merchant failed (${res.status}): ${error}`);
|
|
124
|
+
}
|
|
125
|
+
return res.json();
|
|
126
|
+
},
|
|
127
|
+
// GET: /v1/sdk/products/{productRef}
|
|
128
|
+
async getProduct(productRef) {
|
|
129
|
+
const url = `${base}/v1/sdk/products/${encodeURIComponent(productRef)}`;
|
|
130
|
+
const res = await fetch(url, {
|
|
131
|
+
method: "GET",
|
|
132
|
+
headers
|
|
133
|
+
});
|
|
134
|
+
if (!res.ok) {
|
|
135
|
+
const error = await res.text();
|
|
136
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
137
|
+
throw new SolvaPayError(`Get product failed (${res.status}): ${error}`);
|
|
138
|
+
}
|
|
139
|
+
const result = await res.json();
|
|
140
|
+
const data = result.data || {};
|
|
141
|
+
return { ...data, ...result };
|
|
142
|
+
},
|
|
115
143
|
// Product management methods (primarily for integration tests)
|
|
116
144
|
// GET: /v1/sdk/products
|
|
117
145
|
async listProducts() {
|
|
@@ -289,7 +317,7 @@ function createSolvaPayClient(opts) {
|
|
|
289
317
|
body: JSON.stringify({
|
|
290
318
|
productRef: params.productRef,
|
|
291
319
|
planRef: params.planRef,
|
|
292
|
-
|
|
320
|
+
customerRef: params.customerRef
|
|
293
321
|
})
|
|
294
322
|
});
|
|
295
323
|
if (!res.ok) {
|
|
@@ -297,8 +325,32 @@ function createSolvaPayClient(opts) {
|
|
|
297
325
|
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
298
326
|
throw new SolvaPayError(`Create payment intent failed (${res.status}): ${error}`);
|
|
299
327
|
}
|
|
300
|
-
|
|
301
|
-
|
|
328
|
+
return await res.json();
|
|
329
|
+
},
|
|
330
|
+
// POST: /v1/sdk/payment-intents (purpose: credit_topup)
|
|
331
|
+
async createTopupPaymentIntent(params) {
|
|
332
|
+
const idempotencyKey = params.idempotencyKey || `topup-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
333
|
+
const url = `${base}/v1/sdk/payment-intents`;
|
|
334
|
+
const res = await fetch(url, {
|
|
335
|
+
method: "POST",
|
|
336
|
+
headers: {
|
|
337
|
+
...headers,
|
|
338
|
+
"Idempotency-Key": idempotencyKey
|
|
339
|
+
},
|
|
340
|
+
body: JSON.stringify({
|
|
341
|
+
customerRef: params.customerRef,
|
|
342
|
+
purpose: "credit_topup",
|
|
343
|
+
amount: params.amount,
|
|
344
|
+
currency: params.currency,
|
|
345
|
+
description: params.description
|
|
346
|
+
})
|
|
347
|
+
});
|
|
348
|
+
if (!res.ok) {
|
|
349
|
+
const error = await res.text();
|
|
350
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
351
|
+
throw new SolvaPayError(`Create topup payment intent failed (${res.status}): ${error}`);
|
|
352
|
+
}
|
|
353
|
+
return await res.json();
|
|
302
354
|
},
|
|
303
355
|
// POST: /v1/sdk/payment-intents/{paymentIntentId}/process
|
|
304
356
|
async processPaymentIntent(params) {
|
|
@@ -375,6 +427,54 @@ function createSolvaPayClient(opts) {
|
|
|
375
427
|
}
|
|
376
428
|
return result;
|
|
377
429
|
},
|
|
430
|
+
// POST: /v1/sdk/purchases/{purchaseRef}/reactivate
|
|
431
|
+
async reactivatePurchase(params) {
|
|
432
|
+
const url = `${base}/v1/sdk/purchases/${params.purchaseRef}/reactivate`;
|
|
433
|
+
const res = await fetch(url, {
|
|
434
|
+
method: "POST",
|
|
435
|
+
headers
|
|
436
|
+
});
|
|
437
|
+
if (!res.ok) {
|
|
438
|
+
const error = await res.text();
|
|
439
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
440
|
+
if (res.status === 404) {
|
|
441
|
+
throw new SolvaPayError(`Purchase not found: ${error}`);
|
|
442
|
+
}
|
|
443
|
+
if (res.status === 400) {
|
|
444
|
+
throw new SolvaPayError(
|
|
445
|
+
`Purchase cannot be reactivated: ${error}`
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
throw new SolvaPayError(`Reactivate purchase failed (${res.status}): ${error}`);
|
|
449
|
+
}
|
|
450
|
+
const responseText = await res.text();
|
|
451
|
+
let responseData;
|
|
452
|
+
try {
|
|
453
|
+
responseData = JSON.parse(responseText);
|
|
454
|
+
} catch (parseError) {
|
|
455
|
+
log(`\u274C Failed to parse response as JSON: ${parseError}`);
|
|
456
|
+
throw new SolvaPayError(
|
|
457
|
+
`Invalid JSON response from reactivate purchase endpoint: ${responseText.substring(0, 200)}`
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
if (!responseData || typeof responseData !== "object") {
|
|
461
|
+
log(`\u274C Invalid response structure: ${JSON.stringify(responseData)}`);
|
|
462
|
+
throw new SolvaPayError(`Invalid response structure from reactivate purchase endpoint`);
|
|
463
|
+
}
|
|
464
|
+
let result;
|
|
465
|
+
if (responseData.purchase && typeof responseData.purchase === "object") {
|
|
466
|
+
result = responseData.purchase;
|
|
467
|
+
} else if (responseData.reference) {
|
|
468
|
+
result = responseData;
|
|
469
|
+
} else {
|
|
470
|
+
result = responseData.purchase || responseData;
|
|
471
|
+
}
|
|
472
|
+
if (!result || typeof result !== "object") {
|
|
473
|
+
log(`\u274C Invalid purchase data in response. Full response:`, responseData);
|
|
474
|
+
throw new SolvaPayError(`Invalid purchase data in reactivate purchase response`);
|
|
475
|
+
}
|
|
476
|
+
return result;
|
|
477
|
+
},
|
|
378
478
|
// POST: /v1/sdk/user-info
|
|
379
479
|
async getUserInfo(params) {
|
|
380
480
|
const url = `${base}/v1/sdk/user-info`;
|
|
@@ -390,6 +490,20 @@ function createSolvaPayClient(opts) {
|
|
|
390
490
|
}
|
|
391
491
|
return await res.json();
|
|
392
492
|
},
|
|
493
|
+
// GET: /v1/sdk/customers/:customerRef/balance
|
|
494
|
+
async getCustomerBalance(params) {
|
|
495
|
+
const url = `${base}/v1/sdk/customers/${params.customerRef}/balance`;
|
|
496
|
+
const res = await fetch(url, {
|
|
497
|
+
method: "GET",
|
|
498
|
+
headers
|
|
499
|
+
});
|
|
500
|
+
if (!res.ok) {
|
|
501
|
+
const error = await res.text();
|
|
502
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
503
|
+
throw new SolvaPayError(`Get customer balance failed (${res.status}): ${error}`);
|
|
504
|
+
}
|
|
505
|
+
return await res.json();
|
|
506
|
+
},
|
|
393
507
|
// POST: /v1/sdk/checkout-sessions
|
|
394
508
|
async createCheckoutSession(params) {
|
|
395
509
|
const url = `${base}/v1/sdk/checkout-sessions`;
|
|
@@ -421,6 +535,21 @@ function createSolvaPayClient(opts) {
|
|
|
421
535
|
}
|
|
422
536
|
const result = await res.json();
|
|
423
537
|
return result;
|
|
538
|
+
},
|
|
539
|
+
// POST: /v1/sdk/activate
|
|
540
|
+
async activatePlan(params) {
|
|
541
|
+
const url = `${base}/v1/sdk/activate`;
|
|
542
|
+
const res = await fetch(url, {
|
|
543
|
+
method: "POST",
|
|
544
|
+
headers,
|
|
545
|
+
body: JSON.stringify(params)
|
|
546
|
+
});
|
|
547
|
+
if (!res.ok) {
|
|
548
|
+
const error = await res.text();
|
|
549
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
550
|
+
throw new SolvaPayError(`Activate plan failed (${res.status}): ${error}`);
|
|
551
|
+
}
|
|
552
|
+
return await res.json();
|
|
424
553
|
}
|
|
425
554
|
};
|
|
426
555
|
}
|
|
@@ -576,6 +705,24 @@ var PaywallError = class extends Error {
|
|
|
576
705
|
this.name = "PaywallError";
|
|
577
706
|
}
|
|
578
707
|
};
|
|
708
|
+
function paywallErrorToClientPayload(error) {
|
|
709
|
+
const sc = error.structuredContent;
|
|
710
|
+
const base = {
|
|
711
|
+
success: false,
|
|
712
|
+
error: sc.kind === "activation_required" ? "Activation required" : "Payment required",
|
|
713
|
+
product: sc.product,
|
|
714
|
+
checkoutUrl: sc.checkoutUrl,
|
|
715
|
+
message: sc.message
|
|
716
|
+
};
|
|
717
|
+
if (sc.kind === "activation_required") {
|
|
718
|
+
base.kind = "activation_required";
|
|
719
|
+
if (sc.plans !== void 0) base.plans = sc.plans;
|
|
720
|
+
if (sc.balance !== void 0) base.balance = sc.balance;
|
|
721
|
+
if (sc.productDetails !== void 0) base.productDetails = sc.productDetails;
|
|
722
|
+
if (sc.confirmationUrl !== void 0) base.confirmationUrl = sc.confirmationUrl;
|
|
723
|
+
}
|
|
724
|
+
return base;
|
|
725
|
+
}
|
|
579
726
|
var sharedCustomerLookupDeduplicator = createRequestDeduplicator({
|
|
580
727
|
cacheTTL: 6e4,
|
|
581
728
|
// Cache results for 60 seconds (reduces API calls significantly)
|
|
@@ -614,8 +761,6 @@ var SolvaPayPaywall = class {
|
|
|
614
761
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
615
762
|
async protect(handler, metadata = {}, getCustomerRef) {
|
|
616
763
|
const product = this.resolveProduct(metadata);
|
|
617
|
-
const configuredPlanRef = metadata.plan?.trim();
|
|
618
|
-
const usagePlanRef = configuredPlanRef || "unspecified";
|
|
619
764
|
const usageType = metadata.usageType || "requests";
|
|
620
765
|
return async (args) => {
|
|
621
766
|
const startTime = Date.now();
|
|
@@ -629,12 +774,13 @@ var SolvaPayPaywall = class {
|
|
|
629
774
|
}
|
|
630
775
|
let resolvedMeterName;
|
|
631
776
|
try {
|
|
632
|
-
const limitsCacheKey = `${backendCustomerRef}:${product}:${
|
|
777
|
+
const limitsCacheKey = `${backendCustomerRef}:${product}:${usageType}`;
|
|
633
778
|
const cachedLimits = this.limitsCache.get(limitsCacheKey);
|
|
634
779
|
const now = Date.now();
|
|
635
780
|
let withinLimits;
|
|
636
781
|
let remaining;
|
|
637
782
|
let checkoutUrl;
|
|
783
|
+
let lastLimitsCheck;
|
|
638
784
|
const hasFreshCachedLimits = cachedLimits && now - cachedLimits.timestamp < this.limitsCacheTTL;
|
|
639
785
|
if (hasFreshCachedLimits) {
|
|
640
786
|
checkoutUrl = cachedLimits.checkoutUrl;
|
|
@@ -658,9 +804,9 @@ var SolvaPayPaywall = class {
|
|
|
658
804
|
const limitsCheck = await this.apiClient.checkLimits({
|
|
659
805
|
customerRef: backendCustomerRef,
|
|
660
806
|
productRef: product,
|
|
661
|
-
...configuredPlanRef ? { planRef: configuredPlanRef } : {},
|
|
662
807
|
meterName: usageType
|
|
663
808
|
});
|
|
809
|
+
lastLimitsCheck = limitsCheck;
|
|
664
810
|
withinLimits = limitsCheck.withinLimits;
|
|
665
811
|
remaining = limitsCheck.remaining;
|
|
666
812
|
checkoutUrl = limitsCheck.checkoutUrl;
|
|
@@ -683,12 +829,25 @@ var SolvaPayPaywall = class {
|
|
|
683
829
|
this.trackUsage(
|
|
684
830
|
backendCustomerRef,
|
|
685
831
|
product,
|
|
686
|
-
usagePlanRef,
|
|
687
832
|
resolvedMeterName || usageType,
|
|
688
833
|
"paywall",
|
|
689
834
|
requestId,
|
|
690
835
|
latencyMs2
|
|
691
836
|
);
|
|
837
|
+
if (lastLimitsCheck?.activationRequired) {
|
|
838
|
+
const confirmationUrl = lastLimitsCheck.confirmationUrl;
|
|
839
|
+
const payCheckoutUrl = confirmationUrl || lastLimitsCheck.checkoutUrl || checkoutUrl || "";
|
|
840
|
+
throw new PaywallError("Activation required", {
|
|
841
|
+
kind: "activation_required",
|
|
842
|
+
product,
|
|
843
|
+
message: "Product activation is required before this tool can be used.",
|
|
844
|
+
checkoutUrl: payCheckoutUrl,
|
|
845
|
+
confirmationUrl,
|
|
846
|
+
plans: lastLimitsCheck.plans,
|
|
847
|
+
balance: lastLimitsCheck.balance,
|
|
848
|
+
productDetails: lastLimitsCheck.product
|
|
849
|
+
});
|
|
850
|
+
}
|
|
692
851
|
throw new PaywallError("Payment required", {
|
|
693
852
|
kind: "payment_required",
|
|
694
853
|
product,
|
|
@@ -701,7 +860,6 @@ var SolvaPayPaywall = class {
|
|
|
701
860
|
this.trackUsage(
|
|
702
861
|
backendCustomerRef,
|
|
703
862
|
product,
|
|
704
|
-
usagePlanRef,
|
|
705
863
|
resolvedMeterName || usageType,
|
|
706
864
|
"success",
|
|
707
865
|
requestId,
|
|
@@ -720,7 +878,6 @@ var SolvaPayPaywall = class {
|
|
|
720
878
|
this.trackUsage(
|
|
721
879
|
backendCustomerRef,
|
|
722
880
|
product,
|
|
723
|
-
usagePlanRef,
|
|
724
881
|
resolvedMeterName || usageType,
|
|
725
882
|
"fail",
|
|
726
883
|
requestId,
|
|
@@ -791,7 +948,8 @@ var SolvaPayPaywall = class {
|
|
|
791
948
|
this.customerCreationAttempts.add(customerRef);
|
|
792
949
|
try {
|
|
793
950
|
const createParams = {
|
|
794
|
-
email: options?.email || `${customerRef}-${Date.now()}@auto-created.local
|
|
951
|
+
email: options?.email || `${customerRef}-${Date.now()}@auto-created.local`,
|
|
952
|
+
metadata: {}
|
|
795
953
|
};
|
|
796
954
|
if (options?.name) {
|
|
797
955
|
createParams.name = options.name;
|
|
@@ -815,7 +973,10 @@ var SolvaPayPaywall = class {
|
|
|
815
973
|
return searchResult.customerRef;
|
|
816
974
|
}
|
|
817
975
|
} catch (lookupError) {
|
|
818
|
-
this.log(
|
|
976
|
+
this.log(
|
|
977
|
+
`\u26A0\uFE0F Failed to lookup existing customer by externalRef after 409:`,
|
|
978
|
+
lookupError instanceof Error ? lookupError.message : lookupError
|
|
979
|
+
);
|
|
819
980
|
}
|
|
820
981
|
}
|
|
821
982
|
const isEmailConflict = errorMessage.includes("email") || errorMessage.includes("identifier email");
|
|
@@ -838,7 +999,8 @@ var SolvaPayPaywall = class {
|
|
|
838
999
|
try {
|
|
839
1000
|
const retryParams = {
|
|
840
1001
|
email: `${customerRef}-${Date.now()}@auto-created.local`,
|
|
841
|
-
externalRef
|
|
1002
|
+
externalRef,
|
|
1003
|
+
metadata: {}
|
|
842
1004
|
};
|
|
843
1005
|
if (options?.name) {
|
|
844
1006
|
retryParams.name = options.name;
|
|
@@ -875,14 +1037,14 @@ var SolvaPayPaywall = class {
|
|
|
875
1037
|
}
|
|
876
1038
|
return backendRef;
|
|
877
1039
|
}
|
|
878
|
-
async trackUsage(customerRef,
|
|
1040
|
+
async trackUsage(customerRef, productRef, action, outcome, requestId, actionDuration) {
|
|
879
1041
|
await withRetry(
|
|
880
1042
|
() => this.apiClient.trackUsage({
|
|
881
1043
|
customerRef,
|
|
882
1044
|
actionType: "api_call",
|
|
883
1045
|
units: 1,
|
|
884
1046
|
outcome,
|
|
885
|
-
|
|
1047
|
+
productRef,
|
|
886
1048
|
duration: actionDuration,
|
|
887
1049
|
metadata: { action: action || "api_requests", requestId },
|
|
888
1050
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -1134,8 +1296,10 @@ var McpAdapter = class {
|
|
|
1134
1296
|
const ref = await this.options.getCustomerRef(args, extra);
|
|
1135
1297
|
return AdapterUtils.ensureCustomerRef(ref);
|
|
1136
1298
|
}
|
|
1137
|
-
const customerRefFromExtra = extra?.authInfo?.extra?.customer_ref;
|
|
1138
|
-
const
|
|
1299
|
+
const customerRefFromExtra = typeof extra?.authInfo?.extra?.customer_ref === "string" ? String(extra.authInfo.extra.customer_ref) : void 0;
|
|
1300
|
+
const customerRefFromArgs = typeof args.auth === "object" && args.auth !== null && typeof args.auth.customer_ref === "string" ? String(args.auth.customer_ref) : void 0;
|
|
1301
|
+
const directCustomerRef = typeof args.customer_ref === "string" ? args.customer_ref : void 0;
|
|
1302
|
+
const customerRef = (customerRefFromExtra || customerRefFromArgs || directCustomerRef || "anonymous").trim();
|
|
1139
1303
|
return AdapterUtils.ensureCustomerRef(customerRef);
|
|
1140
1304
|
}
|
|
1141
1305
|
formatResponse(result, _context) {
|
|
@@ -1159,17 +1323,7 @@ var McpAdapter = class {
|
|
|
1159
1323
|
content: [
|
|
1160
1324
|
{
|
|
1161
1325
|
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
|
-
)
|
|
1326
|
+
text: JSON.stringify(paywallErrorToClientPayload(error), null, 2)
|
|
1173
1327
|
}
|
|
1174
1328
|
],
|
|
1175
1329
|
isError: true,
|
|
@@ -1456,6 +1610,12 @@ function createSolvaPay(config) {
|
|
|
1456
1610
|
}
|
|
1457
1611
|
return apiClient.createPaymentIntent(params);
|
|
1458
1612
|
},
|
|
1613
|
+
createTopupPaymentIntent(params) {
|
|
1614
|
+
if (!apiClient.createTopupPaymentIntent) {
|
|
1615
|
+
throw new SolvaPayError2("createTopupPaymentIntent is not available on this API client");
|
|
1616
|
+
}
|
|
1617
|
+
return apiClient.createTopupPaymentIntent(params);
|
|
1618
|
+
},
|
|
1459
1619
|
processPaymentIntent(params) {
|
|
1460
1620
|
if (!apiClient.processPaymentIntent) {
|
|
1461
1621
|
throw new SolvaPayError2("processPaymentIntent is not available on this API client");
|
|
@@ -1472,11 +1632,17 @@ function createSolvaPay(config) {
|
|
|
1472
1632
|
if (!apiClient.createCustomer) {
|
|
1473
1633
|
throw new SolvaPayError2("createCustomer is not available on this API client");
|
|
1474
1634
|
}
|
|
1475
|
-
return apiClient.createCustomer(params);
|
|
1635
|
+
return apiClient.createCustomer({ ...params, metadata: params.metadata ?? {} });
|
|
1476
1636
|
},
|
|
1477
1637
|
getCustomer(params) {
|
|
1478
1638
|
return apiClient.getCustomer(params);
|
|
1479
1639
|
},
|
|
1640
|
+
getCustomerBalance(params) {
|
|
1641
|
+
if (!apiClient.getCustomerBalance) {
|
|
1642
|
+
throw new SolvaPayError2("getCustomerBalance is not available on this API client");
|
|
1643
|
+
}
|
|
1644
|
+
return apiClient.getCustomerBalance(params);
|
|
1645
|
+
},
|
|
1480
1646
|
createCheckoutSession(params) {
|
|
1481
1647
|
return apiClient.createCheckoutSession({
|
|
1482
1648
|
customerRef: params.customerRef,
|
|
@@ -1487,6 +1653,12 @@ function createSolvaPay(config) {
|
|
|
1487
1653
|
createCustomerSession(params) {
|
|
1488
1654
|
return apiClient.createCustomerSession(params);
|
|
1489
1655
|
},
|
|
1656
|
+
activatePlan(params) {
|
|
1657
|
+
if (!apiClient.activatePlan) {
|
|
1658
|
+
throw new SolvaPayError2("activatePlan is not available on this API client");
|
|
1659
|
+
}
|
|
1660
|
+
return apiClient.activatePlan(params);
|
|
1661
|
+
},
|
|
1490
1662
|
bootstrapMcpProduct(params) {
|
|
1491
1663
|
if (!apiClient.bootstrapMcpProduct) {
|
|
1492
1664
|
throw new SolvaPayError2("bootstrapMcpProduct is not available on this API client");
|
|
@@ -1508,9 +1680,8 @@ function createSolvaPay(config) {
|
|
|
1508
1680
|
// Payable API for framework-specific handlers
|
|
1509
1681
|
payable(options = {}) {
|
|
1510
1682
|
const product = options.productRef || options.product || process.env.SOLVAPAY_PRODUCT || "default-product";
|
|
1511
|
-
const plan = options.planRef || options.plan;
|
|
1512
1683
|
const usageType = options.usageType || "requests";
|
|
1513
|
-
const metadata = { product,
|
|
1684
|
+
const metadata = { product, usageType };
|
|
1514
1685
|
return {
|
|
1515
1686
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1516
1687
|
http(businessLogic, adapterOptions) {
|
|
@@ -1574,7 +1745,8 @@ var McpBearerAuthError = class extends Error {
|
|
|
1574
1745
|
function base64UrlDecode(input) {
|
|
1575
1746
|
const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
1576
1747
|
const padded = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
|
|
1577
|
-
|
|
1748
|
+
const bytes = Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
|
|
1749
|
+
return new TextDecoder().decode(bytes);
|
|
1578
1750
|
}
|
|
1579
1751
|
function extractBearerToken(authorization) {
|
|
1580
1752
|
if (!authorization) return null;
|
|
@@ -1848,6 +2020,24 @@ async function syncCustomerCore(request, options = {}) {
|
|
|
1848
2020
|
return handleRouteError(error, "Sync customer", "Failed to sync customer");
|
|
1849
2021
|
}
|
|
1850
2022
|
}
|
|
2023
|
+
async function getCustomerBalanceCore(request, options = {}) {
|
|
2024
|
+
try {
|
|
2025
|
+
const userResult = await getAuthenticatedUserCore(request);
|
|
2026
|
+
if (isErrorResult(userResult)) {
|
|
2027
|
+
return userResult;
|
|
2028
|
+
}
|
|
2029
|
+
const { userId, email, name } = userResult;
|
|
2030
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
2031
|
+
const customerRef = await solvaPay.ensureCustomer(userId, userId, {
|
|
2032
|
+
email: email || void 0,
|
|
2033
|
+
name: name || void 0
|
|
2034
|
+
});
|
|
2035
|
+
const result = await solvaPay.getCustomerBalance({ customerRef });
|
|
2036
|
+
return result;
|
|
2037
|
+
} catch (error) {
|
|
2038
|
+
return handleRouteError(error, "Get customer credits", "Failed to get customer credits");
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
1851
2041
|
|
|
1852
2042
|
// src/helpers/payment.ts
|
|
1853
2043
|
async function createPaymentIntentCore(request, body, options = {}) {
|
|
@@ -1874,7 +2064,7 @@ async function createPaymentIntentCore(request, body, options = {}) {
|
|
|
1874
2064
|
customerRef
|
|
1875
2065
|
});
|
|
1876
2066
|
return {
|
|
1877
|
-
|
|
2067
|
+
processorPaymentId: paymentIntent.processorPaymentId,
|
|
1878
2068
|
clientSecret: paymentIntent.clientSecret,
|
|
1879
2069
|
publishableKey: paymentIntent.publishableKey,
|
|
1880
2070
|
accountId: paymentIntent.accountId,
|
|
@@ -1884,6 +2074,57 @@ async function createPaymentIntentCore(request, body, options = {}) {
|
|
|
1884
2074
|
return handleRouteError(error, "Create payment intent", "Payment intent creation failed");
|
|
1885
2075
|
}
|
|
1886
2076
|
}
|
|
2077
|
+
async function createTopupPaymentIntentCore(request, body, options = {}) {
|
|
2078
|
+
try {
|
|
2079
|
+
if (!body.amount || body.amount <= 0) {
|
|
2080
|
+
return {
|
|
2081
|
+
error: "Missing or invalid amount: must be a positive number",
|
|
2082
|
+
status: 400
|
|
2083
|
+
};
|
|
2084
|
+
}
|
|
2085
|
+
if (!body.currency) {
|
|
2086
|
+
return {
|
|
2087
|
+
error: "Missing required parameter: currency",
|
|
2088
|
+
status: 400
|
|
2089
|
+
};
|
|
2090
|
+
}
|
|
2091
|
+
if (body.currency !== body.currency.toUpperCase()) {
|
|
2092
|
+
return {
|
|
2093
|
+
error: `Invalid currency "${body.currency}": must be an uppercase ISO 4217 code (e.g. "USD", "EUR")`,
|
|
2094
|
+
status: 400
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
const customerResult = await syncCustomerCore(request, {
|
|
2098
|
+
solvaPay: options.solvaPay,
|
|
2099
|
+
includeEmail: options.includeEmail,
|
|
2100
|
+
includeName: options.includeName
|
|
2101
|
+
});
|
|
2102
|
+
if (isErrorResult(customerResult)) {
|
|
2103
|
+
return customerResult;
|
|
2104
|
+
}
|
|
2105
|
+
const customerRef = customerResult;
|
|
2106
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
2107
|
+
const paymentIntent = await solvaPay.createTopupPaymentIntent({
|
|
2108
|
+
customerRef,
|
|
2109
|
+
amount: body.amount,
|
|
2110
|
+
currency: body.currency,
|
|
2111
|
+
description: body.description
|
|
2112
|
+
});
|
|
2113
|
+
return {
|
|
2114
|
+
processorPaymentId: paymentIntent.processorPaymentId,
|
|
2115
|
+
clientSecret: paymentIntent.clientSecret,
|
|
2116
|
+
publishableKey: paymentIntent.publishableKey,
|
|
2117
|
+
accountId: paymentIntent.accountId,
|
|
2118
|
+
customerRef
|
|
2119
|
+
};
|
|
2120
|
+
} catch (error) {
|
|
2121
|
+
return handleRouteError(
|
|
2122
|
+
error,
|
|
2123
|
+
"Create topup payment intent",
|
|
2124
|
+
"Topup payment intent creation failed"
|
|
2125
|
+
);
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
1887
2128
|
async function processPaymentIntentCore(request, body, options = {}) {
|
|
1888
2129
|
try {
|
|
1889
2130
|
if (!body.paymentIntentId || !body.productRef) {
|
|
@@ -2046,10 +2287,107 @@ async function cancelPurchaseCore(request, body, options = {}) {
|
|
|
2046
2287
|
return handleRouteError(error, "Cancel purchase", "Failed to cancel purchase");
|
|
2047
2288
|
}
|
|
2048
2289
|
}
|
|
2290
|
+
async function reactivatePurchaseCore(request, body, options = {}) {
|
|
2291
|
+
try {
|
|
2292
|
+
if (!body.purchaseRef) {
|
|
2293
|
+
return {
|
|
2294
|
+
error: "Missing required parameter: purchaseRef is required",
|
|
2295
|
+
status: 400
|
|
2296
|
+
};
|
|
2297
|
+
}
|
|
2298
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
2299
|
+
if (!solvaPay.apiClient.reactivatePurchase) {
|
|
2300
|
+
return {
|
|
2301
|
+
error: "Reactivate purchase method not available on SDK client",
|
|
2302
|
+
status: 500
|
|
2303
|
+
};
|
|
2304
|
+
}
|
|
2305
|
+
let reactivatedPurchase = await solvaPay.apiClient.reactivatePurchase({
|
|
2306
|
+
purchaseRef: body.purchaseRef
|
|
2307
|
+
});
|
|
2308
|
+
if (!reactivatedPurchase || typeof reactivatedPurchase !== "object") {
|
|
2309
|
+
return {
|
|
2310
|
+
error: "Invalid response from reactivate purchase endpoint",
|
|
2311
|
+
status: 500
|
|
2312
|
+
};
|
|
2313
|
+
}
|
|
2314
|
+
const responseObj = reactivatedPurchase;
|
|
2315
|
+
if (responseObj.purchase && typeof responseObj.purchase === "object") {
|
|
2316
|
+
reactivatedPurchase = responseObj.purchase;
|
|
2317
|
+
}
|
|
2318
|
+
if (!reactivatedPurchase.reference) {
|
|
2319
|
+
return {
|
|
2320
|
+
error: "Reactivate purchase response missing required fields",
|
|
2321
|
+
status: 500
|
|
2322
|
+
};
|
|
2323
|
+
}
|
|
2324
|
+
if (reactivatedPurchase.cancelledAt) {
|
|
2325
|
+
return {
|
|
2326
|
+
error: `Purchase reactivation failed: cancelledAt is still set`,
|
|
2327
|
+
status: 500
|
|
2328
|
+
};
|
|
2329
|
+
}
|
|
2330
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2331
|
+
return reactivatedPurchase;
|
|
2332
|
+
} catch (error) {
|
|
2333
|
+
if (error instanceof SolvaPayError4) {
|
|
2334
|
+
const errorMessage = error.message;
|
|
2335
|
+
if (errorMessage.includes("not found")) {
|
|
2336
|
+
return {
|
|
2337
|
+
error: "Purchase not found",
|
|
2338
|
+
status: 404,
|
|
2339
|
+
details: errorMessage
|
|
2340
|
+
};
|
|
2341
|
+
}
|
|
2342
|
+
if (errorMessage.includes("cannot be reactivated") || errorMessage.includes("not pending cancellation") || errorMessage.includes("already been fully cancelled") || errorMessage.includes("already ended")) {
|
|
2343
|
+
return {
|
|
2344
|
+
error: "Purchase cannot be reactivated",
|
|
2345
|
+
status: 400,
|
|
2346
|
+
details: errorMessage
|
|
2347
|
+
};
|
|
2348
|
+
}
|
|
2349
|
+
return {
|
|
2350
|
+
error: errorMessage,
|
|
2351
|
+
status: 500,
|
|
2352
|
+
details: errorMessage
|
|
2353
|
+
};
|
|
2354
|
+
}
|
|
2355
|
+
return handleRouteError(error, "Reactivate purchase", "Failed to reactivate purchase");
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
// src/helpers/activation.ts
|
|
2360
|
+
async function activatePlanCore(request, body, options = {}) {
|
|
2361
|
+
try {
|
|
2362
|
+
if (!body.productRef || !body.planRef) {
|
|
2363
|
+
return {
|
|
2364
|
+
error: "Missing required parameters: productRef and planRef are required",
|
|
2365
|
+
status: 400
|
|
2366
|
+
};
|
|
2367
|
+
}
|
|
2368
|
+
const customerResult = await syncCustomerCore(request, {
|
|
2369
|
+
solvaPay: options.solvaPay,
|
|
2370
|
+
includeEmail: options.includeEmail,
|
|
2371
|
+
includeName: options.includeName
|
|
2372
|
+
});
|
|
2373
|
+
if (isErrorResult(customerResult)) {
|
|
2374
|
+
return customerResult;
|
|
2375
|
+
}
|
|
2376
|
+
const customerRef = customerResult;
|
|
2377
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
2378
|
+
return await solvaPay.activatePlan({
|
|
2379
|
+
customerRef,
|
|
2380
|
+
productRef: body.productRef,
|
|
2381
|
+
planRef: body.planRef
|
|
2382
|
+
});
|
|
2383
|
+
} catch (error) {
|
|
2384
|
+
return handleRouteError(error, "Activate plan", "Plan activation failed");
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2049
2387
|
|
|
2050
2388
|
// src/helpers/plans.ts
|
|
2051
2389
|
import { getSolvaPayConfig as getSolvaPayConfig2 } from "@solvapay/core";
|
|
2052
|
-
async function listPlansCore(request) {
|
|
2390
|
+
async function listPlansCore(request, options = {}) {
|
|
2053
2391
|
try {
|
|
2054
2392
|
const url = new URL(request.url);
|
|
2055
2393
|
const productRef = url.searchParams.get("productRef");
|
|
@@ -2059,19 +2397,20 @@ async function listPlansCore(request) {
|
|
|
2059
2397
|
status: 400
|
|
2060
2398
|
};
|
|
2061
2399
|
}
|
|
2062
|
-
const
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2400
|
+
const apiClient = options.solvaPay?.apiClient ?? (() => {
|
|
2401
|
+
const config = getSolvaPayConfig2();
|
|
2402
|
+
if (!config.apiKey) return null;
|
|
2403
|
+
return createSolvaPayClient({
|
|
2404
|
+
apiKey: config.apiKey,
|
|
2405
|
+
apiBaseUrl: config.apiBaseUrl
|
|
2406
|
+
});
|
|
2407
|
+
})();
|
|
2408
|
+
if (!apiClient) {
|
|
2066
2409
|
return {
|
|
2067
2410
|
error: "Server configuration error: SolvaPay secret key not configured",
|
|
2068
2411
|
status: 500
|
|
2069
2412
|
};
|
|
2070
2413
|
}
|
|
2071
|
-
const apiClient = createSolvaPayClient({
|
|
2072
|
-
apiKey: solvapaySecretKey,
|
|
2073
|
-
apiBaseUrl: solvapayApiBaseUrl
|
|
2074
|
-
});
|
|
2075
2414
|
if (!apiClient.listPlans) {
|
|
2076
2415
|
return {
|
|
2077
2416
|
error: "List plans method not available",
|
|
@@ -2088,6 +2427,159 @@ async function listPlansCore(request) {
|
|
|
2088
2427
|
}
|
|
2089
2428
|
}
|
|
2090
2429
|
|
|
2430
|
+
// src/helpers/merchant.ts
|
|
2431
|
+
import { getSolvaPayConfig as getSolvaPayConfig3 } from "@solvapay/core";
|
|
2432
|
+
async function getMerchantCore(_request, options = {}) {
|
|
2433
|
+
try {
|
|
2434
|
+
const apiClient = options.solvaPay?.apiClient ?? (() => {
|
|
2435
|
+
const config = getSolvaPayConfig3();
|
|
2436
|
+
if (!config.apiKey) return null;
|
|
2437
|
+
return createSolvaPayClient({
|
|
2438
|
+
apiKey: config.apiKey,
|
|
2439
|
+
apiBaseUrl: config.apiBaseUrl
|
|
2440
|
+
});
|
|
2441
|
+
})();
|
|
2442
|
+
if (!apiClient) {
|
|
2443
|
+
return {
|
|
2444
|
+
error: "Server configuration error: SolvaPay secret key not configured",
|
|
2445
|
+
status: 500
|
|
2446
|
+
};
|
|
2447
|
+
}
|
|
2448
|
+
if (!apiClient.getMerchant) {
|
|
2449
|
+
return {
|
|
2450
|
+
error: "Get merchant method not available",
|
|
2451
|
+
status: 500
|
|
2452
|
+
};
|
|
2453
|
+
}
|
|
2454
|
+
const merchant = await apiClient.getMerchant();
|
|
2455
|
+
return merchant;
|
|
2456
|
+
} catch (error) {
|
|
2457
|
+
return handleRouteError(error, "Get merchant", "Failed to fetch merchant");
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
// src/helpers/product.ts
|
|
2462
|
+
import { getSolvaPayConfig as getSolvaPayConfig4 } from "@solvapay/core";
|
|
2463
|
+
async function getProductCore(request, options = {}) {
|
|
2464
|
+
try {
|
|
2465
|
+
const url = new URL(request.url);
|
|
2466
|
+
const productRef = url.searchParams.get("productRef");
|
|
2467
|
+
if (!productRef) {
|
|
2468
|
+
return {
|
|
2469
|
+
error: "Missing required parameter: productRef",
|
|
2470
|
+
status: 400
|
|
2471
|
+
};
|
|
2472
|
+
}
|
|
2473
|
+
const apiClient = options.solvaPay?.apiClient ?? (() => {
|
|
2474
|
+
const config = getSolvaPayConfig4();
|
|
2475
|
+
if (!config.apiKey) return null;
|
|
2476
|
+
return createSolvaPayClient({
|
|
2477
|
+
apiKey: config.apiKey,
|
|
2478
|
+
apiBaseUrl: config.apiBaseUrl
|
|
2479
|
+
});
|
|
2480
|
+
})();
|
|
2481
|
+
if (!apiClient) {
|
|
2482
|
+
return {
|
|
2483
|
+
error: "Server configuration error: SolvaPay secret key not configured",
|
|
2484
|
+
status: 500
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2487
|
+
if (!apiClient.getProduct) {
|
|
2488
|
+
return {
|
|
2489
|
+
error: "Get product method not available",
|
|
2490
|
+
status: 500
|
|
2491
|
+
};
|
|
2492
|
+
}
|
|
2493
|
+
const product = await apiClient.getProduct(productRef);
|
|
2494
|
+
return product;
|
|
2495
|
+
} catch (error) {
|
|
2496
|
+
return handleRouteError(error, "Get product", "Failed to fetch product");
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
// src/helpers/purchase.ts
|
|
2501
|
+
async function checkPurchaseCore(request, options = {}) {
|
|
2502
|
+
try {
|
|
2503
|
+
const userResult = await getAuthenticatedUserCore(request, {
|
|
2504
|
+
includeEmail: options.includeEmail,
|
|
2505
|
+
includeName: options.includeName
|
|
2506
|
+
});
|
|
2507
|
+
if (isErrorResult(userResult)) {
|
|
2508
|
+
return userResult;
|
|
2509
|
+
}
|
|
2510
|
+
const { userId, email, name } = userResult;
|
|
2511
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
2512
|
+
const cachedCustomerRef = request.headers.get("x-solvapay-customer-ref");
|
|
2513
|
+
if (cachedCustomerRef) {
|
|
2514
|
+
try {
|
|
2515
|
+
const customer = await solvaPay.getCustomer({ customerRef: cachedCustomerRef });
|
|
2516
|
+
if (customer && customer.customerRef) {
|
|
2517
|
+
if (customer.externalRef && customer.externalRef === userId) {
|
|
2518
|
+
const filteredPurchases = (customer.purchases || []).filter(
|
|
2519
|
+
(p) => p.status === "active"
|
|
2520
|
+
);
|
|
2521
|
+
return {
|
|
2522
|
+
customerRef: customer.customerRef,
|
|
2523
|
+
email: customer.email,
|
|
2524
|
+
name: customer.name,
|
|
2525
|
+
purchases: filteredPurchases
|
|
2526
|
+
};
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
} catch {
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
try {
|
|
2533
|
+
const customerRef = await solvaPay.ensureCustomer(userId, userId, {
|
|
2534
|
+
email: email || void 0,
|
|
2535
|
+
name: name || void 0
|
|
2536
|
+
});
|
|
2537
|
+
const customer = await solvaPay.getCustomer({ customerRef });
|
|
2538
|
+
const filteredPurchases = (customer.purchases || []).filter((p) => p.status === "active");
|
|
2539
|
+
return {
|
|
2540
|
+
customerRef: customer.customerRef || userId,
|
|
2541
|
+
email: customer.email,
|
|
2542
|
+
name: customer.name,
|
|
2543
|
+
purchases: filteredPurchases
|
|
2544
|
+
};
|
|
2545
|
+
} catch {
|
|
2546
|
+
return {
|
|
2547
|
+
customerRef: userId,
|
|
2548
|
+
purchases: []
|
|
2549
|
+
};
|
|
2550
|
+
}
|
|
2551
|
+
} catch (error) {
|
|
2552
|
+
return handleRouteError(error, "Check purchase", "Failed to check purchase");
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
// src/helpers/usage.ts
|
|
2557
|
+
async function trackUsageCore(request, body, options = {}) {
|
|
2558
|
+
try {
|
|
2559
|
+
const userResult = await getAuthenticatedUserCore(request);
|
|
2560
|
+
if (isErrorResult(userResult)) {
|
|
2561
|
+
return userResult;
|
|
2562
|
+
}
|
|
2563
|
+
const { userId, email, name } = userResult;
|
|
2564
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
2565
|
+
const customerRef = await solvaPay.ensureCustomer(userId, userId, {
|
|
2566
|
+
email: email || void 0,
|
|
2567
|
+
name: name || void 0
|
|
2568
|
+
});
|
|
2569
|
+
await solvaPay.trackUsage({
|
|
2570
|
+
customerRef,
|
|
2571
|
+
actionType: body.actionType,
|
|
2572
|
+
units: body.units,
|
|
2573
|
+
productRef: body.productRef,
|
|
2574
|
+
description: body.description,
|
|
2575
|
+
metadata: body.metadata
|
|
2576
|
+
});
|
|
2577
|
+
return { success: true };
|
|
2578
|
+
} catch (error) {
|
|
2579
|
+
return handleRouteError(error, "Track usage", "Track usage failed");
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2091
2583
|
// src/index.ts
|
|
2092
2584
|
function verifyWebhook({
|
|
2093
2585
|
body,
|
|
@@ -2129,29 +2621,38 @@ export {
|
|
|
2129
2621
|
McpBearerAuthError,
|
|
2130
2622
|
PaywallError,
|
|
2131
2623
|
VIRTUAL_TOOL_DEFINITIONS,
|
|
2624
|
+
activatePlanCore,
|
|
2132
2625
|
buildAuthInfoFromBearer,
|
|
2133
2626
|
cancelPurchaseCore,
|
|
2627
|
+
checkPurchaseCore,
|
|
2134
2628
|
createCheckoutSessionCore,
|
|
2135
2629
|
createCustomerSessionCore,
|
|
2136
2630
|
createMcpOAuthBridge,
|
|
2137
2631
|
createPaymentIntentCore,
|
|
2138
2632
|
createSolvaPay,
|
|
2139
2633
|
createSolvaPayClient,
|
|
2634
|
+
createTopupPaymentIntentCore,
|
|
2140
2635
|
createVirtualTools,
|
|
2141
2636
|
decodeJwtPayload,
|
|
2142
2637
|
extractBearerToken,
|
|
2143
2638
|
getAuthenticatedUserCore,
|
|
2639
|
+
getCustomerBalanceCore,
|
|
2144
2640
|
getCustomerRefFromBearerAuthHeader,
|
|
2145
2641
|
getCustomerRefFromJwtPayload,
|
|
2642
|
+
getMerchantCore,
|
|
2146
2643
|
getOAuthAuthorizationServerResponse,
|
|
2147
2644
|
getOAuthProtectedResourceResponse,
|
|
2645
|
+
getProductCore,
|
|
2148
2646
|
handleRouteError,
|
|
2149
2647
|
isErrorResult,
|
|
2150
2648
|
jsonSchemaToZodRawShape,
|
|
2151
2649
|
listPlansCore,
|
|
2650
|
+
paywallErrorToClientPayload,
|
|
2152
2651
|
processPaymentIntentCore,
|
|
2652
|
+
reactivatePurchaseCore,
|
|
2153
2653
|
registerVirtualToolsMcpImpl,
|
|
2154
2654
|
syncCustomerCore,
|
|
2655
|
+
trackUsageCore,
|
|
2155
2656
|
verifyWebhook,
|
|
2156
2657
|
withRetry
|
|
2157
2658
|
};
|