@openconductor/mcp-sdk 1.0.1 → 1.1.0
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/README.md +43 -16
- package/dist/errors/index.d.mts +29 -1
- package/dist/errors/index.d.ts +29 -1
- package/dist/errors/index.js +38 -1
- package/dist/errors/index.js.map +1 -1
- package/dist/errors/index.mjs +36 -2
- package/dist/errors/index.mjs.map +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +204 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +196 -2
- package/dist/index.mjs.map +1 -1
- package/dist/payment/index.d.mts +137 -0
- package/dist/payment/index.d.ts +137 -0
- package/dist/payment/index.js +253 -0
- package/dist/payment/index.js.map +1 -0
- package/dist/payment/index.mjs +246 -0
- package/dist/payment/index.mjs.map +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs.map +1 -1
- package/dist/validate/index.js.map +1 -1
- package/dist/validate/index.mjs.map +1 -1
- package/package.json +11 -3
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
interface PaymentConfig {
|
|
2
|
+
/** OpenConductor API key for billing */
|
|
3
|
+
apiKey: string;
|
|
4
|
+
/** Base URL for billing API (default: https://api.openconductor.ai) */
|
|
5
|
+
apiUrl?: string;
|
|
6
|
+
/** Default upgrade URL to show users */
|
|
7
|
+
upgradeUrl?: string;
|
|
8
|
+
/** Enable test mode (skips actual billing) */
|
|
9
|
+
testMode?: boolean;
|
|
10
|
+
}
|
|
11
|
+
interface CreditRequirement {
|
|
12
|
+
/** Number of credits to deduct per call */
|
|
13
|
+
credits: number;
|
|
14
|
+
}
|
|
15
|
+
interface SubscriptionRequirement {
|
|
16
|
+
/** Required subscription tier (e.g., 'pro', 'enterprise') */
|
|
17
|
+
tier: string;
|
|
18
|
+
/** Alternative tiers that also grant access */
|
|
19
|
+
allowedTiers?: string[];
|
|
20
|
+
}
|
|
21
|
+
interface StripeRequirement {
|
|
22
|
+
/** Stripe Price ID for per-call billing */
|
|
23
|
+
priceId: string;
|
|
24
|
+
}
|
|
25
|
+
type PaymentRequirement = CreditRequirement | SubscriptionRequirement | StripeRequirement;
|
|
26
|
+
interface UserContext {
|
|
27
|
+
/** User ID for billing lookup */
|
|
28
|
+
userId: string;
|
|
29
|
+
/** Optional API key override */
|
|
30
|
+
apiKey?: string;
|
|
31
|
+
}
|
|
32
|
+
interface BillingStatus {
|
|
33
|
+
/** Whether the user can proceed */
|
|
34
|
+
allowed: boolean;
|
|
35
|
+
/** Current credit balance (if applicable) */
|
|
36
|
+
credits?: number;
|
|
37
|
+
/** Current subscription tier (if applicable) */
|
|
38
|
+
tier?: string;
|
|
39
|
+
/** Reason for denial (if not allowed) */
|
|
40
|
+
reason?: string;
|
|
41
|
+
/** URL for upgrade/purchase */
|
|
42
|
+
actionUrl?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Initialize payment/billing configuration
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* initPayment({
|
|
50
|
+
* apiKey: process.env.OPENCONDUCTOR_API_KEY!,
|
|
51
|
+
* upgradeUrl: 'https://myapp.com/upgrade'
|
|
52
|
+
* })
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
declare function initPayment(config: PaymentConfig): void;
|
|
56
|
+
/**
|
|
57
|
+
* Get current payment configuration
|
|
58
|
+
*/
|
|
59
|
+
declare function getPaymentConfig(): PaymentConfig | null;
|
|
60
|
+
interface RequirePaymentOptions {
|
|
61
|
+
/** Tool name for billing records */
|
|
62
|
+
toolName?: string;
|
|
63
|
+
/** Function to extract user ID from input */
|
|
64
|
+
getUserId?: (input: unknown) => string | undefined;
|
|
65
|
+
/** Custom error handler */
|
|
66
|
+
onPaymentError?: (error: Error) => void;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* One-line payment middleware for MCP tools
|
|
70
|
+
*
|
|
71
|
+
* @example Credits-based billing
|
|
72
|
+
* ```typescript
|
|
73
|
+
* const paidTool = requirePayment({ credits: 10 })(myHandler)
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* @example Subscription tier requirement
|
|
77
|
+
* ```typescript
|
|
78
|
+
* const premiumTool = requirePayment({ tier: 'pro' })(myHandler)
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* @example With wrapTool
|
|
82
|
+
* ```typescript
|
|
83
|
+
* const safePaidTool = wrapTool(
|
|
84
|
+
* requirePayment({ credits: 5 })(myHandler),
|
|
85
|
+
* { name: 'premium-analysis' }
|
|
86
|
+
* )
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
declare function requirePayment<TInput extends {
|
|
90
|
+
userId?: string;
|
|
91
|
+
}, TOutput>(requirement: PaymentRequirement, options?: RequirePaymentOptions): (handler: (input: TInput) => TOutput | Promise<TOutput>) => ((input: TInput) => Promise<TOutput>);
|
|
92
|
+
/**
|
|
93
|
+
* Create a paid tool with built-in payment verification
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```typescript
|
|
97
|
+
* const analyzeData = createPaidTool({
|
|
98
|
+
* name: 'analyze-data',
|
|
99
|
+
* payment: { credits: 10 },
|
|
100
|
+
* handler: async (input) => {
|
|
101
|
+
* // Your tool logic
|
|
102
|
+
* return result
|
|
103
|
+
* }
|
|
104
|
+
* })
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
declare function createPaidTool<TInput extends {
|
|
108
|
+
userId?: string;
|
|
109
|
+
}, TOutput>(config: {
|
|
110
|
+
name: string;
|
|
111
|
+
payment: PaymentRequirement;
|
|
112
|
+
handler: (input: TInput) => TOutput | Promise<TOutput>;
|
|
113
|
+
getUserId?: (input: TInput) => string | undefined;
|
|
114
|
+
}): (input: TInput) => Promise<TOutput>;
|
|
115
|
+
/**
|
|
116
|
+
* Check if a user can access a paid feature without executing it
|
|
117
|
+
* Useful for UI gating
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```typescript
|
|
121
|
+
* const canAccess = await canUserAccess('user_123', { credits: 10 }, 'premium-tool')
|
|
122
|
+
* if (!canAccess.allowed) {
|
|
123
|
+
* showUpgradePrompt(canAccess.actionUrl)
|
|
124
|
+
* }
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
declare function canUserAccess(userId: string, requirement: PaymentRequirement, toolName?: string): Promise<BillingStatus>;
|
|
128
|
+
/**
|
|
129
|
+
* Get user's current billing status
|
|
130
|
+
*/
|
|
131
|
+
declare function getUserBillingStatus(userId: string): Promise<{
|
|
132
|
+
credits: number;
|
|
133
|
+
tier: string;
|
|
134
|
+
active: boolean;
|
|
135
|
+
} | null>;
|
|
136
|
+
|
|
137
|
+
export { type BillingStatus, type CreditRequirement, type PaymentConfig, type PaymentRequirement, type RequirePaymentOptions, type StripeRequirement, type SubscriptionRequirement, type UserContext, canUserAccess, createPaidTool, getPaymentConfig, getUserBillingStatus, initPayment, requirePayment };
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/errors/codes.ts
|
|
4
|
+
var ErrorCodes = {
|
|
5
|
+
CONFIGURATION_ERROR: -32010,
|
|
6
|
+
PAYMENT_REQUIRED: -32011,
|
|
7
|
+
INSUFFICIENT_CREDITS: -32012,
|
|
8
|
+
SUBSCRIPTION_REQUIRED: -32013
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// src/errors/index.ts
|
|
12
|
+
var MCPError = class extends Error {
|
|
13
|
+
code;
|
|
14
|
+
data;
|
|
15
|
+
constructor(code, message, data) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = "MCPError";
|
|
18
|
+
this.code = code;
|
|
19
|
+
this.data = data;
|
|
20
|
+
if (Error.captureStackTrace) {
|
|
21
|
+
Error.captureStackTrace(this, this.constructor);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Returns JSON-RPC 2.0 formatted error object
|
|
26
|
+
*/
|
|
27
|
+
toJSON() {
|
|
28
|
+
return {
|
|
29
|
+
code: this.code,
|
|
30
|
+
message: this.message,
|
|
31
|
+
...this.data && { data: this.data }
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Create error response for JSON-RPC
|
|
36
|
+
*/
|
|
37
|
+
toResponse(id = null) {
|
|
38
|
+
return {
|
|
39
|
+
jsonrpc: "2.0",
|
|
40
|
+
id,
|
|
41
|
+
error: this.toJSON()
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
var ConfigurationError = class extends MCPError {
|
|
46
|
+
constructor(setting, reason) {
|
|
47
|
+
super(ErrorCodes.CONFIGURATION_ERROR, `Invalid configuration '${setting}': ${reason}`, {
|
|
48
|
+
setting,
|
|
49
|
+
reason
|
|
50
|
+
});
|
|
51
|
+
this.name = "ConfigurationError";
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var PaymentRequiredError = class extends MCPError {
|
|
55
|
+
constructor(toolName, options) {
|
|
56
|
+
super(ErrorCodes.PAYMENT_REQUIRED, `Payment required to use '${toolName}'`, {
|
|
57
|
+
tool: toolName,
|
|
58
|
+
...options?.upgradeUrl && { upgradeUrl: options.upgradeUrl },
|
|
59
|
+
...options?.priceId && { priceId: options.priceId }
|
|
60
|
+
});
|
|
61
|
+
this.name = "PaymentRequiredError";
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
var InsufficientCreditsError = class extends MCPError {
|
|
65
|
+
constructor(required, available, options) {
|
|
66
|
+
super(ErrorCodes.INSUFFICIENT_CREDITS, `Insufficient credits: need ${required}, have ${available}`, {
|
|
67
|
+
required,
|
|
68
|
+
available,
|
|
69
|
+
...options?.purchaseUrl && { purchaseUrl: options.purchaseUrl }
|
|
70
|
+
});
|
|
71
|
+
this.name = "InsufficientCreditsError";
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
var SubscriptionRequiredError = class extends MCPError {
|
|
75
|
+
constructor(requiredTier, currentTier, options) {
|
|
76
|
+
const msg = currentTier ? `Subscription '${requiredTier}' required (current: '${currentTier}')` : `Subscription '${requiredTier}' required`;
|
|
77
|
+
super(ErrorCodes.SUBSCRIPTION_REQUIRED, msg, {
|
|
78
|
+
requiredTier,
|
|
79
|
+
...currentTier && { currentTier },
|
|
80
|
+
...options?.upgradeUrl && { upgradeUrl: options.upgradeUrl }
|
|
81
|
+
});
|
|
82
|
+
this.name = "SubscriptionRequiredError";
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// src/payment/index.ts
|
|
87
|
+
var paymentConfig = null;
|
|
88
|
+
function initPayment(config) {
|
|
89
|
+
paymentConfig = {
|
|
90
|
+
apiUrl: "https://api.openconductor.ai",
|
|
91
|
+
testMode: false,
|
|
92
|
+
...config
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function getPaymentConfig() {
|
|
96
|
+
return paymentConfig;
|
|
97
|
+
}
|
|
98
|
+
async function checkBilling(params) {
|
|
99
|
+
const config = paymentConfig;
|
|
100
|
+
if (!config) {
|
|
101
|
+
throw new ConfigurationError("payment", "Payment not initialized. Call initPayment() first.");
|
|
102
|
+
}
|
|
103
|
+
if (config.testMode) {
|
|
104
|
+
return { allowed: true, credits: 9999, tier: "test" };
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const response = await fetch(`${config.apiUrl}/v1/billing/check`, {
|
|
108
|
+
method: "POST",
|
|
109
|
+
headers: {
|
|
110
|
+
"Content-Type": "application/json",
|
|
111
|
+
"Authorization": `Bearer ${config.apiKey}`
|
|
112
|
+
},
|
|
113
|
+
body: JSON.stringify({
|
|
114
|
+
userId: params.userId,
|
|
115
|
+
requirement: params.requirement,
|
|
116
|
+
tool: params.toolName
|
|
117
|
+
})
|
|
118
|
+
});
|
|
119
|
+
if (!response.ok) {
|
|
120
|
+
const errorBody = await response.text();
|
|
121
|
+
console.error("[payment] Billing check failed:", response.status, errorBody);
|
|
122
|
+
return {
|
|
123
|
+
allowed: false,
|
|
124
|
+
reason: "Billing service unavailable",
|
|
125
|
+
actionUrl: config.upgradeUrl
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return await response.json();
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error("[payment] Billing check error:", error);
|
|
131
|
+
return {
|
|
132
|
+
allowed: false,
|
|
133
|
+
reason: "Unable to verify billing status",
|
|
134
|
+
actionUrl: config.upgradeUrl
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async function deductCredits(params) {
|
|
139
|
+
const config = paymentConfig;
|
|
140
|
+
if (!config) return false;
|
|
141
|
+
if (config.testMode) return true;
|
|
142
|
+
try {
|
|
143
|
+
const response = await fetch(`${config.apiUrl}/v1/billing/deduct`, {
|
|
144
|
+
method: "POST",
|
|
145
|
+
headers: {
|
|
146
|
+
"Content-Type": "application/json",
|
|
147
|
+
"Authorization": `Bearer ${config.apiKey}`
|
|
148
|
+
},
|
|
149
|
+
body: JSON.stringify({
|
|
150
|
+
userId: params.userId,
|
|
151
|
+
credits: params.credits,
|
|
152
|
+
tool: params.toolName,
|
|
153
|
+
callId: params.callId
|
|
154
|
+
})
|
|
155
|
+
});
|
|
156
|
+
return response.ok;
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error("[payment] Credit deduction error:", error);
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function isCreditRequirement(req) {
|
|
163
|
+
return "credits" in req && typeof req.credits === "number";
|
|
164
|
+
}
|
|
165
|
+
function isSubscriptionRequirement(req) {
|
|
166
|
+
return "tier" in req && typeof req.tier === "string";
|
|
167
|
+
}
|
|
168
|
+
function isStripeRequirement(req) {
|
|
169
|
+
return "priceId" in req && typeof req.priceId === "string";
|
|
170
|
+
}
|
|
171
|
+
function requirePayment(requirement, options = {}) {
|
|
172
|
+
return (handler) => {
|
|
173
|
+
const { toolName = "unknown", getUserId } = options;
|
|
174
|
+
return async (input) => {
|
|
175
|
+
const userId = getUserId?.(input) ?? input.userId;
|
|
176
|
+
if (!userId) {
|
|
177
|
+
throw new PaymentRequiredError(toolName, {
|
|
178
|
+
upgradeUrl: paymentConfig?.upgradeUrl
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
const status = await checkBilling({
|
|
182
|
+
userId,
|
|
183
|
+
requirement,
|
|
184
|
+
toolName
|
|
185
|
+
});
|
|
186
|
+
if (!status.allowed) {
|
|
187
|
+
if (isCreditRequirement(requirement)) {
|
|
188
|
+
throw new InsufficientCreditsError(
|
|
189
|
+
requirement.credits,
|
|
190
|
+
status.credits ?? 0,
|
|
191
|
+
{ purchaseUrl: status.actionUrl }
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
if (isSubscriptionRequirement(requirement)) {
|
|
195
|
+
throw new SubscriptionRequiredError(
|
|
196
|
+
requirement.tier,
|
|
197
|
+
status.tier,
|
|
198
|
+
{ upgradeUrl: status.actionUrl }
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
throw new PaymentRequiredError(toolName, {
|
|
202
|
+
upgradeUrl: status.actionUrl,
|
|
203
|
+
...isStripeRequirement(requirement) && { priceId: requirement.priceId }
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
const result = await handler(input);
|
|
207
|
+
if (isCreditRequirement(requirement)) {
|
|
208
|
+
await deductCredits({
|
|
209
|
+
userId,
|
|
210
|
+
credits: requirement.credits,
|
|
211
|
+
toolName
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
return result;
|
|
215
|
+
};
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function createPaidTool(config) {
|
|
219
|
+
return requirePayment(config.payment, {
|
|
220
|
+
toolName: config.name,
|
|
221
|
+
getUserId: config.getUserId
|
|
222
|
+
})(config.handler);
|
|
223
|
+
}
|
|
224
|
+
async function canUserAccess(userId, requirement, toolName = "check") {
|
|
225
|
+
return checkBilling({ userId, requirement, toolName });
|
|
226
|
+
}
|
|
227
|
+
async function getUserBillingStatus(userId) {
|
|
228
|
+
const config = paymentConfig;
|
|
229
|
+
if (!config) return null;
|
|
230
|
+
if (config.testMode) {
|
|
231
|
+
return { credits: 9999, tier: "test", active: true };
|
|
232
|
+
}
|
|
233
|
+
try {
|
|
234
|
+
const response = await fetch(`${config.apiUrl}/v1/billing/status/${userId}`, {
|
|
235
|
+
headers: {
|
|
236
|
+
"Authorization": `Bearer ${config.apiKey}`
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
if (!response.ok) return null;
|
|
240
|
+
return await response.json();
|
|
241
|
+
} catch {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
exports.canUserAccess = canUserAccess;
|
|
247
|
+
exports.createPaidTool = createPaidTool;
|
|
248
|
+
exports.getPaymentConfig = getPaymentConfig;
|
|
249
|
+
exports.getUserBillingStatus = getUserBillingStatus;
|
|
250
|
+
exports.initPayment = initPayment;
|
|
251
|
+
exports.requirePayment = requirePayment;
|
|
252
|
+
//# sourceMappingURL=index.js.map
|
|
253
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/errors/codes.ts","../../src/errors/index.ts","../../src/payment/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * JSON-RPC 2.0 Standard Error Codes\n * https://www.jsonrpc.org/specification#error_object\n */\nexport const ErrorCodes = {\n // JSON-RPC 2.0 Standard Errors\n PARSE_ERROR: -32700,\n INVALID_REQUEST: -32600,\n METHOD_NOT_FOUND: -32601,\n INVALID_PARAMS: -32602,\n INTERNAL_ERROR: -32603,\n\n // MCP-Specific Errors (-32000 to -32099 reserved for implementation)\n TOOL_NOT_FOUND: -32001,\n TOOL_EXECUTION_ERROR: -32002,\n RESOURCE_NOT_FOUND: -32003,\n AUTHENTICATION_ERROR: -32004,\n AUTHORIZATION_ERROR: -32005,\n RATE_LIMIT_ERROR: -32006,\n TIMEOUT_ERROR: -32007,\n VALIDATION_ERROR: -32008,\n DEPENDENCY_ERROR: -32009,\n CONFIGURATION_ERROR: -32010,\n PAYMENT_REQUIRED: -32011,\n INSUFFICIENT_CREDITS: -32012,\n SUBSCRIPTION_REQUIRED: -32013,\n} as const\n\nexport type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes]\n","import { ErrorCodes, type ErrorCode } from './codes'\n\nexport { ErrorCodes, type ErrorCode } from './codes'\n\n/**\n * Base error class for MCP servers\n * Formats errors according to JSON-RPC 2.0 specification\n */\nexport class MCPError extends Error {\n public readonly code: ErrorCode\n public readonly data?: Record<string, unknown>\n\n constructor(\n code: ErrorCode,\n message: string,\n data?: Record<string, unknown>\n ) {\n super(message)\n this.name = 'MCPError'\n this.code = code\n this.data = data\n\n // Maintains proper stack trace in V8 environments\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, this.constructor)\n }\n }\n\n /**\n * Returns JSON-RPC 2.0 formatted error object\n */\n toJSON() {\n return {\n code: this.code,\n message: this.message,\n ...(this.data && { data: this.data }),\n }\n }\n\n /**\n * Create error response for JSON-RPC\n */\n toResponse(id: string | number | null = null) {\n return {\n jsonrpc: '2.0' as const,\n id,\n error: this.toJSON(),\n }\n }\n}\n\n/**\n * Thrown when tool input validation fails\n */\nexport class ValidationError extends MCPError {\n constructor(field: string, reason: string, value?: unknown) {\n super(ErrorCodes.INVALID_PARAMS, `Validation failed for '${field}': ${reason}`, {\n field,\n reason,\n ...(value !== undefined && { value }),\n })\n this.name = 'ValidationError'\n }\n}\n\n/**\n * Thrown when a requested tool doesn't exist\n */\nexport class ToolNotFoundError extends MCPError {\n constructor(toolName: string) {\n super(ErrorCodes.TOOL_NOT_FOUND, `Tool '${toolName}' not found`, {\n tool: toolName,\n })\n this.name = 'ToolNotFoundError'\n }\n}\n\n/**\n * Thrown when tool execution fails\n */\nexport class ToolExecutionError extends MCPError {\n constructor(toolName: string, reason: string, cause?: Error) {\n super(ErrorCodes.TOOL_EXECUTION_ERROR, `Tool '${toolName}' failed: ${reason}`, {\n tool: toolName,\n reason,\n ...(cause && { cause: cause.message }),\n })\n this.name = 'ToolExecutionError'\n }\n}\n\n/**\n * Thrown when a requested resource doesn't exist\n */\nexport class ResourceNotFoundError extends MCPError {\n constructor(resourceUri: string) {\n super(ErrorCodes.RESOURCE_NOT_FOUND, `Resource '${resourceUri}' not found`, {\n uri: resourceUri,\n })\n this.name = 'ResourceNotFoundError'\n }\n}\n\n/**\n * Thrown when authentication fails\n */\nexport class AuthenticationError extends MCPError {\n constructor(reason: string = 'Authentication required') {\n super(ErrorCodes.AUTHENTICATION_ERROR, reason)\n this.name = 'AuthenticationError'\n }\n}\n\n/**\n * Thrown when authorization fails (authenticated but not permitted)\n */\nexport class AuthorizationError extends MCPError {\n constructor(action: string, resource?: string) {\n const msg = resource\n ? `Not authorized to ${action} on '${resource}'`\n : `Not authorized to ${action}`\n super(ErrorCodes.AUTHORIZATION_ERROR, msg, {\n action,\n ...(resource && { resource }),\n })\n this.name = 'AuthorizationError'\n }\n}\n\n/**\n * Thrown when rate limits are exceeded\n */\nexport class RateLimitError extends MCPError {\n constructor(retryAfterMs?: number) {\n super(ErrorCodes.RATE_LIMIT_ERROR, 'Rate limit exceeded', {\n ...(retryAfterMs && { retryAfterMs }),\n })\n this.name = 'RateLimitError'\n }\n}\n\n/**\n * Thrown when an operation times out\n */\nexport class TimeoutError extends MCPError {\n constructor(operation: string, timeoutMs: number) {\n super(ErrorCodes.TIMEOUT_ERROR, `Operation '${operation}' timed out after ${timeoutMs}ms`, {\n operation,\n timeoutMs,\n })\n this.name = 'TimeoutError'\n }\n}\n\n/**\n * Thrown when a required dependency is unavailable\n */\nexport class DependencyError extends MCPError {\n constructor(dependency: string, reason: string) {\n super(ErrorCodes.DEPENDENCY_ERROR, `Dependency '${dependency}' unavailable: ${reason}`, {\n dependency,\n reason,\n })\n this.name = 'DependencyError'\n }\n}\n\n/**\n * Thrown when server configuration is invalid\n */\nexport class ConfigurationError extends MCPError {\n constructor(setting: string, reason: string) {\n super(ErrorCodes.CONFIGURATION_ERROR, `Invalid configuration '${setting}': ${reason}`, {\n setting,\n reason,\n })\n this.name = 'ConfigurationError'\n }\n}\n\n/**\n * Thrown when payment is required to access a tool\n */\nexport class PaymentRequiredError extends MCPError {\n constructor(toolName: string, options?: { upgradeUrl?: string; priceId?: string }) {\n super(ErrorCodes.PAYMENT_REQUIRED, `Payment required to use '${toolName}'`, {\n tool: toolName,\n ...(options?.upgradeUrl && { upgradeUrl: options.upgradeUrl }),\n ...(options?.priceId && { priceId: options.priceId }),\n })\n this.name = 'PaymentRequiredError'\n }\n}\n\n/**\n * Thrown when user doesn't have enough credits\n */\nexport class InsufficientCreditsError extends MCPError {\n constructor(required: number, available: number, options?: { purchaseUrl?: string }) {\n super(ErrorCodes.INSUFFICIENT_CREDITS, `Insufficient credits: need ${required}, have ${available}`, {\n required,\n available,\n ...(options?.purchaseUrl && { purchaseUrl: options.purchaseUrl }),\n })\n this.name = 'InsufficientCreditsError'\n }\n}\n\n/**\n * Thrown when a subscription tier is required\n */\nexport class SubscriptionRequiredError extends MCPError {\n constructor(requiredTier: string, currentTier?: string, options?: { upgradeUrl?: string }) {\n const msg = currentTier \n ? `Subscription '${requiredTier}' required (current: '${currentTier}')`\n : `Subscription '${requiredTier}' required`\n super(ErrorCodes.SUBSCRIPTION_REQUIRED, msg, {\n requiredTier,\n ...(currentTier && { currentTier }),\n ...(options?.upgradeUrl && { upgradeUrl: options.upgradeUrl }),\n })\n this.name = 'SubscriptionRequiredError'\n }\n}\n","import {\n PaymentRequiredError,\n InsufficientCreditsError,\n SubscriptionRequiredError,\n ConfigurationError,\n} from '../errors'\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface PaymentConfig {\n /** OpenConductor API key for billing */\n apiKey: string\n /** Base URL for billing API (default: https://api.openconductor.ai) */\n apiUrl?: string\n /** Default upgrade URL to show users */\n upgradeUrl?: string\n /** Enable test mode (skips actual billing) */\n testMode?: boolean\n}\n\nexport interface CreditRequirement {\n /** Number of credits to deduct per call */\n credits: number\n}\n\nexport interface SubscriptionRequirement {\n /** Required subscription tier (e.g., 'pro', 'enterprise') */\n tier: string\n /** Alternative tiers that also grant access */\n allowedTiers?: string[]\n}\n\nexport interface StripeRequirement {\n /** Stripe Price ID for per-call billing */\n priceId: string\n}\n\nexport type PaymentRequirement = \n | CreditRequirement \n | SubscriptionRequirement \n | StripeRequirement\n\nexport interface UserContext {\n /** User ID for billing lookup */\n userId: string\n /** Optional API key override */\n apiKey?: string\n}\n\nexport interface BillingStatus {\n /** Whether the user can proceed */\n allowed: boolean\n /** Current credit balance (if applicable) */\n credits?: number\n /** Current subscription tier (if applicable) */\n tier?: string\n /** Reason for denial (if not allowed) */\n reason?: string\n /** URL for upgrade/purchase */\n actionUrl?: string\n}\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nlet paymentConfig: PaymentConfig | null = null\n\n/**\n * Initialize payment/billing configuration\n * \n * @example\n * ```typescript\n * initPayment({\n * apiKey: process.env.OPENCONDUCTOR_API_KEY!,\n * upgradeUrl: 'https://myapp.com/upgrade'\n * })\n * ```\n */\nexport function initPayment(config: PaymentConfig): void {\n paymentConfig = {\n apiUrl: 'https://api.openconductor.ai',\n testMode: false,\n ...config,\n }\n}\n\n/**\n * Get current payment configuration\n */\nexport function getPaymentConfig(): PaymentConfig | null {\n return paymentConfig\n}\n\n// ============================================================================\n// Billing Client\n// ============================================================================\n\ninterface CheckBillingParams {\n userId: string\n requirement: PaymentRequirement\n toolName: string\n}\n\ninterface DeductCreditsParams {\n userId: string\n credits: number\n toolName: string\n callId?: string\n}\n\nasync function checkBilling(params: CheckBillingParams): Promise<BillingStatus> {\n const config = paymentConfig\n if (!config) {\n throw new ConfigurationError('payment', 'Payment not initialized. Call initPayment() first.')\n }\n\n // Test mode - always allow\n if (config.testMode) {\n return { allowed: true, credits: 9999, tier: 'test' }\n }\n\n try {\n const response = await fetch(`${config.apiUrl}/v1/billing/check`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n },\n body: JSON.stringify({\n userId: params.userId,\n requirement: params.requirement,\n tool: params.toolName,\n }),\n })\n\n if (!response.ok) {\n // Non-2xx response - treat as billing check failed\n const errorBody = await response.text()\n console.error('[payment] Billing check failed:', response.status, errorBody)\n return {\n allowed: false,\n reason: 'Billing service unavailable',\n actionUrl: config.upgradeUrl,\n }\n }\n\n return await response.json() as BillingStatus\n } catch (error) {\n // Network error - fail open or closed based on config\n console.error('[payment] Billing check error:', error)\n return {\n allowed: false,\n reason: 'Unable to verify billing status',\n actionUrl: config.upgradeUrl,\n }\n }\n}\n\nasync function deductCredits(params: DeductCreditsParams): Promise<boolean> {\n const config = paymentConfig\n if (!config) return false\n\n // Test mode - don't actually deduct\n if (config.testMode) return true\n\n try {\n const response = await fetch(`${config.apiUrl}/v1/billing/deduct`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n },\n body: JSON.stringify({\n userId: params.userId,\n credits: params.credits,\n tool: params.toolName,\n callId: params.callId,\n }),\n })\n\n return response.ok\n } catch (error) {\n console.error('[payment] Credit deduction error:', error)\n return false\n }\n}\n\n// ============================================================================\n// Type Guards\n// ============================================================================\n\nfunction isCreditRequirement(req: PaymentRequirement): req is CreditRequirement {\n return 'credits' in req && typeof req.credits === 'number'\n}\n\nfunction isSubscriptionRequirement(req: PaymentRequirement): req is SubscriptionRequirement {\n return 'tier' in req && typeof req.tier === 'string'\n}\n\nfunction isStripeRequirement(req: PaymentRequirement): req is StripeRequirement {\n return 'priceId' in req && typeof req.priceId === 'string'\n}\n\n// ============================================================================\n// Main Middleware\n// ============================================================================\n\nexport interface RequirePaymentOptions {\n /** Tool name for billing records */\n toolName?: string\n /** Function to extract user ID from input */\n getUserId?: (input: unknown) => string | undefined\n /** Custom error handler */\n onPaymentError?: (error: Error) => void\n}\n\n/**\n * One-line payment middleware for MCP tools\n * \n * @example Credits-based billing\n * ```typescript\n * const paidTool = requirePayment({ credits: 10 })(myHandler)\n * ```\n * \n * @example Subscription tier requirement\n * ```typescript\n * const premiumTool = requirePayment({ tier: 'pro' })(myHandler)\n * ```\n * \n * @example With wrapTool\n * ```typescript\n * const safePaidTool = wrapTool(\n * requirePayment({ credits: 5 })(myHandler),\n * { name: 'premium-analysis' }\n * )\n * ```\n */\nexport function requirePayment<TInput extends { userId?: string }, TOutput>(\n requirement: PaymentRequirement,\n options: RequirePaymentOptions = {}\n) {\n return (\n handler: (input: TInput) => TOutput | Promise<TOutput>\n ): ((input: TInput) => Promise<TOutput>) => {\n \n const { toolName = 'unknown', getUserId } = options\n\n return async (input: TInput): Promise<TOutput> => {\n // Extract user ID\n const userId = getUserId?.(input) ?? input.userId\n \n if (!userId) {\n throw new PaymentRequiredError(toolName, {\n upgradeUrl: paymentConfig?.upgradeUrl,\n })\n }\n\n // Check billing status\n const status = await checkBilling({\n userId,\n requirement,\n toolName,\n })\n\n if (!status.allowed) {\n // Throw appropriate error based on requirement type\n if (isCreditRequirement(requirement)) {\n throw new InsufficientCreditsError(\n requirement.credits,\n status.credits ?? 0,\n { purchaseUrl: status.actionUrl }\n )\n }\n\n if (isSubscriptionRequirement(requirement)) {\n throw new SubscriptionRequiredError(\n requirement.tier,\n status.tier,\n { upgradeUrl: status.actionUrl }\n )\n }\n\n // Generic payment required\n throw new PaymentRequiredError(toolName, {\n upgradeUrl: status.actionUrl,\n ...(isStripeRequirement(requirement) && { priceId: requirement.priceId }),\n })\n }\n\n // Execute the handler\n const result = await handler(input)\n\n // Deduct credits after successful execution (if credit-based)\n if (isCreditRequirement(requirement)) {\n await deductCredits({\n userId,\n credits: requirement.credits,\n toolName,\n })\n }\n\n return result\n }\n }\n}\n\n// ============================================================================\n// Convenience Functions\n// ============================================================================\n\n/**\n * Create a paid tool with built-in payment verification\n * \n * @example\n * ```typescript\n * const analyzeData = createPaidTool({\n * name: 'analyze-data',\n * payment: { credits: 10 },\n * handler: async (input) => {\n * // Your tool logic\n * return result\n * }\n * })\n * ```\n */\nexport function createPaidTool<TInput extends { userId?: string }, TOutput>(config: {\n name: string\n payment: PaymentRequirement\n handler: (input: TInput) => TOutput | Promise<TOutput>\n getUserId?: (input: TInput) => string | undefined\n}): (input: TInput) => Promise<TOutput> {\n return requirePayment<TInput, TOutput>(config.payment, {\n toolName: config.name,\n getUserId: config.getUserId as (input: unknown) => string | undefined,\n })(config.handler)\n}\n\n/**\n * Check if a user can access a paid feature without executing it\n * Useful for UI gating\n * \n * @example\n * ```typescript\n * const canAccess = await canUserAccess('user_123', { credits: 10 }, 'premium-tool')\n * if (!canAccess.allowed) {\n * showUpgradePrompt(canAccess.actionUrl)\n * }\n * ```\n */\nexport async function canUserAccess(\n userId: string,\n requirement: PaymentRequirement,\n toolName: string = 'check'\n): Promise<BillingStatus> {\n return checkBilling({ userId, requirement, toolName })\n}\n\n/**\n * Get user's current billing status\n */\nexport async function getUserBillingStatus(userId: string): Promise<{\n credits: number\n tier: string\n active: boolean\n} | null> {\n const config = paymentConfig\n if (!config) return null\n\n if (config.testMode) {\n return { credits: 9999, tier: 'test', active: true }\n }\n\n try {\n const response = await fetch(`${config.apiUrl}/v1/billing/status/${userId}`, {\n headers: {\n 'Authorization': `Bearer ${config.apiKey}`,\n },\n })\n\n if (!response.ok) return null\n return await response.json()\n } catch {\n return null\n }\n}\n"]}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
// src/errors/codes.ts
|
|
2
|
+
var ErrorCodes = {
|
|
3
|
+
CONFIGURATION_ERROR: -32010,
|
|
4
|
+
PAYMENT_REQUIRED: -32011,
|
|
5
|
+
INSUFFICIENT_CREDITS: -32012,
|
|
6
|
+
SUBSCRIPTION_REQUIRED: -32013
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// src/errors/index.ts
|
|
10
|
+
var MCPError = class extends Error {
|
|
11
|
+
code;
|
|
12
|
+
data;
|
|
13
|
+
constructor(code, message, data) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = "MCPError";
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.data = data;
|
|
18
|
+
if (Error.captureStackTrace) {
|
|
19
|
+
Error.captureStackTrace(this, this.constructor);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Returns JSON-RPC 2.0 formatted error object
|
|
24
|
+
*/
|
|
25
|
+
toJSON() {
|
|
26
|
+
return {
|
|
27
|
+
code: this.code,
|
|
28
|
+
message: this.message,
|
|
29
|
+
...this.data && { data: this.data }
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create error response for JSON-RPC
|
|
34
|
+
*/
|
|
35
|
+
toResponse(id = null) {
|
|
36
|
+
return {
|
|
37
|
+
jsonrpc: "2.0",
|
|
38
|
+
id,
|
|
39
|
+
error: this.toJSON()
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var ConfigurationError = class extends MCPError {
|
|
44
|
+
constructor(setting, reason) {
|
|
45
|
+
super(ErrorCodes.CONFIGURATION_ERROR, `Invalid configuration '${setting}': ${reason}`, {
|
|
46
|
+
setting,
|
|
47
|
+
reason
|
|
48
|
+
});
|
|
49
|
+
this.name = "ConfigurationError";
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var PaymentRequiredError = class extends MCPError {
|
|
53
|
+
constructor(toolName, options) {
|
|
54
|
+
super(ErrorCodes.PAYMENT_REQUIRED, `Payment required to use '${toolName}'`, {
|
|
55
|
+
tool: toolName,
|
|
56
|
+
...options?.upgradeUrl && { upgradeUrl: options.upgradeUrl },
|
|
57
|
+
...options?.priceId && { priceId: options.priceId }
|
|
58
|
+
});
|
|
59
|
+
this.name = "PaymentRequiredError";
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
var InsufficientCreditsError = class extends MCPError {
|
|
63
|
+
constructor(required, available, options) {
|
|
64
|
+
super(ErrorCodes.INSUFFICIENT_CREDITS, `Insufficient credits: need ${required}, have ${available}`, {
|
|
65
|
+
required,
|
|
66
|
+
available,
|
|
67
|
+
...options?.purchaseUrl && { purchaseUrl: options.purchaseUrl }
|
|
68
|
+
});
|
|
69
|
+
this.name = "InsufficientCreditsError";
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var SubscriptionRequiredError = class extends MCPError {
|
|
73
|
+
constructor(requiredTier, currentTier, options) {
|
|
74
|
+
const msg = currentTier ? `Subscription '${requiredTier}' required (current: '${currentTier}')` : `Subscription '${requiredTier}' required`;
|
|
75
|
+
super(ErrorCodes.SUBSCRIPTION_REQUIRED, msg, {
|
|
76
|
+
requiredTier,
|
|
77
|
+
...currentTier && { currentTier },
|
|
78
|
+
...options?.upgradeUrl && { upgradeUrl: options.upgradeUrl }
|
|
79
|
+
});
|
|
80
|
+
this.name = "SubscriptionRequiredError";
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// src/payment/index.ts
|
|
85
|
+
var paymentConfig = null;
|
|
86
|
+
function initPayment(config) {
|
|
87
|
+
paymentConfig = {
|
|
88
|
+
apiUrl: "https://api.openconductor.ai",
|
|
89
|
+
testMode: false,
|
|
90
|
+
...config
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function getPaymentConfig() {
|
|
94
|
+
return paymentConfig;
|
|
95
|
+
}
|
|
96
|
+
async function checkBilling(params) {
|
|
97
|
+
const config = paymentConfig;
|
|
98
|
+
if (!config) {
|
|
99
|
+
throw new ConfigurationError("payment", "Payment not initialized. Call initPayment() first.");
|
|
100
|
+
}
|
|
101
|
+
if (config.testMode) {
|
|
102
|
+
return { allowed: true, credits: 9999, tier: "test" };
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const response = await fetch(`${config.apiUrl}/v1/billing/check`, {
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: {
|
|
108
|
+
"Content-Type": "application/json",
|
|
109
|
+
"Authorization": `Bearer ${config.apiKey}`
|
|
110
|
+
},
|
|
111
|
+
body: JSON.stringify({
|
|
112
|
+
userId: params.userId,
|
|
113
|
+
requirement: params.requirement,
|
|
114
|
+
tool: params.toolName
|
|
115
|
+
})
|
|
116
|
+
});
|
|
117
|
+
if (!response.ok) {
|
|
118
|
+
const errorBody = await response.text();
|
|
119
|
+
console.error("[payment] Billing check failed:", response.status, errorBody);
|
|
120
|
+
return {
|
|
121
|
+
allowed: false,
|
|
122
|
+
reason: "Billing service unavailable",
|
|
123
|
+
actionUrl: config.upgradeUrl
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return await response.json();
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.error("[payment] Billing check error:", error);
|
|
129
|
+
return {
|
|
130
|
+
allowed: false,
|
|
131
|
+
reason: "Unable to verify billing status",
|
|
132
|
+
actionUrl: config.upgradeUrl
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async function deductCredits(params) {
|
|
137
|
+
const config = paymentConfig;
|
|
138
|
+
if (!config) return false;
|
|
139
|
+
if (config.testMode) return true;
|
|
140
|
+
try {
|
|
141
|
+
const response = await fetch(`${config.apiUrl}/v1/billing/deduct`, {
|
|
142
|
+
method: "POST",
|
|
143
|
+
headers: {
|
|
144
|
+
"Content-Type": "application/json",
|
|
145
|
+
"Authorization": `Bearer ${config.apiKey}`
|
|
146
|
+
},
|
|
147
|
+
body: JSON.stringify({
|
|
148
|
+
userId: params.userId,
|
|
149
|
+
credits: params.credits,
|
|
150
|
+
tool: params.toolName,
|
|
151
|
+
callId: params.callId
|
|
152
|
+
})
|
|
153
|
+
});
|
|
154
|
+
return response.ok;
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.error("[payment] Credit deduction error:", error);
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function isCreditRequirement(req) {
|
|
161
|
+
return "credits" in req && typeof req.credits === "number";
|
|
162
|
+
}
|
|
163
|
+
function isSubscriptionRequirement(req) {
|
|
164
|
+
return "tier" in req && typeof req.tier === "string";
|
|
165
|
+
}
|
|
166
|
+
function isStripeRequirement(req) {
|
|
167
|
+
return "priceId" in req && typeof req.priceId === "string";
|
|
168
|
+
}
|
|
169
|
+
function requirePayment(requirement, options = {}) {
|
|
170
|
+
return (handler) => {
|
|
171
|
+
const { toolName = "unknown", getUserId } = options;
|
|
172
|
+
return async (input) => {
|
|
173
|
+
const userId = getUserId?.(input) ?? input.userId;
|
|
174
|
+
if (!userId) {
|
|
175
|
+
throw new PaymentRequiredError(toolName, {
|
|
176
|
+
upgradeUrl: paymentConfig?.upgradeUrl
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
const status = await checkBilling({
|
|
180
|
+
userId,
|
|
181
|
+
requirement,
|
|
182
|
+
toolName
|
|
183
|
+
});
|
|
184
|
+
if (!status.allowed) {
|
|
185
|
+
if (isCreditRequirement(requirement)) {
|
|
186
|
+
throw new InsufficientCreditsError(
|
|
187
|
+
requirement.credits,
|
|
188
|
+
status.credits ?? 0,
|
|
189
|
+
{ purchaseUrl: status.actionUrl }
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
if (isSubscriptionRequirement(requirement)) {
|
|
193
|
+
throw new SubscriptionRequiredError(
|
|
194
|
+
requirement.tier,
|
|
195
|
+
status.tier,
|
|
196
|
+
{ upgradeUrl: status.actionUrl }
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
throw new PaymentRequiredError(toolName, {
|
|
200
|
+
upgradeUrl: status.actionUrl,
|
|
201
|
+
...isStripeRequirement(requirement) && { priceId: requirement.priceId }
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
const result = await handler(input);
|
|
205
|
+
if (isCreditRequirement(requirement)) {
|
|
206
|
+
await deductCredits({
|
|
207
|
+
userId,
|
|
208
|
+
credits: requirement.credits,
|
|
209
|
+
toolName
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
return result;
|
|
213
|
+
};
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function createPaidTool(config) {
|
|
217
|
+
return requirePayment(config.payment, {
|
|
218
|
+
toolName: config.name,
|
|
219
|
+
getUserId: config.getUserId
|
|
220
|
+
})(config.handler);
|
|
221
|
+
}
|
|
222
|
+
async function canUserAccess(userId, requirement, toolName = "check") {
|
|
223
|
+
return checkBilling({ userId, requirement, toolName });
|
|
224
|
+
}
|
|
225
|
+
async function getUserBillingStatus(userId) {
|
|
226
|
+
const config = paymentConfig;
|
|
227
|
+
if (!config) return null;
|
|
228
|
+
if (config.testMode) {
|
|
229
|
+
return { credits: 9999, tier: "test", active: true };
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
const response = await fetch(`${config.apiUrl}/v1/billing/status/${userId}`, {
|
|
233
|
+
headers: {
|
|
234
|
+
"Authorization": `Bearer ${config.apiKey}`
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
if (!response.ok) return null;
|
|
238
|
+
return await response.json();
|
|
239
|
+
} catch {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export { canUserAccess, createPaidTool, getPaymentConfig, getUserBillingStatus, initPayment, requirePayment };
|
|
245
|
+
//# sourceMappingURL=index.mjs.map
|
|
246
|
+
//# sourceMappingURL=index.mjs.map
|