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