Guides · August 9, 2023
Payments for Headless Stores: Providers, MoR, and Tax Reality
Headless commerce payments come down to three decisions: pick a payment processor or a merchant of record, decide who owns PCI scope and 3DS, and get tax collection right before launch, not after.
By Polo Themes
The short answer: for most headless commerce builds, use a payment processor's hosted UI element (Stripe Elements/Payment Element, Braintree Drop-in, or Adyen Web Components) rather than building your own card form, decide early whether you need a merchant of record (MoR) or a standard payment service provider (PSP) based on how many tax jurisdictions you sell into, and treat sales tax/VAT/GST as an architecture decision, not a launch-week afterthought. Get those three right and the rest of headless checkout — webhooks, subscriptions, multi-currency — falls into place.
Headless commerce moves the storefront out of the platform's templating engine and into a framework you control, usually Next.js, talking to a commerce backend over an API. That flexibility is exactly why payments get harder. A platform like Shopify hands you a checkout, tax rules, and PCI compliance as a package deal — see our Shopify themes for how much of that is handled for you out of the box. Headless gives that up in exchange for control, which means someone on your team now owns decisions that used to be invisible: which processor, whose UI renders the card fields, who calculates tax, and who is liable when a chargeback or a compliance audit shows up.
Payment Processor vs. Merchant of Record: The First Decision
Every headless payments stack starts with this fork. A payment service provider (Stripe, Braintree, Adyen, PayPal) moves money and gives you a compliant way to collect card details, but your business is still the merchant of record — you register for tax in every jurisdiction where you have nexus, you file those returns, and you carry the compliance risk if you get it wrong. A merchant of record (Paddle, Lemon Squeezy, FastSpring, and increasingly Stripe's own MoR-style offerings for certain categories) sits between you and the customer as the legal seller: they collect the tax, remit it, handle chargebacks under their own merchant account, and absorb a meaningful slice of the compliance burden — for a materially higher take rate, often 5-10% versus roughly 2.9% for a plain PSP.
The decision usually comes down to geographic reach and product type. A store selling into three or four countries with a bookkeeper who already knows how to file those returns is almost always better off with a straight PSP — the fee difference compounds fast at any real volume. A store selling digital products or subscriptions into dozens of countries, especially B2C SaaS-adjacent products where VAT/GST registration thresholds get hit quickly, often comes out ahead with an MoR despite the higher fee, because the alternative is registering for VAT in twenty different jurisdictions and hoping the internal team keeps up with rate changes.
- Choose a PSP (Stripe, Braintree, Adyen) if you sell primarily domestically or in a handful of markets, sell physical goods with existing fulfillment/tax infrastructure, or already have accounting resources to handle multi-jurisdiction filing.
- Choose an MoR (Paddle, FastSpring, Lemon Squeezy) if you sell digital goods or subscriptions globally, are a small team without dedicated tax/finance staff, or are entering markets with VAT/GST thresholds you'll cross within the first year.
- Consider a hybrid — PSP for your home market plus an MoR layer for international digital sales — once volume justifies the integration overhead of running two payment paths.
Who Renders the Card Form: PCI Scope in a Headless Build
The single most consequential technical decision in headless payments is whether your own frontend code ever touches raw card data. If it does — even transiently, even if you never store it — you fall into a higher PCI DSS SAQ tier that requires annual audits, network segmentation, and a level of ongoing compliance overhead most small commerce teams have no appetite for. The practical answer, almost without exception, is to never let your React components render a raw `<input type="text" name="cardNumber">`. Instead, embed the processor's hosted iframe element — Stripe's Payment Element, Braintree's Drop-in, or Adyen's Web Components — so the card data goes directly from the customer's browser into the processor's iframe and never transits your servers or your own JavaScript context at all. This keeps most headless stores at PCI SAQ A, the lightest self-assessment tier, essentially just a checklist confirming you're using the hosted element correctly.
This matters even more in a Next.js headless build than in a traditional monolith, because the temptation to "just build a nice custom checkout form" is stronger when you already control every pixel of the page. Resist it for the card fields specifically. You can absolutely build a fully custom checkout flow — shipping address, order summary, promo codes, order review — around the hosted payment element; the element itself just needs to stay a sandboxed iframe you don't reach into.
3D Secure, SCA, and Why Headless Checkouts Break More Often
Strong Customer Authentication (SCA) under European PSD2 rules, and 3D Secure 2 more broadly, require an authentication challenge — the bank's own popup or redirect — for a growing share of card transactions, particularly in the EU/UK and increasingly in other regions as issuers tighten fraud rules. This is where headless checkouts most commonly break in ways a platform checkout wouldn't, because the redirect/challenge flow has to be built explicitly rather than being handled by the platform's existing checkout page.
Two failure modes show up repeatedly. First, teams test only with cards that never trigger a 3DS challenge, ship to production, and then watch conversion drop when real European customers hit an authentication popup the frontend never anticipated. Second, the client-side confirmation step (confirming the payment intent after a 3DS challenge resolves) gets built as a fire-and-forget call, so a customer who completes the bank challenge but then closes the tab leaves an order in limbo — paid on the processor's side, never marked as such in the commerce backend. The fix for both is the same: build against the processor's test cards that explicitly trigger 3DS, and always reconcile final order state from a server-side webhook, never from the client's post-redirect callback alone.
Webhooks Are the Source of Truth, Not the Checkout Response
This is the pattern that trips up more headless commerce builds than any other: treating the response from the client-side "confirm payment" call as proof the order succeeded. In a traditional platform checkout this distinction is invisible because the platform owns both sides. In headless, your Next.js frontend and your commerce backend are separate systems, and the only durable, tamper-resistant signal that money actually moved is the processor's server-to-server webhook — `payment_intent.succeeded`, `charge.refunded`, `invoice.payment_failed`, and their equivalents on other processors. Client-side confirmation can lie (a closed tab, a flaky network, a customer who refreshes mid-flow); the webhook cannot, because it comes from the processor's own servers after the charge has actually cleared.
The practical architecture: your frontend triggers payment confirmation and shows an optimistic "processing" state, but the order only flips to paid in your database when the webhook handler receives and verifies the signed event. Verify the webhook signature on every request — this is the step teams skip under launch pressure, and it's the one that turns your payment endpoint into an open door for anyone who can guess the URL and POST a fake "payment succeeded" event. Idempotency matters just as much: processors retry webhook delivery on any non-2xx response, so a handler that isn't safe to run twice on the same event will eventually double-fulfill an order.
Tax Reality: Nexus, VAT/GST, and Why This Isn't Optional
Sales tax in a headless build is not a checkbox you enable in a theme setting — it's a calculation your backend has to perform correctly at order time, using either a dedicated tax service or an MoR that absorbs the problem entirely. In the US, economic nexus rules (post-Wayfair) mean you can owe sales tax in a state purely from crossing a revenue or transaction-count threshold there, with no physical presence at all — and every state sets its own threshold and rate structure. Selling internationally adds VAT (EU, UK) and GST (Australia, Canada, others), each with its own registration thresholds, invoicing requirements, and digital-goods rules that often differ from physical-goods rules in the same country.
Three realistic paths, in order of engineering effort: use your PSP's built-in tax product (Stripe Tax and similar calculate and, in some configurations, remit on your behalf across supported jurisdictions), integrate a dedicated tax engine (Avalara, TaxJar) that plugs into your checkout and order pipeline as a rate-calculation and filing service, or hand the entire problem to an MoR that becomes the seller of record and removes it from your stack altogether. What doesn't work at any real scale is hardcoding a flat tax rate per country in your storefront config and hoping it stays accurate — rates change, thresholds get crossed, and a headless build that treats tax as a static config value instead of a live calculation is a compliance liability waiting to surface at the worst possible time, usually during an audit or a sudden spike in international orders.
Subscriptions and Stored Payment Methods
If any part of the catalog is recurring — subscriptions, memberships, usage-based billing — the payments architecture needs a plan for stored payment methods from day one, because retrofitting it later means asking existing customers to re-enter their cards. The standard approach is to let the processor tokenize and store the payment method (Stripe's Setup Intents and saved payment methods, Braintree's vault, Adyen's tokenization) so your backend only ever holds an opaque token, never card data. Card networks now push network tokens and automatic card-updater services through the processor, which quietly refresh expired or reissued card numbers behind the scenes — a detail worth confirming your processor supports, since involuntary churn from expired cards is one of the largest silent revenue leaks in subscription commerce.
A Practical Checklist Before Launch
- Decide PSP vs. MoR based on jurisdiction count and product type, not just fee percentage alone.
- Render card fields only through the processor's hosted element — never a custom input touching raw card data.
- Test the full 3DS/SCA challenge path with the processor's dedicated test cards, not just happy-path cards.
- Mark orders as paid only from a signature-verified, idempotent webhook handler — never from the client-side confirmation callback.
- Pick a tax approach (processor tax product, dedicated tax engine, or MoR) before launch, not after the first nexus threshold is crossed.
- If selling subscriptions, confirm the processor supports network tokens/card-updater to reduce involuntary churn.
None of this is specific to any one frontend framework, but it does shape how the checkout UI should be designed — clear states for "processing," "authentication required," and "confirmed," rather than a single spinner that hides what's actually happening between your frontend, the processor, and the bank. If you're designing that flow, our Figma UI kits include commerce-focused checkout and product screens that are a reasonable starting point for laying out those states clearly before any of the payment logic gets wired in.
Frequently Asked Questions
Do I need a merchant of record, or is a PSP like Stripe enough?
A PSP is enough for most stores selling into a small number of markets with existing accounting support. An MoR earns its higher fee once you're selling digital goods or subscriptions into many countries and don't have the internal capacity to register for and file VAT/GST in each one.
Can I build my own card input form for a headless checkout?
Technically yes, but it pushes you into a much heavier PCI compliance tier. Almost every headless store is better served by the processor's hosted element (Stripe Payment Element, Braintree Drop-in, Adyen Web Components), which keeps raw card data out of your own code entirely.
Why did my headless checkout work in testing but fail for real European customers?
This is almost always an untested 3D Secure/SCA challenge path. Test cards that never trigger 3DS won't reveal the bug; you need to explicitly test with cards that force the authentication challenge and confirm your flow handles the redirect and the resulting webhook correctly.
Is Stripe Tax or a dedicated tax engine like Avalara the better choice?
Stripe Tax is simpler to integrate if you're already on Stripe and sell in jurisdictions it supports well. A dedicated engine like Avalara or TaxJar makes more sense once you need multi-processor support, more complex product taxability rules, or filing services beyond what a processor's built-in tax product covers.