API Versioning
URL-based Versioning
// Version 1 API
app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);
// Version 1 Implementation
class PaymentProcessorV1 {
async createPayment(data: PaymentDataV1): Promise<PaymentResponseV1> {
return await finfusion.v1.payments.create(data);
}
}
// Version 2 Implementation with new features
class PaymentProcessorV2 {
async createPayment(data: PaymentDataV2): Promise<PaymentResponseV2> {
// New validation logic
await this.validateEnhancedPaymentData(data);
// New fraud detection
const riskScore = await this.assessRisk(data);
return await finfusion.v2.payments.create({
...data,
riskScore,
enhancedAuth: true
});
}
}
Header-based Versioning
class VersionRouter {
route(req: Request): ApiVersion {
const version = req.headers['x-api-version'];
switch (version) {
case '2024-01-01':
return new ApiVersion2024Q1();
case '2023-07-01':
return new ApiVersion2023Q3();
default:
return new LatestApiVersion();
}
}
}
// Middleware implementation
app.use((req, res, next) => {
const version = req.headers['x-api-version'];
req.apiVersion = versionRouter.route(req);
next();
});
Migration Guides
Migration: v1 to v2
// Old v1 payment creation
const paymentV1 = await finfusion.payments.create({
amount: 1000,
currency: 'USD',
source: 'card_123'
});
// New v2 payment creation
const paymentV2 = await finfusion.payments.create({
amount: {
value: 1000,
currency: 'USD'
},
source: {
type: 'card',
id: 'card_123',
authentication: {
type: '3ds2',
returnUrl: 'https://example.com/return'
}
},
metadata: {
migrationVersion: 'v2'
}
});
// Migration helper
class PaymentMigrationHelper {
static convertV1ToV2(v1Payment: PaymentV1): PaymentV2 {
return {
amount: {
value: v1Payment.amount,
currency: v1Payment.currency
},
source: {
type: this.determineSourceType(v1Payment.source),
id: v1Payment.source,
authentication: this.getDefaultAuthentication()
}
};
}
}
Troubleshooting
Common Issues
API Authentication Failures
class AuthTroubleshooter {
async diagnose(error: AuthError): Promise<DiagnosisResult> {
// Check API key format
if (!this.isValidApiKeyFormat(error.apiKey)) {
return {
issue: 'Invalid API key format',
solution: 'Ensure API key follows format: fin_live_xxxxxx'
};
}
// Check API key permissions
const keyInfo = await this.checkApiKeyPermissions(error.apiKey);
if (!keyInfo.hasRequiredPermissions) {
return {
issue: 'Insufficient permissions',
solution: `Missing permissions: ${keyInfo.missingPermissions.join(', ')}`
};
}
// Check IP whitelist
const ipStatus = await this.checkIpWhitelist(error.ipAddress);
if (!ipStatus.allowed) {
return {
issue: 'IP not whitelisted',
solution: `Add IP ${error.ipAddress} to whitelist`
};
}
}
}
Payment Failures
class PaymentTroubleshooter {
async diagnose(paymentId: string): Promise<DiagnosisResult> {
const payment = await this.getPaymentDetails(paymentId);
const logs = await this.getPaymentLogs(paymentId);
// Check for common issues
if (payment.status === 'failed') {
switch (payment.error.code) {
case 'insufficient_funds':
return this.handleInsufficientFunds(payment);
case 'card_declined':
return this.handleCardDeclined(payment, logs);
case '3ds_failed':
return this.handle3DSFailure(payment, logs);
default:
return this.handleGenericFailure(payment, logs);
}
}
}
private async handleCardDeclined(
payment: Payment,
logs: PaymentLogs
): Promise<DiagnosisResult> {
const recentAttempts = await this.getRecentAttempts(payment.customerId);
return {
issue: 'Card declined by issuer',
details: {
declineCode: payment.error.decline_code,
issuerResponse: payment.error.issuer_response,
recentFailures: recentAttempts.length
},
solution: this.getDeclineSolution(payment.error.decline_code)
};
}
}
Debugging Tools
Request Logger
class RequestLogger {
private readonly loggingService: LoggingService;
constructor() {
this.loggingService = new LoggingService({
level: process.env.LOG_LEVEL || 'debug'
});
}
async logRequest(req: Request, res: Response): Promise<void> {
const requestId = req.headers['x-request-id'];
const startTime = process.hrtime();
// Log request
await this.loggingService.log({
level: 'debug',
requestId,
method: req.method,
path: req.path,
headers: this.sanitizeHeaders(req.headers),
body: this.sanitizeBody(req.body)
});
// Log response
res.on('finish', () => {
const [seconds, nanoseconds] = process.hrtime(startTime);
const duration = seconds * 1000 + nanoseconds / 1000000;
this.loggingService.log({
level: 'debug',
requestId,
statusCode: res.statusCode,
duration,
headers: this.sanitizeHeaders(res.getHeaders()),
body: this.sanitizeBody(res.body)
});
});
}
private sanitizeHeaders(headers: any): any {
const sanitized = { ...headers };
delete sanitized.authorization;
delete sanitized.cookie;
return sanitized;
}
private sanitizeBody(body: any): any {
// Implement PCI-compliant data sanitization
return this.removeSensitiveData(body);
}
}
Transaction Tracer
class TransactionTracer {
private readonly spans: Map<string, Span> = new Map();
startSpan(name: string, parentId?: string): string {
const spanId = crypto.randomUUID();
const span: Span = {
id: spanId,
name,
parentId,
startTime: Date.now(),
events: []
};
this.spans.set(spanId, span);
return spanId;
}
addEvent(spanId: string, event: string, data?: any): void {
const span = this.spans.get(spanId);
if (span) {
span.events.push({
timestamp: Date.now(),
event,
data
});
}
}
endSpan(spanId: string): void {
const span = this.spans.get(spanId);
if (span) {
span.endTime = Date.now();
span.duration = span.endTime - span.startTime;
// Process span data
this.processSpan(span);
}
}
private processSpan(span: Span): void {
// Analyze span data
// Generate traces
// Store for debugging
}
}