Skip to content

Hidden Costs of a Stripe Integration: PCI, Disputes, Tax

The integration takes a week. PCI scope, fraud rules, chargebacks, sales tax, refunds, and reconciliation last for years. What each costs, and what Flint runs.
Operations12 min readBy Flint Pay, Product & API Team

The payment integration is the easy week. The cost arrives after launch: a PCI attestation every year, fraud rules to tune, chargebacks with deadlines, sales tax per address, refunds that match the charge, and payouts that match your orders.

Tip: The short answer

  • On a payment-first API such as Stripe, each job is a system your team wires, watches, and pays for when it breaks.
  • Flint, an order-first commerce API, runs checkout, tax, the dispute record, receipts, refunds, returns, and the payout ledger against the order, with Stripe processing underneath.
  • You stop building tax wiring, refund math, receipt emails, dispute evidence lookups across your tables, and payout matching.

At a glance#

PCI scopeStripe and other payment-first APIsWith Elements, your page and every script on it; Checkout is Stripe's hosted pageFlintFlint's hosted page; cards stay in Stripe's frames
Fraud and riskStripe and other payment-first APIsTune rules, staff a review queueFlintDefault rules, API rules with dry-run preview, review webhooks
DisputesStripe and other payment-first APIsBuild a deadline queue, gather evidence from your tablesFlintEach dispute linked to its order, with deadline and response UI
Sales taxStripe and other payment-first APIsCall a tax API, set amounts, split refund taxFlintTax on the order, reversed by refunds, exported in reports
ReceiptsStripe and other payment-first APIsBuild itemized emails from your order tableFlintItemized receipts and refund receipts from the order
Refunds and returnsStripe and other payment-first APIsCompute each refund's tax; run returns by handFlintRefund by line item; returns with policies and restocking
ReconciliationStripe and other payment-first APIsWalk payout transactions back to your ordersFlintEach payout row carries its order_id

Note: What stays with you

Flint is not a merchant of record, so as on any platform that isn't one, you keep:

  • Dispute responses: deciding whether to fight, and gathering and submitting evidence
  • Sales tax registration, filing, and payment, and deciding where you owe it
  • The PCI self-assessment; your acquirer or a QSA decides which questionnaire you file
  • Access decisions: your fraud policy, and approving or declining each review

What you're responsible for when you sell online lists these with primary sources.

PCI scope#

Stripe says a business accepting payments must "annually attest to this compliance," and the questionnaire depends on how card numbers reach the processor. Stripe's PCI guide puts Checkout and Elements, whose card fields are iframes on Stripe's domain, on SAQ A, and card numbers sent to Stripe's API from your code on SAQ D, "the most demanding of the SAQs." The PCI Security Standards Council built SAQ A-EP primarily for merchant-built forms that post card data to the processor.

Since January 2025, SAQ A merchants must "confirm their site is not susceptible to attacks from scripts that could affect the merchant's e-commerce system(s)." The Council's FAQ 1588 applies that to embedded payment forms, not to a redirect to a third-party payment page. A coding agent that writes plain card inputs posting to your API puts you on SAQ D.

Who owns the checkout page's PCI scope
Card fieldsStripeYou keep card inputs in Stripe's iframesFlintA hosted checkout session sends the buyer to Flint's checkout page; card numbers go into Stripe's frames and never reach your servers or Flint's
The page around themStripeEvery script on it is yours, and an embedded form must meet the script-attack criterionFlintFlint runs the page, the session, and the code around the card fields
AttestationStripeYou file the annual attestationFlintYou still attest; Flint documents the data flow you take to your acquirer or QSA

With embedded payments on your own checkout, cards still stay in Stripe's frames, but the page, its scripts, and its Content Security Policy are yours; securing a headless checkout lists the controls.

Fraud and risk#

Stripe Radar has default rules, custom rules "if your Radar plan supports this feature," and allow rules by request to support. Stripe's rules documentation warns:

While rules can help you automate your existing workflows, they can also negatively affect your business if used incorrectly. For example, a rule can automatically allow a large number of elevated or high-risk payments or block a large number of legitimate payments, if it isn't set up properly.

Stripe, Stripe Radar rules documentation

The same page adds that "fraud patterns constantly change." A block rule written after one bad week keeps blocking good customers, and fraud that gets through comes back as disputes: the goods, the disputed amount, and a fee.

Who tunes and watches fraud rules
RulesStripeCustom rules you write and keep currentFlintRisk checks on every card and ACH debit payment, with two rules on from day one: block Flint's default lists (which start empty), and review payments the processor rates high risk
ChangesStripeRegular review against your disputes and refundsFlintYour own rules as JSON through the API or Dashboard, previewed before they touch a payment; risk.matched_risk_rule_id names the rule that decided a payment
ReviewsStripeA review queue someone has to workFlintYou still decide each review; fraud_warning.created lets you pause fulfillment before a dispute

A risk preview returns is_valid and every problem with its field path, and saves nothing:

