Skip to main content
โšก Calmops

Payment Processing: Stripe, Adyen, and Marketplaces

Introduction

Payment processing is the backbone of any e-commerce or fintech application. Whether you’re building a simple checkout flow or a complex marketplace with seller payouts, understanding payment infrastructure is essential.

In this guide, we’ll explore the leading payment processors (Stripe, Adyen), marketplace payment patterns, compliance requirements, and implementation best practices.


Understanding Payment Processing

The Payment Ecosystem

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                     PAYMENT PROCESSING FLOW                           โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                     โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚  โ”‚ Customerโ”‚โ”€โ”€โ”€โ”€โ–บโ”‚  Merchant   โ”‚โ”€โ”€โ”€โ”€โ–บโ”‚  Payment โ”‚โ”€โ”€โ”€โ”€โ–บโ”‚  Bank  โ”‚ โ”‚
โ”‚  โ”‚         โ”‚     โ”‚  Website    โ”‚     โ”‚  Gateway โ”‚     โ”‚ Networkโ”‚ โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚                                                                     โ”‚
โ”‚  Components:                                                        โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚  โ”‚                                                               โ”‚ โ”‚
โ”‚  โ”‚  Customer   โ†’ Frontend (Checkout)                            โ”‚ โ”‚
โ”‚  โ”‚    โ†“                                                           โ”‚ โ”‚
โ”‚  โ”‚  Merchant   โ†’ Backend (API)                                   โ”‚ โ”‚
โ”‚  โ”‚    โ†“                                                           โ”‚ โ”‚
โ”‚  โ”‚  Payment    โ†’ Stripe, Adyen, Braintree                        โ”‚ โ”‚
โ”‚  โ”‚  Gateway                                                       โ”‚ โ”‚
โ”‚  โ”‚    โ†“                                                           โ”‚ โ”‚
โ”‚  โ”‚  Card       โ†’ Visa, Mastercard, Amex                          โ”‚ โ”‚
โ”‚  โ”‚  Network                                                       โ”‚ โ”‚
โ”‚  โ”‚    โ†“                                                           โ”‚ โ”‚
โ”‚  โ”‚  Issuing/  โ†’ Customer's bank                                 โ”‚ โ”‚
โ”‚  โ”‚  Acquiring                                                      โ”‚ โ”‚
โ”‚  โ”‚    Bank                                                        โ”‚ โ”‚
โ”‚  โ”‚                                                               โ”‚ โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚                                                                     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Key Terms

  • Merchant: Business selling goods/services
  • Cardholder: Customer making payment
  • Issuing Bank: Customer’s bank (card issuer)
  • Acquiring Bank: Merchant’s bank (receives funds)
  • Card Network: Visa, Mastercard, Amex (facilitates)
  • Payment Gateway: Processes card transactions
  • PCI DSS: Security standard for card data

Stripe: Developer-Friendly Payments

Overview

Stripe is known for its developer-friendly APIs, comprehensive documentation, and extensive features. It’s ideal for startups and scaling companies.

Core Concepts

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                        STRIPE CORE FLOW                               โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                     โ”‚
โ”‚  1. Create Customer                                                โ”‚
โ”‚     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                   โ”‚
โ”‚     โ”‚ stripe.customers.create({               โ”‚                   โ”‚
โ”‚     โ”‚   email: "[email protected]",       โ”‚                   โ”‚
โ”‚     โ”‚   name: "John Doe"                     โ”‚                   โ”‚
โ”‚     โ”‚   metadata: { user_id: "123" }         โ”‚                   โ”‚
โ”‚     โ”‚ })                                     โ”‚                   โ”‚
โ”‚     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                   โ”‚
โ”‚                                                                     โ”‚
โ”‚  2. Create Payment Intent                                         โ”‚
โ”‚     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                   โ”‚
โ”‚     โ”‚ stripe.paymentIntents.create({         โ”‚                   โ”‚
โ”‚     โ”‚   amount: 2999,       // in cents      โ”‚                   โ”‚
โ”‚     โ”‚   currency: "usd",                     โ”‚                   โ”‚
โ”‚     โ”‚   customer: "cus_...",                 โ”‚                   โ”‚
โ”‚     โ”‚   payment_method: "pm_...",            โ”‚                   โ”‚
โ”‚     โ”‚   confirm: true,                       โ”‚                   โ”‚
โ”‚     โ”‚   return_url: "https://..."            โ”‚                   โ”‚
โ”‚     โ”‚ })                                     โ”‚                   โ”‚
โ”‚     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                   โ”‚
โ”‚                                                                     โ”‚
โ”‚  3. Handle 3D Secure (if required)                                โ”‚
โ”‚     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                   โ”‚
โ”‚     โ”‚ {                                      โ”‚                   โ”‚
โ”‚     โ”‚   status: "requires_action",          โ”‚                   โ”‚
โ”‚     โ”‚   next_action: {                       โ”‚                   โ”‚
โ”‚     โ”‚     type: "use_stripe_sdk",           โ”‚                   โ”‚
โ”‚     โ”‚     stripe_sdk: { ... }                โ”‚                   โ”‚
โ”‚     โ”‚   }                                   โ”‚                   โ”‚
โ”‚     โ”‚ }                                      โ”‚                   โ”‚
โ”‚     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                   โ”‚
โ”‚                                                                     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Implementation Examples

