@thorium-dev-group/x402-mcp-extension
Version:
X402-MCP Protocol Extension
195 lines • 9.25 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PaymentOrchestrator = void 0;
const schemas_1 = require("../../../shared/schemas");
const PaymentError_1 = require("../../../shared/errors/PaymentError");
const error_codes_1 = require("../../../shared/error-codes");
class PaymentOrchestrator {
options;
logger;
constructor(options) {
this.options = options;
this.logger = options.loggerFactory.createLogger(PaymentOrchestrator.name);
}
async validatePayment(handlerName, paymentRequirements, extra) {
this.logger.info('Validating payment for handler', {
operation: 'payment_validation_start',
handlerName: handlerName,
amount: paymentRequirements.maxAmountRequired
});
try {
const paymentResponse = await this.sendPaymentRequiredRequest(paymentRequirements, extra);
this.logger.info('Payment required request sent', {
operation: 'payment_required_request_sent',
handlerName: handlerName,
paymentResponse: paymentResponse
});
await this.validatePaymentProof(paymentResponse, paymentRequirements, extra);
this.logger.info('Payment validation successful', {
operation: 'payment_validation_success',
handlerName: handlerName
});
}
catch (error) {
this.logger.error('Payment validation failed', {
operation: 'payment_validation_failed',
handlerName: handlerName,
error: error instanceof Error ? error.message : String(error)
});
if (error instanceof PaymentError_1.PaymentError) {
throw error;
}
else {
throw new PaymentError_1.PaymentError(error_codes_1.ERROR_CODES.PAYMENT_INVALID, `Payment validation failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
async settlePayment(extra) {
this.logger.info('Settling payment', {
operation: 'payment_settlement_start'
});
try {
const paymentProof = extra.paymentProof;
const paymentRequirements = extra.paymentRequirements;
if (!paymentProof || !paymentRequirements) {
throw new PaymentError_1.PaymentError(error_codes_1.ERROR_CODES.PAYMENT_INVALID, 'Payment proof or requirements not found in context');
}
const paymentResult = await this.executePayment(paymentProof, paymentRequirements);
if (!paymentResult.success) {
throw new PaymentError_1.PaymentError(error_codes_1.ERROR_CODES.PAYMENT_EXECUTION_FAILED, `Payment settlement failed: ${paymentResult.errorReason}`);
}
await extra.sendNotification({
jsonrpc: '2.0',
method: schemas_1.PaymentResultNotificationSchema.shape.method.value,
params: {
success: true,
transaction: paymentResult.transaction,
network: paymentResult.network,
requestId: extra.requestId,
payer: paymentResult.payer,
},
});
this.logger.info('Payment settled successfully', {
operation: 'payment_settlement_success',
transaction: paymentResult.transaction
});
}
catch (error) {
this.logger.error('Payment settlement failed', {
operation: 'payment_settlement_failed',
error: error instanceof Error ? error.message : String(error)
});
await extra.sendNotification({
jsonrpc: '2.0',
method: schemas_1.PaymentResultNotificationSchema.shape.method.value,
params: {
success: false,
errorReason: error instanceof Error ? error.message : String(error),
network: this.options.network,
requestId: extra.requestId,
},
});
if (error instanceof PaymentError_1.PaymentError) {
throw error;
}
else {
throw new PaymentError_1.PaymentError(error_codes_1.ERROR_CODES.PAYMENT_EXECUTION_FAILED, `Payment settlement failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
async sendPaymentRequiredRequest(paymentRequirements, extra) {
const requestId = extra.requestId;
const paymentRequest = {
jsonrpc: '2.0',
id: requestId,
method: 'x402/payment_required',
params: {
scheme: paymentRequirements.scheme,
network: paymentRequirements.network,
maxAmountRequired: paymentRequirements.maxAmountRequired,
resource: paymentRequirements.resource,
description: paymentRequirements.description,
mimeType: paymentRequirements.mimeType,
payTo: paymentRequirements.payTo,
maxTimeoutSeconds: paymentRequirements.maxTimeoutSeconds,
asset: paymentRequirements.asset,
outputSchema: paymentRequirements.outputSchema,
extra: paymentRequirements.extra,
x402Version: 1,
requestId: requestId,
},
};
try {
const response = await extra.sendRequest(paymentRequest, schemas_1.PaymentRequiredResponseSchema);
return response;
}
catch (error) {
this.logger.error('Failed to send payment request', {
operation: 'send_payment_required_request',
error: error
});
if (this.isMethodNotFoundError(error)) {
throw new PaymentError_1.PaymentError(error_codes_1.ERROR_CODES.PAYMENT_REQUIRED, `Payment required: ${paymentRequirements.description || 'Payment is required for this operation'}`, {
amount: paymentRequirements.maxAmountRequired,
asset: paymentRequirements.asset,
paymentAddress: this.options.payTo,
network: this.options.network,
});
}
throw new PaymentError_1.PaymentError(error.code ? error.code : error_codes_1.ERROR_CODES.PAYMENT_INVALID, 'Failed to send payment request', error);
}
}
isMethodNotFoundError(error) {
return error?.code === -32601 ||
error?.message?.includes('Method not found') ||
error?.message?.includes('method not found');
}
async validatePaymentProof(response, paymentRequirements, extra) {
const payment = response.result.payment;
if (!payment || !payment.payload?.signature) {
this.logger.error('Invalid payment proof: missing signature', {
operation: 'validate_payment_proof',
payment: payment
});
throw new PaymentError_1.PaymentError(error_codes_1.ERROR_CODES.PAYMENT_INVALID, 'Invalid payment proof: missing signature');
}
if (payment.x402Version !== 1) {
this.logger.error('Invalid x402 version', {
operation: 'validate_payment_proof',
payment: payment
});
throw new PaymentError_1.PaymentError(error_codes_1.ERROR_CODES.INVALID_REQUEST, 'Invalid x402 version');
}
if (payment.scheme !== 'exact') {
this.logger.error('Invalid payment scheme', {
operation: 'validate_payment_proof',
payment: payment
});
throw new PaymentError_1.PaymentError(error_codes_1.ERROR_CODES.PAYMENT_INVALID, 'Invalid payment scheme');
}
if (payment.network !== paymentRequirements.network) {
this.logger.error('Invalid payment proof: network mismatch', {
operation: 'validate_payment_proof',
payment: payment
});
throw new PaymentError_1.PaymentError(error_codes_1.ERROR_CODES.PAYMENT_INVALID, 'Invalid payment proof: network mismatch');
}
const res = await this.options.facilitator.validatePayment(payment, paymentRequirements);
if (!res.isValid) {
this.logger.error('Payment proof validation failed', {
operation: 'validate_payment_proof',
payment: payment,
failReason: res.invalidReason
});
throw new PaymentError_1.PaymentError(error_codes_1.ERROR_CODES.PAYMENT_INVALID, res.invalidReason || 'Payment proof validation failed');
}
extra.paymentProof = payment;
extra.paymentRequirements = paymentRequirements;
return payment;
}
async executePayment(paymentProof, paymentRequirements) {
return await this.options.facilitator.executePayment(paymentProof, paymentRequirements);
}
}
exports.PaymentOrchestrator = PaymentOrchestrator;
//# sourceMappingURL=PaymentOrchestrator.js.map