Skip to Content
MarketplaceUS Tax Compliance

US Tax Compliance (Stripe Tax)

Sales tax in the US is complex — rates vary by state, county, and city, and marketplace facilitator laws require platforms like ours to collect and remit tax on behalf of sellers. We use Stripe Tax to handle jurisdiction detection, rate calculation, reporting, and filing. This page explains how tax flows through our system.

Quick Reference

ConceptValue
Integration modeCheckout Sessions with automatic_tax: { enabled: true }
Tax behaviorExclusive (tax added on top of item prices)
Applies toNA region only (EU region: taxAmount = 0)
Tax code for goodstxcd_99999999 (General - Tangible Goods)
Tax code for buyer feetxcd_10000000 (General - Services)
Where tax is capturedsession.total_details.amount_tax in checkout webhook
Where tax is storedTransaction.taxAmount (total) and Order.taxAmount (prorated per seller)

How Tax Flows Through the System

Checkout: Tax Codes and Behavior

When we create a Stripe Checkout Session for NA-region transactions, each line item gets two tax-related properties:

tax_behavior: 'exclusive'

Tax is added on top of the line item price. A 100itembecomes100 item becomes 108 at checkout if the tax rate is 8%. This is the US standard.

Tax Codes

We assign Stripe product tax codes so that Stripe Tax can determine taxability per jurisdiction:

Line ItemTax CodeWhy
Seller order (cards + shipping)txcd_99999999 (Tangible Goods)Trading cards are tangible personal property, taxable in virtually all US states
Service fee (buyer protection)txcd_10000000 (General Services)Services are not taxable in many states, but marketplace facilitator laws may override this

Without tax codes, Stripe Tax defaults to treating everything as tangible goods. The service fee would be over-taxed in states that exempt services. By assigning the correct tax code, Stripe Tax makes accurate per-jurisdiction determinations.

// In CheckoutService.buildSyntheticLineItems() // Seller line item: tangible goods lineItems.push({ name: "Cards from SellerName (3 cards)", unitAmount: group.subtotal + group.shipping, quantity: 1, taxBehavior: "exclusive", taxCode: "txcd_99999999", // Tangible goods }) // Service fee: general services lineItems.push({ name: "Service fees and buyer protection", unitAmount: buyerFee.total, quantity: 1, taxBehavior: "exclusive", taxCode: "txcd_10000000", // General services })

What About Seller Fees?

Seller fees are not relevant for Stripe Tax — they are deducted from the seller’s payout, not charged at checkout. Stripe Tax never sees them.

Capturing Tax After Payment

When the checkout.session.completed webhook fires, we extract the tax amount from the Stripe session:

// In checkout.handler.ts const taxAmount = region === "NA" ? (session.total_details?.amount_tax ?? 0) : 0

This gets stored on the Transaction alongside the other financial fields. The tax amount represents the total tax collected across all line items for the entire checkout.

Tax Proration Across Orders

A single transaction can contain orders for multiple sellers. We need to split the total tax proportionally:

// prorateTax uses the same algorithm as prorateBuyerFee const proratedTax = prorateTax( transaction.taxAmount, // e.g., 800 cents ($8) orderSubtotals, // e.g., [7000, 3000] transaction.subtotal // e.g., 10000 ) // Result: [560, 240] — proportional by subtotal

The prorated per-order tax is for internal tracking and display only. Stripe’s tax reports use their own per-line-item calculation, which is always accurate. Our proration may differ by a cent or two due to rounding, but this doesn’t affect tax remittance.

Tax in the Fund Model

Tax is included in the order’s charged, which means it’s automatically accounted for in refunds and transfers:

charged = subtotal + shipping + buyerFee + taxAmount

Refunds: Tax Included Automatically

The refundable amount formula is unchanged:

refundableAmount = charged - refunded - transferred

Since charged includes tax, a full refund returns the tax to the buyer. Stripe automatically handles the tax reversal in their reporting — we don’t need to call any special API.

Transfers: Tax Stays on Platform