import stripe
from stripe import StripeClient

class StripePayments:
    """
    Stripe payment integration
    """
    
    def __init__(self, api_key: str):
        stripe.api_key = api_key
        self.webhook_secret = os.getenv("STRIPE_WEBHOOK_SECRET")
    
    def create_customer(self, email: str, name: str, metadata: dict = None):
        """
        Create a Stripe customer
        """
        customer = stripe.Customer.create(
            email=email,
            name=name,
            metadata=metadata or {}
        )
        
        return {
            "customer_id": customer.id,
            "email": customer.email,
            "created": customer.created
        }
    
    def create_payment_intent(
        self,
        amount: int,
        currency: str,
        customer_id: str = None,
        metadata: dict = None,
        capture_method: str = "automatic"
    ):
        """
        Create a payment intent for checkout
        """
        
        intent_params = {
            "amount": amount,  # Amount in smallest currency unit
            "currency": currency.lower(),
            "payment_method_types": [
                "card",           # Credit/debit cards
                # Add more:
                # "alipay",       # Alipay
                # "wechat_pay",   # WeChat Pay
                # "bancontact",   # Bancontact
                # "ideal",        # iDEAL
            ],
            "capture_method": capture_method,  # "automatic" or "manual"
            "metadata": metadata or {}
        }
        
        if customer_id:
            intent_params["customer"] = customer_id
        
        payment_intent = stripe.PaymentIntent.create(**intent_params)
        
        return {
            "client_secret": payment_intent.client_secret,
            "payment_intent_id": payment_intent.id,
            "status": payment_intent.status
        }
    
    def confirm_payment(self, payment_intent_id: str, payment_method_id: str):
        """
        Confirm a payment intent
        """
        
        payment_intent = stripe.PaymentIntent.confirm(
            payment_intent_id,
            payment_method=payment_method_id
        )
        
        return self._format_payment_result(payment_intent)
    
    def handle_3ds_authentication(self, payment_intent_id: str):
        """
        Handle 3D Secure authentication
        """
        
        payment_intent = stripe.PaymentIntent.retrieve(payment_intent_id)
        
        if payment_intent.status == "requires_action":
            return {
                "requires_action": True,
                "client_secret": payment_intent.client_secret,
                "next_action": payment_intent.next_action
            }
        
        return {
            "requires_action": False,
            "status": payment_intent.status
        }
    
    def refund_payment(self, payment_intent_id: str, amount: int = None):
        """
        Refund a payment
        """
        
        refund_params = {"payment_intent": payment_intent_id}
        
        if amount:
            refund_params["amount"] = amount  # Partial refund
        
        refund = stripe.Refund.create(**refund_params)
        
        return {
            "refund_id": refund.id,
            "status": refund.status,
            "amount": refund.amount
        }
    
    def create_subscription(
        self,
        customer_id: str,
        price_id: str,
        payment_behavior: str = "default_incomplete"
    ):
        """
        Create a subscription
        """
        
        subscription = stripe.Subscription.create(
            customer=customer_id,
            items=[{"price": price_id}],
            payment_behavior=payment_behavior,
            expand=["latest_invoice.payment_intent"]
        )
        
        return {
            "subscription_id": subscription.id,
            "status": subscription.status,
            "client_secret": (
                subscription.latest_invoice.payment_intent.client_secret
                if subscription.latest_invoice.payment_intent
                else None
            )
        }
    
    def _format_payment_result(self, payment_intent):
        """
        Format payment intent to standardized response
        """
        
        return {
            "payment_intent_id": payment_intent.id,
            "status": payment_intent.status,
            "amount": payment_intent.amount,
            "currency": payment_intent.currency,
            "succeeded": payment_intent.status == "succeeded"
        }
    
    def construct_webhook_event(self, payload: bytes, signature: str):
        """
        Verify and construct webhook event
        """
        
        return stripe.Webhook.construct_event(
            payload,
            signature,
            self.webhook_secret
        )

