@w3payments/adapters
v1.2.2
Published
Payment vendor adapters for W3 Payments platform
Maintainers
Readme
@w3payments/adapters
Vendor-specific payment adapter implementations for the W3 Payments Platform. This package provides the integration layer between the W3 Payments core engine and external payment vendors using the adapter pattern for extensibility.
Installation
npm install @w3payments/adaptersDependencies: Automatically installs @w3payments/common.
Key Features
- MeshPay integration - Complete Mesh Connect SDK implementation
- Adapter pattern architecture - Extensible design for adding new vendors
- Vendor abstraction - Unified interface across different payment providers
- Error handling - Vendor-specific error mapping and recovery
- Type safety - Full TypeScript support for all vendor APIs
🚀 Current Integrations
MeshPay Adapter
MeshPay provides crypto-to-crypto payment processing and exchange integrations.
Features
- Crypto-to-crypto transfers - BTC, ETH, USDC, SOL and more
- Exchange integrations - Coinbase, Kraken, Binance support
- Secure authentication - OAuth-based user consent flow
- Real-time status - Payment tracking and confirmation
- Multi-network support - Bitcoin, Ethereum, Solana, Polygon
Basic Usage
import { MeshPayAdapter } from '@w3payments/adapters/meshpay';
import type { CreatePaymentOptions } from '@w3payments/common';
// Initialize adapter
const adapter = new MeshPayAdapter({
clientId: 'your_mesh_client_id',
clientSecret: 'your_mesh_client_secret',
environment: 'sandbox' // or 'production'
});
// Create payment session
const paymentOptions: CreatePaymentOptions = {
amount: '100.00',
currency: 'USD',
destinations: [{
address: '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa',
networkId: 'bitcoin',
symbol: 'BTC'
}],
targetCurrency: 'BTC',
targetNetwork: 'bitcoin'
};
const session = await adapter.createSession(paymentOptions);
console.log('MeshPay session URL:', session.url);Supported Payment Methods
// Crypto wallet transfers
const cryptoOptions = {
amount: '50.00',
currency: 'USD',
targetCurrency: 'USDC',
targetNetwork: 'ethereum',
// User pays with existing crypto from their wallet
paymentMethodFilter: {
walletFilter: ['BTC', 'ETH', 'USDC']
}
};
// Exchange account transfers
const exchangeOptions = {
amount: '100.00',
currency: 'USD',
targetCurrency: 'SOL',
targetNetwork: 'solana',
// User pays from exchange account (Coinbase, Kraken, etc.)
paymentMethodFilter: {
exchangeFilter: ['coinbase', 'kraken', 'binance']
}
};Configuration Options
interface MeshPayConfig {
clientId: string; // MeshPay client ID
clientSecret: string; // MeshPay client secret
environment: 'sandbox' | 'production';
// Optional settings
settings?: {
transferFinishTimeout?: number; // Default: 60000ms
catalogPageSize?: number; // Default: 20
enableTestMode?: boolean; // Default: false
};
}
const adapter = new MeshPayAdapter({
clientId: process.env.MESH_CLIENT_ID!,
clientSecret: process.env.MESH_CLIENT_SECRET!,
environment: 'production',
settings: {
transferFinishTimeout: 120000, // 2 minutes
catalogPageSize: 50,
enableTestMode: false
}
});Session Management
// Create session with specific options
const session = await adapter.createSession({
amount: '75.00',
currency: 'USD',
destinations: [/* ... */],
// MeshPay specific options
vendorOptions: {
userId: 'user_123', // Optional user identifier
metadata: { // Optional metadata
orderId: 'order_456',
merchantId: 'merchant_789'
}
}
});
// Check session status
const status = await adapter.getSessionStatus(session.id);
console.log('Payment status:', status.status);
console.log('Transaction ID:', status.transactionId);
console.log('Network fees:', status.networkFees);🔧 Creating Custom Adapters
Adapter Interface
All adapters must implement the VendorAdapter interface:
import type {
VendorAdapter,
CreatePaymentOptions,
CheckoutSession,
PaymentResult
} from '@w3payments/common';
interface VendorAdapter {
// Create new payment session
createSession(options: CreatePaymentOptions): Promise<CheckoutSession>;
// Get payment status
getSessionStatus(sessionId: string): Promise<PaymentResult>;
// Optional: Get supported payment methods
getSupportedMethods?(): Promise<PaymentMethod[]>;
// Optional: Validate configuration
validateConfig?(): Promise<boolean>;
}Example Custom Adapter
import type {
VendorAdapter,
CreatePaymentOptions,
CheckoutSession,
PaymentResult
} from '@w3payments/common';
export class CustomVendorAdapter implements VendorAdapter {
private apiKey: string;
private baseUrl: string;
constructor(config: { apiKey: string; environment: string }) {
this.apiKey = config.apiKey;
this.baseUrl = config.environment === 'production'
? 'https://api.customvendor.com'
: 'https://sandbox.customvendor.com';
}
async createSession(options: CreatePaymentOptions): Promise<CheckoutSession> {
// Validate input
if (!options.amount || !options.destinations?.length) {
throw new Error('Invalid payment options');
}
// Transform W3 format to vendor format
const vendorPayload = this.transformPaymentOptions(options);
// Make API call
const response = await fetch(`${this.baseUrl}/sessions`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(vendorPayload)
});
if (!response.ok) {
throw new Error(`Vendor API error: ${response.statusText}`);
}
const data = await response.json();
// Transform vendor response to W3 format
return {
id: data.sessionId,
vendorId: 'custom-vendor',
url: data.checkoutUrl,
status: 'pending',
expiresAt: new Date(data.expirationTime)
};
}
async getSessionStatus(sessionId: string): Promise<PaymentResult> {
const response = await fetch(`${this.baseUrl}/sessions/${sessionId}`, {
headers: {
'Authorization': `Bearer ${this.apiKey}`
}
});
if (!response.ok) {
throw new Error(`Failed to get session status: ${response.statusText}`);
}
const data = await response.json();
return {
status: this.mapVendorStatus(data.status),
transactionId: data.transactionHash,
amount: data.finalAmount,
currency: data.finalCurrency,
networkFees: data.fees,
completedAt: data.completedAt ? new Date(data.completedAt) : undefined
};
}
private transformPaymentOptions(options: CreatePaymentOptions) {
// Transform W3 payment options to vendor API format
return {
amount: parseFloat(options.amount),
sourceCurrency: options.currency,
targetCurrency: options.targetCurrency,
destinationAddress: options.destinations[0].address,
network: options.targetNetwork,
// Add vendor-specific fields
};
}
private mapVendorStatus(vendorStatus: string): PaymentStatus {
// Map vendor status to standard W3 status
switch (vendorStatus.toLowerCase()) {
case 'created':
case 'pending':
return 'pending';
case 'processing':
case 'confirming':
return 'processing';
case 'completed':
case 'success':
return 'completed';
case 'failed':
case 'error':
case 'cancelled':
return 'failed';
default:
return 'pending';
}
}
}Registering Custom Adapters
import { AdapterRegistry } from '@w3payments/core';
import { CustomVendorAdapter } from './custom-vendor-adapter';
// Register with core
const registry = new AdapterRegistry();
registry.register('custom-vendor', new CustomVendorAdapter({
apiKey: process.env.CUSTOM_VENDOR_API_KEY!,
environment: 'sandbox'
}));🔒 Security Considerations
API Key Management
// ✅ Good: Use environment variables
const adapter = new MeshPayAdapter({
clientId: process.env.MESH_CLIENT_ID!,
clientSecret: process.env.MESH_CLIENT_SECRET!,
environment: 'production'
});
// ❌ Bad: Hardcoded secrets
const adapter = new MeshPayAdapter({
clientId: 'mesh_client_123',
clientSecret: 'mesh_secret_456', // Never do this!
environment: 'production'
});Request Validation
export class SecureAdapter implements VendorAdapter {
async createSession(options: CreatePaymentOptions): Promise<CheckoutSession> {
// Validate required fields
this.validatePaymentOptions(options);
// Sanitize input data
const sanitizedOptions = this.sanitizeInput(options);
// Proceed with API call
return this.callVendorAPI(sanitizedOptions);
}
private validatePaymentOptions(options: CreatePaymentOptions): void {
if (!options.amount || parseFloat(options.amount) <= 0) {
throw new Error('Invalid amount');
}
if (!options.destinations?.length) {
throw new Error('At least one destination required');
}
// Validate addresses
for (const dest of options.destinations) {
if (!this.isValidAddress(dest.address, dest.networkId)) {
throw new Error(`Invalid address for network ${dest.networkId}`);
}
}
}
private sanitizeInput(options: CreatePaymentOptions): CreatePaymentOptions {
return {
...options,
amount: parseFloat(options.amount).toFixed(8), // Normalize precision
destinations: options.destinations.map(dest => ({
...dest,
address: dest.address.trim(), // Remove whitespace
}))
};
}
}⚠️ Error Handling
Adapter-Specific Errors
export class AdapterError extends Error {
constructor(
message: string,
public vendorId: string,
public vendorCode?: string,
public details?: any
) {
super(message);
this.name = 'AdapterError';
}
}
// Usage in adapter
async createSession(options: CreatePaymentOptions): Promise<CheckoutSession> {
try {
const response = await this.callVendorAPI(options);
return this.transformResponse(response);
} catch (error) {
// Transform vendor errors to standardized format
throw new AdapterError(
'Failed to create payment session',
'mesh-pay',
error.code,
{ originalError: error.message }
);
}
}Retry Logic
async function withRetry<T>(
operation: () => Promise<T>,
maxRetries: number = 3,
delay: number = 1000
): Promise<T> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
if (attempt === maxRetries) {
throw error;
}
// Exponential backoff
await new Promise(resolve => setTimeout(resolve, delay * attempt));
}
}
throw new Error('Max retries exceeded');
}
// Use in adapter
async getSessionStatus(sessionId: string): Promise<PaymentResult> {
return withRetry(() => this.fetchSessionStatus(sessionId));
}🧪 Testing Adapters
Mock Implementations
export class MockMeshPayAdapter implements VendorAdapter {
private sessions = new Map<string, any>();
async createSession(options: CreatePaymentOptions): Promise<CheckoutSession> {
const sessionId = `mock_session_${Date.now()}`;
this.sessions.set(sessionId, {
...options,
status: 'pending',
createdAt: new Date()
});
return {
id: sessionId,
vendorId: 'mesh-pay',
url: `https://mock-meshpay.example.com/session/${sessionId}`,
status: 'pending'
};
}
async getSessionStatus(sessionId: string): Promise<PaymentResult> {
const session = this.sessions.get(sessionId);
if (!session) {
throw new Error('Session not found');
}
return {
status: 'completed', // Mock successful payment
transactionId: `mock_tx_${sessionId}`,
amount: session.amount,
currency: session.currency
};
}
}Integration Tests
import { MeshPayAdapter } from '@w3payments/adapters/meshpay';
describe('MeshPayAdapter', () => {
let adapter: MeshPayAdapter;
beforeEach(() => {
adapter = new MeshPayAdapter({
clientId: 'test_client_id',
clientSecret: 'test_client_secret',
environment: 'sandbox'
});
});
test('creates payment session successfully', async () => {
const options = {
amount: '100.00',
currency: 'USD',
destinations: [{
address: 'test_address',
networkId: 'ethereum',
symbol: 'USDC'
}]
};
const session = await adapter.createSession(options);
expect(session.id).toBeDefined();
expect(session.vendorId).toBe('mesh-pay');
expect(session.url).toMatch(/^https:/);
});
});📚 Planned Integrations
Coming Soon
- IronPay - Fiat-to-crypto onramping with ACH support
- Circle - USDC payment processing and programmable wallets
- Stripe Crypto - Traditional payment integration with crypto settlement
- Coinbase Commerce - Direct Coinbase integration for merchants
- BitPay - Bitcoin payment processing and invoicing
Future Considerations
- PayPal Crypto - When available for business integration
- Square Crypto - Point-of-sale crypto payments
- Shopify Payments - E-commerce platform integration
📄 License
Proprietary - All rights reserved
💡 Pro Tip: When building custom adapters, always implement comprehensive error handling and validate all inputs. The adapter pattern allows you to maintain consistent behavior across different vendors while handling their unique requirements.
