Home » Free ACP Payments Module » Sales Tax

Sales Tax with Stripe Tax

The ACP Payment Module does not reimplement tax law, it delegates the entire problem to Stripe Tax. Tax is off by default. Turn it on with one config flag plus your Stripe key, and the module computes line-level tax the moment a buyer's address is known, folds it into the session totals, and lets Stripe's hosted checkout collect addresses and apply automatic tax on the direct payment path. Your job reduces to registering where you owe tax and configuring your product tax categories in Stripe.

Why Tax Is Delegated, Not Built In

Sales tax for digital products is one of the most jurisdiction-dependent problems in commerce. In the United States alone, economic nexus rules differ by state, digital goods are taxable in some states and exempt in others, and thresholds shift with legislation, the full landscape fills our entire sales tax pillar. The EU adds VAT with destination-based rates, and other regions add their own schemes. Any tax table embedded in a commerce module would be wrong within months, and confidently wrong tax math is worse than none.

So the module draws the line cleanly: it owns the mechanics, when tax is computed, on what basis, how it lands in line items and totals, and Stripe Tax owns the rates, rules, and categorization. Stripe maintains the jurisdiction logic, monitors your registrations, and produces the reporting your filings need. This mirrors the module's overall philosophy of handing specialized problems to systems that already solved them, the same reason payment pages are hosted and orders are handed off to your own systems.

Turning It On

Two switches, both already in your setup if you followed the quickstart:

// config.json
"tax": { "enabled": true }

// environment
STRIPE_SECRET_KEY=sk_live_...

With tax.enabled false (the default), no tax is calculated anywhere and totals contain no tax entry, the correct state for sellers below registration thresholds or selling only non-taxable goods. With it true but no Stripe key, the module runs its no-tax calculator, the flag only takes effect when real Stripe access exists. With both set, two things change at once: the checkout service starts calling Stripe Tax for session calculations, and hosted Stripe Checkout pages switch on automatic tax with required billing address collection. Before enabling, activate Stripe Tax in your dashboard, add your tax registrations, and set a default product tax category, digital goods categories matter because several states exempt them.

When Tax Appears in a Session

Tax needs a location, and the module computes it as soon as one exists, choosing the best available basis. The billing address supplied on the ACP complete call wins when present, otherwise the fulfillment address from a session update is used. With no address at all, tax is zero and the session shows pre-tax totals, which is the honest answer for an anonymous cart. This ordering means an ACP platform that sends fulfillment_details early gets tax-inclusive totals on the update response, while one that only provides a billing address at payment time still gets tax correctly applied just before the charge, the module re-prices the session with the address applied, then charges the tax-inclusive total.

The calculation itself is per line: each line's subtotal goes to Stripe Tax with its line id as the reference, and the returned per-line amounts land on each line's tax and total fields, with the sum appearing as a typed tax entry in the session totals. Line-level granularity matters for carts mixing differently-taxed items, a license and a service, for example, and it flows through to the order payload your systems receive, so your invoices can show tax per item. Amounts stay integer cents throughout, consistent with the money rules in the security model.

The Two Payment Paths Differ Slightly

On the delegated path (ACP complete with a payment token), the module computes tax itself through the Stripe Tax API as described above, because it controls the final charge amount. On the direct path (hosted Stripe Checkout from the MCP checkout tool), the module cannot know the buyer's address before redirecting, an anonymous chat shopper has not typed one. So it defers: line items are sent tax-exclusive, the Checkout page is created with automatic tax enabled and billing address required, and Stripe computes and displays tax on the payment page itself once the shopper enters their address. The shopper always sees final tax before paying on either path, the difference is only where the computation runs. One operational consequence: the in-chat total the agent quotes on the direct path is pre-tax, and the precise tax appears on the payment page, your product copy can simply say tax is calculated at payment.

What This Does Not Do for You

Stripe Tax calculates, collects, and reports, but registration and filing remain merchant responsibilities. You still decide where you are obligated to register (economic nexus thresholds for US states, VAT registration abroad), you still file returns, and you still choose accurate product tax categories. Stripe's monitoring surfaces when your sales approach a state's threshold, and its exports feed either your accountant or an automated filing service. For sellers building out this side of the house, our guides on economic nexus thresholds and quarterly taxes cover the obligations around the calculation. If you operate purely below thresholds today, leaving tax.enabled false and revisiting quarterly is a legitimate strategy, flipping it on later is a one-line change with no migration.

Testing Tax Behavior

For unit tests and demos, the package ships a FixedRateTaxCalculator, construct it with a rate like 0.1 and every line gets a deterministic 10 percent, useful for asserting the totals plumbing without network calls. Integration testing against real Stripe Tax works in test mode: enable tax, create a session, update it with a fulfillment_details address in a state where you hold a test registration, and read back the session, the tax entry should appear in totals and on each line. Then complete with a different billing address and confirm the recomputation used the billing address, that ordering rule is the one most worth pinning down in a test. Embedders can also swap in their own TaxCalculator implementation entirely, the interface is one method, which is the escape hatch for a team already contracted with a different tax provider.

Scope note: the module currently models tax on item subtotals with digital fulfillment defaults, which fits the software, license, and digital goods sellers it targets. Physical goods with shipping-dependent tax add fulfillment cost into the calculation, support for richer fulfillment options is the kind of seam the open source repository accepts contributions for.

Common Questions

Do digital products even get taxed? Increasingly yes, and unevenly. A growing majority of US states tax some category of digital goods or SaaS, while others exempt them entirely, and the answer often differs between a downloaded file, a streamed service, and a subscription. This is exactly the categorization problem Stripe Tax's product tax codes exist to encode, and our guide to digital product taxes maps the state-by-state picture.

What currency and country combinations work? Stripe Tax follows your Stripe account's supported markets and handles US sales tax, EU VAT, and other schemes Stripe supports. The module passes the session currency through unchanged, one currency per store, and Stripe applies the destination rules for the buyer's address.

Where does the collected tax go? Into your normal Stripe balance alongside the sale, earmarked in Stripe's reporting. The module's order payload carries per-line tax amounts so your books can separate revenue from collected tax from day one, a distinction your accountant will insist on, see bookkeeping basics.