Stripe Elements (Frontend)

// Stripe Elements for React

import { loadStripe } from '@stripe/stripe-js';
import { Elements, PaymentElement, useStripe, useElements } from '@stripe/react-stripe-js';

const stripePromise = loadStripe('pk_test_...');

function CheckoutForm({ clientSecret }) {
  const stripe = useStripe();
  const elements = useElements();
  const [error, setError] = useState(null);
  const [processing, setProcessing] = useState(false);

  const handleSubmit = async (event) => {
    event.preventDefault();
    
    if (!stripe || !elements) return;

    setProcessing(true);

    const { error: submitError } = await stripe.confirmPayment({
      elements,
      confirmParams: {
        return_url: 'https://your-site.com/success',
      },
    });

    if (submitError) {
      setError(submitError.message);
      setProcessing(false);
    }
    // If successful, Stripe redirects to return_url
  };

  return (
    <form onSubmit={handleSubmit}>
      <PaymentElement />
      <button disabled={!stripe || processing}>
        {processing ? 'Processing...' : 'Pay Now'}
      </button>
      {error && <div className="error">{error}</div>}
    </form>
  );
}

function App() {
  // Get clientSecret from your backend
  const [clientSecret, setClientSecret] = useState('');

  useEffect(() => {
    fetch('/create-payment-intent', { method: 'POST' })
      .then(res => res.json())
      .then(data => setClientSecret(data.clientSecret));
  }, []);

  return (
    <Elements stripe={stripePromise} options={{ clientSecret }}>
      <CheckoutForm />
    </Elements>
  );
}

Adyen: Enterprise Payments

Overview

Adyen is favored by large enterprises and global companies due to its unified platform, extensive acquiring network, and advanced features.

Key Differences: Stripe vs Adyen

Feature Stripe Adyen
Target Startups to mid-market Enterprise, enterprise+
Pricing Simple, volume discounts Custom negotiation
Acquiring Third-party Direct (own acquiring)
Currencies 135+ 135+
Local Payment Methods Many Extensive
Payouts Stripe Connect Adyen for Platforms
Enterprise Features Custom accounts Dedicated support

Adyen Implementation

from adyen import Adyen
from adyen.services import checkout_api

class AdyenPayments:
    """
    Adyen payment integration
    """
    
    def __init__(self, api_key: str, merchant_account: str, environment: str = "test"):
        self.adyen = Adyen()
        self.adyen.payment.appengine_apikey = api_key
        self.merchant_account = merchant_account
        self.environment = environment
    
    def create_payment_session(
        self,
        amount: int,
        currency: str,
        return_url: str,
        reference: str,
        metadata: dict = None
    ):
        """
        Create a payment session (Adyen's equivalent to PaymentIntent)
        """
        
        request = {
            "merchantAccount": self.merchant_account,
            "amount": {
                "value": amount,
                "currency": currency
            },
            "returnUrl": return_url,
            "reference": reference,
            "metadata": metadata or {},
            "channel": "Web",  # Web, iOS, Android
            "countryCode": "US",
            "shopperEmail": "[email protected]",
            "shopperReference": "user_123"
        }
        
        result = self.adyen.checkout.payments_api.payments(request)
        
        return {
            "session_id": result.session_data if hasattr(result, 'session_data') else None,
            "psp_reference": result.psp_reference if hasattr(result, 'psp_reference') else None,
            "result_code": result.result_code if hasattr(result, 'result_code') else None,
            "action": result.action if hasattr(result, 'action') else None
        }
    
    def handle_redirect_result(self, redirect_result: str):
        """
        Handle payment result from redirect
        """
        
        request = {
            "merchantAccount": self.merchant_account,
            "redirectResult": redirect_result
        }
        
        result = self.adyen.checkout.payments_api.paymentsDetails(request)
        
        return {
            "psp_reference": result.psp_reference,
            "result_code": result.result_code,
            "merchant_reference": result.merchant_reference,
            "success": result.result_code in ["Authorised", "Received"]
        }
    
    def create_payout(
        self,
        amount: int,
        currency: str,
        target_reference: str,
        target_type: str,  # "bankAccount" or "card"
        payout_details: dict
    ):
        """
        Create a payout (e.g., to seller in marketplace)
        """
        
        request = {
            "merchantAccount": self.merchant_account,
            "amount": {
                "value": amount,
                "currency": currency
            },
            "reference": f"payout_{int(time.time())}",
            "target": {
                "type": target_type,
                **payout_details
            }
        }
        
        result = self.adyen.payouts_api.payout(request)
        
        return {
            "psp_reference": result.psp_reference,
            "result_code": result.result_code,
            "status": result.status if hasattr(result, 'status') else None
        }
    
    def capture_payment(self, psp_reference: str, amount: int, currency: str):
        """
        Capture an authorized payment
        """
        
        request = {
            "merchantAccount": self.merchant_account,
            "modificationAmount": {
                "value": amount,
                "currency": currency
            },
            "originalReference": psp_reference
        }
        
        result = self.adyen.checkout.modifications_api.capture_authorised_payment(request)
        
        return {
            "psp_reference": result.psp_reference,
            "response": result.response
        }

