Payment Processing

Payment processing is how software takes money — turning a "Buy" click into funds in your account. It sits at the intersection of several hard requirements: it must be secure (you're handling money and card data), reliable (a dropped payment is a lost sale or a double charge), and compliant (PCI-DSS governs how card data is handled). Get it wrong and the failure modes are expensive: chargebacks, security breaches, or customers billed twice.

The modern reality is that you almost never build this yourself — providers like Stripe, Adyen, Braintree, and PayPal handle the dangerous parts (touching raw card numbers, connecting to the card networks, staying compliant), and you integrate their APIs. This page is about doing that integration correctly: understanding the flow well enough to handle the money-critical details — webhooks, idempotency, and keeping card data off your servers — that separate a robust billing system from a liability.

TL;DR

How a Card Payment Flows

The crucial architectural point: card data flows customer → provider directly, never through your backend, and fulfillment is driven by the webhook, not the client response.

Quick Example

A Stripe PaymentIntent flow — server creates the intent, client confirms, webhook fulfills:

Note what's absent from your server: the card number. Note what drives fulfillment: the verified webhook, not the browser.

Core Concepts

Keep Card Data Off Your Servers (PCI Scope)

Handling raw card numbers subjects you to the full weight of PCI-DSS (the card-industry security standard) — audits, controls, and enormous breach liability. The entire point of modern providers is to avoid this: their hosted payment fields (Stripe Elements, Checkout) collect the card in an iframe/SDK that sends it directly to the provider, returning a token to you. Because raw card data never touches your systems, you qualify for the vastly simpler PCI self-assessment. The rule is absolute: never accept, log, or store raw card numbers. Doing so — even "temporarily" — is a serious security and compliance failure. See API Security and Secrets Management for the surrounding practices.

Webhooks Are the Source of Truth

The client-side payment result is unreliable — the customer can close the tab, lose connection, or the browser can die right after paying but before your frontend records it. The webhook (the provider POSTing payment_intent.succeeded to your server) is the authoritative, reliable signal that money moved. Fulfill orders and grant access on the webhook, not on the frontend response. This means everything on the webhooks page applies here — verify signatures, respond fast, and process idempotently, because a mis-handled payment webhook means unfulfilled paid orders or double fulfillment.

Idempotency: Never Double-Charge

Networks retry; users double-click; requests time out and get resent. Without protection, one purchase becomes two charges — a fast route to angry customers and chargebacks. Payment APIs support idempotency keys: attach a unique key (e.g. the order ID) to a charge request, and the provider guarantees that retrying with the same key returns the original result instead of charging again. Use them on every money-moving call. See the idempotency discussion in API design.

Beyond One-Time Charges

Real billing systems involve more than single payments, and providers abstract each:

The lesson throughout: these are deep problem domains, and the provider's built-in handling (Stripe Billing for subscriptions, automatic SCA, Tax) is almost always better than reinventing them.

Common Mistakes

Touching Raw Card Data

Accepting card numbers on your own form/server — even to "just pass them along" — pulls you into full PCI scope and creates catastrophic breach liability. Always use the provider's hosted fields/SDK so cards go directly to them. This is the cardinal rule of payments.

Fulfilling on the Client Response Instead of the Webhook

Granting access or shipping the order based on the browser's success callback means lost fulfillments when the tab closes at the wrong moment — and potential fraud. The verified webhook is the source of truth; drive fulfillment from it.

No Idempotency

Omitting idempotency keys means retries and double-clicks create duplicate charges. Every charge/payment call needs an idempotency key tied to the logical operation (order ID) so retries are safe.

Trusting Client-Sent Amounts

Letting the client tell the server how much to charge lets an attacker pay $1 for a $100 item. Compute the amount server-side from the order/cart in your database — never trust a price sent from the browser.

Ignoring Failure and Edge Cases

Declined cards, insufficient funds, expired cards, SCA challenges, partial refunds, and disputes are normal, not exceptional. Handle declines gracefully (clear messaging, retry paths), implement dunning for failed subscription renewals, and have a process for disputes. A payment flow that only handles the happy path fails constantly in production.

Building Your Own Payment Stack

Unless you're a payments company, connecting to card networks, achieving PCI Level 1, and handling global compliance yourself is a multi-year liability, not a feature. Integrate a provider; spend your effort on the integration's correctness, not on rebuilding what Stripe already did.

FAQ

Should I use Stripe, or build payments myself?

Use a provider — Stripe, Adyen, Braintree, PayPal, etc. They've solved the genuinely hard, dangerous parts (card-network connectivity, PCI Level 1 compliance, fraud tooling, global payment methods). Building your own is appropriate essentially only for payment companies. Your job is a correct integration, which is plenty of engineering on its own.

What is PCI compliance and do I have to worry about it?

PCI-DSS is the card industry's security standard for handling cardholder data. You minimize your obligations by never touching raw card data — using hosted fields (Stripe Elements/Checkout) so cards go straight to the provider. That qualifies you for a simple self-assessment questionnaire rather than full audits. Storing card numbers yourself triggers the heavy, expensive end of PCI.

Why are webhooks so important for payments?

Because the client-side result is unreliable — customers close tabs, lose connectivity, or crash right after paying. The webhook is the provider reliably telling your server the final outcome, so you fulfill based on confirmed payment rather than a browser callback that may never arrive. Verify and process it idempotently.

How do I prevent double charges?

Idempotency keys on every charge request: attach a unique key (like the order ID), and the provider ensures that retries with the same key don't create a second charge. Combined with server-side amount calculation and webhook-driven fulfillment, this keeps the money side correct under retries and network failures.

How do subscriptions and taxes work?

Lean on the provider's abstractions — Stripe Billing (and equivalents) handle recurring charges, proration, trials, and dunning; Stripe Tax and services like Avalara handle jurisdiction-specific sales tax/VAT. These are deep domains (especially tax); the provider's built-in handling is almost always the right choice over building it yourself.

Related Topics

References