cURL
curl -X POST https://api.withflintpay.com/v1/risk-previews \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "mode": "inline",
    "action": "review",
    "predicate": {
      "all": [
        {"attribute": "amount_money", "operator": "gte", "amount_money": {"amount": 50000, "currency": "USD"}},
        {"attribute": "is_guest", "operator": "eq", "value": true},
        {"attribute": "payment_method_type", "operator": "eq", "value": "card"}
      ]
    }
  }'

Send the same action and predicate with a description to POST /v1/risk-rules to turn it on. Each attempt records the rules in force when it started, so an edit never changes an attempt in flight. Declining a review of a captured payment refunds it with reason fraudulent, and add_to_block_lists adds its card, email, and IP address to the block lists.

Disputes#

A dispute starts a clock. Stripe says you usually have 7 to 21 days, depending on the card network, and "you've only one opportunity to submit your response." On Stripe's standard pricing, every dispute costs a $15.00 received fee, and answering it manually adds a $15.00 countered fee that comes back only if you win. Braintree lists a 15.00 USD chargeback fee, and on Adyen, "after you defend the dispute, you can no longer supply additional information."

One developer on Hacker News:

If a customer buys your product for $10, they use your product, then they issue a chargeback then Stripe will clawback $25.

joshstrange, Hacker News, on Stripe's added dispute fee

The evidence lives in your tables, and if the charge.dispute.created handler fails and nobody notices, the dispute is lost by default at the deadline.

Who handles a dispute
Opening the caseStripeAn event handler and a deadline queue you buildFlintdispute.created and dispute.needs_response open the case, and each dispute records evidence_due_at and reason
Order contextStripeEvidence queried from your own tablesFlintThe dispute carries its payment_intent_id, order_id, and customer_id, and a refund on the disputed payment fails with reason payment_disputed, so the buyer isn't paid back twice
RespondingStripeOne submissionFlintRespond in the Flint Dashboard or Stripe's embedded dispute component in your app; evidence goes to the processor, not through Flint
FeesStripeA $15.00 received fee, win or loseFlintCard dispute fees are the network's own cost with no Flint markup, and Flint doesn't charge one that exceeds your offer's maximum ($50.00 by default); an ACH dispute or return is $15.00

This call submits the response, because submit defaults to true. The half-written evidence is now the only response the bank sees:

JavaScript
// Save what we have so far; someone will finish the response tomorrow.
await stripe.disputes.update(dispute.id, {
  evidence: {
    customer_email_address: order.email,
    shipping_tracking_number: order.trackingNumber,
    product_description: order.items.map((item) => item.name).join(", "),
  },
});

Evidence is staged only when you send submit: false.

As on any processor, the disputed amount leaves your balance when the chargeback opens.

Sales tax#

Stripe Tax "returns zero tax" without a registration in the customer's location. Tax Basic costs 0.5% per transaction through the no-code integrations or $0.50 per transaction through the API, where you're registered, with 10 calculation calls included and 5¢ for each after that. Filing is separate: "You must file and remit the tax you collect for every location where you're registered," and having Stripe register for you takes Tax Complete, from $90.00 per month on a 1-year contract.

The failures are quiet: an amount set before the address is final, a missed registration that collects zero, a partial refund that forgets its tax. A seller generally owes the tax it should have collected, whether or not the buyer paid it.

Who wires sales tax into each payment
CalculationStripe with PaymentIntentsA tax calculation call per cart, which you wireFlintTax on the order, recalculated when anything that affects it changes
ChargingStripe with PaymentIntentsThe PaymentIntent amount set from amount_totalFlintCheckout collects the order's current total, so the buyer pays the tax the order records
RefundsStripe with PaymentIntentsEach refund's tax share, worked out yourselfFlintA line-item refund reverses that line's tax in proportion
ReportingStripe with PaymentIntentsThe calculation attached for reportingFlintThe tax_transactions_itemized_v1 report exports each calculation and reversal
PriceStripe with PaymentIntentsTax Basic through the API: $0.50 per transaction where you're registered, 10 calculation calls included, 5¢ for each after thatFlintAutomatic tax adds 0.50% on payments that carry tax

Connect automatic tax once, add a registration for each state where you collect, and set tax on the order:

cURL
curl -X POST https://api.withflintpay.com/v1/orders \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: order-mugs-001" \
  -d '{
    "line_items": [{
      "type": "custom",
      "name": "Ceramic mug",
      "quantity": 2,
      "unit_price_money": {"amount": 2400, "currency": "USD"},
      "fulfillment": {"requirement": "none"},
      "tax": {"taxable": true, "line_item_tax_category": "physical_goods"}
    }],
    "tax": {
      "enabled": true,
      "calculation": {"mode": "automatic", "price_mode": "additive"},
      "location": {
        "address_source": "provided",
        "address_type": "shipping_address",
        "address": {
          "line1": "200 Spectrum Center Drive",
          "city": "Irvine",
          "state": "CA",
          "postal_code": "92618",
          "country": "US"
        }
      }
    }
  }'