Marketplace Payments

Overview

Marketplace payments require handling split payments between platforms and sellers, which adds complexity beyond simple checkout.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                   MARKETPLACE PAYMENT FLOW                           โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                     โ”‚
โ”‚  Customer pays $100 for item                                        โ”‚
โ”‚                                                                     โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚  Platform takes 10% commission = $10                      โ”‚   โ”‚
โ”‚  โ”‚  Seller receives 90% = $90                                 โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ”‚                                                                     โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚                    PAYMENT FLOW                              โ”‚   โ”‚
โ”‚  โ”‚                                                             โ”‚   โ”‚
โ”‚  โ”‚  Customer โ”€โ”€โ–บ Platform โ”€โ”€โ–บ Stripe/Adyen โ”€โ”€โ–บ Seller       โ”‚   โ”‚
โ”‚  โ”‚   $100        Hold       Split        Payout              โ”‚   โ”‚
โ”‚  โ”‚                           $10                              โ”‚   โ”‚
โ”‚  โ”‚                                     $90                    โ”‚   โ”‚
โ”‚  โ”‚                                                             โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ”‚                                                                     โ”‚
โ”‚  Key Components:                                                    โ”‚
โ”‚  โ€ข Platform account (connected accounts for sellers)              โ”‚
โ”‚  โ€ข Split payments (application fee)                               โ”‚
โ”‚  โ€ข Delayed payouts (hold funds until delivery)                   โ”‚
โ”‚  โ€ข KYC/AML compliance for sellers                                 โ”‚
โ”‚  โ€ข Refund handling                                               โ”‚
โ”‚                                                                     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Stripe Connect Implementation

