@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.cjs CHANGED
@@ -4314,35 +4314,44 @@ __export(index_exports, {
4314
4314
  McpBearerAuthError: () => McpBearerAuthError,
4315
4315
  PaywallError: () => PaywallError,
4316
4316
  VIRTUAL_TOOL_DEFINITIONS: () => VIRTUAL_TOOL_DEFINITIONS,
4317
+ activatePlanCore: () => activatePlanCore,
4317
4318
  buildAuthInfoFromBearer: () => buildAuthInfoFromBearer,
4318
4319
  cancelPurchaseCore: () => cancelPurchaseCore,
4320
+ checkPurchaseCore: () => checkPurchaseCore,
4319
4321
  createCheckoutSessionCore: () => createCheckoutSessionCore,
4320
4322
  createCustomerSessionCore: () => createCustomerSessionCore,
4321
4323
  createMcpOAuthBridge: () => createMcpOAuthBridge,
4322
4324
  createPaymentIntentCore: () => createPaymentIntentCore,
4323
4325
  createSolvaPay: () => createSolvaPay,
4324
4326
  createSolvaPayClient: () => createSolvaPayClient,
4327
+ createTopupPaymentIntentCore: () => createTopupPaymentIntentCore,
4325
4328
  createVirtualTools: () => createVirtualTools,
4326
4329
  decodeJwtPayload: () => decodeJwtPayload,
4327
4330
  extractBearerToken: () => extractBearerToken,
4328
4331
  getAuthenticatedUserCore: () => getAuthenticatedUserCore,
4332
+ getCustomerBalanceCore: () => getCustomerBalanceCore,
4329
4333
  getCustomerRefFromBearerAuthHeader: () => getCustomerRefFromBearerAuthHeader,
4330
4334
  getCustomerRefFromJwtPayload: () => getCustomerRefFromJwtPayload,
4335
+ getMerchantCore: () => getMerchantCore,
4331
4336
  getOAuthAuthorizationServerResponse: () => getOAuthAuthorizationServerResponse,
4332
4337
  getOAuthProtectedResourceResponse: () => getOAuthProtectedResourceResponse,
4338
+ getProductCore: () => getProductCore,
4333
4339
  handleRouteError: () => handleRouteError,
4334
4340
  isErrorResult: () => isErrorResult,
4335
4341
  jsonSchemaToZodRawShape: () => jsonSchemaToZodRawShape,
4336
4342
  listPlansCore: () => listPlansCore,
4343
+ paywallErrorToClientPayload: () => paywallErrorToClientPayload,
4337
4344
  processPaymentIntentCore: () => processPaymentIntentCore,
4345
+ reactivatePurchaseCore: () => reactivatePurchaseCore,
4338
4346
  registerVirtualToolsMcpImpl: () => registerVirtualToolsMcpImpl,
4339
4347
  syncCustomerCore: () => syncCustomerCore,
4348
+ trackUsageCore: () => trackUsageCore,
4340
4349
  verifyWebhook: () => verifyWebhook,
4341
4350
  withRetry: () => withRetry
4342
4351
  });
4343
4352
  module.exports = __toCommonJS(index_exports);
4344
4353
  var import_node_crypto = __toESM(require("crypto"), 1);
4345
- var import_core6 = require("@solvapay/core");
4354
+ var import_core8 = require("@solvapay/core");
4346
4355
 
4347
4356
  // src/client.ts
4348
4357
  var import_core = require("@solvapay/core");
