Stripe
In short: A payment service provider for online payments (checkout, subscriptions, webhooks) — known for a clear separation between test mode and live mode.
In more detail: In test mode, the complete checkout flow can be played through in a way that’s technically identical to live operation (with test credit card numbers), without any full business verification. Only switching to live mode (real money) requires proof such as a business registration/commercial register entry.
Our context: At Emzett it keeps running in test mode for the time being, because no business has been registered yet — the checkout logic stays in the code, so that later only the keys have to be swapped.
In Depth
Stripe was founded in 2010 out of the Collison brothers’ observation that online payments were disproportionately complicated for developers — PCI compliance, bank connections and fraud checks had to be solved anew by practically every company on its own. The core idea was a payment integration that can be added with just a few lines of code, while Stripe hides all the regulatory and security complexity underneath. This has become the industry standard: many SaaS products, marketplaces and subscription services today build directly on Stripe instead of developing their own payment processing.
Stripe models the entire payment flow with two building blocks: Checkout (a PCI-compliant payment page hosted by Stripe, to which you redirect the customer instead of accepting card data yourself) and webhooks (asynchronous notifications with which Stripe tells your own server that a payment has really been completed). This is a deliberate security and reliability pattern: the redirect back to your own success page after payment is NOT the reliable point at which you should, for example, mark an order as paid — a customer can close the tab before the redirect happens, open the success page again manually, or a browser crash can prevent the redirect entirely. Only the signed webhook event (checkout.session.completed) is the source of truth, because it comes directly from Stripe’s servers and is delivered independently of the customer’s browser.
Webhook security
A common beginner’s mistake: trusting webhook endpoints without checking. Since the webhook URL has to be publicly reachable (Stripe must be able to call it from outside), anyone could theoretically send a fake checkout.session.completed message and thereby obtain a “paid” order. Stripe therefore signs every webhook request with an HMAC signature header (Stripe-Signature), which you have to verify on the server with your own webhook secret before trusting the content at all:
const sig = request.headers.get("stripe-signature")!
const event = stripe.webhooks.constructEvent(
rawBody, // IMPORTANT: the unmodified request body, not the parsed JSON
sig,
process.env.STRIPE_WEBHOOK_SECRET!,
)The emphasis on the “unmodified body” is no detail: many frameworks automatically parse the request body into JSON by default before your own code sees it — but the signature check needs exactly the raw bytes as Stripe signed them, otherwise verification fails (a classic stumbling block in Stripe integrations).
Idempotency keys and subscriptions
Another important building block is idempotency keys: since network errors can cause a request to arrive twice (the client sends it again because the response didn’t arrive), every critical request such as “charge payment” can be given a unique key — Stripe recognises duplicates by it and guarantees the action is only carried out once, no matter how often the same request arrives.
const session = await stripe.checkout.sessions.create(
{ line_items, mode: "payment", success_url, cancel_url },
{ idempotencyKey: orderId },
)For recurring payments, Stripe offers its own mode: "subscription", which automatically manages billing cycles, pro-rata changes (proration when switching plans in the middle of a cycle) and failed payment attempts (dunning — automatic retries when a card has expired), instead of you having to rebuild this logic yourself.
Compared with the alternatives
Compared with PayPal, Stripe offers much more granular APIs and better developer documentation, but lacks PayPal’s huge installed user base (many customers trust a familiar PayPal button more than an unfamiliar credit card form). Adyen is aimed more at large customers with very individual requirements and more complex contract terms, while Stripe deliberately relies on self-service without contract negotiations. Mollie and Klarna are strong in Europe with local payment methods (SEPA direct debit, instant bank transfer, pay by invoice) — Stripe now supports many of these methods too, but was originally more focused on credit cards.
For the transition from test to live mode: both modes have completely separate API keys, customers, products and webhook endpoints — there’s no way to “promote” test data; switching practically means a clean restart with the live keys.
See also: Environment Variables, JSON