@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/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(body)
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
- customerReference: params.customerRef
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
- const result = await res.json();
301
- return result;
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}:${configuredPlanRef || ""}:${usageType}`;
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(`\u26A0\uFE0F Failed to lookup existing customer by externalRef after 409:`, lookupError instanceof Error ? lookupError.message : lookupError);
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, _productRef, _planRef, action, outcome, requestId, actionDuration) {
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
- productReference: _productRef,
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 customerRef = typeof customerRefFromExtra === "string" && customerRefFromExtra.trim() ? customerRefFromExtra.trim() : "anonymous";
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, plan, usageType };
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
- return Buffer.from(padded, "base64").toString("utf8");
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
- id: paymentIntent.id,
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 config = getSolvaPayConfig2();
2063
- const solvapaySecretKey = config.apiKey;
2064
- const solvapayApiBaseUrl = config.apiBaseUrl;
2065
- if (!solvapaySecretKey) {
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
  };