class MarketplacePayments:
    """
    Stripe Connect for marketplace payments
    """
    
    def __init__(self, api_key: str):
        stripe.api_key = api_key
    
    def create_seller_account(self, seller_email: str, business_type: str = "individual"):
        """
        Create a connected account for a seller
        """
        
        account = stripe.Account.create(
            type="express",  # express, standard, or custom
            email=seller_email,
            business_type=business_type,
            capabilities={
                "transfers": {"requested": True}
            }
        )
        
        return {
            "account_id": account.id,
            "email": account.email,
            "charges_enabled": account.charges_enabled,
            "payouts_enabled": account.payouts_enabled
        }
    
    def create_seller_onboarding_link(self, account_id: str, return_url: str, refresh_url: str):
        """
        Create onboarding link for seller
        """
        
        account_link = stripe.AccountLink.create(
            account=account_id,
            refresh_url=refresh_url,
            return_url=return_url,
            type="account_onboarding"
        )
        
        return {
            "url": account_link.url,
            "expires_at": account_link.expires_at
        }
    
    def create_marketplace_payment(
        self,
        amount: int,
        currency: str,
        seller_account_id: str,
        application_fee_percent: float,
        customer_id: str = None,
        metadata: dict = None
    ):
        """
        Create payment with split to seller
        """
        
        # Calculate application fee (platform commission)
        application_fee = int(amount * (application_fee_percent / 100))
        
        payment_params = {
            "amount": amount,
            "currency": currency,
            "payment_method_types": ["card"],
            "transfer_data": {
                "destination": seller_account_id  # Seller receives funds
            },
            "application_fee_amount": application_fee,  # Platform keeps this
            "metadata": metadata or {}
        }
        
        if customer_id:
            payment_params["customer"] = customer_id
        
        payment_intent = stripe.PaymentIntent.create(**payment_params)
        
        return {
            "client_secret": payment_intent.client_secret,
            "payment_intent_id": payment_intent.id,
            "application_fee": application_fee,
            "seller_amount": amount - application_fee
        }
    
    def create_direct_charge(
        self,
        amount: int,
        currency: str,
        seller_account_id: str,
        connected_payment_method_id: str,
        application_fee_amount: int
    ):
        """
        Charge directly on seller's behalf (for marketplace model)
        """
        
        payment_params = {
            "amount": amount,
            "currency": currency,
            "payment_method": connected_payment_method_id,
            "confirm": True,
            "transfer_data": {
                "destination": seller_account_id
            },
            "application_fee_amount": application_fee_amount
        }
        
        payment_intent = stripe.PaymentIntent.create(**payment_params)
        
        return {
            "payment_intent_id": payment_intent.id,
            "status": payment_intent.status,
            "seller_amount": amount - application_fee_amount
        }
    
    def create_payout_to_seller(
        self,
        seller_account_id: str,
        amount: int,
        currency: str,
        reference: str = None
    ):
        """
        Create manual payout to seller
        """
        
        transfer = stripe.Transfer.create(
            amount=amount,
            currency=currency,
            destination=seller_account_id,
            description=reference or "Marketplace payout"
        )
        
        return {
            "transfer_id": transfer.id,
            "amount": transfer.amount,
            "destination": transfer.destination,
            "created": transfer.created
        }
    
    def handle_marketplace_refund(
        self,
        payment_intent_id: str,
        refund_amount: int = None,
        refund_application_fee: bool = True
    ):
        """
        Handle refund in marketplace
        """
        
        refund_params = {
            "payment_intent": payment_intent_id,
            "refund_application_fee": refund_application_fee
        }
        
        if refund_amount:
            refund_params["amount"] = refund_amount
        
        refund = stripe.Refund.create(**refund_params)
        
        return {
            "refund_id": refund.id,
            "status": refund.status,
            "amount": refund.amount
        }

Strong Customer Authentication (SCA)

Overview

PSD2 mandates Strong Customer Authentication (SCA) for online payments in Europe. This requires two or more factors from:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              STRONG CUSTOMER AUTHENTICATION (SCA)                    โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                     โ”‚
โ”‚  Two or more independent factors:                                   โ”‚
โ”‚                                                                     โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚  โ”‚    Knowledge     โ”‚  โ”‚   Possession     โ”‚  โ”‚    Inherence     โ”‚ โ”‚
โ”‚  โ”‚  (something you โ”‚  โ”‚ (something you โ”‚  โ”‚  (something you โ”‚ โ”‚
โ”‚  โ”‚     know)       โ”‚  โ”‚    have)        โ”‚  โ”‚     are)        โ”‚ โ”‚
โ”‚  โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚
โ”‚  โ”‚ โ€ข Password      โ”‚  โ”‚ โ€ข Phone         โ”‚  โ”‚ โ€ข Fingerprint   โ”‚ โ”‚
โ”‚  โ”‚ โ€ข PIN           โ”‚  โ”‚ โ€ข Card reader   โ”‚  โ”‚ โ€ข Face ID       โ”‚ โ”‚
โ”‚  โ”‚ โ€ข Security Q    โ”‚  โ”‚ โ€ข Hardware tokenโ”‚  โ”‚ โ€ข Voice ID      โ”‚ โ”‚
โ”‚  โ”‚                 โ”‚  โ”‚ โ€ข Email code    โ”‚  โ”‚ โ€ข Retina scan   โ”‚ โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚                                                                     โ”‚
โ”‚  SCA Required: Card payments, account access                       โ”‚
โ”‚  Exceptions: Low risk, trusted merchants, recurring payments       โ”‚
โ”‚                                                                     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Implementing SCA with Stripe

