Introduction
Payment processing is the backbone of e-commerce and SaaS businesses. Building a robust payment system requires understanding multiple payment gateways, handling edge cases, ensuring PCI compliance, and managing failures gracefully. Many teams build payment systems without proper error handling, resulting in lost transactions and customer frustration.
This comprehensive guide covers production-grade payment processing with Stripe, Square, and PayPal, including integration patterns, error handling, and compliance.
Core Concepts
Payment Gateway
Service that processes credit card and digital payments.
Merchant Account
Bank account for receiving payments.
PCI-DSS
Payment Card Industry Data Security Standard for handling card data.
Tokenization
Converting sensitive card data into non-sensitive tokens.
Webhook
Server-to-server notification of payment events.
Idempotency
Ensuring duplicate requests produce same result.
Settlement
Process of transferring funds to merchant account.
Chargeback
Customer dispute of a transaction.
3D Secure
Authentication protocol for card payments.
Recurring Billing
Automatic periodic charges for subscriptions.
Payment Processing Architecture
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Client Application โ
โ (Web, Mobile, Point of Sale) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Payment Form โ
โ (Stripe Elements, Square Web Payments) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Your Backend Server โ
โ (Create Payment Intent, Handle Webhooks) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโ
โ โ โ
โโโโโโโโโผโโโโโโโ โโโโโโโโผโโโโโโโ โโโโโโโผโโโโโโโ
โ Stripe โ โ Square โ โ PayPal โ
โ Gateway โ โ Gateway โ โ Gateway โ
โโโโโโโโโฌโโโโโโโ โโโโโโโโฌโโโโโโโ โโโโโโโฌโโโโโโโ
โ โ โ
โโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Payment Database โ
โ (Store transactions, receipts, logs) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Stripe Integration
Setup and Configuration
import stripe
from flask import Flask, request, jsonify
from datetime import datetime
import logging
app = Flask(__name__)
stripe.api_key = "sk_live_YOUR_KEY"
logger = logging.getLogger(__name__)
# Configuration
STRIPE_CONFIG = {
"publishable_key": "pk_live_YOUR_KEY",
"webhook_secret": "whsec_YOUR_SECRET",
"api_version": "2024-04-10"
}
Create Payment Intent
@app.route("/create-payment-intent", methods=["POST"])
def create_payment_intent():
"""Create Stripe payment intent"""
try:
data = request.json
# Validate input
if not data.get("amount") or not data.get("currency"):
return jsonify({"error": "Missing amount or currency"}), 400
# Create payment intent
intent = stripe.PaymentIntent.create(
amount=int(data["amount"] * 100), # Convert to cents
currency=data["currency"],
metadata={
"order_id": data.get("order_id"),
"customer_id": data.get("customer_id")
},
# Idempotency key prevents duplicate charges
idempotency_key=data.get("idempotency_key")
)
logger.info(f"Payment intent created: {intent.id}")
return jsonify({
"client_secret": intent.client_secret,
"payment_intent_id": intent.id
})
except stripe.error.CardError as e:
logger.error(f"Card error: {e.user_message}")
return jsonify({"error": e.user_message}), 400
except stripe.error.RateLimitError:
logger.error("Rate limit exceeded")
return jsonify({"error": "Rate limit exceeded"}), 429
except stripe.error.APIConnectionError:
logger.error("API connection error")
return jsonify({"error": "Connection error"}), 503
except Exception as e:
logger.error(f"Unexpected error: {str(e)}")
return jsonify({"error": "Payment processing failed"}), 500
Handle Webhooks
@app.route("/webhook", methods=["POST"])
def handle_webhook():
"""Handle Stripe webhook events"""
payload = request.get_data()
sig_header = request.headers.get("Stripe-Signature")
try:
# Verify webhook signature
event = stripe.Webhook.construct_event(
payload,
sig_header,
STRIPE_CONFIG["webhook_secret"]
)
except ValueError:
logger.error("Invalid payload")
return jsonify({"error": "Invalid payload"}), 400
except stripe.error.SignatureVerificationError:
logger.error("Invalid signature")
return jsonify({"error": "Invalid signature"}), 400
# Handle events
if event["type"] == "payment_intent.succeeded":
payment_intent = event["data"]["object"]
handle_payment_succeeded(payment_intent)
elif event["type"] == "payment_intent.payment_failed":
payment_intent = event["data"]["object"]
handle_payment_failed(payment_intent)
elif event["type"] == "charge.refunded":
charge = event["data"]["object"]
handle_refund(charge)
return jsonify({"status": "success"}), 200
def handle_payment_succeeded(payment_intent):
"""Process successful payment"""
logger.info(f"Payment succeeded: {payment_intent['id']}")
# Update database
order_id = payment_intent["metadata"]["order_id"]
# db.orders.update(order_id, status="paid")
# Send confirmation email
# send_email(order_id)
# Trigger fulfillment
# trigger_fulfillment(order_id)
def handle_payment_failed(payment_intent):
"""Handle failed payment"""
logger.error(f"Payment failed: {payment_intent['id']}")
# Update database
order_id = payment_intent["metadata"]["order_id"]
# db.orders.update(order_id, status="payment_failed")
# Notify customer
# send_failure_email(order_id)
Recurring Billing
def create_subscription(customer_id, price_id, payment_method_id):
"""Create recurring subscription"""
# Create or get customer
customer = stripe.Customer.create(
id=customer_id,
payment_method=payment_method_id,
invoice_settings={"default_payment_method": payment_method_id}
)
# Create subscription
subscription = stripe.Subscription.create(
customer=customer.id,
items=[{"price": price_id}],
payment_settings={
"save_default_payment_method": "on_subscription"
}
)
logger.info(f"Subscription created: {subscription.id}")
return subscription
def cancel_subscription(subscription_id):
"""Cancel subscription"""
subscription = stripe.Subscription.delete(subscription_id)
logger.info(f"Subscription cancelled: {subscription_id}")
return subscription
def update_subscription(subscription_id, price_id):
"""Update subscription price"""
subscription = stripe.Subscription.modify(
subscription_id,
items=[{"price": price_id}]
)
logger.info(f"Subscription updated: {subscription_id}")
return subscription
Square Integration
Setup and Configuration
from squareup.client import Client
from squareup.api.payments_api import PaymentsApi
from squareup.models import Money, CreatePaymentRequest
import uuid
# Initialize Square client
client = Client(
access_token="YOUR_ACCESS_TOKEN",
environment="production"
)
payments_api = client.payments
Create Payment
def create_square_payment(amount_cents, source_id, idempotency_key=None):
"""Create Square payment"""
if not idempotency_key:
idempotency_key = str(uuid.uuid4())
payment_body = CreatePaymentRequest(
source_id=source_id,
amount_money=Money(
amount=amount_cents,
currency="USD"
),
idempotency_key=idempotency_key,
autocomplete=True
)
try:
result = payments_api.create_payment(payment_body)
if result.is_success():
payment = result.result
print(f"Payment created: {payment.id}")
return payment
elif result.is_client_error():
print(f"Client error: {result.errors}")
return None
elif result.is_server_error():
print(f"Server error: {result.errors}")
return None
except Exception as e:
print(f"Error: {str(e)}")
return None
def refund_square_payment(payment_id, amount_cents=None):
"""Refund Square payment"""
refund_body = {
"payment_id": payment_id,
"amount_money": Money(
amount=amount_cents,
currency="USD"
) if amount_cents else None
}
try:
result = payments_api.refund_payment(refund_body)
if result.is_success():
print(f"Refund created: {result.result.id}")
return result.result
else:
print(f"Error: {result.errors}")
return None
except Exception as e:
print(f"Error: {str(e)}")
return None
PayPal Integration
Setup and Configuration
from paypalrestsdk import Api, Payment
import paypalrestsdk
# Configure PayPal
paypalrestsdk.configure({
"mode": "live", # sandbox or live
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
})
Create Payment
def create_paypal_payment(amount, description, return_url, cancel_url):
"""Create PayPal payment"""
payment = Payment({
"intent": "sale",
"payer": {
"payment_method": "paypal"
},
"redirect_urls": {
"return_url": return_url,
"cancel_url": cancel_url
},
"transactions": [{
"amount": {
"total": str(amount),
"currency": "USD",
"details": {
"subtotal": str(amount)
}
},
"description": description
}]
})
if payment.create():
print(f"Payment created: {payment.id}")
# Get approval URL
for link in payment.links:
if link.rel == "approval_url":
return {
"payment_id": payment.id,
"approval_url": link.href
}
else:
print(f"Error: {payment.error}")
return None
def execute_paypal_payment(payment_id, payer_id):
"""Execute PayPal payment after approval"""
payment = Payment.find(payment_id)
if payment.execute({"payer_id": payer_id}):
print(f"Payment executed: {payment.id}")
return payment
else:
print(f"Error: {payment.error}")
return None
Error Handling & Retry Logic
import time
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10)
)
def process_payment_with_retry(payment_data):
"""Process payment with automatic retry"""
try:
intent = stripe.PaymentIntent.create(
amount=payment_data["amount"],
currency=payment_data["currency"]
)
return intent
except stripe.error.RateLimitError:
logger.warning("Rate limit, retrying...")
raise
except stripe.error.APIConnectionError:
logger.warning("Connection error, retrying...")
raise
except stripe.error.CardError as e:
logger.error(f"Card error: {e.user_message}")
# Don't retry card errors
raise
# Idempotency for safety
def safe_payment_creation(payment_data, idempotency_key):
"""Create payment safely with idempotency"""
# Check if payment already exists
existing = check_payment_exists(idempotency_key)
if existing:
logger.info(f"Payment already exists: {existing.id}")
return existing
# Create new payment
intent = stripe.PaymentIntent.create(
amount=payment_data["amount"],
currency=payment_data["currency"],
idempotency_key=idempotency_key
)
return intent
PCI Compliance
Best Practices
# 1. Never store raw card data
# โ WRONG
def bad_payment_storage(card_number, cvv):
db.save({"card": card_number, "cvv": cvv})
# โ
CORRECT
def good_payment_storage(payment_method_id):
# Store only the token
db.save({"payment_method_id": payment_method_id})
# 2. Use HTTPS everywhere
# 3. Implement strong authentication
# 4. Encrypt sensitive data
# 5. Use tokenization
# 6. Implement proper logging
def log_payment_event(event_type, payment_id, status):
"""Log payment events without sensitive data"""
logger.info(f"Event: {event_type}, Payment: {payment_id}, Status: {status}")
# Never log card numbers, CVV, or other sensitive data
# 7. Regular security audits
# 8. Keep dependencies updated
Comparison: Stripe vs Square vs PayPal
| Feature | Stripe | Square | PayPal |
|---|---|---|---|
| Transaction Fee | 2.9% + $0.30 | 2.6% + $0.10 | 2.9% + $0.30 |
| Setup Time | 1-2 days | 1-2 days | 1-2 days |
| API Quality | Excellent | Good | Good |
| Documentation | Excellent | Good | Good |
| Webhook Support | Yes | Yes | Yes |
| Recurring Billing | Yes | Yes | Yes |
| International | 135+ countries | Limited | 200+ countries |
| Dispute Handling | Excellent | Good | Good |
Monitoring & Analytics
class PaymentAnalytics:
def __init__(self):
self.transactions = []
def log_transaction(self, payment_id, amount, status, gateway):
"""Log transaction"""
self.transactions.append({
"payment_id": payment_id,
"amount": amount,
"status": status,
"gateway": gateway,
"timestamp": datetime.now()
})
def get_daily_revenue(self, date):
"""Calculate daily revenue"""
daily = [
t["amount"] for t in self.transactions
if t["timestamp"].date() == date and t["status"] == "succeeded"
]
return sum(daily)
def get_failure_rate(self):
"""Calculate payment failure rate"""
if not self.transactions:
return 0
failed = len([t for t in self.transactions if t["status"] == "failed"])
return (failed / len(self.transactions)) * 100
def get_gateway_comparison(self):
"""Compare gateway performance"""
gateways = {}
for t in self.transactions:
if t["gateway"] not in gateways:
gateways[t["gateway"]] = {"total": 0, "succeeded": 0}
gateways[t["gateway"]]["total"] += 1
if t["status"] == "succeeded":
gateways[t["gateway"]]["succeeded"] += 1
return gateways
External Resources
Official Documentation
Security & Compliance
Learning Resources
Conclusion
Building production payment systems requires careful integration with payment gateways, proper error handling, and strict compliance with PCI-DSS standards. Choose the right gateway for your use case, implement robust error handling and retry logic, and monitor transactions continuously.
Start with Stripe for best-in-class API and documentation, consider Square for lower fees, and use PayPal for international reach. Always prioritize security and compliance over convenience.
Comments