Prerequisites
- Basic understanding of JavaScript ๐
- TypeScript installation โก
- VS Code or preferred IDE ๐ป
What you'll learn
- Understand the concept fundamentals ๐ฏ
- Apply the concept in real projects ๐๏ธ
- Debug common issues ๐
- Write type-safe code โจ
๐ฏ Introduction
Welcome to this exciting tutorial on building a payment integration system for e-commerce backends! ๐ In this guide, weโll explore how to create a secure, type-safe payment processing system using TypeScript.
Youโll discover how proper type definitions and interfaces can make your payment system robust and reliable. Whether youโre building an online store ๐, a subscription service ๐ฆ, or a marketplace ๐ช, understanding payment integration is essential for any e-commerce application.
By the end of this tutorial, youโll feel confident building payment systems that handle real money safely! Letโs dive in! ๐โโ๏ธ
๐ Understanding Payment Integration
๐ค What is Payment Integration?
Payment integration is like having a secure cashier ๐ฐ in your digital store. Think of it as a bridge between your application and payment providers (like Stripe, PayPal, or Square) that handles money transfers safely and reliably.
In TypeScript terms, payment integration involves creating type-safe interfaces for:
- โจ Payment methods (cards, wallets, bank transfers)
- ๐ Transaction processing and validation
- ๐ก๏ธ Security and error handling
๐ก Why Use TypeScript for Payment Systems?
Hereโs why TypeScript is perfect for payment integration:
- Type Safety ๐: Catch payment errors at compile-time
- Better Documentation ๐: Types describe payment flows clearly
- Refactoring Confidence ๐ง: Change payment logic without fear
- IDE Support ๐ป: Autocomplete for payment APIs
Real-world example: Imagine processing a credit card payment. With TypeScript, you can ensure all required fields are present before sending to the payment gateway!
๐ง Basic Syntax and Usage
๐ Simple Payment Types
Letโs start with fundamental payment types:
// ๐ณ Credit card information
interface CreditCard {
number: string; // ๐ณ Card number (encrypted!)
expMonth: number; // ๐
Expiration month
expYear: number; // ๐
Expiration year
cvv: string; // ๐ Security code
holderName: string; // ๐ค Cardholder name
}
// ๐ฐ Payment amount
interface PaymentAmount {
value: number; // ๐ต Amount in cents
currency: string; // ๐ Currency code (USD, EUR, etc.)
displayValue: string; // ๐ Formatted for display
}
// ๐งพ Payment request
interface PaymentRequest {
amount: PaymentAmount;
paymentMethod: CreditCard;
description?: string; // ๐ Optional description
metadata?: Record<string, any>; // ๐ Custom data
}
๐ก Explanation: Notice how we store amounts in cents to avoid floating-point issues! The metadata
field lets you attach custom data to payments.
๐ฏ Payment Status Types
Here are common payment states:
// ๐ฆ Payment status
type PaymentStatus =
| "pending" // โณ Processing
| "succeeded" // โ
Completed
| "failed" // โ Failed
| "cancelled" // ๐ซ Cancelled
| "refunded"; // ๐ธ Refunded
// ๐ Payment result
interface PaymentResult {
id: string; // ๐ Unique payment ID
status: PaymentStatus; // ๐ฆ Current status
amount: PaymentAmount; // ๐ฐ Payment amount
createdAt: Date; // ๐
When created
processedAt?: Date; // โ
When completed
error?: PaymentError; // โ Error details
}
// โ ๏ธ Payment error
interface PaymentError {
code: string; // ๐ข Error code
message: string; // ๐ฌ User-friendly message
type: "card_error" | "api_error" | "validation_error";
}
๐ก Practical Examples
๐ Example 1: Payment Processor Class
Letโs build a real payment processor:
// ๐ณ Payment gateway interface
interface PaymentGateway {
processPayment(request: PaymentRequest): Promise<PaymentResult>;
refundPayment(paymentId: string, amount?: number): Promise<PaymentResult>;
getPaymentStatus(paymentId: string): Promise<PaymentResult>;
}
// ๐๏ธ Payment processor implementation
class PaymentProcessor implements PaymentGateway {
private apiKey: string;
private webhookSecret: string;
constructor(apiKey: string, webhookSecret: string) {
this.apiKey = apiKey;
this.webhookSecret = webhookSecret;
console.log("๐ณ Payment processor initialized!");
}
// ๐ฐ Process a payment
async processPayment(request: PaymentRequest): Promise<PaymentResult> {
console.log(`๐ณ Processing payment of ${request.amount.displayValue}...`);
// ๐ Validate payment request
this.validatePaymentRequest(request);
// ๐ Simulate API call to payment gateway
const result = await this.simulatePaymentProcessing(request);
if (result.status === "succeeded") {
console.log("โ
Payment successful!");
} else {
console.log(`โ Payment failed: ${result.error?.message}`);
}
return result;
}
// ๐ Validate payment request
private validatePaymentRequest(request: PaymentRequest): void {
const { amount, paymentMethod } = request;
// ๐ฐ Check amount
if (amount.value <= 0) {
throw new Error("โ Payment amount must be positive!");
}
// ๐ณ Validate card number (basic check)
if (!this.isValidCardNumber(paymentMethod.number)) {
throw new Error("โ Invalid card number!");
}
// ๐
Check expiration
const currentDate = new Date();
const expDate = new Date(paymentMethod.expYear, paymentMethod.expMonth - 1);
if (expDate < currentDate) {
throw new Error("โ Card has expired!");
}
}
// ๐ณ Basic card validation (Luhn algorithm)
private isValidCardNumber(cardNumber: string): boolean {
const digits = cardNumber.replace(/\s/g, '');
return digits.length >= 13 && digits.length <= 19;
}
// ๐ฎ Simulate payment processing
private async simulatePaymentProcessing(
request: PaymentRequest
): Promise<PaymentResult> {
// ๐ฒ Simulate random success/failure
const isSuccessful = Math.random() > 0.1;
return new Promise((resolve) => {
setTimeout(() => {
resolve({
id: `pay_${Date.now()}`,
status: isSuccessful ? "succeeded" : "failed",
amount: request.amount,
createdAt: new Date(),
processedAt: isSuccessful ? new Date() : undefined,
error: isSuccessful ? undefined : {
code: "card_declined",
message: "Your card was declined ๐ณโ",
type: "card_error"
}
});
}, 1500); // Simulate network delay
});
}
// ๐ธ Refund a payment
async refundPayment(
paymentId: string,
amount?: number
): Promise<PaymentResult> {
console.log(`๐ธ Processing refund for payment ${paymentId}...`);
return {
id: `ref_${Date.now()}`,
status: "refunded",
amount: {
value: amount || 0,
currency: "USD",
displayValue: `$${(amount || 0) / 100}`
},
createdAt: new Date(),
processedAt: new Date()
};
}
// ๐ Get payment status
async getPaymentStatus(paymentId: string): Promise<PaymentResult> {
console.log(`๐ Checking status for payment ${paymentId}...`);
return {
id: paymentId,
status: "succeeded",
amount: {
value: 5000,
currency: "USD",
displayValue: "$50.00"
},
createdAt: new Date()
};
}
}
// ๐ฎ Let's use it!
const processor = new PaymentProcessor("sk_test_123", "whsec_456");
const payment: PaymentRequest = {
amount: {
value: 2999, // $29.99
currency: "USD",
displayValue: "$29.99"
},
paymentMethod: {
number: "4242 4242 4242 4242",
expMonth: 12,
expYear: 2025,
cvv: "123",
holderName: "John Doe"
},
description: "TypeScript Course ๐"
};
// Process the payment
processor.processPayment(payment).then(result => {
console.log("Payment result:", result);
});
๐ฏ Try it yourself: Add support for different payment methods like PayPal or Apple Pay!
๐ฎ Example 2: Subscription Management
Letโs handle recurring payments:
// ๐
Subscription plan
interface SubscriptionPlan {
id: string;
name: string;
price: PaymentAmount;
interval: "monthly" | "yearly";
features: string[];
emoji: string; // Every plan needs an emoji!
}
// ๐ค Subscription status
interface Subscription {
id: string;
customerId: string;
planId: string;
status: "active" | "cancelled" | "past_due" | "trialing";
currentPeriodEnd: Date;
cancelAtPeriodEnd: boolean;
}
// ๐๏ธ Subscription manager
class SubscriptionManager {
private plans: Map<string, SubscriptionPlan> = new Map();
private subscriptions: Map<string, Subscription> = new Map();
private paymentProcessor: PaymentProcessor;
constructor(paymentProcessor: PaymentProcessor) {
this.paymentProcessor = paymentProcessor;
this.initializePlans();
}
// ๐ฏ Initialize subscription plans
private initializePlans(): void {
const plans: SubscriptionPlan[] = [
{
id: "basic",
name: "Basic Plan",
price: { value: 999, currency: "USD", displayValue: "$9.99" },
interval: "monthly",
features: ["โ
10 products", "โ
Basic support", "โ
1 user"],
emoji: "๐"
},
{
id: "pro",
name: "Pro Plan",
price: { value: 2999, currency: "USD", displayValue: "$29.99" },
interval: "monthly",
features: ["โ
Unlimited products", "โ
Priority support", "โ
5 users", "โ
Analytics"],
emoji: "๐"
},
{
id: "enterprise",
name: "Enterprise Plan",
price: { value: 9999, currency: "USD", displayValue: "$99.99" },
interval: "monthly",
features: ["โ
Everything in Pro", "โ
Dedicated support", "โ
Unlimited users", "โ
Custom features"],
emoji: "๐"
}
];
plans.forEach(plan => {
this.plans.set(plan.id, plan);
console.log(`${plan.emoji} Added plan: ${plan.name}`);
});
}
// ๐ Subscribe to a plan
async subscribe(
customerId: string,
planId: string,
paymentMethod: CreditCard
): Promise<Subscription> {
const plan = this.plans.get(planId);
if (!plan) {
throw new Error(`โ Plan ${planId} not found!`);
}
console.log(`๐ฏ Subscribing customer ${customerId} to ${plan.name}...`);
// ๐ณ Process initial payment
const paymentResult = await this.paymentProcessor.processPayment({
amount: plan.price,
paymentMethod,
description: `Subscription to ${plan.name}`,
metadata: { customerId, planId }
});
if (paymentResult.status !== "succeeded") {
throw new Error(`โ Payment failed: ${paymentResult.error?.message}`);
}
// โ
Create subscription
const subscription: Subscription = {
id: `sub_${Date.now()}`,
customerId,
planId,
status: "active",
currentPeriodEnd: this.calculatePeriodEnd(plan.interval),
cancelAtPeriodEnd: false
};
this.subscriptions.set(subscription.id, subscription);
console.log(`โ
Subscription created! Welcome to ${plan.name} ${plan.emoji}`);
return subscription;
}
// ๐
Calculate period end date
private calculatePeriodEnd(interval: "monthly" | "yearly"): Date {
const date = new Date();
if (interval === "monthly") {
date.setMonth(date.getMonth() + 1);
} else {
date.setFullYear(date.getFullYear() + 1);
}
return date;
}
// ๐ซ Cancel subscription
cancelSubscription(subscriptionId: string, immediate: boolean = false): void {
const subscription = this.subscriptions.get(subscriptionId);
if (!subscription) {
throw new Error(`โ Subscription ${subscriptionId} not found!`);
}
if (immediate) {
subscription.status = "cancelled";
console.log("๐ซ Subscription cancelled immediately!");
} else {
subscription.cancelAtPeriodEnd = true;
console.log("๐
Subscription will cancel at period end");
}
}
// ๐ Get subscription details
getSubscriptionDetails(subscriptionId: string): {
subscription: Subscription;
plan: SubscriptionPlan;
} | null {
const subscription = this.subscriptions.get(subscriptionId);
if (!subscription) return null;
const plan = this.plans.get(subscription.planId);
if (!plan) return null;
return { subscription, plan };
}
}
// ๐ฎ Test subscription system
const subManager = new SubscriptionManager(processor);
// Subscribe to Pro plan
subManager.subscribe("customer_123", "pro", {
number: "4242 4242 4242 4242",
expMonth: 12,
expYear: 2025,
cvv: "123",
holderName: "Jane Smith"
}).then(subscription => {
console.log("๐ Subscription active:", subscription);
});
๐ Advanced Concepts
๐งโโ๏ธ Webhook Handling
When youโre ready to level up, implement webhook handling:
// ๐ฏ Webhook event types
type WebhookEventType =
| "payment.succeeded"
| "payment.failed"
| "subscription.created"
| "subscription.cancelled"
| "refund.processed";
// ๐ฆ Webhook payload
interface WebhookPayload<T = any> {
id: string;
type: WebhookEventType;
data: T;
timestamp: Date;
signature: string;
}
// ๐ช Webhook handler
class WebhookHandler {
private handlers: Map<WebhookEventType, Function[]> = new Map();
// ๐ Register event handler
on(event: WebhookEventType, handler: Function): void {
const handlers = this.handlers.get(event) || [];
handlers.push(handler);
this.handlers.set(event, handlers);
console.log(`๐ฏ Registered handler for ${event}`);
}
// ๐ Process webhook
async processWebhook(payload: WebhookPayload): Promise<void> {
console.log(`๐ฆ Processing webhook: ${payload.type}`);
// ๐ Verify signature (simplified)
if (!this.verifySignature(payload)) {
throw new Error("โ Invalid webhook signature!");
}
// ๐ฏ Execute handlers
const handlers = this.handlers.get(payload.type) || [];
for (const handler of handlers) {
await handler(payload.data);
}
console.log(`โ
Webhook processed: ${payload.type}`);
}
// ๐ Verify webhook signature
private verifySignature(payload: WebhookPayload): boolean {
// In real implementation, verify HMAC signature
return true;
}
}
๐๏ธ Payment Method Tokenization
For production systems, implement tokenization:
// ๐ Tokenized payment method
interface PaymentToken {
token: string; // ๐ Secure token
last4: string; // ๐ณ Last 4 digits
brand: string; // ๐ท๏ธ Card brand
expiryMonth: number; // ๐
Expiry month
expiryYear: number; // ๐
Expiry year
}
// ๐ก๏ธ Tokenization service
class TokenizationService {
// ๐ Tokenize card details
async tokenizeCard(card: CreditCard): Promise<PaymentToken> {
// In real implementation, this would call a secure tokenization API
return {
token: `tok_${Date.now()}`,
last4: card.number.slice(-4),
brand: this.detectCardBrand(card.number),
expiryMonth: card.expMonth,
expiryYear: card.expYear
};
}
// ๐ท๏ธ Detect card brand
private detectCardBrand(cardNumber: string): string {
const firstDigit = cardNumber[0];
if (firstDigit === "4") return "Visa ๐ณ";
if (firstDigit === "5") return "Mastercard ๐ณ";
if (firstDigit === "3") return "Amex ๐ณ";
return "Unknown ๐ณ";
}
}
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Storing Sensitive Data
// โ Wrong way - NEVER store sensitive data!
interface UnsafePayment {
cardNumber: string; // ๐ฅ Security breach!
cvv: string; // ๐ฅ PCI violation!
customerPassword: string; // ๐ฅ Never store passwords!
}
// โ
Correct way - use tokens and encryption!
interface SafePayment {
paymentToken: string; // ๐ Secure token
last4Digits: string; // ๐๏ธ Safe for display
customerId: string; // ๐ Reference only
}
๐คฏ Pitfall 2: Floating Point for Money
// โ Dangerous - floating point precision issues!
function calculateTotal(price: number, tax: number): number {
return price + (price * tax); // ๐ฅ 0.1 + 0.2 !== 0.3
}
// โ
Safe - use integers (cents)!
function calculateTotal(priceInCents: number, taxRate: number): number {
const taxInCents = Math.round(priceInCents * taxRate);
return priceInCents + taxInCents; // โ
Precise calculation!
}
๐ ๏ธ Best Practices
- ๐ฏ Always Use HTTPS: Never process payments over unsecured connections
- ๐ Log Everything: Keep detailed audit trails (without sensitive data!)
- ๐ก๏ธ Implement Idempotency: Prevent duplicate charges with idempotency keys
- ๐จ Use Strong Types: Define interfaces for all payment entities
- โจ Handle Errors Gracefully: Provide clear, actionable error messages
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Payment Gateway Adapter
Create a flexible payment gateway adapter system:
๐ Requirements:
- โ Support multiple payment providers (Stripe, PayPal, Square)
- ๐ท๏ธ Unified interface for all providers
- ๐ค Customer payment method management
- ๐ Automatic retry for failed payments
- ๐จ Each provider needs its own emoji!
๐ Bonus Points:
- Add fraud detection
- Implement 3D Secure authentication
- Create a payment dashboard
๐ก Solution
๐ Click to see solution
// ๐ฏ Unified payment provider interface
interface PaymentProvider {
name: string;
emoji: string;
processPayment(request: PaymentRequest): Promise<PaymentResult>;
refund(paymentId: string, amount?: number): Promise<PaymentResult>;
supportedCurrencies: string[];
}
// ๐ณ Stripe provider implementation
class StripeProvider implements PaymentProvider {
name = "Stripe";
emoji = "๐ฆ";
supportedCurrencies = ["USD", "EUR", "GBP"];
async processPayment(request: PaymentRequest): Promise<PaymentResult> {
console.log(`${this.emoji} Processing with Stripe...`);
// Stripe-specific implementation
return {
id: `stripe_${Date.now()}`,
status: "succeeded",
amount: request.amount,
createdAt: new Date()
};
}
async refund(paymentId: string, amount?: number): Promise<PaymentResult> {
console.log(`${this.emoji} Refunding via Stripe...`);
return {
id: `stripe_ref_${Date.now()}`,
status: "refunded",
amount: { value: amount || 0, currency: "USD", displayValue: "$0.00" },
createdAt: new Date()
};
}
}
// ๐ฐ PayPal provider implementation
class PayPalProvider implements PaymentProvider {
name = "PayPal";
emoji = "๐
ฟ๏ธ";
supportedCurrencies = ["USD", "EUR", "GBP", "CAD"];
async processPayment(request: PaymentRequest): Promise<PaymentResult> {
console.log(`${this.emoji} Processing with PayPal...`);
return {
id: `paypal_${Date.now()}`,
status: "succeeded",
amount: request.amount,
createdAt: new Date()
};
}
async refund(paymentId: string, amount?: number): Promise<PaymentResult> {
console.log(`${this.emoji} Refunding via PayPal...`);
return {
id: `paypal_ref_${Date.now()}`,
status: "refunded",
amount: { value: amount || 0, currency: "USD", displayValue: "$0.00" },
createdAt: new Date()
};
}
}
// ๐ฏ Payment gateway adapter
class PaymentGatewayAdapter {
private providers: Map<string, PaymentProvider> = new Map();
private retryAttempts = 3;
// โ Register provider
registerProvider(provider: PaymentProvider): void {
this.providers.set(provider.name, provider);
console.log(`โ
Registered ${provider.emoji} ${provider.name}`);
}
// ๐ณ Process payment with retry
async processPayment(
providerName: string,
request: PaymentRequest,
retries: number = this.retryAttempts
): Promise<PaymentResult> {
const provider = this.providers.get(providerName);
if (!provider) {
throw new Error(`โ Provider ${providerName} not found!`);
}
let lastError: Error | undefined;
for (let attempt = 1; attempt <= retries; attempt++) {
try {
console.log(`๐ Attempt ${attempt}/${retries}...`);
const result = await provider.processPayment(request);
if (result.status === "succeeded") {
return result;
}
lastError = new Error(result.error?.message || "Payment failed");
} catch (error) {
lastError = error as Error;
console.log(`โ ๏ธ Attempt ${attempt} failed: ${lastError.message}`);
if (attempt < retries) {
await this.delay(1000 * attempt); // Exponential backoff
}
}
}
throw lastError || new Error("Payment failed after all retries");
}
// โฑ๏ธ Delay helper
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
// ๐ Get provider stats
getProviderInfo(): void {
console.log("๐ Available Payment Providers:");
this.providers.forEach(provider => {
console.log(` ${provider.emoji} ${provider.name}`);
console.log(` Currencies: ${provider.supportedCurrencies.join(", ")}`);
});
}
}
// ๐ฎ Test the adapter system
const gateway = new PaymentGatewayAdapter();
gateway.registerProvider(new StripeProvider());
gateway.registerProvider(new PayPalProvider());
// Process a payment
gateway.processPayment("Stripe", {
amount: { value: 4999, currency: "USD", displayValue: "$49.99" },
paymentMethod: {
number: "4242 4242 4242 4242",
expMonth: 12,
expYear: 2025,
cvv: "123",
holderName: "Test User"
}
}).then(result => {
console.log("โ
Payment processed:", result);
});
๐ Key Takeaways
Youโve learned so much! Hereโs what you can now do:
- โ Create payment systems with proper type safety ๐ช
- โ Handle sensitive data securely and responsibly ๐ก๏ธ
- โ Implement subscriptions and recurring payments ๐ฏ
- โ Build provider adapters for flexibility ๐
- โ Process webhooks and async events safely! ๐
Remember: When handling payments, security and reliability are paramount! Always follow PCI compliance guidelines. ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve mastered payment integration basics!
Hereโs what to do next:
- ๐ป Build a complete checkout flow
- ๐๏ธ Integrate a real payment provider SDK
- ๐ Learn about PCI compliance and security
- ๐ Explore advanced features like SCA and 3DS!
Remember: Every successful e-commerce platform started with someone learning payment integration. Keep building, keep learning, and most importantly, keep your customersโ data safe! ๐
Happy coding! ๐๐โจ