class SCAPayments:
    """
    Handle Strong Customer Authentication
    """
    
    def __init__(self, api_key: str):
        stripe.api_key = api_key
    
    def check_if_sca_required(self, payment_intent_id: str) -> dict:
        """
        Check if payment requires SCA
        """
        
        payment_intent = stripe.PaymentIntent.retrieve(payment_intent_id)
        
        # Check payment status
        if payment_intent.status == "requires_action":
            # SCA is required - 3D Secure
            return {
                "sca_required": True,
                "authentication": payment_intent.next_action
            }
        
        return {"sca_required": False}
    
    def handle_sca_verification(self, payment_intent_id: str):
        """
        Create SetupIntent for future authentication
        """
        
        # For storing payment method with SCA
        setup_intent = stripe.SetupIntent.create(
            usage="off_session",  # Use without customer present
            payment_method_types=["card"]
        )
        
        return {
            "client_secret": setup_intent.client_secret,
            "setup_intent_id": setup_intent.id
        }
    
    def use_existing_payment_method(self, customer_id: str, payment_method_id: str):
        """
        Use stored payment method (may trigger SCA)
        """
        
        payment_intent = stripe.PaymentIntent.create(
            amount=2999,
            currency="eur",
            customer=customer_id,
            payment_method=payment_method_id,
            confirm=True,
            off_session=True,  # Customer not present
            # Stripe will handle SCA automatically
        )
        
        if payment_intent.status == "requires_action":
            # Need to complete authentication
            return {
                "requires_action": True,
                "client_secret": payment_intent.client_secret
            }
        
        return {
            "requires_action": False,
            "succeeded": payment_intent.status == "succeeded"
        }
    
    def request_exemption(self, payment_intent_id: str, exemption_type: str):
        """
        Request SCA exemption (suitable cases only)
        """
        
        # Valid exemption types:
        # - low_value: Payments under โ‚ฌ30
        # - trusted_beneficiary: Trusted merchants
        # - corporate: Corporate card payments
        # - recurring: Subsequent payments in series
        
        payment_intent = stripe.PaymentIntent.modify(
            payment_intent_id,
            payment_method_options={
                "card": {
                    "request_three_d_secure": "automatic"
                }
            }
        )
        
        return {"exemption_requested": exemption_type}

Common Pitfalls

1. Not Handling Webhooks Properly

# Anti-pattern: No webhook handling
def bad_webhook_handling():
    """
    Anti-pattern: Ignoring webhooks
    """
    # Only checking payment status on page load
    # Result: Missed payment confirmations, lost revenue
    
    return "This approach is unreliable"

# Good pattern: Robust webhook handling
def good_webhook_handling():
    """
    Handle all payment events reliably
    """
    
    from flask import Flask, request
    
    app = Flask(__name__)
    
    @app.route('/webhooks/stripe', methods=['POST'])
    def handle_stripe_webhook():
        payload = request.get_data()
        sig_header = request.headers.get('stripe-signature')
        
        try:
            event = stripe.Webhook.construct_event(
                payload, sig_header, WEBHOOK_SECRET
            )
        except ValueError:
            return 'Invalid payload', 400
        except stripe.error.SignatureVerificationError:
            return 'Invalid signature', 400
        
        # Handle specific events
        event_handlers = {
            'payment_intent.succeeded': handle_payment_success,
            'payment_intent.payment_failed': handle_payment_failure,
            'charge.refunded': handle_refund,
            'customer.subscription.created': handle_subscription_created,
            'customer.subscription.deleted': handle_subscription_cancelled,
            'invoice.payment_failed': handle_invoice_failed,
        }
        
        handler = event_handlers.get(event['type'])
        if handler:
            handler(event['data']['object'])
        
        return '', 200
    
    return app

2. Not Implementing Idempotency

# Anti-pattern: No idempotency
def bad_idempotency():
    """
    Anti-pattern: Duplicate charges on retry
    """
    # User clicks "Pay" twice, network fails, user retries
    # Result: Multiple charges!
    
    return "Dangerous: Can cause duplicate charges"

# Good pattern: Idempotent requests
def good_idempotency():
    """
    Use idempotency keys to prevent duplicate charges
    """
    
    import uuid
    
    def create_idempotent_payment(amount: int, customer_id: str):
        """
        Create payment with idempotency key
        """
        
        # Generate unique key per payment attempt
        idempotency_key = f"{customer_id}_{amount}_{uuid.uuid4()}"
        
        # Include in request
        payment_intent = stripe.PaymentIntent.create(
            amount=amount,
            currency="usd",
            customer=customer_id,
            idempotency_key=idempotency_key
        )
        
        # If same key is used, Stripe returns same result
        # No duplicate charges!
        
        return payment_intent