When releasing funds to the seller, tax is excluded from the transfer. Tax stays on the platform account for Stripe Tax remittance:

// In computeTransferAmount() const remainingRatio = (charged - refunded) / charged const remainingTax = Math.round(taxAmount * remainingRatio) transferAmount = charged - refunded - sellerFee - buyerFee - remainingTax // Tax withheld for platform - transferred

The remainingRatio handles the case where a partial refund has already returned some tax to the buyer (Stripe splits refunds proportionally across pre-tax and tax portions).

Examples

Subtotal: $90.00 Shipping: $10.00 Buyer fee: $5.00 Tax (8%): $8.40 ───────────────────────── charged: $113.40 Transfer to seller: $113.40 - $0 - $8.00(sellerFee) - $5.00(buyerFee) - $8.40(tax) = $92.00 Seller receives: subtotal + shipping - sellerFee = $92.00 Platform keeps: sellerFee($8) + buyerFee($5) + tax($8.40) = $21.40

How Stripe Tax Handles Refunds

This is one of the most important things to understand: we don’t need to do anything special for tax on refunds.

Because we use Checkout Sessions with automatic_tax: { enabled: true } (not the Custom integration mode), Stripe automatically handles tax reversals when we issue standard refunds:

What we doWhat Stripe does automatically
stripe.refunds.create({ payment_intent, amount: 5400 })Refunds $54 to buyer
Proportionally splits into ~50pretax+ 50 pre-tax + ~4 tax
Creates a Tax Transaction reversal
Updates tax reports (platform owes less tax)

We do not need to call stripe.tax.transactions.createReversal(). That API is only for the “Custom” integration mode (PaymentIntents + Tax Calculation API). Since we use Checkout Sessions, Stripe handles it.

Important Caveats

  • Stripe does not refund its Tax processing fee when you issue a refund
  • Tax reports attribute refunds to the original transaction period, even if the refund occurs later
  • Disputes/chargebacks upheld by the bank are not reflected in Stripe Tax reports
  • Up to 30 partial reversals per sale are supported

EU Region: No Tax

For EU-region transactions, tax handling is completely bypassed:

  • automatic_tax is not enabled on the Checkout Session
  • No tax codes or tax_behavior are set on line items
  • taxAmount = 0 on both Transaction and Order
  • The transfer formula with taxAmount = 0 reduces to the pre-tax formula
  • All existing EU orders are unaffected

Backward Compatibility

taxAmount defaults to 0 in both the Transaction and Order schemas. This means:

  • Existing orders (created before this feature) have taxAmount = 0
  • The transfer formula produces the same result as before when taxAmount = 0
  • The refund formula is completely unchanged (charged - refunded - transferred)
  • No data migration is needed

Stripe Tax Dashboard

Tax registration, reporting, and filing happen in the Stripe Tax dashboard — not in our code:

  • Registration: The platform must register for tax collection in relevant US states
  • Reporting: Stripe generates tax reports showing collected tax per jurisdiction
  • Filing: Stripe can auto-file tax returns in registered states (Tax Filing product)
  • Thresholds: Stripe monitors economic nexus thresholds and notifies when registration is needed

Code Locations

ComponentLocation
Checkout line items (tax codes)packages/backend/features/checkout/src/checkout.service.ts
Stripe checkout integrationpackages/integrations/stripe/src/payments/checkout.ts
Webhook tax capturepackages/backend/features/stripe-webhooks/src/lib/handlers/checkout.handler.ts
Tax proration utilitypackages/backend/features/orders/src/prorate-tax.ts
Order orchestrator (proration)packages/backend/features/orders/src/order-orchestrator.service.ts
Transfer formulapackages/backend/features/orders/src/compute-transfer-amount.ts
Fund release servicepackages/backend/features/orders/src/fund-release.service.ts
Transaction modelpackages/core/db/src/mongo/marketplace/transaction.model.ts
Order modelpackages/core/db/src/mongo/marketplace/order.model.ts
Last updated on