← Blog

System Design for Checkout (Commercetools) — A Guide for Developers

What a production-grade checkout on Commercetools with Stripe or Braintree needs: idempotency, minimal PCI scope, immutable records, correct money storage, and webhook-driven payment events.

  • system-design
  • commercetools
  • checkout
  • payments
  • security
  • node

Designing a reliable and secure checkout system requires a solid understanding of certain foundational principles, particularly when working with headless commerce platforms like Commercetools and integrating with Stripe or Braintree.

Let's go over a few critical concepts.

Idempotency

Definition: Idempotency ensures that making the same request multiple times has the same effect as making it once. This is particularly important in payments where duplicate network requests (due to retries or user error) must not result in double charges.

Example in Node.js middleware:

let idempotencyDB = {};

const idempotencyMiddleware = (req, res, next) => {
    if (req.method === 'POST') {
        //get the key from the header
        const idempotencyKey = req.headers['Idempotency-Key'];
        if (idempotencyKey) {
            if (idempotencyDB[idempotencyKey]) {
                return res.status(200).json(idempotencyDB[idempotencyKey]);
            } else {
                res.on('finish', () => {
                    idempotencyDB[idempotencyKey] = res.locals.response;
                });
            }
        }
    }
    next();
};

app.use(idempotencyMiddleware);

Note: In production systems, use a durable store like Redis instead of an in-memory cache.

PCI Compliance (Payment Card Industry Data Security Standard)

Security is non-negotiable when handling payment data. If you are integrating a PSP, your goal should be to minimize your PCI scope as much as possible.

  • Never store raw card details: Instead, use tokens provided by PSPs like Stripe or Braintree.
  • Use strong encryption: Data in transit should be encrypted using TLS. Sensitive data at rest (e.g., billing info) must be encrypted using strong algorithms like AES-256.
  • Authentication & Access Control: Restrict access to payment data using roles and permission controls.
  • Regular Security Scanning: Automate vulnerability scanning. For example, GitHub offers security analysis in the Security tab using tools like Dependabot:

GitHub repository Security tab, with the "Enable Dependabot alerts" button highlighted

  • Avoid storing PII: Save only the user ID or payment method token (e.g., paymentMethodId) to retrieve payment details via the PSP's API.
  • Implement strong access control measures: Access to cardholder data must be restricted to only those who need it. Developers need to build systems that enforce user roles and permissions effectively.

Immutable and Durable Systems

Immutability means that once a record (e.g., a transaction) is written, it should not be changed. This makes the system auditable and helps with debugging.

Durability refers to the ability to persist data reliably even in the case of system crashes.

  • Write logs and records in an append-only fashion.
  • Use event sourcing or audit logs to record each state change.
  • Save payment data in the smallest grain possible. For example:
    • Represent money in cents or micros instead of floats.
    • Prefer DECIMAL(19,4) for currency precision across international transactions.

Handling Money in Databases

Financial amounts must be stored with accuracy and consistency.

Use the Right Data Type

  • For U.S. dollars only: Store values in cents (INT).
  • For global currency support: Use DECIMAL(19, 4) or greater (depending on business needs).

Avoid Floating Point Math

// ❌ Don't do this
let total = 0.1 + 0.2; // 0.30000000000000004

// ✅ Use integer math (in cents)
let totalCents = 10 + 20; // 30

Consistent Rounding

Standardize rounding (e.g., Banker's Rounding) and apply it consistently in both backend and frontend.

Payment Events & Webhooks

When integrating with external PSPs, event-driven design is essential to reflect the true state of payments in your system.

Set up webhook handlers for these key events:

EventDescription
authorizeCard has been validated, and funds are reserved.
captureFunds have been transferred. Usually occurs after fulfillment.
cancelPayment was voided before being captured.
refundFunds were returned to the customer after capture.

Architecture Recommendation

  • Create a payments-service that listens to webhooks.
  • On event receipt:
    1. Validate the webhook (e.g., signature verification).
    2. Update the associated order/payment status in Commercetools.
    3. Store the raw event payload for audit purposes.

Example Architecture Diagram

Sequence diagram of the checkout flow: the frontend gets a card token from the PSP SDK and calls /checkout; the backend validates the cart in Commercetools, creates a payment intent with the PSP, saves an idempotent payment record and returns success or failure; later the PSP calls /webhooks/payment and the backend validates the event, updates the order in Commercetools and stores an immutable log

If you're designing a checkout system using Commercetools, Stripe, or Braintree, adhering to these principles ensures security, accuracy, and auditability — the hallmarks of a production-grade payment system.


Originally published on Medium on July 11, 2025.