3. Ignoring Payment Failures

# Anti-pattern: Not handling declined cards
def bad_failure_handling():
    """
    Anti-pattern: Generic error message
    """
    
    try:
        # Process payment
        pass
    except Exception as e:
        # What type of failure?
        return "Payment failed"  # Not helpful!

# Good pattern: Specific error handling
def good_failure_handling():
    """
    Handle different failure types specifically
    """
    
    def handle_payment_error(error):
        error_code = error.get("code")
        
        # Map of actionable error messages
        error_messages = {
            "card_declined": {
                "message": "Your card was declined. Please try another card.",
                "action": "request_new_card"
            },
            "expired_card": {
                "message": "Your card has expired. Please use a different card.",
                "action": "request_new_card"
            },
            "incorrect_cvc": {
                "message": "The security code is incorrect. Please check and try again.",
                "action": "request_cvc"
            },
            "insufficient_funds": {
                "message": "Insufficient funds. Please try another card.",
                "action": "request_new_card"
            },
            "processing_error": {
                "message": "A processing error occurred. Please try again.",
                "action": "retry"
            },
            "lost_card": {
                "message": "This card has been reported lost. Please use a different card.",
                "action": "request_new_card"
            },
            "stolen_card": {
                "message": "This card has been reported stolen. Please contact your bank.",
                "action": "request_new_card"
            }
        }
        
        error_info = error_messages.get(
            error_code, 
            {"message": "An error occurred. Please try again.", "action": "support"}
        )
        
        return error_info

Best Practices

1. Always Use Test Mode First

# Use Stripe test card numbers
TEST_CARDS = {
    "success": "4242424242424242",
    "declined": "4000000000000002",
    "insufficient_funds": "4000000000009995",
    "expired_card": "4000000000000069",
    "incorrect_cvc": "4000000000000127",
    "processing_error": "4000000000000119",
    # 3D Secure
    "3d_success": "4000000000003220",
    "3d_failure": "4000000000003246",
    "3d_issuer_unavailable": "4000000000003055"
}

# Test different scenarios
def test_payment_scenarios():
    # Test successful payment
    # Test declined card
    # Test 3D Secure flow
    # Test refund flow
    # Test webhook delivery
    pass

2. Store Only What You Need

# Minimize PCI DSS scope
# Don't store card details on your servers!

# Good pattern: Use Stripe's tokenization
def good_tokenization():
    """
    Let Stripe handle card data
    """
    
    # Frontend: Stripe.js collects card details
    # Backend: Only receives token or payment method ID
    # Never touches raw card numbers
    
    return "Low PCI compliance burden"

# Anti-pattern: Storing card numbers
def bad_storage():
    """
    Anti-pattern: Storing card numbers (high PCI scope!)
    """
    # NEVER store: card number, CVV, PIN
    # Only store: last 4 digits, expiry, card brand
    pass

3. Handle Currencies Properly

def handle_currencies():
    """
    Currency handling best practices
    """
    
    # Always use smallest currency unit (cents, pence)
    # Don't use floats for money!
    
    # Good: 2999 = $29.99
    # Bad: 29.99 (floating point errors!)
    
    from decimal import Decimal
    
    def dollars_to_cents(dollars: float) -> int:
        """Convert dollars to cents"""
        return int(Decimal(str(dollars)) * 100)
    
    def cents_to_dollars(cents: int) -> float:
        """Convert cents to dollars"""
        return float(Decimal(cents) / 100)

External Resources


Conclusion

Payment processing is complex but critical for any business handling transactions. Whether you choose Stripe for its developer experience or Adyen for enterprise capabilities, understanding the fundamentals of payment flows, marketplace patterns, and compliance requirements will help you build robust financial systems.

Key takeaways:

  • Stripe is ideal for startups and mid-market; Adyen for enterprises
  • Marketplace payments require connected accounts and split payments
  • SCA (Strong Customer Authentication) is mandatory in Europe
  • Always use idempotency keys to prevent duplicate charges
  • Handle webhooks reliably for payment status updates
  • Minimize PCI DSS scope by using tokenization
  • Use webhooks instead of polling for payment status

Building a robust payment system requires careful attention to edge cases, security, and compliance. Take advantage of the extensive documentation and testing tools provided by payment processors to ensure your implementation is production-ready.

Comments