The response carries tax_money on each line item and pricing_amounts.total_money: $3.72 of tax on $48.00 at Irvine's 7.75% combined rate, for $51.72. A state with no registration in effect calculates zero tax, and Flint doesn't register, file, or pay for you.

Receipts, refunds, and returns#

A Stripe PaymentIntent carries an amount, with optional amount_details line items documented for card-network data, and a refund names a Charge or PaymentIntent and an amount. Itemized receipts for a Stripe payment and Stripe partial refunds walk through the traps.

Who builds receipts, refunds, and returns
ReceiptsStripeItemized receipt emails you build from your own order table, sent once per paymentFlintReceipts built from the order, and refund receipts listing the refunded lines; turn off receipts to send your own, and the webhooks keep firing
RefundsStripeEach refunded item's share of discount and tax, worked out by youFlintPOST /v1/refunds with refund_type of full, amount, or targets; Flint works out the payments and the tax
ReturnsStripeTracked by handFlintReturns with policies, buyer self-serve, and restocking

Flint doesn't buy return labels, so your backend attaches one from your carrier or 3PL.

Reconciliation#

A payout is one deposit of many balance transactions, and a fee or refund that doesn't map back to an order becomes an unexplained line in your books. Stripe payout reconciliation works through that matching.

Matching a payout to your orders
The payoutStripeYou match every balance transaction in each payoutFlintGET /v1/balance-transactions?payout_id=...&expand=order lists everything a payout carried, and the rows' net_money adds up to the payout's amount_money
Each lineStripeMatched through the IDs and metadata you stored at checkoutFlintEach row has gross, fee, net, and the order_id when it belongs to an order
Fees and refundsStripeYou match fees and refunds that don't map cleanlyFlintDispute and ACH event fees are event_fee entries in GET /v1/merchant-billing-entries, reaching a payout only as a merchant_billing_collection row

Reconciliation covers each movement type.

Run the operations on Flint#

Flint charges 3.79% + 35¢ per card payment, with Stripe's processing included and no monthly fee. A free sandbox runs risk rules and refunds against Stripe test mode, plus automatic tax once you connect it. Have your coding agent wire it up:

Shell
claude mcp add --transport http flint-docs https://developers.withflintpay.com/mcp
Text
Use the flint-docs MCP server. With the sandbox key in
FLINT_API_KEY, create orders with automatic tax for each
cart and send buyers to a hosted checkout session. Add a
webhook route that verifies Flint deliveries and, for
dispute.* events, fetches the dispute and upserts a case
with order_id, reason, status, is_action_required, and
evidence_due_at. Add a risk rule that reviews guest card
payments of $500 or more, checked first with
POST /v1/risk-previews. Show me a paid order, a line-item
refund that reverses its tax, and the payout ledger rows
for that order.
DisputesThe dispute record, deadlines, response sessions, accepting, and bank returns.Risk controlsDefault rules, your own rules and lists, previews, reviews, and fraud warnings.Sales taxAutomatic tax on the order, registrations, refunds that reverse tax, and tax reports.Going liveVerify your business, create a live key, register webhooks, and confirm with one real payment.

To see tax come back on a line-item refund, connect automatic tax in the Dashboard and add a test registration first.

Sign up for a free sandbox. Create an order, pay it through hosted checkout with a test card, and refund one line item. The sandbox needs no credit card. Read the refunds guide.

FAQ#

What are the hidden costs of a Stripe integration?#

The work after launch: an annual PCI attestation, Radar rules and a review queue, disputes answered within 7 to 21 days with one submission and $15.00 fees, Stripe Tax wired into each payment, receipts and refund math, and payout matching. Each needs code, an owner, and monitoring.

Which PCI SAQ applies to Stripe Elements?#

Stripe's PCI guide puts Checkout and Elements on SAQ A, and since January 2025 an embedded form also requires confirming your page isn't susceptible to script attacks. Sending card numbers through your own server is SAQ D, and your acquirer or a QSA makes the final call.

How long do I have to respond to a Stripe chargeback?#

Usually 7 to 21 days, depending on the card network, with one submission, and you lose by default after the deadline. Stripe's submit parameter defaults to true, so send submit: false to stage evidence.

Does Flint file sales tax returns?#

No. Flint calculates US sales tax on the order from your registrations, collects it at checkout, reverses it on refunds, and exports it in reports; you register, file, and pay the states, because Flint is not a merchant of record.

Does Flint respond to disputes for me?#

No. Flint links each dispute to its order with its deadline, sends webhooks, and gives your team the Flint Dashboard or an embedded component to respond; your team decides whether to fight and submits the evidence.

Sources#

Everything attributed to Stripe, Braintree, Adyen, and the PCI Security Standards Council above comes from their own documentation and pricing pages, and the developer quote from its Hacker News thread, all checked on 2026-10-28.