@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.js CHANGED
@@ -38,12 +38,10 @@ function createSolvaPayClient(opts) {
38
38
  // POST: /v1/sdk/usages
39
39
  async trackUsage(params) {
40
40
  const url = `${base}/v1/sdk/usages`;
41
- const { customerRef, ...rest } = params;
42
- const body = { ...rest, customerId: customerRef };
43
41
  const res = await fetch(url, {
44
42
  method: "POST",
45
43
  headers,
46
- body: JSON.stringify(body)
44
+ body: JSON.stringify(params)
47
45
  });
48
46
  if (!res.ok) {
49
47
  const error = await res.text();
@@ -111,6 +109,36 @@ function createSolvaPayClient(opts) {
111
109
  purchases: customer.purchases || []
112
110
  };
113
111
  },
112
+ // GET: /v1/sdk/merchant
113
+ async getMerchant() {
114
+ const url = `${base}/v1/sdk/merchant`;
115
+ const res = await fetch(url, {
116
+ method: "GET",
117
+ headers
118
+ });
119
+ if (!res.ok) {
120
+ const error = await res.text();
121
+ log(`\u274C API Error: ${res.status} - ${error}`);
122
+ throw new SolvaPayError(`Get merchant failed (${res.status}): ${error}`);
123
+ }
124
+ return res.json();
125
+ },
126
+ // GET: /v1/sdk/products/{productRef}
127
+ async getProduct(productRef) {
128
+ const url = `${base}/v1/sdk/products/${encodeURIComponent(productRef)}`;
129
+ const res = await fetch(url, {
130
+ method: "GET",
131
+ headers
132
+ });
133
+ if (!res.ok) {
134
+ const error = await res.text();
135
+ log(`\u274C API Error: ${res.status} - ${error}`);
136
+ throw new SolvaPayError(`Get product failed (${res.status}): ${error}`);
137
+ }
138
+ const result = await res.json();
139
+ const data = result.data || {};
140
+ return { ...data, ...result };
141
+ },
114
142
  // Product management methods (primarily for integration tests)
115
143
  // GET: /v1/sdk/products
116
144
  async listProducts() {
@@ -288,7 +316,7 @@ function createSolvaPayClient(opts) {
288
316
  body: JSON.stringify({
289
317
  productRef: params.productRef,
290
318
  planRef: params.planRef,
291
- customerReference: params.customerRef
319
+ customerRef: params.customerRef
292
320
  })
293
321
  });
294
322
  if (!res.ok) {
@@ -296,8 +324,32 @@ function createSolvaPayClient(opts) {
296
324
  log(`\u274C API Error: ${res.status} - ${error}`);
297
325
  throw new SolvaPayError(`Create payment intent failed (${res.status}): ${error}`);
298
326
  }
299
- const result = await res.json();
300
- return result;
327
+ return await res.json();
328
+ },
329
+ // POST: /v1/sdk/payment-intents (purpose: credit_topup)
330
+ async createTopupPaymentIntent(params) {
331
+ const idempotencyKey = params.idempotencyKey || `topup-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
332
+ const url = `${base}/v1/sdk/payment-intents`;
333
+ const res = await fetch(url, {
334
+ method: "POST",
335
+ headers: {
336
+ ...headers,
337
+ "Idempotency-Key": idempotencyKey
338
+ },
339
+ body: JSON.stringify({
340
+ customerRef: params.customerRef,
341
+ purpose: "credit_topup",
342
+ amount: params.amount,
343
+ currency: params.currency,
344
+ description: params.description
345
+ })
346
+ });
347
+ if (!res.ok) {
348
+ const error = await res.text();
349
+ log(`\u274C API Error: ${res.status} - ${error}`);
350
+ throw new SolvaPayError(`Create topup payment intent failed (${res.status}): ${error}`);
351
+ }
352
+ return await res.json();
301
353
  },
302
354
  // POST: /v1/sdk/payment-intents/{paymentIntentId}/process
303
355
  async processPaymentIntent(params) {
@@ -374,6 +426,54 @@ function createSolvaPayClient(opts) {
374
426
  }
375
427
  return result;
376
428
  },
429
+ // POST: /v1/sdk/purchases/{purchaseRef}/reactivate
430
+ async reactivatePurchase(params) {
431
+ const url = `${base}/v1/sdk/purchases/${params.purchaseRef}/reactivate`;
432
+ const res = await fetch(url, {
433
+ method: "POST",
434
+ headers
435
+ });
436
+ if (!res.ok) {
437
+ const error = await res.text();
438
+ log(`\u274C API Error: ${res.status} - ${error}`);
439
+ if (res.status === 404) {
440
+ throw new SolvaPayError(`Purchase not found: ${error}`);
441
+ }
442
+ if (res.status === 400) {
443
+ throw new SolvaPayError(
444
+ `Purchase cannot be reactivated: ${error}`
445
+ );
446
+ }
447
+ throw new SolvaPayError(`Reactivate purchase failed (${res.status}): ${error}`);
448
+ }
449
+ const responseText = await res.text();
450
+ let responseData;
451
+ try {
452
+ responseData = JSON.parse(responseText);
453
+ } catch (parseError) {
454
+ log(`\u274C Failed to parse response as JSON: ${parseError}`);
455
+ throw new SolvaPayError(
456
+ `Invalid JSON response from reactivate purchase endpoint: ${responseText.substring(0, 200)}`
457
+ );
458
+ }
459
+ if (!responseData || typeof responseData !== "object") {
460
+ log(`\u274C Invalid response structure: ${JSON.stringify(responseData)}`);
461
+ throw new SolvaPayError(`Invalid response structure from reactivate purchase endpoint`);
462
+ }
463
+ let result;
464
+ if (responseData.purchase && typeof responseData.purchase === "object") {
465
+ result = responseData.purchase;
466
+ } else if (responseData.reference) {
467
+ result = responseData;
468
+ } else {
469
+ result = responseData.purchase || responseData;
470
+ }
471
+ if (!result || typeof result !== "object") {
472
+ log(`\u274C Invalid purchase data in response. Full response:`, responseData);
473
+ throw new SolvaPayError(`Invalid purchase data in reactivate purchase response`);
474
+ }
475
+ return result;
476
+ },
377
477
  // POST: /v1/sdk/user-info
378
478
  async getUserInfo(params) {
379
479
  const url = `${base}/v1/sdk/user-info`;
@@ -389,6 +489,20 @@ function createSolvaPayClient(opts) {
389
489
  }
390
490
  return await res.json();
391
491
  },
492
+ // GET: /v1/sdk/customers/:customerRef/balance
493
+ async getCustomerBalance(params) {
494
+ const url = `${base}/v1/sdk/customers/${params.customerRef}/balance`;
495
+ const res = await fetch(url, {
496
+ method: "GET",
497
+ headers
498
+ });
499
+ if (!res.ok) {
500
+ const error = await res.text();
501
+ log(`\u274C API Error: ${res.status} - ${error}`);
502
+ throw new SolvaPayError(`Get customer balance failed (${res.status}): ${error}`);
503
+ }
504
+ return await res.json();
505
+ },
392
506
  // POST: /v1/sdk/checkout-sessions
393
507
  async createCheckoutSession(params) {
394
508
  const url = `${base}/v1/sdk/checkout-sessions`;
@@ -420,6 +534,21 @@ function createSolvaPayClient(opts) {
420
534
  }
421
535
  const result = await res.json();
422
536
  return result;
537
+ },
538
+ // POST: /v1/sdk/activate
539
+ async activatePlan(params) {
540
+ const url = `${base}/v1/sdk/activate`;
541
+ const res = await fetch(url, {
542
+ method: "POST",
543
+ headers,
544
+ body: JSON.stringify(params)
545
+ });
546
+ if (!res.ok) {
547
+ const error = await res.text();
548
+ log(`\u274C API Error: ${res.status} - ${error}`);
549
+ throw new SolvaPayError(`Activate plan failed (${res.status}): ${error}`);
550
+ }
551
+ return await res.json();
423
552
  }
424
553
  };
425
554
  }
@@ -575,6 +704,24 @@ var PaywallError = class extends Error {
575
704
  this.name = "PaywallError";
576
705
  }
577
706
  };
707
+ function paywallErrorToClientPayload(error) {
708
+ const sc = error.structuredContent;
709
+ const base = {
710
+ success: false,
711
+ error: sc.kind === "activation_required" ? "Activation required" : "Payment required",
712
+ product: sc.product,
713
+ checkoutUrl: sc.checkoutUrl,
714
+ message: sc.message
715
+ };
716
+ if (sc.kind === "activation_required") {
717
+ base.kind = "activation_required";
718
+ if (sc.plans !== void 0) base.plans = sc.plans;
719
+ if (sc.balance !== void 0) base.balance = sc.balance;
720
+ if (sc.productDetails !== void 0) base.productDetails = sc.productDetails;
721
+ if (sc.confirmationUrl !== void 0) base.confirmationUrl = sc.confirmationUrl;
722
+ }
723
+ return base;
724
+ }
578
725
  var sharedCustomerLookupDeduplicator = createRequestDeduplicator({
579
726
  cacheTTL: 6e4,
580
727
  // Cache results for 60 seconds (reduces API calls significantly)
@@ -613,8 +760,6 @@ var SolvaPayPaywall = class {
613
760
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
614
761
  async protect(handler, metadata = {}, getCustomerRef) {
615
762
  const product = this.resolveProduct(metadata);
616
- const configuredPlanRef = metadata.plan?.trim();
617
- const usagePlanRef = configuredPlanRef || "unspecified";
618
763
  const usageType = metadata.usageType || "requests";
619
764
  return async (args) => {
620
765
  const startTime = Date.now();
@@ -628,12 +773,13 @@ var SolvaPayPaywall = class {
628
773
  }
629
774
  let resolvedMeterName;
630
775
  try {
631
- const limitsCacheKey = `${backendCustomerRef}:${product}:${configuredPlanRef || ""}:${usageType}`;
776
+ const limitsCacheKey = `${backendCustomerRef}:${product}:${usageType}`;
632
777
  const cachedLimits = this.limitsCache.get(limitsCacheKey);
633
778
  const now = Date.now();
634
779
  let withinLimits;
635
780
  let remaining;
636
781
  let checkoutUrl;
782
+ let lastLimitsCheck;
637
783
  const hasFreshCachedLimits = cachedLimits && now - cachedLimits.timestamp < this.limitsCacheTTL;
638
784
  if (hasFreshCachedLimits) {
639
785
  checkoutUrl = cachedLimits.checkoutUrl;
@@ -657,9 +803,9 @@ var SolvaPayPaywall = class {
657
803
  const limitsCheck = await this.apiClient.checkLimits({
658
804
  customerRef: backendCustomerRef,
659
805
  productRef: product,
660
- ...configuredPlanRef ? { planRef: configuredPlanRef } : {},
661
806
  meterName: usageType
662
807
  });
808
+ lastLimitsCheck = limitsCheck;
663
809
  withinLimits = limitsCheck.withinLimits;
664
810
  remaining = limitsCheck.remaining;
665
811
  checkoutUrl = limitsCheck.checkoutUrl;
@@ -682,12 +828,25 @@ var SolvaPayPaywall = class {
682
828
  this.trackUsage(
683
829
  backendCustomerRef,
684
830
  product,
685
- usagePlanRef,
686
831
  resolvedMeterName || usageType,
687
832
  "paywall",
688
833
  requestId,
689
834
  latencyMs2
690
835
  );
836
+ if (lastLimitsCheck?.activationRequired) {
837
+ const confirmationUrl = lastLimitsCheck.confirmationUrl;
838
+ const payCheckoutUrl = confirmationUrl || lastLimitsCheck.checkoutUrl || checkoutUrl || "";
839
+ throw new PaywallError("Activation required", {
840
+ kind: "activation_required",
841
+ product,
842
+ message: "Product activation is required before this tool can be used.",
843
+ checkoutUrl: payCheckoutUrl,
844
+ confirmationUrl,
845
+ plans: lastLimitsCheck.plans,
846
+ balance: lastLimitsCheck.balance,
847
+ productDetails: lastLimitsCheck.product
848
+ });
849
+ }
691
850
  throw new PaywallError("Payment required", {
692
851
  kind: "payment_required",
693
852
  product,
@@ -700,7 +859,6 @@ var SolvaPayPaywall = class {
700
859
  this.trackUsage(
701
860
  backendCustomerRef,
702
861
  product,
703
- usagePlanRef,
704
862
  resolvedMeterName || usageType,
705
863
  "success",
706
864
  requestId,
@@ -719,7 +877,6 @@ var SolvaPayPaywall = class {
719
877
  this.trackUsage(
720
878
  backendCustomerRef,
721
879
  product,
722
- usagePlanRef,
723
880
  resolvedMeterName || usageType,
724
881
  "fail",
725
882
  requestId,
@@ -790,7 +947,8 @@ var SolvaPayPaywall = class {
790
947
  this.customerCreationAttempts.add(customerRef);
791
948
  try {
792
949
  const createParams = {
793
- email: options?.email || `${customerRef}-${Date.now()}@auto-created.local`
950
+ email: options?.email || `${customerRef}-${Date.now()}@auto-created.local`,
951
+ metadata: {}
794
952
  };
795
953
  if (options?.name) {
796
954
  createParams.name = options.name;
@@ -814,7 +972,10 @@ var SolvaPayPaywall = class {
814
972
  return searchResult.customerRef;
815
973
  }
816
974
  } catch (lookupError) {
817
- this.log(`\u26A0\uFE0F Failed to lookup existing customer by externalRef after 409:`, lookupError instanceof Error ? lookupError.message : lookupError);
975
+ this.log(
976
+ `\u26A0\uFE0F Failed to lookup existing customer by externalRef after 409:`,
977
+ lookupError instanceof Error ? lookupError.message : lookupError
978
+ );
818
979
  }
819
980
  }
820
981
  const isEmailConflict = errorMessage.includes("email") || errorMessage.includes("identifier email");
@@ -837,7 +998,8 @@ var SolvaPayPaywall = class {
837
998
  try {
838
999
  const retryParams = {
839
1000
  email: `${customerRef}-${Date.now()}@auto-created.local`,
840
- externalRef
1001
+ externalRef,
1002
+ metadata: {}
841
1003
  };
842
1004
  if (options?.name) {
843
1005
  retryParams.name = options.name;
@@ -874,14 +1036,14 @@ var SolvaPayPaywall = class {
874
1036
  }
875
1037
  return backendRef;
876
1038
  }
877
- async trackUsage(customerRef, _productRef, _planRef, action, outcome, requestId, actionDuration) {
1039
+ async trackUsage(customerRef, productRef, action, outcome, requestId, actionDuration) {
878
1040
  await withRetry(
879
1041
  () => this.apiClient.trackUsage({
880
1042
  customerRef,
881
1043
  actionType: "api_call",
882
1044
  units: 1,
883
1045
  outcome,
884
- productReference: _productRef,
1046
+ productRef,
885
1047
  duration: actionDuration,
886
1048
  metadata: { action: action || "api_requests", requestId },
887
1049
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
@@ -1133,8 +1295,10 @@ var McpAdapter = class {
1133
1295
  const ref = await this.options.getCustomerRef(args, extra);
1134
1296
  return AdapterUtils.ensureCustomerRef(ref);
1135
1297
  }
1136
- const customerRefFromExtra = extra?.authInfo?.extra?.customer_ref;
1137
- const customerRef = typeof customerRefFromExtra === "string" && customerRefFromExtra.trim() ? customerRefFromExtra.trim() : "anonymous";
1298
+ const customerRefFromExtra = typeof extra?.authInfo?.extra?.customer_ref === "string" ? String(extra.authInfo.extra.customer_ref) : void 0;
1299
+ const customerRefFromArgs = typeof args.auth === "object" && args.auth !== null && typeof args.auth.customer_ref === "string" ? String(args.auth.customer_ref) : void 0;
1300
+ const directCustomerRef = typeof args.customer_ref === "string" ? args.customer_ref : void 0;
1301
+ const customerRef = (customerRefFromExtra || customerRefFromArgs || directCustomerRef || "anonymous").trim();
1138
1302
  return AdapterUtils.ensureCustomerRef(customerRef);
1139
1303
  }
1140
1304
  formatResponse(result, _context) {
@@ -1158,17 +1322,7 @@ var McpAdapter = class {
1158
1322
  content: [
1159
1323
  {
1160
1324
  type: "text",
1161
- text: JSON.stringify(
1162
- {
1163
- success: false,
1164
- error: "Payment required",
1165
- product: error.structuredContent.product,
1166
- checkoutUrl: error.structuredContent.checkoutUrl,
1167
- message: error.structuredContent.message
1168
- },
1169
- null,
1170
- 2
1171
- )
1325
+ text: JSON.stringify(paywallErrorToClientPayload(error), null, 2)
1172
1326
  }
1173
1327
  ],
1174
1328
  isError: true,
@@ -1454,6 +1608,12 @@ function createSolvaPay(config) {
1454
1608
  }
1455
1609
  return apiClient.createPaymentIntent(params);
1456
1610
  },
1611
+ createTopupPaymentIntent(params) {
1612
+ if (!apiClient.createTopupPaymentIntent) {
1613
+ throw new SolvaPayError2("createTopupPaymentIntent is not available on this API client");
1614
+ }
1615
+ return apiClient.createTopupPaymentIntent(params);
1616
+ },
1457
1617
  processPaymentIntent(params) {
1458
1618
  if (!apiClient.processPaymentIntent) {
1459
1619
  throw new SolvaPayError2("processPaymentIntent is not available on this API client");
@@ -1470,11 +1630,17 @@ function createSolvaPay(config) {
1470
1630
  if (!apiClient.createCustomer) {
1471
1631
  throw new SolvaPayError2("createCustomer is not available on this API client");
1472
1632
  }
1473
- return apiClient.createCustomer(params);
1633
+ return apiClient.createCustomer({ ...params, metadata: params.metadata ?? {} });
1474
1634
  },
1475
1635
  getCustomer(params) {
1476
1636
  return apiClient.getCustomer(params);
1477
1637
  },
1638
+ getCustomerBalance(params) {
1639
+ if (!apiClient.getCustomerBalance) {
1640
+ throw new SolvaPayError2("getCustomerBalance is not available on this API client");
1641
+ }
1642
+ return apiClient.getCustomerBalance(params);
1643
+ },
1478
1644
  createCheckoutSession(params) {
1479
1645
  return apiClient.createCheckoutSession({
1480
1646
  customerRef: params.customerRef,
@@ -1485,6 +1651,12 @@ function createSolvaPay(config) {
1485
1651
  createCustomerSession(params) {
1486
1652
  return apiClient.createCustomerSession(params);
1487
1653
  },
1654
+ activatePlan(params) {
1655
+ if (!apiClient.activatePlan) {
1656
+ throw new SolvaPayError2("activatePlan is not available on this API client");
1657
+ }
1658
+ return apiClient.activatePlan(params);
1659
+ },
1488
1660
  bootstrapMcpProduct(params) {
1489
1661
  if (!apiClient.bootstrapMcpProduct) {
1490
1662
  throw new SolvaPayError2("bootstrapMcpProduct is not available on this API client");
@@ -1506,9 +1678,8 @@ function createSolvaPay(config) {
1506
1678
  // Payable API for framework-specific handlers
1507
1679
  payable(options = {}) {
1508
1680
  const product = options.productRef || options.product || process.env.SOLVAPAY_PRODUCT || "default-product";
1509
- const plan = options.planRef || options.plan;
1510
1681
  const usageType = options.usageType || "requests";
1511
- const metadata = { product, plan, usageType };
1682
+ const metadata = { product, usageType };
1512
1683
  return {
1513
1684
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1514
1685
  http(businessLogic, adapterOptions) {
@@ -1634,6 +1805,24 @@ async function syncCustomerCore(request, options = {}) {
1634
1805
  return handleRouteError(error, "Sync customer", "Failed to sync customer");
1635
1806
  }
1636
1807
  }
1808
+ async function getCustomerBalanceCore(request, options = {}) {
1809
+ try {
1810
+ const userResult = await getAuthenticatedUserCore(request);
1811
+ if (isErrorResult(userResult)) {
1812
+ return userResult;
1813
+ }
1814
+ const { userId, email, name } = userResult;
1815
+ const solvaPay = options.solvaPay || createSolvaPay();
1816
+ const customerRef = await solvaPay.ensureCustomer(userId, userId, {
1817
+ email: email || void 0,
1818
+ name: name || void 0
1819
+ });
1820
+ const result = await solvaPay.getCustomerBalance({ customerRef });
1821
+ return result;
1822
+ } catch (error) {
1823
+ return handleRouteError(error, "Get customer credits", "Failed to get customer credits");
1824
+ }
1825
+ }
1637
1826
 
1638
1827
  // src/helpers/payment.ts
1639
1828
  async function createPaymentIntentCore(request, body, options = {}) {
@@ -1660,7 +1849,7 @@ async function createPaymentIntentCore(request, body, options = {}) {
1660
1849
  customerRef
1661
1850
  });
1662
1851
  return {
1663
- id: paymentIntent.id,
1852
+ processorPaymentId: paymentIntent.processorPaymentId,
1664
1853
  clientSecret: paymentIntent.clientSecret,
1665
1854
  publishableKey: paymentIntent.publishableKey,
1666
1855
  accountId: paymentIntent.accountId,
@@ -1670,6 +1859,57 @@ async function createPaymentIntentCore(request, body, options = {}) {
1670
1859
  return handleRouteError(error, "Create payment intent", "Payment intent creation failed");
1671
1860
  }
1672
1861
  }
1862
+ async function createTopupPaymentIntentCore(request, body, options = {}) {
1863
+ try {
1864
+ if (!body.amount || body.amount <= 0) {
1865
+ return {
1866
+ error: "Missing or invalid amount: must be a positive number",
1867
+ status: 400
1868
+ };
1869
+ }
1870
+ if (!body.currency) {
1871
+ return {
1872
+ error: "Missing required parameter: currency",
1873
+ status: 400
1874
+ };
1875
+ }
1876
+ if (body.currency !== body.currency.toUpperCase()) {
1877
+ return {
1878
+ error: `Invalid currency "${body.currency}": must be an uppercase ISO 4217 code (e.g. "USD", "EUR")`,
1879
+ status: 400
1880
+ };
1881
+ }
1882
+ const customerResult = await syncCustomerCore(request, {
1883
+ solvaPay: options.solvaPay,
1884
+ includeEmail: options.includeEmail,
1885
+ includeName: options.includeName
1886
+ });
1887
+ if (isErrorResult(customerResult)) {
1888
+ return customerResult;
1889
+ }
1890
+ const customerRef = customerResult;
1891
+ const solvaPay = options.solvaPay || createSolvaPay();
1892
+ const paymentIntent = await solvaPay.createTopupPaymentIntent({
1893
+ customerRef,
1894
+ amount: body.amount,
1895
+ currency: body.currency,
1896
+ description: body.description
1897
+ });
1898
+ return {
1899
+ processorPaymentId: paymentIntent.processorPaymentId,
1900
+ clientSecret: paymentIntent.clientSecret,
1901
+ publishableKey: paymentIntent.publishableKey,
1902
+ accountId: paymentIntent.accountId,
1903
+ customerRef
1904
+ };
1905
+ } catch (error) {
1906
+ return handleRouteError(
1907
+ error,
1908
+ "Create topup payment intent",
1909
+ "Topup payment intent creation failed"
1910
+ );
1911
+ }
1912
+ }
1673
1913
  async function processPaymentIntentCore(request, body, options = {}) {
1674
1914
  try {
1675
1915
  if (!body.paymentIntentId || !body.productRef) {
@@ -1832,10 +2072,107 @@ async function cancelPurchaseCore(request, body, options = {}) {
1832
2072
  return handleRouteError(error, "Cancel purchase", "Failed to cancel purchase");
1833
2073
  }
1834
2074
  }
2075
+ async function reactivatePurchaseCore(request, body, options = {}) {
2076
+ try {
2077
+ if (!body.purchaseRef) {
2078
+ return {
2079
+ error: "Missing required parameter: purchaseRef is required",
2080
+ status: 400
2081
+ };
2082
+ }
2083
+ const solvaPay = options.solvaPay || createSolvaPay();
2084
+ if (!solvaPay.apiClient.reactivatePurchase) {
2085
+ return {
2086
+ error: "Reactivate purchase method not available on SDK client",
2087
+ status: 500
2088
+ };
2089
+ }
2090
+ let reactivatedPurchase = await solvaPay.apiClient.reactivatePurchase({
2091
+ purchaseRef: body.purchaseRef
2092
+ });
2093
+ if (!reactivatedPurchase || typeof reactivatedPurchase !== "object") {
2094
+ return {
2095
+ error: "Invalid response from reactivate purchase endpoint",
2096
+ status: 500
2097
+ };
2098
+ }
2099
+ const responseObj = reactivatedPurchase;
2100
+ if (responseObj.purchase && typeof responseObj.purchase === "object") {
2101
+ reactivatedPurchase = responseObj.purchase;
2102
+ }
2103
+ if (!reactivatedPurchase.reference) {
2104
+ return {
2105
+ error: "Reactivate purchase response missing required fields",
2106
+ status: 500
2107
+ };
2108
+ }
2109
+ if (reactivatedPurchase.cancelledAt) {
2110
+ return {
2111
+ error: `Purchase reactivation failed: cancelledAt is still set`,
2112
+ status: 500
2113
+ };
2114
+ }
2115
+ await new Promise((resolve) => setTimeout(resolve, 500));
2116
+ return reactivatedPurchase;
2117
+ } catch (error) {
2118
+ if (error instanceof SolvaPayError4) {
2119
+ const errorMessage = error.message;
2120
+ if (errorMessage.includes("not found")) {
2121
+ return {
2122
+ error: "Purchase not found",
2123
+ status: 404,
2124
+ details: errorMessage
2125
+ };
2126
+ }
2127
+ if (errorMessage.includes("cannot be reactivated") || errorMessage.includes("not pending cancellation") || errorMessage.includes("already been fully cancelled") || errorMessage.includes("already ended")) {
2128
+ return {
2129
+ error: "Purchase cannot be reactivated",
2130
+ status: 400,
2131
+ details: errorMessage
2132
+ };
2133
+ }
2134
+ return {
2135
+ error: errorMessage,
2136
+ status: 500,
2137
+ details: errorMessage
2138
+ };
2139
+ }
2140
+ return handleRouteError(error, "Reactivate purchase", "Failed to reactivate purchase");
2141
+ }
2142
+ }
2143
+
2144
+ // src/helpers/activation.ts
2145
+ async function activatePlanCore(request, body, options = {}) {
2146
+ try {
2147
+ if (!body.productRef || !body.planRef) {
2148
+ return {
2149
+ error: "Missing required parameters: productRef and planRef are required",
2150
+ status: 400
2151
+ };
2152
+ }
2153
+ const customerResult = await syncCustomerCore(request, {
2154
+ solvaPay: options.solvaPay,
2155
+ includeEmail: options.includeEmail,
2156
+ includeName: options.includeName
2157
+ });
2158
+ if (isErrorResult(customerResult)) {
2159
+ return customerResult;
2160
+ }
2161
+ const customerRef = customerResult;
2162
+ const solvaPay = options.solvaPay || createSolvaPay();
2163
+ return await solvaPay.activatePlan({
2164
+ customerRef,
2165
+ productRef: body.productRef,
2166
+ planRef: body.planRef
2167
+ });
2168
+ } catch (error) {
2169
+ return handleRouteError(error, "Activate plan", "Plan activation failed");
2170
+ }
2171
+ }
1835
2172
 
1836
2173
  // src/helpers/plans.ts
1837
2174
  import { getSolvaPayConfig as getSolvaPayConfig2 } from "@solvapay/core";
1838
- async function listPlansCore(request) {
2175
+ async function listPlansCore(request, options = {}) {
1839
2176
  try {
1840
2177
  const url = new URL(request.url);
1841
2178
  const productRef = url.searchParams.get("productRef");
@@ -1845,19 +2182,20 @@ async function listPlansCore(request) {
1845
2182
  status: 400
1846
2183
  };
1847
2184
  }
1848
- const config = getSolvaPayConfig2();
1849
- const solvapaySecretKey = config.apiKey;
1850
- const solvapayApiBaseUrl = config.apiBaseUrl;
1851
- if (!solvapaySecretKey) {
2185
+ const apiClient = options.solvaPay?.apiClient ?? (() => {
2186
+ const config = getSolvaPayConfig2();
2187
+ if (!config.apiKey) return null;
2188
+ return createSolvaPayClient({
2189
+ apiKey: config.apiKey,
2190
+ apiBaseUrl: config.apiBaseUrl
2191
+ });
2192
+ })();
2193
+ if (!apiClient) {
1852
2194
  return {
1853
2195
  error: "Server configuration error: SolvaPay secret key not configured",
1854
2196
  status: 500
1855
2197
  };
1856
2198
  }
1857
- const apiClient = createSolvaPayClient({
1858
- apiKey: solvapaySecretKey,
1859
- apiBaseUrl: solvapayApiBaseUrl
1860
- });
1861
2199
  if (!apiClient.listPlans) {
1862
2200
  return {
1863
2201
  error: "List plans method not available",
@@ -1874,6 +2212,89 @@ async function listPlansCore(request) {
1874
2212
  }
1875
2213
  }
1876
2214
 
2215
+ // src/helpers/purchase.ts
2216
+ async function checkPurchaseCore(request, options = {}) {
2217
+ try {
2218
+ const userResult = await getAuthenticatedUserCore(request, {
2219
+ includeEmail: options.includeEmail,
2220
+ includeName: options.includeName
2221
+ });
2222
+ if (isErrorResult(userResult)) {
2223
+ return userResult;
2224
+ }
2225
+ const { userId, email, name } = userResult;
2226
+ const solvaPay = options.solvaPay || createSolvaPay();
2227
+ const cachedCustomerRef = request.headers.get("x-solvapay-customer-ref");
2228
+ if (cachedCustomerRef) {
2229
+ try {
2230
+ const customer = await solvaPay.getCustomer({ customerRef: cachedCustomerRef });
2231
+ if (customer && customer.customerRef) {
2232
+ if (customer.externalRef && customer.externalRef === userId) {
2233
+ const filteredPurchases = (customer.purchases || []).filter(
2234
+ (p) => p.status === "active"
2235
+ );
2236
+ return {
2237
+ customerRef: customer.customerRef,
2238
+ email: customer.email,
2239
+ name: customer.name,
2240
+ purchases: filteredPurchases
2241
+ };
2242
+ }
2243
+ }
2244
+ } catch {
2245
+ }
2246
+ }
2247
+ try {
2248
+ const customerRef = await solvaPay.ensureCustomer(userId, userId, {
2249
+ email: email || void 0,
2250
+ name: name || void 0
2251
+ });
2252
+ const customer = await solvaPay.getCustomer({ customerRef });
2253
+ const filteredPurchases = (customer.purchases || []).filter((p) => p.status === "active");
2254
+ return {
2255
+ customerRef: customer.customerRef || userId,
2256
+ email: customer.email,
2257
+ name: customer.name,
2258
+ purchases: filteredPurchases
2259
+ };
2260
+ } catch {
2261
+ return {
2262
+ customerRef: userId,
2263
+ purchases: []
2264
+ };
2265
+ }
2266
+ } catch (error) {
2267
+ return handleRouteError(error, "Check purchase", "Failed to check purchase");
2268
+ }
2269
+ }
2270
+
2271
+ // src/helpers/usage.ts
2272
+ async function trackUsageCore(request, body, options = {}) {
2273
+ try {
2274
+ const userResult = await getAuthenticatedUserCore(request);
2275
+ if (isErrorResult(userResult)) {
2276
+ return userResult;
2277
+ }
2278
+ const { userId, email, name } = userResult;
2279
+ const solvaPay = options.solvaPay || createSolvaPay();
2280
+ const customerRef = await solvaPay.ensureCustomer(userId, userId, {
2281
+ email: email || void 0,
2282
+ name: name || void 0
2283
+ });
2284
+ await solvaPay.trackUsage({
2285
+ customerRef,
2286
+ actionType: body.actionType,
2287
+ units: body.units,
2288
+ productRef: body.productRef,
2289
+ description: body.description,
2290
+ metadata: body.metadata
2291
+ });
2292
+ return { success: true };
2293
+ } catch (error) {
2294
+ return handleRouteError(error, "Track usage", "Track usage failed");
2295
+ }
2296
+ }
2297
+
1877
2298
  // src/edge.ts
1878
2299
  function timingSafeEqual(a, b) {
1879
2300
  if (a.length !== b.length) return false;
@@ -1928,18 +2349,25 @@ async function verifyWebhook({
1928
2349
  }
1929
2350
  export {
1930
2351
  PaywallError,
2352
+ activatePlanCore,
1931
2353
  cancelPurchaseCore,
2354
+ checkPurchaseCore,
1932
2355
  createCheckoutSessionCore,
1933
2356
  createCustomerSessionCore,
1934
2357
  createPaymentIntentCore,
1935
2358
  createSolvaPay,
1936
2359
  createSolvaPayClient,
2360
+ createTopupPaymentIntentCore,
1937
2361
  getAuthenticatedUserCore,
2362
+ getCustomerBalanceCore,
1938
2363
  handleRouteError,
1939
2364
  isErrorResult,
1940
2365
  listPlansCore,
2366
+ paywallErrorToClientPayload,
1941
2367
  processPaymentIntentCore,
2368
+ reactivatePurchaseCore,
1942
2369
  syncCustomerCore,
2370
+ trackUsageCore,
1943
2371
  verifyWebhook,
1944
2372
  withRetry
1945
2373
  };