@solvapay/server 1.0.0-preview.2 → 1.0.0-preview.21
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/LICENSE.md +21 -0
- package/README.md +97 -32
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/edge.d.ts +2723 -287
- package/dist/edge.js +1192 -251
- package/dist/{esm-5GYCIXIY.js → esm-UW7WCMEK.js} +1 -1
- package/dist/index.cjs +1269 -246
- package/dist/index.d.cts +2793 -287
- package/dist/index.d.ts +2793 -287
- package/dist/index.js +1255 -251
- package/package.json +16 -6
- package/dist/chunk-R5U7XKVJ.js +0 -16
package/dist/index.cjs
CHANGED
|
@@ -4254,24 +4254,41 @@ var init_esm = __esm({
|
|
|
4254
4254
|
// src/index.ts
|
|
4255
4255
|
var index_exports = {};
|
|
4256
4256
|
__export(index_exports, {
|
|
4257
|
+
McpBearerAuthError: () => McpBearerAuthError,
|
|
4257
4258
|
PaywallError: () => PaywallError,
|
|
4259
|
+
VIRTUAL_TOOL_DEFINITIONS: () => VIRTUAL_TOOL_DEFINITIONS,
|
|
4260
|
+
cancelPurchaseCore: () => cancelPurchaseCore,
|
|
4261
|
+
createCheckoutSessionCore: () => createCheckoutSessionCore,
|
|
4262
|
+
createCustomerSessionCore: () => createCustomerSessionCore,
|
|
4263
|
+
createPaymentIntentCore: () => createPaymentIntentCore,
|
|
4258
4264
|
createSolvaPay: () => createSolvaPay,
|
|
4259
4265
|
createSolvaPayClient: () => createSolvaPayClient,
|
|
4266
|
+
createVirtualTools: () => createVirtualTools,
|
|
4267
|
+
decodeJwtPayload: () => decodeJwtPayload,
|
|
4268
|
+
extractBearerToken: () => extractBearerToken,
|
|
4269
|
+
getAuthenticatedUserCore: () => getAuthenticatedUserCore,
|
|
4270
|
+
getCustomerRefFromBearerAuthHeader: () => getCustomerRefFromBearerAuthHeader,
|
|
4271
|
+
getCustomerRefFromJwtPayload: () => getCustomerRefFromJwtPayload,
|
|
4272
|
+
handleRouteError: () => handleRouteError,
|
|
4273
|
+
isErrorResult: () => isErrorResult,
|
|
4274
|
+
listPlansCore: () => listPlansCore,
|
|
4275
|
+
processPaymentIntentCore: () => processPaymentIntentCore,
|
|
4276
|
+
syncCustomerCore: () => syncCustomerCore,
|
|
4260
4277
|
verifyWebhook: () => verifyWebhook,
|
|
4261
4278
|
withRetry: () => withRetry
|
|
4262
4279
|
});
|
|
4263
4280
|
module.exports = __toCommonJS(index_exports);
|
|
4264
4281
|
var import_node_crypto20 = __toESM(require("crypto"), 1);
|
|
4265
|
-
var
|
|
4282
|
+
var import_core6 = require("@solvapay/core");
|
|
4266
4283
|
|
|
4267
4284
|
// src/client.ts
|
|
4268
4285
|
var import_core = require("@solvapay/core");
|
|
4269
4286
|
function createSolvaPayClient(opts) {
|
|
4270
|
-
const base = opts.apiBaseUrl ?? "https://api
|
|
4287
|
+
const base = opts.apiBaseUrl ?? "https://api.solvapay.com";
|
|
4271
4288
|
if (!opts.apiKey) throw new import_core.SolvaPayError("Missing apiKey");
|
|
4272
4289
|
const headers = {
|
|
4273
4290
|
"Content-Type": "application/json",
|
|
4274
|
-
|
|
4291
|
+
Authorization: `Bearer ${opts.apiKey}`
|
|
4275
4292
|
};
|
|
4276
4293
|
const debug = process.env.SOLVAPAY_DEBUG === "true";
|
|
4277
4294
|
const log = (...args) => {
|
|
@@ -4279,15 +4296,10 @@ function createSolvaPayClient(opts) {
|
|
|
4279
4296
|
console.log(...args);
|
|
4280
4297
|
}
|
|
4281
4298
|
};
|
|
4282
|
-
log(`\u{1F50C} SolvaPay API Client initialized`);
|
|
4283
|
-
log(` Backend URL: ${base}`);
|
|
4284
|
-
log(` API Key: ${opts.apiKey.substring(0, 10)}...`);
|
|
4285
4299
|
return {
|
|
4286
4300
|
// POST: /v1/sdk/limits
|
|
4287
4301
|
async checkLimits(params) {
|
|
4288
4302
|
const url = `${base}/v1/sdk/limits`;
|
|
4289
|
-
log(`\u{1F4E1} API Request: POST ${url}`);
|
|
4290
|
-
log(` Params:`, JSON.stringify(params, null, 2));
|
|
4291
4303
|
const res = await fetch(url, {
|
|
4292
4304
|
method: "POST",
|
|
4293
4305
|
headers,
|
|
@@ -4299,37 +4311,27 @@ function createSolvaPayClient(opts) {
|
|
|
4299
4311
|
throw new import_core.SolvaPayError(`Check limits failed (${res.status}): ${error}`);
|
|
4300
4312
|
}
|
|
4301
4313
|
const result = await res.json();
|
|
4302
|
-
log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
4303
|
-
log(`\u{1F50D} DEBUG - checkLimits breakdown:`);
|
|
4304
|
-
log(` - withinLimits: ${result.withinLimits}`);
|
|
4305
|
-
log(` - remaining: ${result.remaining}`);
|
|
4306
|
-
log(` - plan: ${result.plan || "N/A"}`);
|
|
4307
|
-
log(` - checkoutUrl: ${result.checkoutUrl || "N/A"}`);
|
|
4308
|
-
log(` - Full response keys:`, Object.keys(result));
|
|
4309
4314
|
return result;
|
|
4310
4315
|
},
|
|
4311
4316
|
// POST: /v1/sdk/usages
|
|
4312
4317
|
async trackUsage(params) {
|
|
4313
4318
|
const url = `${base}/v1/sdk/usages`;
|
|
4314
|
-
|
|
4315
|
-
|
|
4319
|
+
const { customerRef, ...rest } = params;
|
|
4320
|
+
const body = { ...rest, customerId: customerRef };
|
|
4316
4321
|
const res = await fetch(url, {
|
|
4317
4322
|
method: "POST",
|
|
4318
4323
|
headers,
|
|
4319
|
-
body: JSON.stringify(
|
|
4324
|
+
body: JSON.stringify(body)
|
|
4320
4325
|
});
|
|
4321
4326
|
if (!res.ok) {
|
|
4322
4327
|
const error = await res.text();
|
|
4323
4328
|
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
4324
4329
|
throw new import_core.SolvaPayError(`Track usage failed (${res.status}): ${error}`);
|
|
4325
4330
|
}
|
|
4326
|
-
log(`\u2705 Usage tracked successfully`);
|
|
4327
4331
|
},
|
|
4328
4332
|
// POST: /v1/sdk/customers
|
|
4329
4333
|
async createCustomer(params) {
|
|
4330
4334
|
const url = `${base}/v1/sdk/customers`;
|
|
4331
|
-
log(`\u{1F4E1} API Request: POST ${url}`);
|
|
4332
|
-
log(` Params:`, JSON.stringify(params, null, 2));
|
|
4333
4335
|
const res = await fetch(url, {
|
|
4334
4336
|
method: "POST",
|
|
4335
4337
|
headers,
|
|
@@ -4341,18 +4343,24 @@ function createSolvaPayClient(opts) {
|
|
|
4341
4343
|
throw new import_core.SolvaPayError(`Create customer failed (${res.status}): ${error}`);
|
|
4342
4344
|
}
|
|
4343
4345
|
const result = await res.json();
|
|
4344
|
-
log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
4345
|
-
log(`\u{1F50D} DEBUG - createCustomer response:`);
|
|
4346
|
-
log(` - reference/customerRef: ${result.reference || result.customerRef}`);
|
|
4347
|
-
log(` - Has plan info: ${result.plan ? "YES" : "NO"}`);
|
|
4348
|
-
log(` - Has subscription info: ${result.subscription ? "YES" : "NO"}`);
|
|
4349
|
-
log(` - Full response keys:`, Object.keys(result));
|
|
4350
4346
|
return result;
|
|
4351
4347
|
},
|
|
4352
|
-
// GET: /v1/sdk/customers/{reference}
|
|
4348
|
+
// GET: /v1/sdk/customers/{reference} or /v1/sdk/customers?externalRef={externalRef}|email={email}
|
|
4353
4349
|
async getCustomer(params) {
|
|
4354
|
-
|
|
4355
|
-
|
|
4350
|
+
let url;
|
|
4351
|
+
let isByExternalRef = false;
|
|
4352
|
+
let isByEmail = false;
|
|
4353
|
+
if (params.externalRef) {
|
|
4354
|
+
url = `${base}/v1/sdk/customers?externalRef=${encodeURIComponent(params.externalRef)}`;
|
|
4355
|
+
isByExternalRef = true;
|
|
4356
|
+
} else if (params.email) {
|
|
4357
|
+
url = `${base}/v1/sdk/customers?email=${encodeURIComponent(params.email)}`;
|
|
4358
|
+
isByEmail = true;
|
|
4359
|
+
} else if (params.customerRef) {
|
|
4360
|
+
url = `${base}/v1/sdk/customers/${params.customerRef}`;
|
|
4361
|
+
} else {
|
|
4362
|
+
throw new import_core.SolvaPayError("One of customerRef, externalRef, or email must be provided");
|
|
4363
|
+
}
|
|
4356
4364
|
const res = await fetch(url, {
|
|
4357
4365
|
method: "GET",
|
|
4358
4366
|
headers
|
|
@@ -4363,14 +4371,28 @@ function createSolvaPayClient(opts) {
|
|
|
4363
4371
|
throw new import_core.SolvaPayError(`Get customer failed (${res.status}): ${error}`);
|
|
4364
4372
|
}
|
|
4365
4373
|
const result = await res.json();
|
|
4366
|
-
|
|
4367
|
-
|
|
4374
|
+
let customer = result;
|
|
4375
|
+
if (isByExternalRef || isByEmail) {
|
|
4376
|
+
const directCustomer = result && typeof result === "object" && (result.reference || result.customerRef || result.externalRef) ? result : void 0;
|
|
4377
|
+
const wrappedCustomer = result && typeof result === "object" && result.customer ? result.customer : void 0;
|
|
4378
|
+
const customers = Array.isArray(result) ? result : result && typeof result === "object" && Array.isArray(result.customers) ? result.customers : [];
|
|
4379
|
+
customer = directCustomer || wrappedCustomer || customers[0];
|
|
4380
|
+
if (!customer) {
|
|
4381
|
+
throw new import_core.SolvaPayError(`No customer found with externalRef: ${params.externalRef}`);
|
|
4382
|
+
}
|
|
4383
|
+
}
|
|
4384
|
+
return {
|
|
4385
|
+
customerRef: customer.reference || customer.customerRef,
|
|
4386
|
+
email: customer.email,
|
|
4387
|
+
name: customer.name,
|
|
4388
|
+
externalRef: customer.externalRef,
|
|
4389
|
+
purchases: customer.purchases || []
|
|
4390
|
+
};
|
|
4368
4391
|
},
|
|
4369
|
-
//
|
|
4370
|
-
// GET: /v1/sdk/
|
|
4371
|
-
async
|
|
4372
|
-
const url = `${base}/v1/sdk/
|
|
4373
|
-
log(`\u{1F4E1} API Request: GET ${url}`);
|
|
4392
|
+
// Product management methods (primarily for integration tests)
|
|
4393
|
+
// GET: /v1/sdk/products
|
|
4394
|
+
async listProducts() {
|
|
4395
|
+
const url = `${base}/v1/sdk/products`;
|
|
4374
4396
|
const res = await fetch(url, {
|
|
4375
4397
|
method: "GET",
|
|
4376
4398
|
headers
|
|
@@ -4378,21 +4400,18 @@ function createSolvaPayClient(opts) {
|
|
|
4378
4400
|
if (!res.ok) {
|
|
4379
4401
|
const error = await res.text();
|
|
4380
4402
|
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
4381
|
-
throw new import_core.SolvaPayError(`List
|
|
4403
|
+
throw new import_core.SolvaPayError(`List products failed (${res.status}): ${error}`);
|
|
4382
4404
|
}
|
|
4383
4405
|
const result = await res.json();
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
...
|
|
4388
|
-
...agent.data || {}
|
|
4406
|
+
const products = Array.isArray(result) ? result : result.products || [];
|
|
4407
|
+
return products.map((product) => ({
|
|
4408
|
+
...product,
|
|
4409
|
+
...product.data || {}
|
|
4389
4410
|
}));
|
|
4390
4411
|
},
|
|
4391
|
-
// POST: /v1/sdk/
|
|
4392
|
-
async
|
|
4393
|
-
const url = `${base}/v1/sdk/
|
|
4394
|
-
log(`\u{1F4E1} API Request: POST ${url}`);
|
|
4395
|
-
log(` Params:`, JSON.stringify(params, null, 2));
|
|
4412
|
+
// POST: /v1/sdk/products
|
|
4413
|
+
async createProduct(params) {
|
|
4414
|
+
const url = `${base}/v1/sdk/products`;
|
|
4396
4415
|
const res = await fetch(url, {
|
|
4397
4416
|
method: "POST",
|
|
4398
4417
|
headers,
|
|
@@ -4401,16 +4420,29 @@ function createSolvaPayClient(opts) {
|
|
|
4401
4420
|
if (!res.ok) {
|
|
4402
4421
|
const error = await res.text();
|
|
4403
4422
|
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
4404
|
-
throw new import_core.SolvaPayError(`Create
|
|
4423
|
+
throw new import_core.SolvaPayError(`Create product failed (${res.status}): ${error}`);
|
|
4405
4424
|
}
|
|
4406
4425
|
const result = await res.json();
|
|
4407
|
-
log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
4408
4426
|
return result;
|
|
4409
4427
|
},
|
|
4410
|
-
//
|
|
4411
|
-
async
|
|
4412
|
-
const url = `${base}/v1/sdk/
|
|
4413
|
-
|
|
4428
|
+
// POST: /v1/sdk/products/mcp/bootstrap
|
|
4429
|
+
async bootstrapMcpProduct(params) {
|
|
4430
|
+
const url = `${base}/v1/sdk/products/mcp/bootstrap`;
|
|
4431
|
+
const res = await fetch(url, {
|
|
4432
|
+
method: "POST",
|
|
4433
|
+
headers,
|
|
4434
|
+
body: JSON.stringify(params)
|
|
4435
|
+
});
|
|
4436
|
+
if (!res.ok) {
|
|
4437
|
+
const error = await res.text();
|
|
4438
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
4439
|
+
throw new import_core.SolvaPayError(`Bootstrap MCP product failed (${res.status}): ${error}`);
|
|
4440
|
+
}
|
|
4441
|
+
return await res.json();
|
|
4442
|
+
},
|
|
4443
|
+
// DELETE: /v1/sdk/products/{productRef}
|
|
4444
|
+
async deleteProduct(productRef) {
|
|
4445
|
+
const url = `${base}/v1/sdk/products/${productRef}`;
|
|
4414
4446
|
const res = await fetch(url, {
|
|
4415
4447
|
method: "DELETE",
|
|
4416
4448
|
headers
|
|
@@ -4418,14 +4450,27 @@ function createSolvaPayClient(opts) {
|
|
|
4418
4450
|
if (!res.ok && res.status !== 404) {
|
|
4419
4451
|
const error = await res.text();
|
|
4420
4452
|
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
4421
|
-
throw new import_core.SolvaPayError(`Delete
|
|
4453
|
+
throw new import_core.SolvaPayError(`Delete product failed (${res.status}): ${error}`);
|
|
4454
|
+
}
|
|
4455
|
+
},
|
|
4456
|
+
// POST: /v1/sdk/products/{productRef}/clone
|
|
4457
|
+
async cloneProduct(productRef, overrides) {
|
|
4458
|
+
const url = `${base}/v1/sdk/products/${productRef}/clone`;
|
|
4459
|
+
const res = await fetch(url, {
|
|
4460
|
+
method: "POST",
|
|
4461
|
+
headers,
|
|
4462
|
+
body: JSON.stringify(overrides || {})
|
|
4463
|
+
});
|
|
4464
|
+
if (!res.ok) {
|
|
4465
|
+
const error = await res.text();
|
|
4466
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
4467
|
+
throw new import_core.SolvaPayError(`Clone product failed (${res.status}): ${error}`);
|
|
4422
4468
|
}
|
|
4423
|
-
|
|
4469
|
+
return await res.json();
|
|
4424
4470
|
},
|
|
4425
|
-
// GET: /v1/sdk/
|
|
4426
|
-
async listPlans(
|
|
4427
|
-
const url = `${base}/v1/sdk/
|
|
4428
|
-
log(`\u{1F4E1} API Request: GET ${url}`);
|
|
4471
|
+
// GET: /v1/sdk/products/{productRef}/plans
|
|
4472
|
+
async listPlans(productRef) {
|
|
4473
|
+
const url = `${base}/v1/sdk/products/${productRef}/plans`;
|
|
4429
4474
|
const res = await fetch(url, {
|
|
4430
4475
|
method: "GET",
|
|
4431
4476
|
headers
|
|
@@ -4436,18 +4481,22 @@ function createSolvaPayClient(opts) {
|
|
|
4436
4481
|
throw new import_core.SolvaPayError(`List plans failed (${res.status}): ${error}`);
|
|
4437
4482
|
}
|
|
4438
4483
|
const result = await res.json();
|
|
4439
|
-
log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
4440
4484
|
const plans = Array.isArray(result) ? result : result.plans || [];
|
|
4441
|
-
return plans.map((plan) =>
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4485
|
+
return plans.map((plan) => {
|
|
4486
|
+
const data = plan.data || {};
|
|
4487
|
+
const price = plan.price ?? data.price;
|
|
4488
|
+
const unwrapped = {
|
|
4489
|
+
...data,
|
|
4490
|
+
...plan,
|
|
4491
|
+
...price !== void 0 && { price }
|
|
4492
|
+
};
|
|
4493
|
+
delete unwrapped.data;
|
|
4494
|
+
return unwrapped;
|
|
4495
|
+
});
|
|
4445
4496
|
},
|
|
4446
|
-
// POST: /v1/sdk/
|
|
4497
|
+
// POST: /v1/sdk/products/{productRef}/plans
|
|
4447
4498
|
async createPlan(params) {
|
|
4448
|
-
const url = `${base}/v1/sdk/
|
|
4449
|
-
log(`\u{1F4E1} API Request: POST ${url}`);
|
|
4450
|
-
log(` Params:`, JSON.stringify(params, null, 2));
|
|
4499
|
+
const url = `${base}/v1/sdk/products/${params.productRef}/plans`;
|
|
4451
4500
|
const res = await fetch(url, {
|
|
4452
4501
|
method: "POST",
|
|
4453
4502
|
headers,
|
|
@@ -4459,13 +4508,26 @@ function createSolvaPayClient(opts) {
|
|
|
4459
4508
|
throw new import_core.SolvaPayError(`Create plan failed (${res.status}): ${error}`);
|
|
4460
4509
|
}
|
|
4461
4510
|
const result = await res.json();
|
|
4462
|
-
log(`\u2705 API Response:`, JSON.stringify(result, null, 2));
|
|
4463
4511
|
return result;
|
|
4464
4512
|
},
|
|
4465
|
-
//
|
|
4466
|
-
async
|
|
4467
|
-
const url = `${base}/v1/sdk/
|
|
4468
|
-
|
|
4513
|
+
// PUT: /v1/sdk/products/{productRef}/plans/{planRef}
|
|
4514
|
+
async updatePlan(productRef, planRef, params) {
|
|
4515
|
+
const url = `${base}/v1/sdk/products/${productRef}/plans/${planRef}`;
|
|
4516
|
+
const res = await fetch(url, {
|
|
4517
|
+
method: "PUT",
|
|
4518
|
+
headers,
|
|
4519
|
+
body: JSON.stringify(params)
|
|
4520
|
+
});
|
|
4521
|
+
if (!res.ok) {
|
|
4522
|
+
const error = await res.text();
|
|
4523
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
4524
|
+
throw new import_core.SolvaPayError(`Update plan failed (${res.status}): ${error}`);
|
|
4525
|
+
}
|
|
4526
|
+
return await res.json();
|
|
4527
|
+
},
|
|
4528
|
+
// DELETE: /v1/sdk/products/{productRef}/plans/{planRef}
|
|
4529
|
+
async deletePlan(productRef, planRef) {
|
|
4530
|
+
const url = `${base}/v1/sdk/products/${productRef}/plans/${planRef}`;
|
|
4469
4531
|
const res = await fetch(url, {
|
|
4470
4532
|
method: "DELETE",
|
|
4471
4533
|
headers
|
|
@@ -4475,17 +4537,11 @@ function createSolvaPayClient(opts) {
|
|
|
4475
4537
|
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
4476
4538
|
throw new import_core.SolvaPayError(`Delete plan failed (${res.status}): ${error}`);
|
|
4477
4539
|
}
|
|
4478
|
-
log(`\u2705 Plan deleted successfully`);
|
|
4479
4540
|
},
|
|
4480
4541
|
// POST: /payment-intents
|
|
4481
4542
|
async createPaymentIntent(params) {
|
|
4482
4543
|
const idempotencyKey = params.idempotencyKey || `payment-${params.planRef}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
4483
4544
|
const url = `${base}/v1/sdk/payment-intents`;
|
|
4484
|
-
log(`\u{1F4E1} API Request: POST ${url}`);
|
|
4485
|
-
log(` Agent Ref: ${params.agentRef}`);
|
|
4486
|
-
log(` Plan Ref: ${params.planRef}`);
|
|
4487
|
-
log(` Customer Ref: ${params.customerRef}`);
|
|
4488
|
-
log(` Idempotency Key: ${idempotencyKey}`);
|
|
4489
4545
|
const res = await fetch(url, {
|
|
4490
4546
|
method: "POST",
|
|
4491
4547
|
headers: {
|
|
@@ -4493,7 +4549,7 @@ function createSolvaPayClient(opts) {
|
|
|
4493
4549
|
"Idempotency-Key": idempotencyKey
|
|
4494
4550
|
},
|
|
4495
4551
|
body: JSON.stringify({
|
|
4496
|
-
|
|
4552
|
+
productRef: params.productRef,
|
|
4497
4553
|
planRef: params.planRef,
|
|
4498
4554
|
customerReference: params.customerRef
|
|
4499
4555
|
})
|
|
@@ -4504,12 +4560,128 @@ function createSolvaPayClient(opts) {
|
|
|
4504
4560
|
throw new import_core.SolvaPayError(`Create payment intent failed (${res.status}): ${error}`);
|
|
4505
4561
|
}
|
|
4506
4562
|
const result = await res.json();
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4563
|
+
return result;
|
|
4564
|
+
},
|
|
4565
|
+
// POST: /v1/sdk/payment-intents/{paymentIntentId}/process
|
|
4566
|
+
async processPaymentIntent(params) {
|
|
4567
|
+
const url = `${base}/v1/sdk/payment-intents/${params.paymentIntentId}/process`;
|
|
4568
|
+
const res = await fetch(url, {
|
|
4569
|
+
method: "POST",
|
|
4570
|
+
headers: {
|
|
4571
|
+
...headers,
|
|
4572
|
+
"Content-Type": "application/json"
|
|
4573
|
+
},
|
|
4574
|
+
body: JSON.stringify({
|
|
4575
|
+
productRef: params.productRef,
|
|
4576
|
+
customerRef: params.customerRef,
|
|
4577
|
+
planRef: params.planRef
|
|
4578
|
+
})
|
|
4512
4579
|
});
|
|
4580
|
+
if (!res.ok) {
|
|
4581
|
+
const error = await res.text();
|
|
4582
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
4583
|
+
throw new import_core.SolvaPayError(`Process payment failed (${res.status}): ${error}`);
|
|
4584
|
+
}
|
|
4585
|
+
const result = await res.json();
|
|
4586
|
+
return result;
|
|
4587
|
+
},
|
|
4588
|
+
// POST: /v1/sdk/purchases/{purchaseRef}/cancel
|
|
4589
|
+
async cancelPurchase(params) {
|
|
4590
|
+
const url = `${base}/v1/sdk/purchases/${params.purchaseRef}/cancel`;
|
|
4591
|
+
const requestOptions = {
|
|
4592
|
+
method: "POST",
|
|
4593
|
+
headers
|
|
4594
|
+
};
|
|
4595
|
+
if (params.reason) {
|
|
4596
|
+
requestOptions.body = JSON.stringify({ reason: params.reason });
|
|
4597
|
+
}
|
|
4598
|
+
const res = await fetch(url, requestOptions);
|
|
4599
|
+
if (!res.ok) {
|
|
4600
|
+
const error = await res.text();
|
|
4601
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
4602
|
+
if (res.status === 404) {
|
|
4603
|
+
throw new import_core.SolvaPayError(`Purchase not found: ${error}`);
|
|
4604
|
+
}
|
|
4605
|
+
if (res.status === 400) {
|
|
4606
|
+
throw new import_core.SolvaPayError(
|
|
4607
|
+
`Purchase cannot be cancelled or does not belong to provider: ${error}`
|
|
4608
|
+
);
|
|
4609
|
+
}
|
|
4610
|
+
throw new import_core.SolvaPayError(`Cancel purchase failed (${res.status}): ${error}`);
|
|
4611
|
+
}
|
|
4612
|
+
const responseText = await res.text();
|
|
4613
|
+
let responseData;
|
|
4614
|
+
try {
|
|
4615
|
+
responseData = JSON.parse(responseText);
|
|
4616
|
+
} catch (parseError) {
|
|
4617
|
+
log(`\u274C Failed to parse response as JSON: ${parseError}`);
|
|
4618
|
+
throw new import_core.SolvaPayError(
|
|
4619
|
+
`Invalid JSON response from cancel purchase endpoint: ${responseText.substring(0, 200)}`
|
|
4620
|
+
);
|
|
4621
|
+
}
|
|
4622
|
+
if (!responseData || typeof responseData !== "object") {
|
|
4623
|
+
log(`\u274C Invalid response structure: ${JSON.stringify(responseData)}`);
|
|
4624
|
+
throw new import_core.SolvaPayError(`Invalid response structure from cancel purchase endpoint`);
|
|
4625
|
+
}
|
|
4626
|
+
let result;
|
|
4627
|
+
if (responseData.purchase && typeof responseData.purchase === "object") {
|
|
4628
|
+
result = responseData.purchase;
|
|
4629
|
+
} else if (responseData.reference) {
|
|
4630
|
+
result = responseData;
|
|
4631
|
+
} else {
|
|
4632
|
+
result = responseData.purchase || responseData;
|
|
4633
|
+
}
|
|
4634
|
+
if (!result || typeof result !== "object") {
|
|
4635
|
+
log(`\u274C Invalid purchase data in response. Full response:`, responseData);
|
|
4636
|
+
throw new import_core.SolvaPayError(`Invalid purchase data in cancel purchase response`);
|
|
4637
|
+
}
|
|
4638
|
+
return result;
|
|
4639
|
+
},
|
|
4640
|
+
// POST: /v1/sdk/user-info
|
|
4641
|
+
async getUserInfo(params) {
|
|
4642
|
+
const url = `${base}/v1/sdk/user-info`;
|
|
4643
|
+
const res = await fetch(url, {
|
|
4644
|
+
method: "POST",
|
|
4645
|
+
headers,
|
|
4646
|
+
body: JSON.stringify(params)
|
|
4647
|
+
});
|
|
4648
|
+
if (!res.ok) {
|
|
4649
|
+
const error = await res.text();
|
|
4650
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
4651
|
+
throw new import_core.SolvaPayError(`Get user info failed (${res.status}): ${error}`);
|
|
4652
|
+
}
|
|
4653
|
+
return await res.json();
|
|
4654
|
+
},
|
|
4655
|
+
// POST: /v1/sdk/checkout-sessions
|
|
4656
|
+
async createCheckoutSession(params) {
|
|
4657
|
+
const url = `${base}/v1/sdk/checkout-sessions`;
|
|
4658
|
+
const res = await fetch(url, {
|
|
4659
|
+
method: "POST",
|
|
4660
|
+
headers,
|
|
4661
|
+
body: JSON.stringify(params)
|
|
4662
|
+
});
|
|
4663
|
+
if (!res.ok) {
|
|
4664
|
+
const error = await res.text();
|
|
4665
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
4666
|
+
throw new import_core.SolvaPayError(`Create checkout session failed (${res.status}): ${error}`);
|
|
4667
|
+
}
|
|
4668
|
+
const result = await res.json();
|
|
4669
|
+
return result;
|
|
4670
|
+
},
|
|
4671
|
+
// POST: /v1/sdk/customers/customer-sessions
|
|
4672
|
+
async createCustomerSession(params) {
|
|
4673
|
+
const url = `${base}/v1/sdk/customers/customer-sessions`;
|
|
4674
|
+
const res = await fetch(url, {
|
|
4675
|
+
method: "POST",
|
|
4676
|
+
headers,
|
|
4677
|
+
body: JSON.stringify(params)
|
|
4678
|
+
});
|
|
4679
|
+
if (!res.ok) {
|
|
4680
|
+
const error = await res.text();
|
|
4681
|
+
log(`\u274C API Error: ${res.status} - ${error}`);
|
|
4682
|
+
throw new import_core.SolvaPayError(`Create customer session failed (${res.status}): ${error}`);
|
|
4683
|
+
}
|
|
4684
|
+
const result = await res.json();
|
|
4513
4685
|
return result;
|
|
4514
4686
|
}
|
|
4515
4687
|
};
|
|
@@ -4561,39 +4733,137 @@ function calculateDelay(initialDelay, attempt, strategy) {
|
|
|
4561
4733
|
function sleep(ms) {
|
|
4562
4734
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
4563
4735
|
}
|
|
4736
|
+
function createRequestDeduplicator(options = {}) {
|
|
4737
|
+
const { cacheTTL = 2e3, maxCacheSize = 1e3, cacheErrors = true } = options;
|
|
4738
|
+
const inFlightRequests = /* @__PURE__ */ new Map();
|
|
4739
|
+
const resultCache = /* @__PURE__ */ new Map();
|
|
4740
|
+
let _cleanupInterval = null;
|
|
4741
|
+
if (cacheTTL > 0) {
|
|
4742
|
+
_cleanupInterval = setInterval(
|
|
4743
|
+
() => {
|
|
4744
|
+
const now = Date.now();
|
|
4745
|
+
const entriesToDelete = [];
|
|
4746
|
+
for (const [key, cached] of resultCache.entries()) {
|
|
4747
|
+
if (now - cached.timestamp >= cacheTTL) {
|
|
4748
|
+
entriesToDelete.push(key);
|
|
4749
|
+
}
|
|
4750
|
+
}
|
|
4751
|
+
for (const key of entriesToDelete) {
|
|
4752
|
+
resultCache.delete(key);
|
|
4753
|
+
}
|
|
4754
|
+
if (resultCache.size > maxCacheSize) {
|
|
4755
|
+
const sortedEntries = Array.from(resultCache.entries()).sort(
|
|
4756
|
+
(a, b) => a[1].timestamp - b[1].timestamp
|
|
4757
|
+
);
|
|
4758
|
+
const toRemove = sortedEntries.slice(0, resultCache.size - maxCacheSize);
|
|
4759
|
+
for (const [key] of toRemove) {
|
|
4760
|
+
resultCache.delete(key);
|
|
4761
|
+
}
|
|
4762
|
+
}
|
|
4763
|
+
},
|
|
4764
|
+
Math.min(cacheTTL, 1e3)
|
|
4765
|
+
);
|
|
4766
|
+
}
|
|
4767
|
+
const deduplicate = async (key, fn) => {
|
|
4768
|
+
if (cacheTTL > 0) {
|
|
4769
|
+
const cached = resultCache.get(key);
|
|
4770
|
+
if (cached && Date.now() - cached.timestamp < cacheTTL) {
|
|
4771
|
+
return cached.data;
|
|
4772
|
+
} else if (cached) {
|
|
4773
|
+
resultCache.delete(key);
|
|
4774
|
+
}
|
|
4775
|
+
}
|
|
4776
|
+
let requestPromise = inFlightRequests.get(key);
|
|
4777
|
+
if (!requestPromise) {
|
|
4778
|
+
requestPromise = (async () => {
|
|
4779
|
+
try {
|
|
4780
|
+
const result = await fn();
|
|
4781
|
+
if (cacheTTL > 0) {
|
|
4782
|
+
resultCache.set(key, {
|
|
4783
|
+
data: result,
|
|
4784
|
+
timestamp: Date.now()
|
|
4785
|
+
});
|
|
4786
|
+
}
|
|
4787
|
+
return result;
|
|
4788
|
+
} catch (error) {
|
|
4789
|
+
if (cacheTTL > 0 && cacheErrors) {
|
|
4790
|
+
resultCache.set(key, {
|
|
4791
|
+
data: error,
|
|
4792
|
+
timestamp: Date.now()
|
|
4793
|
+
});
|
|
4794
|
+
}
|
|
4795
|
+
throw error;
|
|
4796
|
+
} finally {
|
|
4797
|
+
inFlightRequests.delete(key);
|
|
4798
|
+
}
|
|
4799
|
+
})();
|
|
4800
|
+
const existingPromise = inFlightRequests.get(key);
|
|
4801
|
+
if (existingPromise) {
|
|
4802
|
+
requestPromise = existingPromise;
|
|
4803
|
+
} else {
|
|
4804
|
+
inFlightRequests.set(key, requestPromise);
|
|
4805
|
+
}
|
|
4806
|
+
}
|
|
4807
|
+
return requestPromise;
|
|
4808
|
+
};
|
|
4809
|
+
const clearCache = (key) => {
|
|
4810
|
+
resultCache.delete(key);
|
|
4811
|
+
};
|
|
4812
|
+
const clearAllCache = () => {
|
|
4813
|
+
resultCache.clear();
|
|
4814
|
+
};
|
|
4815
|
+
const getStats = () => ({
|
|
4816
|
+
inFlight: inFlightRequests.size,
|
|
4817
|
+
cached: resultCache.size
|
|
4818
|
+
});
|
|
4819
|
+
return {
|
|
4820
|
+
deduplicate,
|
|
4821
|
+
clearCache,
|
|
4822
|
+
clearAllCache,
|
|
4823
|
+
getStats
|
|
4824
|
+
};
|
|
4825
|
+
}
|
|
4564
4826
|
|
|
4565
4827
|
// src/paywall.ts
|
|
4566
4828
|
var PaywallError = class extends Error {
|
|
4829
|
+
/**
|
|
4830
|
+
* Creates a new PaywallError instance.
|
|
4831
|
+
*
|
|
4832
|
+
* @param message - Error message
|
|
4833
|
+
* @param structuredContent - Structured content with checkout URLs and metadata
|
|
4834
|
+
*/
|
|
4567
4835
|
constructor(message2, structuredContent) {
|
|
4568
4836
|
super(message2);
|
|
4569
4837
|
this.structuredContent = structuredContent;
|
|
4570
4838
|
this.name = "PaywallError";
|
|
4571
4839
|
}
|
|
4572
4840
|
};
|
|
4841
|
+
var sharedCustomerLookupDeduplicator = createRequestDeduplicator({
|
|
4842
|
+
cacheTTL: 6e4,
|
|
4843
|
+
// Cache results for 60 seconds (reduces API calls significantly)
|
|
4844
|
+
maxCacheSize: 1e3,
|
|
4845
|
+
// Maximum cache entries
|
|
4846
|
+
cacheErrors: false
|
|
4847
|
+
// Don't cache errors - retry on next request
|
|
4848
|
+
});
|
|
4573
4849
|
var SolvaPayPaywall = class {
|
|
4574
4850
|
constructor(apiClient, options = {}) {
|
|
4575
4851
|
this.apiClient = apiClient;
|
|
4576
|
-
this.debug = options.debug ?? process.env.SOLVAPAY_DEBUG
|
|
4852
|
+
this.debug = options.debug ?? process.env.SOLVAPAY_DEBUG === "true";
|
|
4853
|
+
this.limitsCacheTTL = options.limitsCacheTTL ?? 1e4;
|
|
4577
4854
|
}
|
|
4578
4855
|
customerCreationAttempts = /* @__PURE__ */ new Set();
|
|
4579
4856
|
customerRefMapping = /* @__PURE__ */ new Map();
|
|
4580
|
-
// input ref -> backend ref
|
|
4581
4857
|
debug;
|
|
4858
|
+
limitsCache = /* @__PURE__ */ new Map();
|
|
4859
|
+
limitsCacheTTL;
|
|
4582
4860
|
log(...args) {
|
|
4583
4861
|
if (this.debug) {
|
|
4584
4862
|
console.log(...args);
|
|
4585
4863
|
}
|
|
4586
4864
|
}
|
|
4587
|
-
|
|
4588
|
-
return metadata.
|
|
4589
|
-
}
|
|
4590
|
-
getPackageJsonName() {
|
|
4591
|
-
try {
|
|
4592
|
-
const pkg = require(process.cwd() + "/package.json");
|
|
4593
|
-
return pkg.name;
|
|
4594
|
-
} catch {
|
|
4595
|
-
return void 0;
|
|
4596
|
-
}
|
|
4865
|
+
resolveProduct(metadata) {
|
|
4866
|
+
return metadata.product || process.env.SOLVAPAY_PRODUCT || "default-product";
|
|
4597
4867
|
}
|
|
4598
4868
|
generateRequestId() {
|
|
4599
4869
|
const timestamp = Date.now();
|
|
@@ -4603,47 +4873,122 @@ var SolvaPayPaywall = class {
|
|
|
4603
4873
|
/**
|
|
4604
4874
|
* Core protection method - works for both MCP and HTTP
|
|
4605
4875
|
*/
|
|
4876
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4606
4877
|
async protect(handler, metadata = {}, getCustomerRef) {
|
|
4607
|
-
const
|
|
4608
|
-
const
|
|
4878
|
+
const product = this.resolveProduct(metadata);
|
|
4879
|
+
const configuredPlanRef = metadata.plan?.trim();
|
|
4880
|
+
const usagePlanRef = configuredPlanRef || "unspecified";
|
|
4881
|
+
const usageType = metadata.usageType || "requests";
|
|
4609
4882
|
return async (args) => {
|
|
4610
4883
|
const startTime = Date.now();
|
|
4611
4884
|
const requestId = this.generateRequestId();
|
|
4612
4885
|
const inputCustomerRef = getCustomerRef ? getCustomerRef(args) : args.auth?.customer_ref || "anonymous";
|
|
4613
|
-
|
|
4886
|
+
let backendCustomerRef;
|
|
4887
|
+
if (inputCustomerRef.startsWith("cus_")) {
|
|
4888
|
+
backendCustomerRef = inputCustomerRef;
|
|
4889
|
+
} else {
|
|
4890
|
+
backendCustomerRef = await this.ensureCustomer(inputCustomerRef, inputCustomerRef);
|
|
4891
|
+
}
|
|
4892
|
+
let resolvedMeterName;
|
|
4614
4893
|
try {
|
|
4615
|
-
const
|
|
4616
|
-
this.
|
|
4617
|
-
const
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
if (
|
|
4894
|
+
const limitsCacheKey = `${backendCustomerRef}:${product}:${configuredPlanRef || ""}:${usageType}`;
|
|
4895
|
+
const cachedLimits = this.limitsCache.get(limitsCacheKey);
|
|
4896
|
+
const now = Date.now();
|
|
4897
|
+
let withinLimits;
|
|
4898
|
+
let remaining;
|
|
4899
|
+
let checkoutUrl;
|
|
4900
|
+
const hasFreshCachedLimits = cachedLimits && now - cachedLimits.timestamp < this.limitsCacheTTL;
|
|
4901
|
+
if (hasFreshCachedLimits) {
|
|
4902
|
+
checkoutUrl = cachedLimits.checkoutUrl;
|
|
4903
|
+
resolvedMeterName = cachedLimits.meterName;
|
|
4904
|
+
if (cachedLimits.remaining > 0) {
|
|
4905
|
+
cachedLimits.remaining--;
|
|
4906
|
+
if (cachedLimits.remaining <= 0) {
|
|
4907
|
+
this.limitsCache.delete(limitsCacheKey);
|
|
4908
|
+
}
|
|
4909
|
+
withinLimits = true;
|
|
4910
|
+
remaining = cachedLimits.remaining;
|
|
4911
|
+
} else {
|
|
4912
|
+
withinLimits = false;
|
|
4913
|
+
remaining = 0;
|
|
4914
|
+
this.limitsCache.delete(limitsCacheKey);
|
|
4915
|
+
}
|
|
4916
|
+
} else {
|
|
4917
|
+
if (cachedLimits) {
|
|
4918
|
+
this.limitsCache.delete(limitsCacheKey);
|
|
4919
|
+
}
|
|
4920
|
+
const limitsCheck = await this.apiClient.checkLimits({
|
|
4921
|
+
customerRef: backendCustomerRef,
|
|
4922
|
+
productRef: product,
|
|
4923
|
+
...configuredPlanRef ? { planRef: configuredPlanRef } : {},
|
|
4924
|
+
meterName: usageType
|
|
4925
|
+
});
|
|
4926
|
+
withinLimits = limitsCheck.withinLimits;
|
|
4927
|
+
remaining = limitsCheck.remaining;
|
|
4928
|
+
checkoutUrl = limitsCheck.checkoutUrl;
|
|
4929
|
+
resolvedMeterName = limitsCheck.meterName;
|
|
4930
|
+
const consumedAllowance = withinLimits && remaining > 0;
|
|
4931
|
+
if (consumedAllowance) {
|
|
4932
|
+
remaining = Math.max(0, remaining - 1);
|
|
4933
|
+
}
|
|
4934
|
+
if (consumedAllowance) {
|
|
4935
|
+
this.limitsCache.set(limitsCacheKey, {
|
|
4936
|
+
remaining,
|
|
4937
|
+
checkoutUrl,
|
|
4938
|
+
meterName: resolvedMeterName,
|
|
4939
|
+
timestamp: now
|
|
4940
|
+
});
|
|
4941
|
+
}
|
|
4942
|
+
}
|
|
4943
|
+
if (!withinLimits) {
|
|
4623
4944
|
const latencyMs2 = Date.now() - startTime;
|
|
4624
|
-
this.
|
|
4625
|
-
|
|
4945
|
+
this.trackUsage(
|
|
4946
|
+
backendCustomerRef,
|
|
4947
|
+
product,
|
|
4948
|
+
usagePlanRef,
|
|
4949
|
+
resolvedMeterName || usageType,
|
|
4950
|
+
"paywall",
|
|
4951
|
+
requestId,
|
|
4952
|
+
latencyMs2
|
|
4953
|
+
);
|
|
4626
4954
|
throw new PaywallError("Payment required", {
|
|
4627
4955
|
kind: "payment_required",
|
|
4628
|
-
|
|
4629
|
-
checkoutUrl:
|
|
4630
|
-
message: `
|
|
4956
|
+
product,
|
|
4957
|
+
checkoutUrl: checkoutUrl || "",
|
|
4958
|
+
message: `Purchase required. Remaining: ${remaining}`
|
|
4631
4959
|
});
|
|
4632
4960
|
}
|
|
4633
|
-
this.log(`\u26A1 Executing handler: ${toolName}`);
|
|
4634
4961
|
const result = await handler(args);
|
|
4635
|
-
this.log(`\u2713 Handler completed successfully`);
|
|
4636
4962
|
const latencyMs = Date.now() - startTime;
|
|
4637
|
-
this.
|
|
4638
|
-
|
|
4639
|
-
|
|
4963
|
+
this.trackUsage(
|
|
4964
|
+
backendCustomerRef,
|
|
4965
|
+
product,
|
|
4966
|
+
usagePlanRef,
|
|
4967
|
+
resolvedMeterName || usageType,
|
|
4968
|
+
"success",
|
|
4969
|
+
requestId,
|
|
4970
|
+
latencyMs
|
|
4971
|
+
);
|
|
4640
4972
|
return result;
|
|
4641
4973
|
} catch (error) {
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4974
|
+
if (error instanceof Error) {
|
|
4975
|
+
const errorType = error instanceof PaywallError ? "PaywallError" : "API Error";
|
|
4976
|
+
this.log(`\u274C Error in paywall [${errorType}]: ${error.message}`);
|
|
4977
|
+
} else {
|
|
4978
|
+
this.log(`\u274C Error in paywall:`, error);
|
|
4979
|
+
}
|
|
4980
|
+
if (!(error instanceof PaywallError)) {
|
|
4981
|
+
const latencyMs = Date.now() - startTime;
|
|
4982
|
+
this.trackUsage(
|
|
4983
|
+
backendCustomerRef,
|
|
4984
|
+
product,
|
|
4985
|
+
usagePlanRef,
|
|
4986
|
+
resolvedMeterName || usageType,
|
|
4987
|
+
"fail",
|
|
4988
|
+
requestId,
|
|
4989
|
+
latencyMs
|
|
4990
|
+
);
|
|
4991
|
+
}
|
|
4647
4992
|
throw error;
|
|
4648
4993
|
}
|
|
4649
4994
|
};
|
|
@@ -4653,60 +4998,162 @@ var SolvaPayPaywall = class {
|
|
|
4653
4998
|
* This is a public helper for testing, pre-creating customers, and internal use.
|
|
4654
4999
|
* Only attempts creation once per customer (idempotent).
|
|
4655
5000
|
* Returns the backend customer reference to use in API calls.
|
|
5001
|
+
*
|
|
5002
|
+
* @param customerRef - The customer reference used as a cache key (e.g., Supabase user ID)
|
|
5003
|
+
* @param externalRef - Optional external reference for backend lookup (e.g., Supabase user ID)
|
|
5004
|
+
* If provided, will lookup existing customer by externalRef before creating new one.
|
|
5005
|
+
* The externalRef is stored on the SolvaPay backend for customer lookup.
|
|
5006
|
+
* @param options - Optional customer details (email, name) for customer creation
|
|
4656
5007
|
*/
|
|
4657
|
-
async ensureCustomer(customerRef) {
|
|
5008
|
+
async ensureCustomer(customerRef, externalRef, options) {
|
|
4658
5009
|
if (this.customerRefMapping.has(customerRef)) {
|
|
4659
5010
|
return this.customerRefMapping.get(customerRef);
|
|
4660
5011
|
}
|
|
4661
5012
|
if (customerRef === "anonymous") {
|
|
4662
5013
|
return customerRef;
|
|
4663
5014
|
}
|
|
4664
|
-
if (
|
|
5015
|
+
if (customerRef.startsWith("cus_")) {
|
|
4665
5016
|
return customerRef;
|
|
4666
5017
|
}
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
5018
|
+
const cacheKey = externalRef || customerRef;
|
|
5019
|
+
if (this.customerRefMapping.has(customerRef)) {
|
|
5020
|
+
const cached = this.customerRefMapping.get(customerRef);
|
|
5021
|
+
return cached;
|
|
4670
5022
|
}
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
5023
|
+
const backendRef = await sharedCustomerLookupDeduplicator.deduplicate(cacheKey, async () => {
|
|
5024
|
+
if (externalRef) {
|
|
5025
|
+
try {
|
|
5026
|
+
const existingCustomer = await this.apiClient.getCustomer({ externalRef });
|
|
5027
|
+
if (existingCustomer && existingCustomer.customerRef) {
|
|
5028
|
+
const ref = existingCustomer.customerRef;
|
|
5029
|
+
this.customerRefMapping.set(customerRef, ref);
|
|
5030
|
+
this.customerCreationAttempts.add(customerRef);
|
|
5031
|
+
if (externalRef !== customerRef) {
|
|
5032
|
+
this.customerCreationAttempts.add(externalRef);
|
|
5033
|
+
}
|
|
5034
|
+
return ref;
|
|
5035
|
+
}
|
|
5036
|
+
} catch (error) {
|
|
5037
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5038
|
+
if (!errorMessage.includes("404") && !errorMessage.includes("not found")) {
|
|
5039
|
+
this.log(`\u26A0\uFE0F Error looking up customer by externalRef: ${errorMessage}`);
|
|
5040
|
+
}
|
|
5041
|
+
}
|
|
5042
|
+
}
|
|
5043
|
+
if (this.customerCreationAttempts.has(customerRef) || externalRef && this.customerCreationAttempts.has(externalRef)) {
|
|
5044
|
+
const mappedRef = this.customerRefMapping.get(customerRef);
|
|
5045
|
+
return mappedRef || customerRef;
|
|
5046
|
+
}
|
|
5047
|
+
if (!this.apiClient.createCustomer) {
|
|
5048
|
+
console.warn(
|
|
5049
|
+
`\u26A0\uFE0F Cannot auto-create customer ${customerRef}: createCustomer method not available on API client`
|
|
5050
|
+
);
|
|
5051
|
+
return customerRef;
|
|
5052
|
+
}
|
|
5053
|
+
this.customerCreationAttempts.add(customerRef);
|
|
5054
|
+
try {
|
|
5055
|
+
const createParams = {
|
|
5056
|
+
email: options?.email || `${customerRef}-${Date.now()}@auto-created.local`
|
|
5057
|
+
};
|
|
5058
|
+
if (options?.name) {
|
|
5059
|
+
createParams.name = options.name;
|
|
5060
|
+
}
|
|
5061
|
+
if (externalRef) {
|
|
5062
|
+
createParams.externalRef = externalRef;
|
|
5063
|
+
}
|
|
5064
|
+
const result = await this.apiClient.createCustomer(createParams);
|
|
5065
|
+
const resultObj = result;
|
|
5066
|
+
const ref = resultObj.customerRef || resultObj.reference || customerRef;
|
|
5067
|
+
this.customerRefMapping.set(customerRef, ref);
|
|
5068
|
+
return ref;
|
|
5069
|
+
} catch (error) {
|
|
5070
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5071
|
+
if (errorMessage.includes("409") || errorMessage.includes("already exists")) {
|
|
5072
|
+
if (externalRef) {
|
|
5073
|
+
try {
|
|
5074
|
+
const searchResult = await this.apiClient.getCustomer({ externalRef });
|
|
5075
|
+
if (searchResult && searchResult.customerRef) {
|
|
5076
|
+
this.customerRefMapping.set(customerRef, searchResult.customerRef);
|
|
5077
|
+
return searchResult.customerRef;
|
|
5078
|
+
}
|
|
5079
|
+
} catch (lookupError) {
|
|
5080
|
+
this.log(`\u26A0\uFE0F Failed to lookup existing customer by externalRef after 409:`, lookupError instanceof Error ? lookupError.message : lookupError);
|
|
5081
|
+
}
|
|
5082
|
+
}
|
|
5083
|
+
const isEmailConflict = errorMessage.includes("email") || errorMessage.includes("identifier email");
|
|
5084
|
+
if (externalRef && isEmailConflict && options?.email) {
|
|
5085
|
+
try {
|
|
5086
|
+
const byEmail = await this.apiClient.getCustomer({ email: options.email });
|
|
5087
|
+
if (byEmail && byEmail.customerRef) {
|
|
5088
|
+
this.customerRefMapping.set(customerRef, byEmail.customerRef);
|
|
5089
|
+
this.log(
|
|
5090
|
+
`\u26A0\uFE0F Resolved customer ${customerRef} by email after conflict; using existing customer ${byEmail.customerRef}`
|
|
5091
|
+
);
|
|
5092
|
+
return byEmail.customerRef;
|
|
5093
|
+
}
|
|
5094
|
+
} catch (emailLookupError) {
|
|
5095
|
+
this.log(
|
|
5096
|
+
`\u26A0\uFE0F Email lookup failed after customer conflict for ${customerRef}:`,
|
|
5097
|
+
emailLookupError instanceof Error ? emailLookupError.message : emailLookupError
|
|
5098
|
+
);
|
|
5099
|
+
}
|
|
5100
|
+
try {
|
|
5101
|
+
const retryParams = {
|
|
5102
|
+
email: `${customerRef}-${Date.now()}@auto-created.local`,
|
|
5103
|
+
externalRef
|
|
5104
|
+
};
|
|
5105
|
+
if (options?.name) {
|
|
5106
|
+
retryParams.name = options.name;
|
|
5107
|
+
}
|
|
5108
|
+
const retryResult = await this.apiClient.createCustomer(retryParams);
|
|
5109
|
+
const retryObj = retryResult;
|
|
5110
|
+
const retryRef = retryObj.customerRef || retryObj.reference || customerRef;
|
|
5111
|
+
this.customerRefMapping.set(customerRef, retryRef);
|
|
5112
|
+
this.log(
|
|
5113
|
+
`\u26A0\uFE0F Retried customer creation for ${customerRef} with generated email after email conflict`
|
|
5114
|
+
);
|
|
5115
|
+
return retryRef;
|
|
5116
|
+
} catch (retryError) {
|
|
5117
|
+
this.log(
|
|
5118
|
+
`\u26A0\uFE0F Retry create customer with generated email failed for ${customerRef}:`,
|
|
5119
|
+
retryError instanceof Error ? retryError.message : retryError
|
|
5120
|
+
);
|
|
5121
|
+
}
|
|
5122
|
+
}
|
|
5123
|
+
const unresolvedMessage = errorMessage || "Customer already exists but could not be resolved";
|
|
5124
|
+
throw new Error(
|
|
5125
|
+
`Failed to resolve existing customer for ${customerRef} after conflict: ${unresolvedMessage}. Ensure the existing customer is linked to this externalRef.`
|
|
5126
|
+
);
|
|
5127
|
+
}
|
|
5128
|
+
this.log(
|
|
5129
|
+
`\u274C Failed to auto-create customer ${customerRef}:`,
|
|
5130
|
+
error instanceof Error ? error.message : error
|
|
5131
|
+
);
|
|
5132
|
+
throw error;
|
|
5133
|
+
}
|
|
5134
|
+
});
|
|
5135
|
+
if (backendRef !== customerRef) {
|
|
4685
5136
|
this.customerRefMapping.set(customerRef, backendRef);
|
|
4686
|
-
return backendRef;
|
|
4687
|
-
} catch (error) {
|
|
4688
|
-
this.log(`\u274C Failed to auto-create customer ${customerRef}:`, error instanceof Error ? error.message : error);
|
|
4689
|
-
return customerRef;
|
|
4690
5137
|
}
|
|
5138
|
+
return backendRef;
|
|
4691
5139
|
}
|
|
4692
|
-
async trackUsage(customerRef,
|
|
5140
|
+
async trackUsage(customerRef, _productRef, _planRef, action, outcome, requestId, actionDuration) {
|
|
4693
5141
|
await withRetry(
|
|
4694
5142
|
() => this.apiClient.trackUsage({
|
|
4695
5143
|
customerRef,
|
|
4696
|
-
|
|
4697
|
-
|
|
5144
|
+
actionType: "api_call",
|
|
5145
|
+
units: 1,
|
|
4698
5146
|
outcome,
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
5147
|
+
productReference: _productRef,
|
|
5148
|
+
duration: actionDuration,
|
|
5149
|
+
metadata: { action: action || "api_requests", requestId },
|
|
4702
5150
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4703
5151
|
}),
|
|
4704
5152
|
{
|
|
4705
5153
|
maxRetries: 2,
|
|
4706
5154
|
initialDelay: 500,
|
|
4707
5155
|
shouldRetry: (error) => error.message.includes("Customer not found"),
|
|
4708
|
-
|
|
4709
|
-
onRetry: (error, attempt) => {
|
|
5156
|
+
onRetry: (_error, attempt) => {
|
|
4710
5157
|
console.warn(`\u26A0\uFE0F Customer not found (attempt ${attempt + 1}/3), retrying in 500ms...`);
|
|
4711
5158
|
}
|
|
4712
5159
|
}
|
|
@@ -4725,9 +5172,6 @@ var AdapterUtils = class {
|
|
|
4725
5172
|
if (!customerRef || customerRef === "anonymous") {
|
|
4726
5173
|
return "anonymous";
|
|
4727
5174
|
}
|
|
4728
|
-
if (!customerRef.startsWith("customer_") && !customerRef.startsWith("demo_")) {
|
|
4729
|
-
return `customer_${customerRef.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
4730
|
-
}
|
|
4731
5175
|
return customerRef;
|
|
4732
5176
|
}
|
|
4733
5177
|
/**
|
|
@@ -4744,19 +5188,25 @@ var AdapterUtils = class {
|
|
|
4744
5188
|
audience: options?.audience || process.env.OAUTH_CLIENT_ID || "test-client-id"
|
|
4745
5189
|
});
|
|
4746
5190
|
return payload.sub || null;
|
|
4747
|
-
} catch
|
|
5191
|
+
} catch {
|
|
4748
5192
|
return null;
|
|
4749
5193
|
}
|
|
4750
5194
|
}
|
|
4751
5195
|
};
|
|
4752
5196
|
async function createAdapterHandler(adapter, paywall, metadata, businessLogic) {
|
|
5197
|
+
const backendRefCache = /* @__PURE__ */ new Map();
|
|
5198
|
+
const getCustomerRef = (args) => args.auth?.customer_ref || "anonymous";
|
|
5199
|
+
const protectedHandler = await paywall.protect(businessLogic, metadata, getCustomerRef);
|
|
4753
5200
|
return async (context) => {
|
|
4754
5201
|
try {
|
|
4755
5202
|
const args = await adapter.extractArgs(context);
|
|
4756
5203
|
const customerRef = await adapter.getCustomerRef(context);
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
5204
|
+
let backendRef = backendRefCache.get(customerRef);
|
|
5205
|
+
if (!backendRef) {
|
|
5206
|
+
backendRef = await paywall.ensureCustomer(customerRef, customerRef);
|
|
5207
|
+
backendRefCache.set(customerRef, backendRef);
|
|
5208
|
+
}
|
|
5209
|
+
args.auth = { customer_ref: backendRef };
|
|
4760
5210
|
const result = await protectedHandler(args);
|
|
4761
5211
|
return adapter.formatResponse(result, context);
|
|
4762
5212
|
} catch (error) {
|
|
@@ -4814,7 +5264,7 @@ var HttpAdapter = class {
|
|
|
4814
5264
|
const errorResponse2 = {
|
|
4815
5265
|
success: false,
|
|
4816
5266
|
error: "Payment required",
|
|
4817
|
-
|
|
5267
|
+
product: error.structuredContent.product,
|
|
4818
5268
|
checkoutUrl: error.structuredContent.checkoutUrl,
|
|
4819
5269
|
message: error.structuredContent.message
|
|
4820
5270
|
};
|
|
@@ -4858,7 +5308,7 @@ var NextAdapter = class {
|
|
|
4858
5308
|
if (request.method !== "GET" && request.headers.get("content-type")?.includes("application/json")) {
|
|
4859
5309
|
body = await request.json();
|
|
4860
5310
|
}
|
|
4861
|
-
} catch
|
|
5311
|
+
} catch {
|
|
4862
5312
|
}
|
|
4863
5313
|
let routeParams = {};
|
|
4864
5314
|
if (context?.params) {
|
|
@@ -4887,6 +5337,10 @@ var NextAdapter = class {
|
|
|
4887
5337
|
return AdapterUtils.ensureCustomerRef(jwtSub);
|
|
4888
5338
|
}
|
|
4889
5339
|
}
|
|
5340
|
+
const userId = request.headers.get("x-user-id");
|
|
5341
|
+
if (userId) {
|
|
5342
|
+
return AdapterUtils.ensureCustomerRef(userId);
|
|
5343
|
+
}
|
|
4890
5344
|
const headerRef = request.headers.get("x-customer-ref");
|
|
4891
5345
|
if (headerRef) {
|
|
4892
5346
|
return AdapterUtils.ensureCustomerRef(headerRef);
|
|
@@ -4902,24 +5356,30 @@ var NextAdapter = class {
|
|
|
4902
5356
|
}
|
|
4903
5357
|
formatError(error, _context) {
|
|
4904
5358
|
if (error instanceof PaywallError) {
|
|
4905
|
-
return new Response(
|
|
5359
|
+
return new Response(
|
|
5360
|
+
JSON.stringify({
|
|
5361
|
+
success: false,
|
|
5362
|
+
error: "Payment required",
|
|
5363
|
+
product: error.structuredContent.product,
|
|
5364
|
+
checkoutUrl: error.structuredContent.checkoutUrl,
|
|
5365
|
+
message: error.structuredContent.message
|
|
5366
|
+
}),
|
|
5367
|
+
{
|
|
5368
|
+
status: 402,
|
|
5369
|
+
headers: { "Content-Type": "application/json" }
|
|
5370
|
+
}
|
|
5371
|
+
);
|
|
5372
|
+
}
|
|
5373
|
+
return new Response(
|
|
5374
|
+
JSON.stringify({
|
|
4906
5375
|
success: false,
|
|
4907
|
-
error: "
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
}), {
|
|
4912
|
-
status: 402,
|
|
5376
|
+
error: error instanceof Error ? error.message : "Internal server error"
|
|
5377
|
+
}),
|
|
5378
|
+
{
|
|
5379
|
+
status: 500,
|
|
4913
5380
|
headers: { "Content-Type": "application/json" }
|
|
4914
|
-
}
|
|
4915
|
-
|
|
4916
|
-
return new Response(JSON.stringify({
|
|
4917
|
-
success: false,
|
|
4918
|
-
error: error instanceof Error ? error.message : "Internal server error"
|
|
4919
|
-
}), {
|
|
4920
|
-
status: 500,
|
|
4921
|
-
headers: { "Content-Type": "application/json" }
|
|
4922
|
-
});
|
|
5381
|
+
}
|
|
5382
|
+
);
|
|
4923
5383
|
}
|
|
4924
5384
|
};
|
|
4925
5385
|
|
|
@@ -4936,43 +5396,58 @@ var McpAdapter = class {
|
|
|
4936
5396
|
const ref = await this.options.getCustomerRef(args);
|
|
4937
5397
|
return AdapterUtils.ensureCustomerRef(ref);
|
|
4938
5398
|
}
|
|
4939
|
-
const
|
|
5399
|
+
const auth = args?.auth;
|
|
5400
|
+
const customerRef = auth?.customer_ref || "anonymous";
|
|
4940
5401
|
return AdapterUtils.ensureCustomerRef(customerRef);
|
|
4941
5402
|
}
|
|
4942
5403
|
formatResponse(result, _context) {
|
|
4943
5404
|
const transformed = this.options.transformResponse ? this.options.transformResponse(result) : result;
|
|
4944
5405
|
return {
|
|
4945
|
-
content: [
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
5406
|
+
content: [
|
|
5407
|
+
{
|
|
5408
|
+
type: "text",
|
|
5409
|
+
text: JSON.stringify(transformed, null, 2)
|
|
5410
|
+
}
|
|
5411
|
+
]
|
|
4949
5412
|
};
|
|
4950
5413
|
}
|
|
4951
5414
|
formatError(error, _context) {
|
|
4952
5415
|
if (error instanceof PaywallError) {
|
|
4953
5416
|
return {
|
|
4954
|
-
content: [
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
5417
|
+
content: [
|
|
5418
|
+
{
|
|
5419
|
+
type: "text",
|
|
5420
|
+
text: JSON.stringify(
|
|
5421
|
+
{
|
|
5422
|
+
success: false,
|
|
5423
|
+
error: "Payment required",
|
|
5424
|
+
product: error.structuredContent.product,
|
|
5425
|
+
checkoutUrl: error.structuredContent.checkoutUrl,
|
|
5426
|
+
message: error.structuredContent.message
|
|
5427
|
+
},
|
|
5428
|
+
null,
|
|
5429
|
+
2
|
|
5430
|
+
)
|
|
5431
|
+
}
|
|
5432
|
+
],
|
|
4964
5433
|
isError: true,
|
|
4965
5434
|
structuredContent: error.structuredContent
|
|
4966
5435
|
};
|
|
4967
5436
|
}
|
|
4968
5437
|
return {
|
|
4969
|
-
content: [
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
5438
|
+
content: [
|
|
5439
|
+
{
|
|
5440
|
+
type: "text",
|
|
5441
|
+
text: JSON.stringify(
|
|
5442
|
+
{
|
|
5443
|
+
success: false,
|
|
5444
|
+
error: error instanceof Error ? error.message : "Unknown error occurred"
|
|
5445
|
+
},
|
|
5446
|
+
null,
|
|
5447
|
+
2
|
|
5448
|
+
)
|
|
5449
|
+
}
|
|
5450
|
+
],
|
|
4976
5451
|
isError: true
|
|
4977
5452
|
};
|
|
4978
5453
|
}
|
|
@@ -4980,20 +5455,166 @@ var McpAdapter = class {
|
|
|
4980
5455
|
|
|
4981
5456
|
// src/factory.ts
|
|
4982
5457
|
var import_core2 = require("@solvapay/core");
|
|
5458
|
+
|
|
5459
|
+
// src/virtual-tools.ts
|
|
5460
|
+
var TOOL_GET_USER_INFO = {
|
|
5461
|
+
name: "get_user_info",
|
|
5462
|
+
description: "Get information about the current user and their purchase status for this MCP server. Returns user profile (reference, name, email) and active purchase details including product name, type, dates, and usage limit if applicable.",
|
|
5463
|
+
inputSchema: {
|
|
5464
|
+
type: "object",
|
|
5465
|
+
properties: {},
|
|
5466
|
+
required: []
|
|
5467
|
+
}
|
|
5468
|
+
};
|
|
5469
|
+
var TOOL_UPGRADE = {
|
|
5470
|
+
name: "upgrade",
|
|
5471
|
+
description: "Get available pricing options and checkout URLs for upgrading. Returns a list of available pricing options with their details (price, features) and checkout URLs. Users can click on a checkout URL to purchase. If a specific planRef is provided, returns only the checkout URL for that pricing option.",
|
|
5472
|
+
inputSchema: {
|
|
5473
|
+
type: "object",
|
|
5474
|
+
properties: {
|
|
5475
|
+
planRef: {
|
|
5476
|
+
type: "string",
|
|
5477
|
+
description: 'Optional pricing reference (e.g., "pln_abc123") to get a checkout URL for a specific option. If not provided, returns all available pricing options with their checkout URLs.'
|
|
5478
|
+
}
|
|
5479
|
+
},
|
|
5480
|
+
required: []
|
|
5481
|
+
}
|
|
5482
|
+
};
|
|
5483
|
+
var TOOL_MANAGE_ACCOUNT = {
|
|
5484
|
+
name: "manage_account",
|
|
5485
|
+
description: "Get a URL to the customer portal where users can view and manage their account. The portal shows current account status, billing history, and allows subscription changes. Returns a secure, time-limited URL that the user can click to access their account management page.",
|
|
5486
|
+
inputSchema: {
|
|
5487
|
+
type: "object",
|
|
5488
|
+
properties: {},
|
|
5489
|
+
required: []
|
|
5490
|
+
}
|
|
5491
|
+
};
|
|
5492
|
+
var VIRTUAL_TOOL_DEFINITIONS = [TOOL_GET_USER_INFO, TOOL_UPGRADE, TOOL_MANAGE_ACCOUNT];
|
|
5493
|
+
function mcpTextResult(text) {
|
|
5494
|
+
return { content: [{ type: "text", text }] };
|
|
5495
|
+
}
|
|
5496
|
+
function mcpErrorResult(message2) {
|
|
5497
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: message2 }) }], isError: true };
|
|
5498
|
+
}
|
|
5499
|
+
function createGetUserInfoHandler(apiClient, productRef, getCustomerRef) {
|
|
5500
|
+
return async (args) => {
|
|
5501
|
+
const customerRef = getCustomerRef(args);
|
|
5502
|
+
try {
|
|
5503
|
+
if (!apiClient.getUserInfo) {
|
|
5504
|
+
return mcpErrorResult("getUserInfo is not available on this API client");
|
|
5505
|
+
}
|
|
5506
|
+
const userInfo = await apiClient.getUserInfo({ customerRef, productRef });
|
|
5507
|
+
return mcpTextResult(JSON.stringify(userInfo, null, 2));
|
|
5508
|
+
} catch (error) {
|
|
5509
|
+
return mcpErrorResult(
|
|
5510
|
+
`Failed to retrieve user information: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
5511
|
+
);
|
|
5512
|
+
}
|
|
5513
|
+
};
|
|
5514
|
+
}
|
|
5515
|
+
function createUpgradeHandler(apiClient, productRef, getCustomerRef) {
|
|
5516
|
+
return async (args) => {
|
|
5517
|
+
const customerRef = getCustomerRef(args);
|
|
5518
|
+
const planRef = typeof args.planRef === "string" ? args.planRef : void 0;
|
|
5519
|
+
try {
|
|
5520
|
+
const result = await apiClient.createCheckoutSession({
|
|
5521
|
+
customerReference: customerRef,
|
|
5522
|
+
productRef,
|
|
5523
|
+
...planRef && { planRef }
|
|
5524
|
+
});
|
|
5525
|
+
const checkoutUrl = result.checkoutUrl;
|
|
5526
|
+
if (planRef) {
|
|
5527
|
+
const responseText2 = `## Upgrade
|
|
5528
|
+
|
|
5529
|
+
**[Click here to upgrade \u2192](${checkoutUrl})**
|
|
5530
|
+
|
|
5531
|
+
After completing the checkout, your purchase will be activated immediately.`;
|
|
5532
|
+
return mcpTextResult(responseText2);
|
|
5533
|
+
}
|
|
5534
|
+
const responseText = `## Upgrade Your Subscription
|
|
5535
|
+
|
|
5536
|
+
**[Click here to view pricing options and upgrade \u2192](${checkoutUrl})**
|
|
5537
|
+
|
|
5538
|
+
You'll be able to compare options and select the one that's right for you.`;
|
|
5539
|
+
return mcpTextResult(responseText);
|
|
5540
|
+
} catch (error) {
|
|
5541
|
+
return mcpErrorResult(
|
|
5542
|
+
`Failed to create checkout session: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
5543
|
+
);
|
|
5544
|
+
}
|
|
5545
|
+
};
|
|
5546
|
+
}
|
|
5547
|
+
function createManageAccountHandler(apiClient, productRef, getCustomerRef) {
|
|
5548
|
+
return async (args) => {
|
|
5549
|
+
const customerRef = getCustomerRef(args);
|
|
5550
|
+
try {
|
|
5551
|
+
const session = await apiClient.createCustomerSession({ customerRef, productRef });
|
|
5552
|
+
const portalUrl = session.customerUrl;
|
|
5553
|
+
const responseText = `## Manage Your Account
|
|
5554
|
+
|
|
5555
|
+
Access your account management portal to:
|
|
5556
|
+
- View your current account status
|
|
5557
|
+
- See billing history and invoices
|
|
5558
|
+
- Update payment methods
|
|
5559
|
+
- Cancel or modify your subscription
|
|
5560
|
+
|
|
5561
|
+
**[Open Account Portal \u2192](${portalUrl})**
|
|
5562
|
+
|
|
5563
|
+
This link is secure and will expire after a short period.`;
|
|
5564
|
+
return mcpTextResult(responseText);
|
|
5565
|
+
} catch (error) {
|
|
5566
|
+
return mcpErrorResult(
|
|
5567
|
+
`Failed to create customer portal session: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
5568
|
+
);
|
|
5569
|
+
}
|
|
5570
|
+
};
|
|
5571
|
+
}
|
|
5572
|
+
function createVirtualTools(apiClient, options) {
|
|
5573
|
+
const { product, getCustomerRef, exclude = [] } = options;
|
|
5574
|
+
const excludeSet = new Set(exclude);
|
|
5575
|
+
const allTools = [
|
|
5576
|
+
{
|
|
5577
|
+
...TOOL_GET_USER_INFO,
|
|
5578
|
+
handler: createGetUserInfoHandler(apiClient, product, getCustomerRef)
|
|
5579
|
+
},
|
|
5580
|
+
{
|
|
5581
|
+
...TOOL_UPGRADE,
|
|
5582
|
+
handler: createUpgradeHandler(apiClient, product, getCustomerRef)
|
|
5583
|
+
},
|
|
5584
|
+
{
|
|
5585
|
+
...TOOL_MANAGE_ACCOUNT,
|
|
5586
|
+
handler: createManageAccountHandler(apiClient, product, getCustomerRef)
|
|
5587
|
+
}
|
|
5588
|
+
];
|
|
5589
|
+
return allTools.filter((t) => !excludeSet.has(t.name));
|
|
5590
|
+
}
|
|
5591
|
+
|
|
5592
|
+
// src/factory.ts
|
|
4983
5593
|
function createSolvaPay(config) {
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
5594
|
+
let resolvedConfig;
|
|
5595
|
+
if (!config) {
|
|
5596
|
+
const envConfig = (0, import_core2.getSolvaPayConfig)();
|
|
5597
|
+
resolvedConfig = {
|
|
5598
|
+
apiKey: envConfig.apiKey,
|
|
5599
|
+
apiBaseUrl: envConfig.apiBaseUrl
|
|
5600
|
+
};
|
|
5601
|
+
} else {
|
|
5602
|
+
resolvedConfig = config;
|
|
5603
|
+
}
|
|
5604
|
+
const apiClient = resolvedConfig.apiClient || createSolvaPayClient({
|
|
5605
|
+
apiKey: resolvedConfig.apiKey,
|
|
5606
|
+
apiBaseUrl: resolvedConfig.apiBaseUrl
|
|
4987
5607
|
});
|
|
4988
5608
|
const paywall = new SolvaPayPaywall(apiClient, {
|
|
4989
|
-
debug: process.env.SOLVAPAY_DEBUG !== "false"
|
|
5609
|
+
debug: process.env.SOLVAPAY_DEBUG !== "false",
|
|
5610
|
+
limitsCacheTTL: resolvedConfig.limitsCacheTTL
|
|
4990
5611
|
});
|
|
4991
5612
|
return {
|
|
4992
5613
|
// Direct access to API client for advanced operations
|
|
4993
5614
|
apiClient,
|
|
4994
5615
|
// Common API methods exposed directly for convenience
|
|
4995
|
-
ensureCustomer(customerRef) {
|
|
4996
|
-
return paywall.ensureCustomer(customerRef);
|
|
5616
|
+
ensureCustomer(customerRef, externalRef, options) {
|
|
5617
|
+
return paywall.ensureCustomer(customerRef, externalRef, options);
|
|
4997
5618
|
},
|
|
4998
5619
|
createPaymentIntent(params) {
|
|
4999
5620
|
if (!apiClient.createPaymentIntent) {
|
|
@@ -5001,6 +5622,12 @@ function createSolvaPay(config) {
|
|
|
5001
5622
|
}
|
|
5002
5623
|
return apiClient.createPaymentIntent(params);
|
|
5003
5624
|
},
|
|
5625
|
+
processPaymentIntent(params) {
|
|
5626
|
+
if (!apiClient.processPaymentIntent) {
|
|
5627
|
+
throw new import_core2.SolvaPayError("processPaymentIntent is not available on this API client");
|
|
5628
|
+
}
|
|
5629
|
+
return apiClient.processPaymentIntent(params);
|
|
5630
|
+
},
|
|
5004
5631
|
checkLimits(params) {
|
|
5005
5632
|
return apiClient.checkLimits(params);
|
|
5006
5633
|
},
|
|
@@ -5014,86 +5641,482 @@ function createSolvaPay(config) {
|
|
|
5014
5641
|
return apiClient.createCustomer(params);
|
|
5015
5642
|
},
|
|
5016
5643
|
getCustomer(params) {
|
|
5017
|
-
if (!apiClient.getCustomer) {
|
|
5018
|
-
throw new import_core2.SolvaPayError("getCustomer is not available on this API client");
|
|
5019
|
-
}
|
|
5020
5644
|
return apiClient.getCustomer(params);
|
|
5021
5645
|
},
|
|
5646
|
+
createCheckoutSession(params) {
|
|
5647
|
+
return apiClient.createCheckoutSession({
|
|
5648
|
+
customerReference: params.customerRef,
|
|
5649
|
+
productRef: params.productRef,
|
|
5650
|
+
planRef: params.planRef
|
|
5651
|
+
});
|
|
5652
|
+
},
|
|
5653
|
+
createCustomerSession(params) {
|
|
5654
|
+
return apiClient.createCustomerSession(params);
|
|
5655
|
+
},
|
|
5656
|
+
bootstrapMcpProduct(params) {
|
|
5657
|
+
if (!apiClient.bootstrapMcpProduct) {
|
|
5658
|
+
throw new import_core2.SolvaPayError("bootstrapMcpProduct is not available on this API client");
|
|
5659
|
+
}
|
|
5660
|
+
return apiClient.bootstrapMcpProduct(params);
|
|
5661
|
+
},
|
|
5662
|
+
getVirtualTools(options) {
|
|
5663
|
+
return createVirtualTools(apiClient, options);
|
|
5664
|
+
},
|
|
5022
5665
|
// Payable API for framework-specific handlers
|
|
5023
5666
|
payable(options = {}) {
|
|
5024
|
-
const
|
|
5025
|
-
const plan = options.planRef || options.plan
|
|
5026
|
-
const
|
|
5667
|
+
const product = options.productRef || options.product || process.env.SOLVAPAY_PRODUCT || "default-product";
|
|
5668
|
+
const plan = options.planRef || options.plan;
|
|
5669
|
+
const usageType = options.usageType || "requests";
|
|
5670
|
+
const metadata = { product, plan, usageType };
|
|
5027
5671
|
return {
|
|
5028
|
-
//
|
|
5672
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5029
5673
|
http(businessLogic, adapterOptions) {
|
|
5030
|
-
const adapter = new HttpAdapter(
|
|
5674
|
+
const adapter = new HttpAdapter({
|
|
5675
|
+
...adapterOptions,
|
|
5676
|
+
getCustomerRef: adapterOptions?.getCustomerRef || options.getCustomerRef
|
|
5677
|
+
});
|
|
5678
|
+
const handlerPromise = createAdapterHandler(adapter, paywall, metadata, businessLogic);
|
|
5031
5679
|
return async (req, reply) => {
|
|
5032
|
-
const handler = await
|
|
5033
|
-
adapter,
|
|
5034
|
-
paywall,
|
|
5035
|
-
metadata,
|
|
5036
|
-
businessLogic
|
|
5037
|
-
);
|
|
5680
|
+
const handler = await handlerPromise;
|
|
5038
5681
|
return handler([req, reply]);
|
|
5039
5682
|
};
|
|
5040
5683
|
},
|
|
5041
|
-
//
|
|
5684
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5042
5685
|
next(businessLogic, adapterOptions) {
|
|
5043
|
-
const adapter = new NextAdapter(
|
|
5686
|
+
const adapter = new NextAdapter({
|
|
5687
|
+
...adapterOptions,
|
|
5688
|
+
getCustomerRef: adapterOptions?.getCustomerRef || options.getCustomerRef
|
|
5689
|
+
});
|
|
5690
|
+
const handlerPromise = createAdapterHandler(adapter, paywall, metadata, businessLogic);
|
|
5044
5691
|
return async (request, context) => {
|
|
5045
|
-
const handler = await
|
|
5046
|
-
adapter,
|
|
5047
|
-
paywall,
|
|
5048
|
-
metadata,
|
|
5049
|
-
businessLogic
|
|
5050
|
-
);
|
|
5692
|
+
const handler = await handlerPromise;
|
|
5051
5693
|
return handler([request, context]);
|
|
5052
5694
|
};
|
|
5053
5695
|
},
|
|
5054
|
-
//
|
|
5696
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5055
5697
|
mcp(businessLogic, adapterOptions) {
|
|
5056
|
-
const adapter = new McpAdapter(
|
|
5698
|
+
const adapter = new McpAdapter({
|
|
5699
|
+
...adapterOptions,
|
|
5700
|
+
getCustomerRef: adapterOptions?.getCustomerRef || options.getCustomerRef
|
|
5701
|
+
});
|
|
5702
|
+
const handlerPromise = createAdapterHandler(adapter, paywall, metadata, businessLogic);
|
|
5057
5703
|
return async (args) => {
|
|
5058
|
-
const handler = await
|
|
5059
|
-
adapter,
|
|
5060
|
-
paywall,
|
|
5061
|
-
metadata,
|
|
5062
|
-
businessLogic
|
|
5063
|
-
);
|
|
5704
|
+
const handler = await handlerPromise;
|
|
5064
5705
|
return handler(args);
|
|
5065
5706
|
};
|
|
5066
5707
|
},
|
|
5067
|
-
//
|
|
5708
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5068
5709
|
async function(businessLogic) {
|
|
5069
|
-
const getCustomerRef = (args) =>
|
|
5710
|
+
const getCustomerRef = (args) => {
|
|
5711
|
+
const configuredRef = options.getCustomerRef?.(args);
|
|
5712
|
+
if (typeof configuredRef === "string") {
|
|
5713
|
+
return configuredRef;
|
|
5714
|
+
}
|
|
5715
|
+
return args.auth?.customer_ref || "anonymous";
|
|
5716
|
+
};
|
|
5070
5717
|
return paywall.protect(businessLogic, metadata, getCustomerRef);
|
|
5071
5718
|
}
|
|
5072
5719
|
};
|
|
5073
5720
|
}
|
|
5074
5721
|
};
|
|
5075
5722
|
}
|
|
5076
|
-
|
|
5723
|
+
|
|
5724
|
+
// src/mcp-auth.ts
|
|
5725
|
+
var McpBearerAuthError = class extends Error {
|
|
5726
|
+
constructor(message2) {
|
|
5727
|
+
super(message2);
|
|
5728
|
+
this.name = "McpBearerAuthError";
|
|
5729
|
+
}
|
|
5730
|
+
};
|
|
5731
|
+
function base64UrlDecode(input) {
|
|
5732
|
+
const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
5733
|
+
const padded = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
|
|
5734
|
+
return Buffer.from(padded, "base64").toString("utf8");
|
|
5735
|
+
}
|
|
5736
|
+
function extractBearerToken(authorization) {
|
|
5737
|
+
if (!authorization) return null;
|
|
5738
|
+
if (!authorization.startsWith("Bearer ")) return null;
|
|
5739
|
+
return authorization.slice(7).trim() || null;
|
|
5740
|
+
}
|
|
5741
|
+
function decodeJwtPayload(token) {
|
|
5742
|
+
const parts = token.split(".");
|
|
5743
|
+
if (parts.length < 2) {
|
|
5744
|
+
throw new McpBearerAuthError("Invalid JWT format");
|
|
5745
|
+
}
|
|
5077
5746
|
try {
|
|
5078
|
-
const
|
|
5079
|
-
|
|
5747
|
+
const payloadText = base64UrlDecode(parts[1]);
|
|
5748
|
+
const payload = JSON.parse(payloadText);
|
|
5749
|
+
return payload;
|
|
5080
5750
|
} catch {
|
|
5081
|
-
|
|
5751
|
+
throw new McpBearerAuthError("Invalid JWT payload");
|
|
5752
|
+
}
|
|
5753
|
+
}
|
|
5754
|
+
function getCustomerRefFromJwtPayload(payload, options = {}) {
|
|
5755
|
+
const claimPriority = options.claimPriority || ["customerRef", "customer_ref", "sub"];
|
|
5756
|
+
for (const claim of claimPriority) {
|
|
5757
|
+
const value = payload[claim];
|
|
5758
|
+
if (typeof value === "string" && value.trim()) {
|
|
5759
|
+
return value.trim();
|
|
5760
|
+
}
|
|
5761
|
+
}
|
|
5762
|
+
throw new McpBearerAuthError(
|
|
5763
|
+
`No customer reference claim found (checked: ${claimPriority.join(", ")})`
|
|
5764
|
+
);
|
|
5765
|
+
}
|
|
5766
|
+
function getCustomerRefFromBearerAuthHeader(authorization, options = {}) {
|
|
5767
|
+
const token = extractBearerToken(authorization);
|
|
5768
|
+
if (!token) {
|
|
5769
|
+
throw new McpBearerAuthError("Missing bearer token");
|
|
5770
|
+
}
|
|
5771
|
+
const payload = decodeJwtPayload(token);
|
|
5772
|
+
return getCustomerRefFromJwtPayload(payload, options);
|
|
5773
|
+
}
|
|
5774
|
+
|
|
5775
|
+
// src/helpers/error.ts
|
|
5776
|
+
var import_core3 = require("@solvapay/core");
|
|
5777
|
+
function isErrorResult(result) {
|
|
5778
|
+
return typeof result === "object" && result !== null && "error" in result && "status" in result;
|
|
5779
|
+
}
|
|
5780
|
+
function handleRouteError(error, operationName, defaultMessage) {
|
|
5781
|
+
console.error(`[${operationName}] Error:`, error);
|
|
5782
|
+
if (error instanceof import_core3.SolvaPayError) {
|
|
5783
|
+
const errorMessage2 = error.message;
|
|
5784
|
+
return {
|
|
5785
|
+
error: errorMessage2,
|
|
5786
|
+
status: 500,
|
|
5787
|
+
details: errorMessage2
|
|
5788
|
+
};
|
|
5789
|
+
}
|
|
5790
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
5791
|
+
const message2 = defaultMessage || `${operationName} failed`;
|
|
5792
|
+
return {
|
|
5793
|
+
error: message2,
|
|
5794
|
+
status: 500,
|
|
5795
|
+
details: errorMessage
|
|
5796
|
+
};
|
|
5797
|
+
}
|
|
5798
|
+
|
|
5799
|
+
// src/helpers/auth.ts
|
|
5800
|
+
async function getAuthenticatedUserCore(request, options = {}) {
|
|
5801
|
+
try {
|
|
5802
|
+
const { requireUserId, getUserEmailFromRequest, getUserNameFromRequest } = await import("@solvapay/auth");
|
|
5803
|
+
const userIdOrError = requireUserId(request);
|
|
5804
|
+
if (userIdOrError instanceof Response) {
|
|
5805
|
+
const clonedResponse = userIdOrError.clone();
|
|
5806
|
+
const body = await clonedResponse.json().catch(() => ({ error: "Unauthorized" }));
|
|
5807
|
+
return {
|
|
5808
|
+
error: body.error || "Unauthorized",
|
|
5809
|
+
status: userIdOrError.status,
|
|
5810
|
+
details: body.error || "Unauthorized"
|
|
5811
|
+
};
|
|
5812
|
+
}
|
|
5813
|
+
const userId = userIdOrError;
|
|
5814
|
+
const email = options.includeEmail !== false ? await getUserEmailFromRequest(request) : null;
|
|
5815
|
+
const name = options.includeName !== false ? await getUserNameFromRequest(request) : null;
|
|
5816
|
+
return {
|
|
5817
|
+
userId,
|
|
5818
|
+
email,
|
|
5819
|
+
name
|
|
5820
|
+
};
|
|
5821
|
+
} catch (error) {
|
|
5822
|
+
return handleRouteError(error, "Get authenticated user", "Authentication failed");
|
|
5823
|
+
}
|
|
5824
|
+
}
|
|
5825
|
+
|
|
5826
|
+
// src/helpers/customer.ts
|
|
5827
|
+
async function syncCustomerCore(request, options = {}) {
|
|
5828
|
+
try {
|
|
5829
|
+
const userResult = await getAuthenticatedUserCore(request, {
|
|
5830
|
+
includeEmail: options.includeEmail,
|
|
5831
|
+
includeName: options.includeName
|
|
5832
|
+
});
|
|
5833
|
+
if (isErrorResult(userResult)) {
|
|
5834
|
+
return userResult;
|
|
5835
|
+
}
|
|
5836
|
+
const { userId, email, name } = userResult;
|
|
5837
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
5838
|
+
const customerRef = await solvaPay.ensureCustomer(userId, userId, {
|
|
5839
|
+
email: email || void 0,
|
|
5840
|
+
name: name || void 0
|
|
5841
|
+
});
|
|
5842
|
+
return customerRef;
|
|
5843
|
+
} catch (error) {
|
|
5844
|
+
return handleRouteError(error, "Sync customer", "Failed to sync customer");
|
|
5845
|
+
}
|
|
5846
|
+
}
|
|
5847
|
+
|
|
5848
|
+
// src/helpers/payment.ts
|
|
5849
|
+
async function createPaymentIntentCore(request, body, options = {}) {
|
|
5850
|
+
try {
|
|
5851
|
+
if (!body.planRef || !body.productRef) {
|
|
5852
|
+
return {
|
|
5853
|
+
error: "Missing required parameters: planRef and productRef are required",
|
|
5854
|
+
status: 400
|
|
5855
|
+
};
|
|
5856
|
+
}
|
|
5857
|
+
const customerResult = await syncCustomerCore(request, {
|
|
5858
|
+
solvaPay: options.solvaPay,
|
|
5859
|
+
includeEmail: options.includeEmail,
|
|
5860
|
+
includeName: options.includeName
|
|
5861
|
+
});
|
|
5862
|
+
if (isErrorResult(customerResult)) {
|
|
5863
|
+
return customerResult;
|
|
5864
|
+
}
|
|
5865
|
+
const customerRef = customerResult;
|
|
5866
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
5867
|
+
const paymentIntent = await solvaPay.createPaymentIntent({
|
|
5868
|
+
productRef: body.productRef,
|
|
5869
|
+
planRef: body.planRef,
|
|
5870
|
+
customerRef
|
|
5871
|
+
});
|
|
5872
|
+
return {
|
|
5873
|
+
id: paymentIntent.id,
|
|
5874
|
+
clientSecret: paymentIntent.clientSecret,
|
|
5875
|
+
publishableKey: paymentIntent.publishableKey,
|
|
5876
|
+
accountId: paymentIntent.accountId,
|
|
5877
|
+
customerRef
|
|
5878
|
+
};
|
|
5879
|
+
} catch (error) {
|
|
5880
|
+
return handleRouteError(error, "Create payment intent", "Payment intent creation failed");
|
|
5881
|
+
}
|
|
5882
|
+
}
|
|
5883
|
+
async function processPaymentIntentCore(request, body, options = {}) {
|
|
5884
|
+
try {
|
|
5885
|
+
if (!body.paymentIntentId || !body.productRef) {
|
|
5886
|
+
return {
|
|
5887
|
+
error: "paymentIntentId and productRef are required",
|
|
5888
|
+
status: 400
|
|
5889
|
+
};
|
|
5890
|
+
}
|
|
5891
|
+
const customerResult = await syncCustomerCore(request, {
|
|
5892
|
+
solvaPay: options.solvaPay
|
|
5893
|
+
});
|
|
5894
|
+
if (isErrorResult(customerResult)) {
|
|
5895
|
+
return customerResult;
|
|
5896
|
+
}
|
|
5897
|
+
const customerRef = customerResult;
|
|
5898
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
5899
|
+
const result = await solvaPay.processPaymentIntent({
|
|
5900
|
+
paymentIntentId: body.paymentIntentId,
|
|
5901
|
+
productRef: body.productRef,
|
|
5902
|
+
customerRef,
|
|
5903
|
+
planRef: body.planRef
|
|
5904
|
+
});
|
|
5905
|
+
return result;
|
|
5906
|
+
} catch (error) {
|
|
5907
|
+
return handleRouteError(error, "Process payment intent", "Payment processing failed");
|
|
5908
|
+
}
|
|
5909
|
+
}
|
|
5910
|
+
|
|
5911
|
+
// src/helpers/checkout.ts
|
|
5912
|
+
async function createCheckoutSessionCore(request, body, options = {}) {
|
|
5913
|
+
try {
|
|
5914
|
+
if (!body.productRef) {
|
|
5915
|
+
return {
|
|
5916
|
+
error: "Missing required parameter: productRef is required",
|
|
5917
|
+
status: 400
|
|
5918
|
+
};
|
|
5919
|
+
}
|
|
5920
|
+
const customerResult = await syncCustomerCore(request, {
|
|
5921
|
+
solvaPay: options.solvaPay,
|
|
5922
|
+
includeEmail: options.includeEmail,
|
|
5923
|
+
includeName: options.includeName
|
|
5924
|
+
});
|
|
5925
|
+
if (isErrorResult(customerResult)) {
|
|
5926
|
+
return customerResult;
|
|
5927
|
+
}
|
|
5928
|
+
const customerRef = customerResult;
|
|
5929
|
+
let returnUrl = body.returnUrl || options.returnUrl;
|
|
5930
|
+
if (!returnUrl) {
|
|
5931
|
+
try {
|
|
5932
|
+
const url = new URL(request.url);
|
|
5933
|
+
returnUrl = url.origin;
|
|
5934
|
+
} catch {
|
|
5935
|
+
}
|
|
5936
|
+
}
|
|
5937
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
5938
|
+
const session = await solvaPay.createCheckoutSession({
|
|
5939
|
+
productRef: body.productRef,
|
|
5940
|
+
customerRef,
|
|
5941
|
+
planRef: body.planRef || void 0,
|
|
5942
|
+
returnUrl
|
|
5943
|
+
});
|
|
5944
|
+
return {
|
|
5945
|
+
sessionId: session.sessionId,
|
|
5946
|
+
checkoutUrl: session.checkoutUrl
|
|
5947
|
+
};
|
|
5948
|
+
} catch (error) {
|
|
5949
|
+
return handleRouteError(error, "Create checkout session", "Checkout session creation failed");
|
|
5950
|
+
}
|
|
5951
|
+
}
|
|
5952
|
+
async function createCustomerSessionCore(request, options = {}) {
|
|
5953
|
+
try {
|
|
5954
|
+
const customerResult = await syncCustomerCore(request, {
|
|
5955
|
+
solvaPay: options.solvaPay,
|
|
5956
|
+
includeEmail: options.includeEmail,
|
|
5957
|
+
includeName: options.includeName
|
|
5958
|
+
});
|
|
5959
|
+
if (isErrorResult(customerResult)) {
|
|
5960
|
+
return customerResult;
|
|
5961
|
+
}
|
|
5962
|
+
const customerRef = customerResult;
|
|
5963
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
5964
|
+
const session = await solvaPay.createCustomerSession({
|
|
5965
|
+
customerRef
|
|
5966
|
+
});
|
|
5967
|
+
return session;
|
|
5968
|
+
} catch (error) {
|
|
5969
|
+
return handleRouteError(error, "Create customer session", "Customer session creation failed");
|
|
5970
|
+
}
|
|
5971
|
+
}
|
|
5972
|
+
|
|
5973
|
+
// src/helpers/renewal.ts
|
|
5974
|
+
var import_core4 = require("@solvapay/core");
|
|
5975
|
+
async function cancelPurchaseCore(request, body, options = {}) {
|
|
5976
|
+
try {
|
|
5977
|
+
if (!body.purchaseRef) {
|
|
5978
|
+
return {
|
|
5979
|
+
error: "Missing required parameter: purchaseRef is required",
|
|
5980
|
+
status: 400
|
|
5981
|
+
};
|
|
5982
|
+
}
|
|
5983
|
+
const solvaPay = options.solvaPay || createSolvaPay();
|
|
5984
|
+
if (!solvaPay.apiClient.cancelPurchase) {
|
|
5985
|
+
return {
|
|
5986
|
+
error: "Cancel purchase method not available on SDK client",
|
|
5987
|
+
status: 500
|
|
5988
|
+
};
|
|
5989
|
+
}
|
|
5990
|
+
let cancelledPurchase = await solvaPay.apiClient.cancelPurchase({
|
|
5991
|
+
purchaseRef: body.purchaseRef,
|
|
5992
|
+
reason: body.reason
|
|
5993
|
+
});
|
|
5994
|
+
if (!cancelledPurchase || typeof cancelledPurchase !== "object") {
|
|
5995
|
+
return {
|
|
5996
|
+
error: "Invalid response from cancel purchase endpoint",
|
|
5997
|
+
status: 500
|
|
5998
|
+
};
|
|
5999
|
+
}
|
|
6000
|
+
const responseObj = cancelledPurchase;
|
|
6001
|
+
if (responseObj.purchase && typeof responseObj.purchase === "object") {
|
|
6002
|
+
cancelledPurchase = responseObj.purchase;
|
|
6003
|
+
}
|
|
6004
|
+
if (!cancelledPurchase.reference) {
|
|
6005
|
+
return {
|
|
6006
|
+
error: "Cancel purchase response missing required fields",
|
|
6007
|
+
status: 500
|
|
6008
|
+
};
|
|
6009
|
+
}
|
|
6010
|
+
const isCancelled = cancelledPurchase.status === "cancelled" || cancelledPurchase.cancelledAt;
|
|
6011
|
+
if (!isCancelled) {
|
|
6012
|
+
return {
|
|
6013
|
+
error: `Purchase cancellation failed: backend returned status '${cancelledPurchase.status}' without cancelledAt timestamp`,
|
|
6014
|
+
status: 500
|
|
6015
|
+
};
|
|
6016
|
+
}
|
|
6017
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
6018
|
+
return cancelledPurchase;
|
|
6019
|
+
} catch (error) {
|
|
6020
|
+
if (error instanceof import_core4.SolvaPayError) {
|
|
6021
|
+
const errorMessage = error.message;
|
|
6022
|
+
if (errorMessage.includes("not found")) {
|
|
6023
|
+
return {
|
|
6024
|
+
error: "Purchase not found",
|
|
6025
|
+
status: 404,
|
|
6026
|
+
details: errorMessage
|
|
6027
|
+
};
|
|
6028
|
+
}
|
|
6029
|
+
if (errorMessage.includes("cannot be cancelled") || errorMessage.includes("does not belong to provider")) {
|
|
6030
|
+
return {
|
|
6031
|
+
error: "Purchase cannot be cancelled or does not belong to provider",
|
|
6032
|
+
status: 400,
|
|
6033
|
+
details: errorMessage
|
|
6034
|
+
};
|
|
6035
|
+
}
|
|
6036
|
+
return {
|
|
6037
|
+
error: errorMessage,
|
|
6038
|
+
status: 500,
|
|
6039
|
+
details: errorMessage
|
|
6040
|
+
};
|
|
6041
|
+
}
|
|
6042
|
+
return handleRouteError(error, "Cancel purchase", "Failed to cancel purchase");
|
|
6043
|
+
}
|
|
6044
|
+
}
|
|
6045
|
+
|
|
6046
|
+
// src/helpers/plans.ts
|
|
6047
|
+
var import_core5 = require("@solvapay/core");
|
|
6048
|
+
async function listPlansCore(request) {
|
|
6049
|
+
try {
|
|
6050
|
+
const url = new URL(request.url);
|
|
6051
|
+
const productRef = url.searchParams.get("productRef");
|
|
6052
|
+
if (!productRef) {
|
|
6053
|
+
return {
|
|
6054
|
+
error: "Missing required parameter: productRef",
|
|
6055
|
+
status: 400
|
|
6056
|
+
};
|
|
6057
|
+
}
|
|
6058
|
+
const config = (0, import_core5.getSolvaPayConfig)();
|
|
6059
|
+
const solvapaySecretKey = config.apiKey;
|
|
6060
|
+
const solvapayApiBaseUrl = config.apiBaseUrl;
|
|
6061
|
+
if (!solvapaySecretKey) {
|
|
6062
|
+
return {
|
|
6063
|
+
error: "Server configuration error: SolvaPay secret key not configured",
|
|
6064
|
+
status: 500
|
|
6065
|
+
};
|
|
6066
|
+
}
|
|
6067
|
+
const apiClient = createSolvaPayClient({
|
|
6068
|
+
apiKey: solvapaySecretKey,
|
|
6069
|
+
apiBaseUrl: solvapayApiBaseUrl
|
|
6070
|
+
});
|
|
6071
|
+
if (!apiClient.listPlans) {
|
|
6072
|
+
return {
|
|
6073
|
+
error: "List plans method not available",
|
|
6074
|
+
status: 500
|
|
6075
|
+
};
|
|
6076
|
+
}
|
|
6077
|
+
const plans = await apiClient.listPlans(productRef);
|
|
6078
|
+
return {
|
|
6079
|
+
plans: plans || [],
|
|
6080
|
+
productRef
|
|
6081
|
+
};
|
|
6082
|
+
} catch (error) {
|
|
6083
|
+
return handleRouteError(error, "List plans", "Failed to fetch plans");
|
|
5082
6084
|
}
|
|
5083
6085
|
}
|
|
5084
6086
|
|
|
5085
6087
|
// src/index.ts
|
|
5086
|
-
function verifyWebhook({
|
|
6088
|
+
function verifyWebhook({
|
|
6089
|
+
body,
|
|
6090
|
+
signature,
|
|
6091
|
+
secret
|
|
6092
|
+
}) {
|
|
5087
6093
|
const hmac = import_node_crypto20.default.createHmac("sha256", secret).update(body).digest("hex");
|
|
5088
6094
|
const ok = import_node_crypto20.default.timingSafeEqual(Buffer.from(hmac), Buffer.from(signature));
|
|
5089
|
-
if (!ok) throw new
|
|
6095
|
+
if (!ok) throw new import_core6.SolvaPayError("Invalid webhook signature");
|
|
5090
6096
|
return JSON.parse(body);
|
|
5091
6097
|
}
|
|
5092
6098
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5093
6099
|
0 && (module.exports = {
|
|
6100
|
+
McpBearerAuthError,
|
|
5094
6101
|
PaywallError,
|
|
6102
|
+
VIRTUAL_TOOL_DEFINITIONS,
|
|
6103
|
+
cancelPurchaseCore,
|
|
6104
|
+
createCheckoutSessionCore,
|
|
6105
|
+
createCustomerSessionCore,
|
|
6106
|
+
createPaymentIntentCore,
|
|
5095
6107
|
createSolvaPay,
|
|
5096
6108
|
createSolvaPayClient,
|
|
6109
|
+
createVirtualTools,
|
|
6110
|
+
decodeJwtPayload,
|
|
6111
|
+
extractBearerToken,
|
|
6112
|
+
getAuthenticatedUserCore,
|
|
6113
|
+
getCustomerRefFromBearerAuthHeader,
|
|
6114
|
+
getCustomerRefFromJwtPayload,
|
|
6115
|
+
handleRouteError,
|
|
6116
|
+
isErrorResult,
|
|
6117
|
+
listPlansCore,
|
|
6118
|
+
processPaymentIntentCore,
|
|
6119
|
+
syncCustomerCore,
|
|
5097
6120
|
verifyWebhook,
|
|
5098
6121
|
withRetry
|
|
5099
6122
|
});
|