This guide provides detailed instructions for implementing the multi-provider payment system. The system is designed to be modular, allowing you to support various payment providers (Stripe, PayPal, Authorize.net, etc.) with a consistent API interface.
The payment system follows a layered architecture:
- API Layer: Standardized REST endpoints for client applications
- Service Layer: Business logic and provider-agnostic operations
- Provider Layer: Provider-specific implementations
- Data Layer: Database operations for storing payment information
┌─────────────────┐
│ API Layer │ ← REST endpoints (/api/payment/*)
└────────┬────────┘
│
▼
┌─────────────────┐
│ Service Layer │ ← Business logic, validation
└────────┬────────┘
│
▼
┌─────────────────┐
│ Provider Layer │ ← Provider-specific implementations
└────────┬────────┘
│
▼
┌─────────────────┐
│ Data Layer │ ← Database operations
└─────────────────┘
First, create the database tables as defined in the database schema. This includes:
- Enhanced user tables
- Organization tables
- Payment provider tables
- Customer management tables
- Payment method tables
- Transaction tables (orders, payments, subscriptions)
Create provider-specific implementations for each payment provider you want to support. Each provider should implement a common interface:
// Example interface for payment providers
interface PaymentProvider {
// Customer management
createCustomer(data: CustomerCreateData): Promise<CustomerResponse>;
getCustomer(customerId: string): Promise<CustomerResponse>;
// Payment methods
createPaymentMethod(data: PaymentMethodCreateData): Promise<PaymentMethodResponse>;
deletePaymentMethod(paymentMethodId: string): Promise<void>;
// Payments
createPayment(data: PaymentCreateData): Promise<PaymentResponse>;
refundPayment(paymentId: string, data: RefundData): Promise<RefundResponse>;
// Subscriptions
createSubscription(data: SubscriptionCreateData): Promise<SubscriptionResponse>;
cancelSubscription(subscriptionId: string, atPeriodEnd: boolean): Promise<SubscriptionResponse>;
// Webhooks
registerWebhook(url: string): Promise<WebhookResponse>;
processWebhookEvent(payload: any, signature: string): Promise<WebhookEvent>;
}Implement this interface for each provider:
// Example Stripe implementation
class StripeProvider implements PaymentProvider {
private stripe: Stripe;
constructor(apiKey: string) {
this.stripe = new Stripe(apiKey);
}
async createCustomer(data: CustomerCreateData): Promise<CustomerResponse> {
const customer = await this.stripe.customers.create({
email: data.email,
name: data.name,
phone: data.phone,
metadata: data.metadata
});
return {
provider_id: 'stripe',
provider_customer_id: customer.id,
// Map other fields
};
}
// Implement other methods...
}Create service classes that use the provider implementations but present a unified interface:
// Example payment service
class PaymentService {
private providers: Map<string, PaymentProvider>;
constructor(providers: Map<string, PaymentProvider>) {
this.providers = providers;
}
async createPaymentIntent(data: PaymentIntentCreateData): Promise<PaymentIntentResponse> {
// Validate input
this.validatePaymentIntentData(data);
// Get the appropriate provider
const provider = this.providers.get(data.provider_id);
if (!provider) {
throw new Error(`Provider ${data.provider_id} not supported`);
}
// Create payment intent with provider
const providerResponse = await provider.createPaymentIntent(data);
// Save to database
const paymentIntent = await this.savePaymentIntentToDatabase({
...data,
provider_payment_id: providerResponse.provider_payment_id,
status: providerResponse.status
});
// Return response
return paymentIntent;
}
// Implement other methods...
}Implement the REST API endpoints as defined in the API routes documentation:
// Example Express route handler
app.post('/api/payment/intents', async (req, res) => {
try {
const paymentService = new PaymentService(providers);
const paymentIntent = await paymentService.createPaymentIntent(req.body);
res.status(201).json(paymentIntent);
} catch (error) {
res.status(400).json({ error: error.message });
}
});Implement webhook handling for each provider:
// Example webhook handler
app.post('/api/payment/webhooks/:providerId', async (req, res) => {
const { providerId } = req.params;
const provider = providers.get(providerId);
if (!provider) {
return res.status(404).json({ error: 'Provider not found' });
}
try {
// Get signature from headers (provider-specific)
const signature = req.headers['stripe-signature'];
// Process the webhook event
const event = await provider.processWebhookEvent(req.body, signature);
// Save the event to database
await saveWebhookEvent(event);
// Process the event asynchronously
processEventAsync(event);
// Acknowledge receipt
res.status(200).json({ received: true });
} catch (error) {
console.error('Webhook error:', error);
res.status(400).json({ error: error.message });
}
});When creating an order:
- Validate the products and calculate totals
- Create the order record
- Create order items
- Return the order details
When paying for an order:
- Validate the order status (must be 'pending')
- Get the payment method
- Create a payment through the provider
- Update the order status to 'paid' if successful
- Create an invoice if needed
When creating a subscription:
- Validate the product (must be recurring)
- Get the payment method
- Create a subscription through the provider
- Save the subscription details
- Schedule renewal handling
When canceling a subscription:
- Cancel the subscription through the provider
- Update the subscription status in the database
- Handle prorations if needed
- Use Stripe.js on the frontend to collect payment information securely
- Implement webhook handling for events like
payment_intent.succeeded,subscription.created, etc. - Use Stripe Customer Portal for subscription management
- Implement PayPal Checkout for one-time payments
- Use PayPal Subscriptions API for recurring payments
- Handle IPN (Instant Payment Notification) for webhooks
- Use Accept.js for secure payment collection
- Implement Customer Information Manager (CIM) for saved payment methods
- Set up webhooks for transaction notifications
Test each payment provider integration thoroughly:
- Use test/sandbox credentials for each provider
- Test the full payment flow (create customer → add payment method → make payment)
- Test subscription creation and cancellation
- Test webhook handling with provider's webhook testing tools
- Test error scenarios (declined cards, insufficient funds, etc.)
- PCI Compliance: Never store raw credit card data. Use provider tokens instead.
- API Keys: Store provider API keys securely (environment variables, secrets manager).
- Webhook Signatures: Always validate webhook signatures to prevent fraud.
- HTTPS: Ensure all payment endpoints use HTTPS.
- Input Validation: Validate all input data before processing.
- Logging: Log payment events but exclude sensitive information.
- Database Migrations: Create proper migrations for the payment tables.
- Environment Configuration: Set up different configurations for development, testing, and production.
- Monitoring: Implement monitoring for payment processing and webhook handling.
- Error Handling: Set up proper error handling and alerting for payment failures.
The payment system is designed to be extensible:
- New Providers: Implement the
PaymentProviderinterface for any new provider. - New Payment Methods: Update the database schema and provider implementations.
- Additional Features: Add support for features like recurring billing, metered billing, etc.
This implementation guide provides a framework for building a robust, multi-provider payment system. By following this architecture, you can create a payment system that is:
- Flexible: Support multiple payment providers
- Consistent: Provide a unified API regardless of the provider
- Scalable: Add new providers and features as needed
- Secure: Follow best practices for payment processing
For specific implementation details, refer to the provider's documentation and API references.