@@ -4379,12 +4388,10 @@ function createSolvaPayClient(opts) {
4379
4388
  // POST: /v1/sdk/usages
4380
4389
  async trackUsage(params) {
4381
4390
  const url = `${base}/v1/sdk/usages`;
4382
- const { customerRef, ...rest } = params;
4383
- const body = { ...rest, customerId: customerRef };
4384
4391
  const res = await fetch(url, {
4385
4392
  method: "POST",
4386
4393
  headers,
4387
- body: JSON.stringify(body)
4394
+ body: JSON.stringify(params)
4388
4395
  });
4389
4396
  if (!res.ok) {
4390
4397
  const error = await res.text();
@@ -4452,6 +4459,36 @@ function createSolvaPayClient(opts) {
4452
4459
  purchases: customer.purchases || []
4453
4460
  };
4454
4461
  },
4462
+ // GET: /v1/sdk/merchant
4463
+ async getMerchant() {
4464
+ const url = `${base}/v1/sdk/merchant`;
4465
+ const res = await fetch(url, {
4466
+ method: "GET",
4467
+ headers
4468
+ });
4469
+ if (!res.ok) {
4470
+ const error = await res.text();
4471
+ log(`\u274C API Error: ${res.status} - ${error}`);
4472
+ throw new import_core.SolvaPayError(`Get merchant failed (${res.status}): ${error}`);
4473
+ }
4474
+ return res.json();
4475
+ },
4476
+ // GET: /v1/sdk/products/{productRef}
4477
+ async getProduct(productRef) {
4478
+ const url = `${base}/v1/sdk/products/${encodeURIComponent(productRef)}`;
4479
+ const res = await fetch(url, {
4480
+ method: "GET",
4481
+ headers
4482
+ });
4483
+ if (!res.ok) {
4484
+ const error = await res.text();
4485
+ log(`\u274C API Error: ${res.status} - ${error}`);
4486
+ throw new import_core.SolvaPayError(`Get product failed (${res.status}): ${error}`);
4487
+ }
4488
+ const result = await res.json();
4489
+ const data = result.data || {};
4490
+ return { ...data, ...result };
4491
+ },
4455
4492
  // Product management methods (primarily for integration tests)
4456
4493
  // GET: /v1/sdk/products
4457
4494
  async listProducts() {
@@ -4629,7 +4666,7 @@ function createSolvaPayClient(opts) {
4629
4666
  body: JSON.stringify({
4630
4667
  productRef: params.productRef,
4631
4668
  planRef: params.planRef,
4632
- customerReference: params.customerRef
4669
+ customerRef: params.customerRef
4633
4670
  })
4634
4671
  });
4635
4672
  if (!res.ok) {
@@ -4637,8 +4674,32 @@ function createSolvaPayClient(opts) {
4637
4674
  log(`\u274C API Error: ${res.status} - ${error}`);
4638
4675
  throw new import_core.SolvaPayError(`Create payment intent failed (${res.status}): ${error}`);
4639
4676
  }
4640
- const result = await res.json();
4641
- return result;
4677
+ return await res.json();
4678
+ },
4679
+ // POST: /v1/sdk/payment-intents (purpose: credit_topup)
4680
+ async createTopupPaymentIntent(params) {
4681
+ const idempotencyKey = params.idempotencyKey || `topup-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
4682
+ const url = `${base}/v1/sdk/payment-intents`;
4683
+ const res = await fetch(url, {
4684
+ method: "POST",
4685
+ headers: {
4686
+ ...headers,
4687
+ "Idempotency-Key": idempotencyKey
4688
+ },
4689
+ body: JSON.stringify({
4690
+ customerRef: params.customerRef,
4691
+ purpose: "credit_topup",
4692
+ amount: params.amount,
4693
+ currency: params.currency,
4694
+ description: params.description
4695
+ })
4696
+ });
4697
+ if (!res.ok) {
4698
+ const error = await res.text();
4699
+ log(`\u274C API Error: ${res.status} - ${error}`);
4700
+ throw new import_core.SolvaPayError(`Create topup payment intent failed (${res.status}): ${error}`);
4701
+ }
4702
+ return await res.json();
4642
4703
  },
4643
4704
  // POST: /v1/sdk/payment-intents/{paymentIntentId}/process
4644
4705
  async processPaymentIntent(params) {
@@ -4715,6 +4776,54 @@ function createSolvaPayClient(opts) {
4715
4776
  }
4716
4777
  return result;
4717
4778
  },
4779
+ // POST: /v1/sdk/purchases/{purchaseRef}/reactivate
4780
+ async reactivatePurchase(params) {
4781
+ const url = `${base}/v1/sdk/purchases/${params.purchaseRef}/reactivate`;
4782
+ const res = await fetch(url, {
4783
+ method: "POST",
4784
+ headers
4785
+ });
4786
+ if (!res.ok) {
4787
+ const error = await res.text();
4788
+ log(`\u274C API Error: ${res.status} - ${error}`);
4789
+ if (res.status === 404) {
4790
+ throw new import_core.SolvaPayError(`Purchase not found: ${error}`);
4791
+ }
4792
+ if (res.status === 400) {
4793
+ throw new import_core.SolvaPayError(
4794
+ `Purchase cannot be reactivated: ${error}`
4795
+ );
4796
+ }
4797
+ throw new import_core.SolvaPayError(`Reactivate purchase failed (${res.status}): ${error}`);
4798
+ }
4799
+ const responseText = await res.text();
4800
+ let responseData;
4801
+ try {
4802
+ responseData = JSON.parse(responseText);
4803
+ } catch (parseError) {
4804
+ log(`\u274C Failed to parse response as JSON: ${parseError}`);
4805
+ throw new import_core.SolvaPayError(
4806
+ `Invalid JSON response from reactivate purchase endpoint: ${responseText.substring(0, 200)}`
4807
+ );
4808
+ }
4809
+ if (!responseData || typeof responseData !== "object") {
4810
+ log(`\u274C Invalid response structure: ${JSON.stringify(responseData)}`);
4811
+ throw new import_core.SolvaPayError(`Invalid response structure from reactivate purchase endpoint`);
4812
+ }
4813
+ let result;
4814
+ if (responseData.purchase && typeof responseData.purchase === "object") {
4815
+ result = responseData.purchase;
4816
+ } else if (responseData.reference) {
4817
+ result = responseData;
4818
+ } else {
4819
+ result = responseData.purchase || responseData;
4820
+ }
4821
+ if (!result || typeof result !== "object") {
4822
+ log(`\u274C Invalid purchase data in response. Full response:`, responseData);
4823
+ throw new import_core.SolvaPayError(`Invalid purchase data in reactivate purchase response`);
4824
+ }
4825
+ return result;
4826
+ },
4718
4827
  // POST: /v1/sdk/user-info
4719
4828
  async getUserInfo(params) {
4720
4829
  const url = `${base}/v1/sdk/user-info`;
@@ -4730,6 +4839,20 @@ function createSolvaPayClient(opts) {
4730
4839
  }
4731
4840
  return await res.json();
4732
4841
  },
4842
+ // GET: /v1/sdk/customers/:customerRef/balance
4843
+ async getCustomerBalance(params) {
4844
+ const url = `${base}/v1/sdk/customers/${params.customerRef}/balance`;
4845
+ const res = await fetch(url, {
4846
+ method: "GET",
4847
+ headers
4848
+ });
4849
+ if (!res.ok) {
4850
+ const error = await res.text();
4851
+ log(`\u274C API Error: ${res.status} - ${error}`);
4852
+ throw new import_core.SolvaPayError(`Get customer balance failed (${res.status}): ${error}`);
4853
+ }
4854
+ return await res.json();
4855
+ },
4733
4856
  // POST: /v1/sdk/checkout-sessions
4734
4857
  async createCheckoutSession(params) {
4735
4858
  const url = `${base}/v1/sdk/checkout-sessions`;
@@ -4761,6 +4884,21 @@ function createSolvaPayClient(opts) {
4761
4884
  }
4762
4885
  const result = await res.json();
4763
4886
  return result;
4887
+ },
4888
+ // POST: /v1/sdk/activate
4889
+ async activatePlan(params) {
4890
+ const url = `${base}/v1/sdk/activate`;
4891
+ const res = await fetch(url, {
4892
+ method: "POST",
4893
+ headers,
4894
+ body: JSON.stringify(params)
4895
+ });
4896
+ if (!res.ok) {
4897
+ const error = await res.text();
4898
+ log(`\u274C API Error: ${res.status} - ${error}`);
4899
+ throw new import_core.SolvaPayError(`Activate plan failed (${res.status}): ${error}`);
4900
+ }
4901
+ return await res.json();
4764
4902
  }
4765
4903
  };
4766
4904
  }
@@ -4916,6 +5054,24 @@ var PaywallError = class extends Error {
4916
5054
  this.name = "PaywallError";
4917
5055
  }
4918
5056
  };
5057
+ function paywallErrorToClientPayload(error) {
5058
+ const sc = error.structuredContent;
5059
+ const base = {
5060
+ success: false,
5061
+ error: sc.kind === "activation_required" ? "Activation required" : "Payment required",
5062
+ product: sc.product,
5063
+ checkoutUrl: sc.checkoutUrl,
5064
+ message: sc.message
5065
+ };
5066
+ if (sc.kind === "activation_required") {
5067
+ base.kind = "activation_required";
5068
+ if (sc.plans !== void 0) base.plans = sc.plans;
5069
+ if (sc.balance !== void 0) base.balance = sc.balance;
5070
+ if (sc.productDetails !== void 0) base.productDetails = sc.productDetails;
5071
+ if (sc.confirmationUrl !== void 0) base.confirmationUrl = sc.confirmationUrl;
5072
+ }
5073
+ return base;
5074
+ }
4919
5075
  var sharedCustomerLookupDeduplicator = createRequestDeduplicator({
4920
5076
  cacheTTL: 6e4,
4921
5077
  // Cache results for 60 seconds (reduces API calls significantly)
@@ -4954,8 +5110,6 @@ var SolvaPayPaywall = class {
4954
5110
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
4955
5111
  async protect(handler, metadata = {}, getCustomerRef) {
4956
5112
  const product = this.resolveProduct(metadata);
4957
- const configuredPlanRef = metadata.plan?.trim();
4958
- const usagePlanRef = configuredPlanRef || "unspecified";
4959
5113
  const usageType = metadata.usageType || "requests";
4960
5114
  return async (args) => {
4961
5115
  const startTime = Date.now();
@@ -4969,12 +5123,13 @@ var SolvaPayPaywall = class {
4969
5123
  }
4970
5124
  let resolvedMeterName;
4971
5125
  try {
4972
- const limitsCacheKey = `${backendCustomerRef}:${product}:${configuredPlanRef || ""}:${usageType}`;
5126
+ const limitsCacheKey = `${backendCustomerRef}:${product}:${usageType}`;
4973
5127
  const cachedLimits = this.limitsCache.get(limitsCacheKey);
4974
5128
  const now = Date.now();
4975
5129
  let withinLimits;
4976
5130
  let remaining;
4977
5131
  let checkoutUrl;
5132
+ let lastLimitsCheck;
4978
5133
  const hasFreshCachedLimits = cachedLimits && now - cachedLimits.timestamp < this.limitsCacheTTL;
4979
5134
  if (hasFreshCachedLimits) {
4980
5135
  checkoutUrl = cachedLimits.checkoutUrl;
@@ -4998,9 +5153,9 @@ var SolvaPayPaywall = class {
4998
5153
  const limitsCheck = await this.apiClient.checkLimits({
4999
5154
  customerRef: backendCustomerRef,
5000
5155
  productRef: product,
5001
- ...configuredPlanRef ? { planRef: configuredPlanRef } : {},
5002
5156
  meterName: usageType
5003
5157
  });
5158
+ lastLimitsCheck = limitsCheck;
5004
5159
  withinLimits = limitsCheck.withinLimits;
5005
5160
  remaining = limitsCheck.remaining;
5006
5161
  checkoutUrl = limitsCheck.checkoutUrl;
@@ -5023,12 +5178,25 @@ var SolvaPayPaywall = class {
5023
5178
  this.trackUsage(
5024
5179
  backendCustomerRef,
5025
5180
  product,
5026
- usagePlanRef,
5027
5181
  resolvedMeterName || usageType,
5028
5182
  "paywall",
5029
5183
  requestId,
5030
5184
  latencyMs2
5031
5185
  );
5186
+ if (lastLimitsCheck?.activationRequired) {
5187
+ const confirmationUrl = lastLimitsCheck.confirmationUrl;
5188
+ const payCheckoutUrl = confirmationUrl || lastLimitsCheck.checkoutUrl || checkoutUrl || "";
5189
+ throw new PaywallError("Activation required", {
5190
+ kind: "activation_required",
5191
+ product,
5192
+ message: "Product activation is required before this tool can be used.",
5193
+ checkoutUrl: payCheckoutUrl,
5194
+ confirmationUrl,
5195
+ plans: lastLimitsCheck.plans,
5196
+ balance: lastLimitsCheck.balance,
5197
+ productDetails: lastLimitsCheck.product
5198
+ });
5199
+ }
5032
5200
  throw new PaywallError("Payment required", {
5033
5201
  kind: "payment_required",
5034
5202
  product,
@@ -5041,7 +5209,6 @@ var SolvaPayPaywall = class {
5041
5209
  this.trackUsage(
5042
5210
  backendCustomerRef,
5043
5211
  product,
5044
- usagePlanRef,
5045
5212
  resolvedMeterName || usageType,
5046
5213
  "success",
5047
5214
  requestId,
@@ -5060,7 +5227,6 @@ var SolvaPayPaywall = class {
5060
5227
  this.trackUsage(
5061
5228
  backendCustomerRef,
5062
5229
  product,
5063
- usagePlanRef,
5064
5230
  resolvedMeterName || usageType,
5065
5231
  "fail",
5066
5232
  requestId,
@@ -5131,7 +5297,8 @@ var SolvaPayPaywall = class {
5131
5297
  this.customerCreationAttempts.add(customerRef);
5132
5298
  try {
5133
5299
  const createParams = {
5134
- email: options?.email || `${customerRef}-${Date.now()}@auto-created.local`
5300
+ email: options?.email || `${customerRef}-${Date.now()}@auto-created.local`,
5301
+ metadata: {}
5135
5302
  };
5136
5303
  if (options?.name) {
5137
5304
  createParams.name = options.name;
@@ -5155,7 +5322,10 @@ var SolvaPayPaywall = class {
5155
5322
  return searchResult.customerRef;
5156
5323
  }
5157
5324
  } catch (lookupError) {
5158
- this.log(`\u26A0\uFE0F Failed to lookup existing customer by externalRef after 409:`, lookupError instanceof Error ? lookupError.message : lookupError);
5325
+ this.log(
5326
+ `\u26A0\uFE0F Failed to lookup existing customer by externalRef after 409:`,
5327
+ lookupError instanceof Error ? lookupError.message : lookupError
5328
+ );
5159
5329
  }
5160
5330
  }
5161
5331
  const isEmailConflict = errorMessage.includes("email") || errorMessage.includes("identifier email");
@@ -5178,7 +5348,8 @@ var SolvaPayPaywall = class {
5178
5348
  try {
5179
5349
  const retryParams = {
5180
5350
  email: `${customerRef}-${Date.now()}@auto-created.local`,
5181
- externalRef
5351
+ externalRef,
5352
+ metadata: {}
5182
5353
  };
5183
5354
  if (options?.name) {
5184
5355
  retryParams.name = options.name;
@@ -5215,14 +5386,14 @@ var SolvaPayPaywall = class {
5215
5386
  }
5216
5387
  return backendRef;
5217
5388
  }
5218
- async trackUsage(customerRef, _productRef, _planRef, action, outcome, requestId, actionDuration) {
5389
+ async trackUsage(customerRef, productRef, action, outcome, requestId, actionDuration) {
5219
5390
  await withRetry(
5220
5391
  () => this.apiClient.trackUsage({
5221
5392
  customerRef,
5222
5393
  actionType: "api_call",
5223
5394
  units: 1,
5224
5395
  outcome,
5225
- productReference: _productRef,
5396
+ productRef,
5226
5397
  duration: actionDuration,
5227
5398
  metadata: { action: action || "api_requests", requestId },
5228
5399
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
@@ -5474,8 +5645,10 @@ var McpAdapter = class {
5474
5645
  const ref = await this.options.getCustomerRef(args, extra);
5475
5646
  return AdapterUtils.ensureCustomerRef(ref);
5476
5647
  }
5477
- const customerRefFromExtra = extra?.authInfo?.extra?.customer_ref;
5478
- const customerRef = typeof customerRefFromExtra === "string" && customerRefFromExtra.trim() ? customerRefFromExtra.trim() : "anonymous";
5648
+ const customerRefFromExtra = typeof extra?.authInfo?.extra?.customer_ref === "string" ? String(extra.authInfo.extra.customer_ref) : void 0;
5649
+ const customerRefFromArgs = typeof args.auth === "object" && args.auth !== null && typeof args.auth.customer_ref === "string" ? String(args.auth.customer_ref) : void 0;
5650
+ const directCustomerRef = typeof args.customer_ref === "string" ? args.customer_ref : void 0;
5651
+ const customerRef = (customerRefFromExtra || customerRefFromArgs || directCustomerRef || "anonymous").trim();
5479
5652
  return AdapterUtils.ensureCustomerRef(customerRef);
5480
5653
  }
5481
5654
  formatResponse(result, _context) {
@@ -5499,17 +5672,7 @@ var McpAdapter = class {
5499
5672
  content: [
5500
5673
  {
5501
5674
  type: "text",
5502
- text: JSON.stringify(
5503
- {
5504
- success: false,
5505
- error: "Payment required",
5506
- product: error.structuredContent.product,
5507
- checkoutUrl: error.structuredContent.checkoutUrl,
5508
- message: error.structuredContent.message
5509
- },
5510
- null,
5511
- 2
5512
- )
5675
+ text: JSON.stringify(paywallErrorToClientPayload(error), null, 2)
5513
5676
  }
5514
5677
  ],
5515
5678
  isError: true,
@@ -5796,6 +5959,12 @@ function createSolvaPay(config) {
5796
5959
  }
5797
5960
  return apiClient.createPaymentIntent(params);
5798
5961
  },
5962
+ createTopupPaymentIntent(params) {
5963
+ if (!apiClient.createTopupPaymentIntent) {
5964
+ throw new import_core2.SolvaPayError("createTopupPaymentIntent is not available on this API client");
5965
+ }
5966
+ return apiClient.createTopupPaymentIntent(params);
5967
+ },
5799
5968
  processPaymentIntent(params) {
5800
5969
  if (!apiClient.processPaymentIntent) {
5801
5970
  throw new import_core2.SolvaPayError("processPaymentIntent is not available on this API client");
@@ -5812,11 +5981,17 @@ function createSolvaPay(config) {
5812
5981
  if (!apiClient.createCustomer) {
5813
5982
  throw new import_core2.SolvaPayError("createCustomer is not available on this API client");
5814
5983
  }
5815
- return apiClient.createCustomer(params);
5984
+ return apiClient.createCustomer({ ...params, metadata: params.metadata ?? {} });
5816
5985
  },
5817
5986
  getCustomer(params) {
5818
5987
  return apiClient.getCustomer(params);
5819
5988
  },
5989
+ getCustomerBalance(params) {
5990
+ if (!apiClient.getCustomerBalance) {
5991
+ throw new import_core2.SolvaPayError("getCustomerBalance is not available on this API client");
5992
+ }
5993
+ return apiClient.getCustomerBalance(params);
5994
+ },
5820
5995
  createCheckoutSession(params) {
5821
5996
  return apiClient.createCheckoutSession({
5822
5997
  customerRef: params.customerRef,
@@ -5827,6 +6002,12 @@ function createSolvaPay(config) {
5827
6002
  createCustomerSession(params) {
5828
6003
  return apiClient.createCustomerSession(params);
5829
6004
  },
6005
+ activatePlan(params) {
6006
+ if (!apiClient.activatePlan) {
6007
+ throw new import_core2.SolvaPayError("activatePlan is not available on this API client");
6008
+ }
6009
+ return apiClient.activatePlan(params);
6010
+ },
5830
6011
  bootstrapMcpProduct(params) {
5831
6012
  if (!apiClient.bootstrapMcpProduct) {
5832
6013
  throw new import_core2.SolvaPayError("bootstrapMcpProduct is not available on this API client");
@@ -5848,9 +6029,8 @@ function createSolvaPay(config) {
5848
6029
  // Payable API for framework-specific handlers
5849
6030
  payable(options = {}) {
5850
6031
  const product = options.productRef || options.product || process.env.SOLVAPAY_PRODUCT || "default-product";
5851
- const plan = options.planRef || options.plan;
5852
6032
  const usageType = options.usageType || "requests";
5853
- const metadata = { product, plan, usageType };
6033
+ const metadata = { product, usageType };
5854
6034
  return {
5855
6035
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5856
6036
  http(businessLogic, adapterOptions) {
@@ -5914,7 +6094,8 @@ var McpBearerAuthError = class extends Error {
5914
6094
  function base64UrlDecode(input) {
5915
6095
  const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
5916
6096
  const padded = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
5917
- return Buffer.from(padded, "base64").toString("utf8");
6097
+ const bytes = Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
6098
+ return new TextDecoder().decode(bytes);
5918
6099
  }
5919
6100
  function extractBearerToken(authorization) {
5920
6101
  if (!authorization) return null;
@@ -6188,6 +6369,24 @@ async function syncCustomerCore(request, options = {}) {
6188
6369
  return handleRouteError(error, "Sync customer", "Failed to sync customer");
6189
6370
  }
6190
6371
  }
6372
+ async function getCustomerBalanceCore(request, options = {}) {
6373
+ try {
6374
+ const userResult = await getAuthenticatedUserCore(request);
6375
+ if (isErrorResult(userResult)) {
6376
+ return userResult;
6377
+ }
6378
+ const { userId, email, name } = userResult;
6379
+ const solvaPay = options.solvaPay || createSolvaPay();
6380
+ const customerRef = await solvaPay.ensureCustomer(userId, userId, {
6381
+ email: email || void 0,
6382
+ name: name || void 0
6383
+ });
6384
+ const result = await solvaPay.getCustomerBalance({ customerRef });
6385
+ return result;
6386
+ } catch (error) {
6387
+ return handleRouteError(error, "Get customer credits", "Failed to get customer credits");
6388
+ }
6389
+ }
6191
6390
 
6192
6391
  // src/helpers/payment.ts
6193
6392
  async function createPaymentIntentCore(request, body, options = {}) {
@@ -6214,7 +6413,7 @@ async function createPaymentIntentCore(request, body, options = {}) {
6214
6413
  customerRef
6215
6414
  });
6216
6415
  return {
6217
- id: paymentIntent.id,
6416
+ processorPaymentId: paymentIntent.processorPaymentId,
6218
6417
  clientSecret: paymentIntent.clientSecret,
6219
6418
  publishableKey: paymentIntent.publishableKey,
6220
6419
  accountId: paymentIntent.accountId,
@@ -6224,6 +6423,57 @@ async function createPaymentIntentCore(request, body, options = {}) {
6224
6423
  return handleRouteError(error, "Create payment intent", "Payment intent creation failed");
6225
6424
  }
6226
6425
  }
6426
+ async function createTopupPaymentIntentCore(request, body, options = {}) {
6427
+ try {
6428
+ if (!body.amount || body.amount <= 0) {
6429
+ return {
6430
+ error: "Missing or invalid amount: must be a positive number",
6431
+ status: 400
6432
+ };
6433
+ }
6434
+ if (!body.currency) {
6435
+ return {
6436
+ error: "Missing required parameter: currency",
6437
+ status: 400
6438
+ };
6439
+ }
6440
+ if (body.currency !== body.currency.toUpperCase()) {
6441
+ return {
6442
+ error: `Invalid currency "${body.currency}": must be an uppercase ISO 4217 code (e.g. "USD", "EUR")`,
6443
+ status: 400
6444
+ };
6445
+ }
6446
+ const customerResult = await syncCustomerCore(request, {
6447
+ solvaPay: options.solvaPay,
6448
+ includeEmail: options.includeEmail,
6449
+ includeName: options.includeName
6450
+ });
6451
+ if (isErrorResult(customerResult)) {
6452
+ return customerResult;
6453
+ }
6454
+ const customerRef = customerResult;
6455
+ const solvaPay = options.solvaPay || createSolvaPay();
6456
+ const paymentIntent = await solvaPay.createTopupPaymentIntent({
6457
+ customerRef,
6458
+ amount: body.amount,
6459
+ currency: body.currency,
6460
+ description: body.description
6461
+ });
6462
+ return {
6463
+ processorPaymentId: paymentIntent.processorPaymentId,
6464
+ clientSecret: paymentIntent.clientSecret,
6465
+ publishableKey: paymentIntent.publishableKey,
6466
+ accountId: paymentIntent.accountId,
6467
+ customerRef
6468
+ };
6469
+ } catch (error) {
6470
+ return handleRouteError(
6471
+ error,
6472
+ "Create topup payment intent",
6473
+ "Topup payment intent creation failed"
6474
+ );
6475
+ }
6476
+ }
6227
6477
  async function processPaymentIntentCore(request, body, options = {}) {
6228
6478
  try {
6229
6479
  if (!body.paymentIntentId || !body.productRef) {
@@ -6386,10 +6636,107 @@ async function cancelPurchaseCore(request, body, options = {}) {
6386
6636
  return handleRouteError(error, "Cancel purchase", "Failed to cancel purchase");
6387
6637
  }
6388
6638
  }
6639
+ async function reactivatePurchaseCore(request, body, options = {}) {
6640
+ try {
6641
+ if (!body.purchaseRef) {
6642
+ return {
6643
+ error: "Missing required parameter: purchaseRef is required",
6644
+ status: 400
6645
+ };
6646
+ }
6647
+ const solvaPay = options.solvaPay || createSolvaPay();
6648
+ if (!solvaPay.apiClient.reactivatePurchase) {
6649
+ return {
6650
+ error: "Reactivate purchase method not available on SDK client",
6651
+ status: 500
6652
+ };
6653
+ }
6654
+ let reactivatedPurchase = await solvaPay.apiClient.reactivatePurchase({
6655
+ purchaseRef: body.purchaseRef
6656
+ });
6657
+ if (!reactivatedPurchase || typeof reactivatedPurchase !== "object") {
6658
+ return {
6659
+ error: "Invalid response from reactivate purchase endpoint",
6660
+ status: 500
6661
+ };
6662
+ }
6663
+ const responseObj = reactivatedPurchase;
6664
+ if (responseObj.purchase && typeof responseObj.purchase === "object") {
6665
+ reactivatedPurchase = responseObj.purchase;
6666
+ }
6667
+ if (!reactivatedPurchase.reference) {
6668
+ return {
6669
+ error: "Reactivate purchase response missing required fields",
6670
+ status: 500
6671
+ };
6672
+ }
6673
+ if (reactivatedPurchase.cancelledAt) {
6674
+ return {
6675
+ error: `Purchase reactivation failed: cancelledAt is still set`,
6676
+ status: 500
6677
+ };
6678
+ }
6679
+ await new Promise((resolve) => setTimeout(resolve, 500));
6680
+ return reactivatedPurchase;
6681
+ } catch (error) {
6682
+ if (error instanceof import_core4.SolvaPayError) {
6683
+ const errorMessage = error.message;
6684
+ if (errorMessage.includes("not found")) {
6685
+ return {
6686
+ error: "Purchase not found",
6687
+ status: 404,
6688
+ details: errorMessage
6689
+ };
6690
+ }
6691
+ if (errorMessage.includes("cannot be reactivated") || errorMessage.includes("not pending cancellation") || errorMessage.includes("already been fully cancelled") || errorMessage.includes("already ended")) {
6692
+ return {
6693
+ error: "Purchase cannot be reactivated",
6694
+ status: 400,
6695
+ details: errorMessage
6696
+ };
6697
+ }
6698
+ return {
6699
+ error: errorMessage,
6700
+ status: 500,
6701
+ details: errorMessage
6702
+ };
6703
+ }
6704
+ return handleRouteError(error, "Reactivate purchase", "Failed to reactivate purchase");
6705
+ }
6706
+ }
6707
+
6708
+ // src/helpers/activation.ts
6709
+ async function activatePlanCore(request, body, options = {}) {
6710
+ try {
6711
+ if (!body.productRef || !body.planRef) {
6712
+ return {
6713
+ error: "Missing required parameters: productRef and planRef are required",
6714
+ status: 400
6715
+ };
6716
+ }
6717
+ const customerResult = await syncCustomerCore(request, {
6718
+ solvaPay: options.solvaPay,
6719
+ includeEmail: options.includeEmail,
6720
+ includeName: options.includeName
6721
+ });
6722
+ if (isErrorResult(customerResult)) {
6723
+ return customerResult;
6724
+ }
6725
+ const customerRef = customerResult;
6726
+ const solvaPay = options.solvaPay || createSolvaPay();
6727
+ return await solvaPay.activatePlan({
6728
+ customerRef,
6729
+ productRef: body.productRef,
6730
+ planRef: body.planRef
6731
+ });
6732
+ } catch (error) {
6733
+ return handleRouteError(error, "Activate plan", "Plan activation failed");
6734
+ }
6735
+ }
6389
6736
 
6390
6737
  // src/helpers/plans.ts
6391
6738
  var import_core5 = require("@solvapay/core");
6392
- async function listPlansCore(request) {
6739
+ async function listPlansCore(request, options = {}) {
6393
6740
  try {
6394
6741
  const url = new URL(request.url);
6395
6742
  const productRef = url.searchParams.get("productRef");
@@ -6399,19 +6746,20 @@ async function listPlansCore(request) {
6399
6746
  status: 400
6400
6747
  };
6401
6748
  }
6402
- const config = (0, import_core5.getSolvaPayConfig)();
6403
- const solvapaySecretKey = config.apiKey;
6404
- const solvapayApiBaseUrl = config.apiBaseUrl;
6405
- if (!solvapaySecretKey) {
6749
+ const apiClient = options.solvaPay?.apiClient ?? (() => {
6750
+ const config = (0, import_core5.getSolvaPayConfig)();
6751
+ if (!config.apiKey) return null;
6752
+ return createSolvaPayClient({
6753
+ apiKey: config.apiKey,
6754
+ apiBaseUrl: config.apiBaseUrl
6755
+ });
6756
+ })();
6757
+ if (!apiClient) {
6406
6758
  return {
6407
6759
  error: "Server configuration error: SolvaPay secret key not configured",
6408
6760
  status: 500
6409
6761
  };
6410
6762
  }
6411
- const apiClient = createSolvaPayClient({
6412
- apiKey: solvapaySecretKey,
6413
- apiBaseUrl: solvapayApiBaseUrl
6414
- });
6415
6763
  if (!apiClient.listPlans) {
6416
6764
  return {
6417
6765
  error: "List plans method not available",
@@ -6428,6 +6776,159 @@ async function listPlansCore(request) {
6428
6776
  }
6429
6777
  }
6430
6778
 
6779
+ // src/helpers/merchant.ts
6780
+ var import_core6 = require("@solvapay/core");
6781
+ async function getMerchantCore(_request, options = {}) {
6782
+ try {
6783
+ const apiClient = options.solvaPay?.apiClient ?? (() => {
6784
+ const config = (0, import_core6.getSolvaPayConfig)();
6785
+ if (!config.apiKey) return null;
6786
+ return createSolvaPayClient({
6787
+ apiKey: config.apiKey,
6788
+ apiBaseUrl: config.apiBaseUrl
6789
+ });
6790
+ })();
6791
+ if (!apiClient) {
6792
+ return {
6793
+ error: "Server configuration error: SolvaPay secret key not configured",
6794
+ status: 500
6795
+ };
6796
+ }
6797
+ if (!apiClient.getMerchant) {
6798
+ return {
6799
+ error: "Get merchant method not available",
6800
+ status: 500
6801
+ };
6802
+ }
6803
+ const merchant = await apiClient.getMerchant();
6804
+ return merchant;
6805
+ } catch (error) {
6806
+ return handleRouteError(error, "Get merchant", "Failed to fetch merchant");
6807
+ }
6808
+ }
6809
+
6810
+ // src/helpers/product.ts
6811
+ var import_core7 = require("@solvapay/core");
6812
+ async function getProductCore(request, options = {}) {
6813
+ try {
6814
+ const url = new URL(request.url);
6815
+ const productRef = url.searchParams.get("productRef");
6816
+ if (!productRef) {
6817
+ return {
6818
+ error: "Missing required parameter: productRef",
6819
+ status: 400
6820
+ };
6821
+ }
6822
+ const apiClient = options.solvaPay?.apiClient ?? (() => {
6823
+ const config = (0, import_core7.getSolvaPayConfig)();
6824
+ if (!config.apiKey) return null;
6825
+ return createSolvaPayClient({
6826
+ apiKey: config.apiKey,
6827
+ apiBaseUrl: config.apiBaseUrl
6828
+ });
6829
+ })();
6830
+ if (!apiClient) {
6831
+ return {
6832
+ error: "Server configuration error: SolvaPay secret key not configured",
6833
+ status: 500
6834
+ };
6835
+ }
6836
+ if (!apiClient.getProduct) {
6837
+ return {
6838
+ error: "Get product method not available",
6839
+ status: 500
6840
+ };
6841
+ }
6842
+ const product = await apiClient.getProduct(productRef);
6843
+ return product;
6844
+ } catch (error) {
6845
+ return handleRouteError(error, "Get product", "Failed to fetch product");
6846
+ }
6847
+ }
6848
+
6849
+ // src/helpers/purchase.ts
6850
+ async function checkPurchaseCore(request, options = {}) {
6851
+ try {
6852
+ const userResult = await getAuthenticatedUserCore(request, {
6853
+ includeEmail: options.includeEmail,
6854
+ includeName: options.includeName
6855
+ });
6856
+ if (isErrorResult(userResult)) {
6857
+ return userResult;
6858
+ }
6859
+ const { userId, email, name } = userResult;
6860
+ const solvaPay = options.solvaPay || createSolvaPay();
6861
+ const cachedCustomerRef = request.headers.get("x-solvapay-customer-ref");
6862
+ if (cachedCustomerRef) {
6863
+ try {
6864
+ const customer = await solvaPay.getCustomer({ customerRef: cachedCustomerRef });
6865
+ if (customer && customer.customerRef) {
6866
+ if (customer.externalRef && customer.externalRef === userId) {
6867
+ const filteredPurchases = (customer.purchases || []).filter(
6868
+ (p) => p.status === "active"
6869
+ );
6870
+ return {
6871
+ customerRef: customer.customerRef,
6872
+ email: customer.email,
6873
+ name: customer.name,
6874
+ purchases: filteredPurchases
6875
+ };
6876
+ }
6877
+ }
6878
+ } catch {
6879
+ }
6880
+ }
6881
+ try {
6882
+ const customerRef = await solvaPay.ensureCustomer(userId, userId, {
6883
+ email: email || void 0,
6884
+ name: name || void 0
6885
+ });
6886
+ const customer = await solvaPay.getCustomer({ customerRef });
6887
+ const filteredPurchases = (customer.purchases || []).filter((p) => p.status === "active");
6888
+ return {
6889
+ customerRef: customer.customerRef || userId,
6890
+ email: customer.email,
6891
+ name: customer.name,
6892
+ purchases: filteredPurchases
6893
+ };
6894
+ } catch {
6895
+ return {
6896
+ customerRef: userId,
6897
+ purchases: []
6898
+ };
6899
+ }
6900
+ } catch (error) {
6901
+ return handleRouteError(error, "Check purchase", "Failed to check purchase");
6902
+ }
6903
+ }
6904
+
6905
+ // src/helpers/usage.ts
6906
+ async function trackUsageCore(request, body, options = {}) {
6907
+ try {
6908
+ const userResult = await getAuthenticatedUserCore(request);
6909
+ if (isErrorResult(userResult)) {
6910
+ return userResult;
6911
+ }
6912
+ const { userId, email, name } = userResult;
6913
+ const solvaPay = options.solvaPay || createSolvaPay();
6914
+ const customerRef = await solvaPay.ensureCustomer(userId, userId, {
6915
+ email: email || void 0,
6916
+ name: name || void 0
6917
+ });
6918
+ await solvaPay.trackUsage({
6919
+ customerRef,
6920
+ actionType: body.actionType,
6921
+ units: body.units,
6922
+ productRef: body.productRef,
6923
+ description: body.description,
6924
+ metadata: body.metadata
6925
+ });
6926
+ return { success: true };
6927
+ } catch (error) {
6928
+ return handleRouteError(error, "Track usage", "Track usage failed");
6929
+ }
6930
+ }
6931
+
6431
6932
  // src/index.ts
6432
6933
  function verifyWebhook({
6433
6934
  body,
@@ -6435,34 +6936,34 @@ function verifyWebhook({
6435
6936
  secret
6436
6937
  }) {
6437
6938
  const toleranceSec = 300;
6438
- if (!signature) throw new import_core6.SolvaPayError("Missing webhook signature");
6939
+ if (!signature) throw new import_core8.SolvaPayError("Missing webhook signature");
6439
6940
  const parts = signature.split(",");
6440
6941
  const tPart = parts.find((p) => p.startsWith("t="));
6441
6942
  const v1Part = parts.find((p) => p.startsWith("v1="));
6442
6943
  if (!tPart || !v1Part) {
6443
- throw new import_core6.SolvaPayError("Malformed webhook signature");
6944
+ throw new import_core8.SolvaPayError("Malformed webhook signature");
6444
6945
  }
6445
6946
  const timestamp = parseInt(tPart.slice(2), 10);
6446
6947
  const receivedHmac = v1Part.slice(3);
6447
6948
  if (Number.isNaN(timestamp) || !receivedHmac) {
6448
- throw new import_core6.SolvaPayError("Malformed webhook signature");
6949
+ throw new import_core8.SolvaPayError("Malformed webhook signature");
6449
6950
  }
6450
6951
  if (toleranceSec > 0) {
6451
6952
  const age = Math.abs(Math.floor(Date.now() / 1e3) - timestamp);
6452
6953
  if (age > toleranceSec) {
6453
- throw new import_core6.SolvaPayError("Webhook signature timestamp too old");
6954
+ throw new import_core8.SolvaPayError("Webhook signature timestamp too old");
6454
6955
  }
6455
6956
  }
6456
6957
  const expectedHmac = import_node_crypto.default.createHmac("sha256", secret).update(`${timestamp}.${body}`).digest("hex");
6457
6958
  if (receivedHmac.length !== expectedHmac.length) {
6458
- throw new import_core6.SolvaPayError("Invalid webhook signature");
6959
+ throw new import_core8.SolvaPayError("Invalid webhook signature");
6459
6960
  }
6460
6961
  const ok = import_node_crypto.default.timingSafeEqual(Buffer.from(expectedHmac), Buffer.from(receivedHmac));
6461
- if (!ok) throw new import_core6.SolvaPayError("Invalid webhook signature");
6962
+ if (!ok) throw new import_core8.SolvaPayError("Invalid webhook signature");
6462
6963
  try {
6463
6964
  return JSON.parse(body);
6464
6965
  } catch {
6465
- throw new import_core6.SolvaPayError("Invalid webhook payload: body is not valid JSON");
6966
+ throw new import_core8.SolvaPayError("Invalid webhook payload: body is not valid JSON");
6466
6967
  }
6467
6968
  }
6468
6969
  // Annotate the CommonJS export names for ESM import in node:
@@ -6470,29 +6971,38 @@ function verifyWebhook({
6470
6971
  McpBearerAuthError,
6471
6972
  PaywallError,
6472
6973
  VIRTUAL_TOOL_DEFINITIONS,
6974
+ activatePlanCore,
6473
6975
  buildAuthInfoFromBearer,
6474
6976
  cancelPurchaseCore,
6977
+ checkPurchaseCore,
6475
6978
  createCheckoutSessionCore,
6476
6979
  createCustomerSessionCore,
6477
6980
  createMcpOAuthBridge,
6478
6981
  createPaymentIntentCore,
6479
6982
  createSolvaPay,
6480
6983
  createSolvaPayClient,
6984
+ createTopupPaymentIntentCore,
6481
6985
  createVirtualTools,
6482
6986
  decodeJwtPayload,
6483
6987
  extractBearerToken,
6484
6988
  getAuthenticatedUserCore,
6989
+ getCustomerBalanceCore,
6485
6990
  getCustomerRefFromBearerAuthHeader,
6486
6991
  getCustomerRefFromJwtPayload,
6992
+ getMerchantCore,
6487
6993
  getOAuthAuthorizationServerResponse,
6488
6994
  getOAuthProtectedResourceResponse,
6995
+ getProductCore,
6489
6996
  handleRouteError,
6490
6997
  isErrorResult,
6491
6998
  jsonSchemaToZodRawShape,
6492
6999
  listPlansCore,
7000
+ paywallErrorToClientPayload,
6493
7001
  processPaymentIntentCore,
7002
+ reactivatePurchaseCore,
6494
7003
  registerVirtualToolsMcpImpl,
6495
7004
  syncCustomerCore,
7005
+ trackUsageCore,
6496
7006
  verifyWebhook,
6497
7007
  withRetry
6498
7008
  });