UNPKG

ws402

Version:

WebSocket implementation of X402 protocol for pay-as-you-go digital resources with automatic refunds

226 lines (225 loc) 7.66 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProxyPaymentProvider = void 0; /** * Proxy Payment Provider for WS402 * * Delegates all payment operations to a centralized gateway server. * The gateway handles: * - Payment verification on blockchain * - Refund processing (has private keys) * - Centralized logging and auditing * * Benefits: * - No private keys on WS402 servers * - Multiple WS402 servers can share one gateway * - Centralized payment logic and security * - Easier compliance and auditing */ class ProxyPaymentProvider { constructor(config) { this.config = { gatewayUrl: config.gatewayUrl.replace(/\/$/, ''), // Remove trailing slash apiKey: config.apiKey, timeout: config.timeout || 30000, // 30 seconds retryAttempts: config.retryAttempts || 3, customHeaders: config.customHeaders, }; this.log('✅ ProxyPaymentProvider initialized'); this.log(` Gateway: ${this.config.gatewayUrl}`); } /** * Generate payment details by requesting from gateway */ generatePaymentDetails(amount) { this.validateAmount(amount); // For synchronous generation, we return a structure that includes // instructions to call the gateway. The actual payment details // should be fetched from the gateway in the HTTP schema endpoint. return { type: 'proxy', amount, gatewayUrl: this.config.gatewayUrl, instructions: { step1: 'Payment details will be fetched from gateway', step2: 'Use /ws402/schema endpoint to get blockchain payment details', } }; } /** * Request payment details from gateway (async version for HTTP endpoints) */ async requestPaymentDetails(amount, resourceId, estimatedDuration) { this.validateAmount(amount); const endpoint = `${this.config.gatewayUrl}/api/payment/generate`; this.log('📤 Requesting payment details from gateway', { amount, resourceId, estimatedDuration, }); try { const response = await this.makeRequest(endpoint, 'POST', { amount, resourceId, estimatedDuration, }); this.log('✅ Payment details received from gateway'); return response; } catch (error) { this.log('❌ Failed to get payment details:', error.message); throw new Error(`Gateway error: ${error.message}`); } } /** * Verify payment by delegating to gateway */ async verifyPayment(proof) { const endpoint = `${this.config.gatewayUrl}/api/payment/verify`; this.log('📤 Sending payment verification to gateway', { proofType: proof.type || 'unknown', }); try { const response = await this.makeRequest(endpoint, 'POST', { proof, timestamp: Date.now(), }); this.log('✅ Payment verification response received', { valid: response.valid, amount: response.amount, }); return { valid: response.valid, amount: response.amount, reason: response.reason, }; } catch (error) { this.log('❌ Payment verification failed:', error.message); return { valid: false, amount: 0, reason: `Gateway error: ${error.message}`, }; } } /** * Issue refund by delegating to gateway */ async issueRefund(proof, amount) { const endpoint = `${this.config.gatewayUrl}/api/payment/refund`; this.log('📤 Requesting refund from gateway', { amount, }); try { const response = await this.makeRequest(endpoint, 'POST', { proof, amount, timestamp: Date.now(), }); this.log('✅ Refund processed by gateway', { txHash: response.txHash, status: response.status, }); } catch (error) { this.log('❌ Refund failed:', error.message); throw new Error(`Gateway refund error: ${error.message}`); } } /** * Make HTTP request to gateway with retry logic */ async makeRequest(url, method = 'POST', body, attempt = 1) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.config.timeout); try { const headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.config.apiKey}`, 'X-WS402-Provider': 'proxy', ...this.config.customHeaders, }; const response = await fetch(url, { method, headers, body: body ? JSON.stringify(body) : undefined, signal: controller.signal, }); clearTimeout(timeoutId); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.message || `Gateway returned ${response.status}: ${response.statusText}`); } return await response.json(); } catch (error) { clearTimeout(timeoutId); // Retry on network errors if (attempt < this.config.retryAttempts && this.isRetryableError(error)) { this.log(`⚠️ Request failed, retrying (${attempt}/${this.config.retryAttempts})...`); await this.delay(1000 * attempt); // Exponential backoff return this.makeRequest(url, method, body, attempt + 1); } throw error; } } /** * Check if error is retryable */ isRetryableError(error) { // Retry on network errors, timeouts, 5xx errors return (error.name === 'AbortError' || error.code === 'ECONNREFUSED' || error.code === 'ETIMEDOUT' || error.message.includes('500') || error.message.includes('502') || error.message.includes('503') || error.message.includes('504')); } /** * Delay helper for retry logic */ delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Validate amount is positive */ validateAmount(amount) { if (amount <= 0) { throw new Error('Amount must be positive'); } } /** * Log activity */ log(message, data) { console.log(`[ProxyPaymentProvider] ${message}`, data || ''); } /** * Get gateway info */ getGatewayInfo() { return { gatewayUrl: this.config.gatewayUrl, timeout: this.config.timeout, retryAttempts: this.config.retryAttempts, }; } /** * Health check - ping gateway */ async healthCheck() { const endpoint = `${this.config.gatewayUrl}/api/health`; try { const response = await this.makeRequest(endpoint, 'GET'); return response.status === 'ok'; } catch (error) { return false; } } } exports.ProxyPaymentProvider = ProxyPaymentProvider;