You close a deal on Friday. The customer is in France. Stripe created the subscription, the invoice looks fine, and then someone asks the question that usually arrives too late: what VAT percentage should we charge?
That question sounds simple. In France, it usually isn't. The problem isn't just the headline rate. It's deciding whether the sale is B2B or B2C, whether the customer's VAT ID is valid, whether the product category changes the rate, and whether your billing code can survive a validation dependency failing in the middle of checkout.
France matters here for two reasons. First, its VAT regime is unusually detailed. Second, VAT itself has deep roots there. France adopted VAT nationally on 10 April 1954, and that model later became the template for modern VAT systems across Europe, as outlined in EBSCO's history of value-added taxes in Europe. If you're building EU billing, France isn't an edge market. It's one of the places that shaped the rules you're coding against.
If you're building subscription checkout, invoicing, or marketplace flows, the job isn't memorizing rates. It's turning tax rules into code paths that behave correctly under normal conditions and under failure. That's the part most VAT guides skip. If your stack looks anything like the scenarios in this SaaS billing EU guide, you need decision logic, validation strategy, and fallback rules, not just a list of percentages.
Table of Contents
- Your SaaS Just Got a French Customer Now What
- France's VAT Rates Explained
- How Place of Supply Determines French VAT
- B2B vs B2C Billing and The Reverse Charge Mechanism
- The Crucial Step VAT ID Validation via VIES
- Developer's Guide to Implementing French VAT
- From VAT Complexity to Compliant Automation
Your SaaS Just Got a French Customer Now What
The first French customer usually exposes every shortcut in your billing stack.
At first, everything looks familiar. You already support EU customers. You already collect billing country. You already calculate VAT somewhere in your checkout code. Then France shows up and forces a harder question: are you applying a country rate, or are you classifying the transaction correctly?
A common failure pattern looks like this:
- Checkout collects only country: The app sees
FRand applies one default tax rate. - Sales marks the customer as business: Someone expects no VAT because “they gave us a company name.”
- Finance spots the invoice later: There's no validated VAT ID, no reverse charge logic, and no reliable audit trail.
- Engineering gets the fix ticket: Now tax treatment has to be retrofitted into pricing, invoicing, and account provisioning.
That's why “vat percentage in france” isn't just a search query for accountants. It's a systems question. Your checkout needs evidence. Your tax engine needs product classification. Your invoice generator needs to know why a rate was used, not just what number was printed.
Practical rule: Never let the tax rate be the first decision. The first decision is always transaction type, customer type, and place of supply.
In real SaaS systems, the cleanest approach is to treat VAT as a decision tree, not a constant. France makes that obvious because the same platform might sell subscriptions, onboarding services, training, or other line items that don't all fit the same tax bucket.
If you only implement “France = one rate,” you'll ship faster for a week and create cleanup work for months.
France's VAT Rates Explained
France uses a four-tier VAT structure. It has a 20% standard rate, 10% intermediate rate, 5.5% reduced rate, and 2.1% super-reduced rate, which is why it's often described as one of the more detailed VAT regimes in the EU, as summarized in Stripe's guide to VAT rates in France. For developers, that means your billing logic can't assume a single France-wide default rate fits every taxable supply.
The rates you actually need in code
| Rate Type | Percentage | Applies To (Examples) |
|---|---|---|
| Standard rate | 20% | Most taxable goods and services, including alcohol and luxury goods |
| Intermediate rate | 10% | Restaurants, prepared food, passenger transport, some housing renovation work, museums, zoos, and certain cultural admissions |
| Reduced rate | 5.5% | Non-prepared food, gas, electricity, renewable energy, feminine hygiene products, and some disability-related equipment and services |
| Super-reduced rate | 2.1% | Certain medicinal products reimbursable by social security, blood products, newspapers, and some press publications |
If you want a quick machine-readable reference for country rates, this France VAT rates page is useful as a lookup layer. It's still only a lookup layer. It won't decide product classification for you.
Why the table is only the starting point
Most SaaS teams care about the 20% rate first, because standard digital services often land there. But France is one of the markets where reduced-rate categories matter enough that hardcoding a single rate creates risk.
Two implementation implications matter immediately:
Product tax codes need to exist If your system supports more than one kind of line item, you need a stable internal classification model. “Subscription,” “support,” and “training” may not belong in the same treatment path.
Invoices need line-level awareness If every invoice stores only one top-level tax rate, you're making future corrections harder. Even if your current product catalog is simple, your schema should support line-specific VAT treatment.
France's VAT model is granular enough that product classification belongs in your billing architecture, not in a spreadsheet owned by finance.
A lot of founders postpone this because their first version sells one digital subscription. That's reasonable. What doesn't work is pretending the first version will stay that simple. The moment you add services, bundles, credits, or marketplace flows, tax logic spreads across your catalog, pricing, and invoice rendering.
The practical takeaway is simple. Store these rates, but don't stop at storing them. Build a rules layer that decides when each one applies.
How Place of Supply Determines French VAT
For digital services, place of supply is the rule that turns “customer in France” into an actual VAT outcome. If you skip this step, your rate table won't help much.
For B2C digital sales, the usual logic is straightforward in practice. If the consumer is in France, your system treats the supply as local to France for VAT purposes, so French VAT treatment applies. That's why a SaaS checkout needs credible customer location evidence before it calculates tax.
For digital services location drives the tax outcome

The easiest mental model is this: for many digital supplies, your app should behave as if the purchase happens where the customer belongs, not where your servers run and not where your company is incorporated.
That leads to two distinct code paths:
- B2C sale to a French customer: your checkout calculates French VAT treatment.
- B2B sale to a business customer: your system needs more inputs before deciding whether to charge VAT or apply reverse charge treatment.
Many implementations frequently get sloppy. Teams often infer B2B from a company-name field or a business email domain. Neither is enough to support reverse charge treatment. Business status has to be backed by proper tax validation.
Territory is not the same as country
France adds another wrinkle. Territorial VAT rules differ in overseas departments and Corsica, and applicable rates can diverge from mainland France. The operational consequence, as noted in Wise's overview of French VAT territory rules, is that a VAT engine shouldn't rely on a single country flag alone. It needs to consider product type, place of supply, and territory.
That matters in code because “France” is not always one tax zone from a billing perspective.
A sound tax decision usually needs at least:
- Customer type: consumer or business
- Customer location detail: not just country, but enough detail to resolve territory when relevant
- Product category: especially if your catalog spans different VAT treatments
- Validation state: whether a VAT ID was checked and what the authoritative result was
If your tax engine accepts only
country=FRand outputs one rate, it's too thin for real EU billing.
There are also zero-rated or exempt edge cases in French VAT treatment for certain cross-border transport and some medically related supplies. Even if your SaaS product never touches those categories, the lesson is still important. A VAT rate is not selected from geography alone. It's selected from geography plus supply type plus legal treatment.
That's the difference between a demo tax calculator and a production billing system.
B2B vs B2C Billing and The Reverse Charge Mechanism
The biggest billing difference isn't the percentage. It's who accounts for the VAT.
For B2C, your system usually behaves like a retail tax collector. If French VAT applies, you add it to the sale, show it clearly at checkout, and print it on the invoice.
For B2B, the path can change completely. In some intra-EU cases, the seller does not charge VAT and the buyer accounts for it under the reverse charge mechanism. That changes pricing display, invoice wording, and the evidence you need to store.
What changes when the buyer is a business
Here's the operational distinction that matters:
| Scenario | Typical system behavior |
|---|---|
| Consumer in France | Charge applicable French VAT |
| Business customer without validated VAT ID | Treat cautiously. Many teams charge VAT until validation is complete |
| Business customer with validated VAT ID in a qualifying cross-border case | Apply reverse charge treatment and issue the invoice accordingly |
The phrase “qualifying cross-border case” matters. Reverse charge is not a general-purpose B2B discount. It depends on the transaction setup and the customer's validated tax status.
In practice, a good B2B flow usually does four things:
- Collect the VAT ID during checkout or account setup
- Validate it before final tax treatment is locked
- Store the validation result with timestamp and request context
- Generate invoice text that reflects the chosen treatment
What works and what breaks in production
What works:
- Asking for the VAT ID early, before payment is finalized
- Recomputing tax after validation, not before
- Saving the validation evidence in your billing database
- Showing users why VAT was added or removed
What breaks:
- Trusting user input blindly: A string that looks like a VAT number is not validation.
- Validating asynchronously after invoicing: If your invoice already went out with zero VAT, you've created a correction problem.
- Treating reverse charge as a UI toggle: Tax treatment must be derived from evidence, not selected by the customer.
- Dropping the audit trail: If finance can't see why VAT was removed, the implementation is incomplete.
Reverse charge is an invoicing and evidence workflow, not just a tax calculation branch.
This is why B2B VAT feels deceptively simple until you implement it. The legal idea is clean. The product work is not. Your checkout, invoice service, CRM, and accounting export all need to agree on the same tax state.
Once teams accept that, the implementation gets much more reliable.
The Crucial Step VAT ID Validation via VIES
A French business customer enters a VAT number at checkout, your tax calculation drops to zero, and payment succeeds. If that number was never validated, the invoice decision is sitting on unverified input.
For billing systems, VAT ID validation is evidence collection tied to a tax outcome. If you apply reverse charge treatment for an EU B2B sale, you need a record showing the VAT number was valid when the transaction was priced and invoiced. That requirement reaches beyond tax logic into checkout reliability, invoice generation, and audit storage.
Validation is a tax control, not a form feature

The usual authority for EU VAT number checks is VIES, the VAT Information Exchange System. The happy path sounds simple. Send the country code and VAT number, receive a valid or invalid result, then choose the tax treatment.
Production systems are less tidy.
A direct VIES integration creates a few predictable problems:
- Legacy protocol mismatch: VIES is SOAP-based, so teams often need an adapter before it fits into a modern checkout or billing service.
- Checkout-time dependency risk: validation can fail during payment, which is the worst moment to depend on a remote tax service.
- Messy response handling: upstream errors are not always consistent, so error parsing and customer messaging need explicit rules.
- Audit requirements: finance and support need the validation result, timestamp, normalized VAT ID, and request context. A transient boolean is not enough.
If you want implementation detail on the validation side, this technical VIES check guide for developers covers the integration patterns and edge cases.
Why direct VIES integrations fail in ordinary ways
The usual mistake is treating validation failure as a retry problem. It is a policy problem first.
Your system needs a defined response for cases like these:
- the VAT number fails local format checks
- VIES is temporarily unavailable
- the customer edits company details after a successful validation
- checkout needs a tax decision immediately
A workable policy usually looks like this:
| Validation outcome | Safer default |
|---|---|
| Clearly invalid format | Keep VAT applied and ask for correction |
| Authoritative valid result | Allow qualifying B2B treatment |
| Upstream service failure | Charge VAT or route to manual review, based on your compliance policy |
| Ambiguous internal state | Do not remove VAT automatically |
Systems that fail toward charging VAT are easier to defend than systems that guess their way into reverse charge treatment.
That trade-off can frustrate sales or conversion teams. It still tends to be the lower-risk choice. Refunding VAT later is operationally annoying, but unsupported zero-VAT invoices create a harder cleanup across finance, support, and accounting exports.
The practical design choice is to treat VAT validation as part of billing infrastructure. It needs timeout handling, retries with limits, stored evidence, and a fallback path your finance team accepts before you ship.
Developer's Guide to Implementing French VAT
The clean implementation is not “call a tax API and hope.” It's a small state machine with explicit fallback behavior.
Start with the checkout flow. Treat tax as a derived outcome from customer type, location, product classification, and VAT validation state. Don't let the frontend decide the final answer on its own.

A checkout flow that fails safely
A solid implementation usually follows this order:
Collect billing country and customer type early
If the customer says they're buying as a business, reveal the VAT ID field before payment confirmation.Classify the product before tax calculation
Your tax engine should know whether the cart contains your standard SaaS subscription or some other line item that may need different treatment.Run format checks before any remote validation call
Fast local checks remove obvious junk input and reduce unnecessary dependency calls.Validate the VAT ID if the transaction could qualify for B2B treatment
Only after that should your backend decide whether to remove VAT.Persist the result
Save the validation status, normalized VAT ID, legal name returned if available, and when the check happened.Render the invoice from stored tax facts, not from frontend assumptions
Invoice generation should consume the same tax decision record your checkout created.
Here's the practical trade-off. If validation is unavailable during checkout, you need to choose between blocking payment, charging VAT by default, or routing the order for follow-up. Most SaaS teams prefer charging VAT by default because it keeps revenue moving and avoids unsupported exemption logic.
Pseudo-code for resilient VAT handling
This is the kind of shape that works well in Node.js, Python, or any service-oriented backend:
async function calculateFrenchVatTreatment(order) {
const productTaxCode = classifyProduct(order.items);
const location = resolveCustomerLocation(order.customer);
const isBusiness = order.customer.customerType === "business";
let vatDecision = {
treatment: "charge_vat",
reason: "default",
vatRateKey: selectRateKey(productTaxCode, location),
validationStatus: "not_required"
};
if (!isBusiness) return vatDecision;
if (!order.customer.vatId) {
vatDecision.reason = "missing_vat_id";
return vatDecision;
}
if (!passesLocalFormatCheck(order.customer.vatId, location.country)) {
vatDecision.reason = "invalid_format";
vatDecision.validationStatus = "invalid";
return vatDecision;
}
try {
const result = await validateVatId(order.customer.vatId);
if (result.status === "valid" && qualifiesForReverseCharge(order, location)) {
vatDecision.treatment = "reverse_charge";
vatDecision.reason = "validated_business_customer";
vatDecision.vatRateKey = null;
vatDecision.validationStatus = "valid";
vatDecision.validatedEntity = {
name: result.name,
address: result.address
};
return vatDecision;
}
vatDecision.reason = "validation_failed";
vatDecision.validationStatus = "invalid";
return vatDecision;
} catch (err) {
vatDecision.reason = "validation_service_unavailable";
vatDecision.validationStatus = "unavailable";
return vatDecision;
}
}
This pattern does two useful things. It centralizes the decision, and it makes failure explicit. Hidden fallback logic is what creates bad invoices.
If you don't want to maintain a direct VIES wrapper, one option is TaxID, which exposes VAT and company ID validation through a single REST endpoint and returns standardized JSON for validation status, company name, and address. The value for developers is mostly operational. You can keep your checkout logic REST-native and handle machine-readable errors instead of parsing brittle upstream responses.
A short implementation review often helps teams align on the user experience:
Invoice and data model details that matter
A reliable French VAT implementation usually stores more than the final rate.
Keep these fields somewhere durable:
- Customer tax identity: legal name, billing address, declared customer type, VAT ID
- Validation evidence: normalized VAT ID, validation result, timestamp, source system
- Tax decision: charged VAT, reverse charge, exempt, or manual review
- Line-level classification: product tax code or internal category
- Rendered invoice basis: the exact treatment used when the invoice was created
Systems that recompute tax from scratch every time an invoice is viewed are hard to audit and easy to break.
One more practical point. Don't bury VAT logic inside webhook handlers only. Webhooks are great for synchronization with Stripe or your PSP, but the tax decision should be made in a service you control and can replay deterministically. That makes corrections, credits, and support investigations much easier.
From VAT Complexity to Compliant Automation
The phrase vat percentage in france sounds like a lookup problem. For developers, it's a workflow problem.
France has multiple VAT rates. Some supplies fall into reduced or super-reduced categories. Place of supply matters. Territory matters. B2B treatment can change the invoice outcome entirely, but only when the customer's VAT status has been validated and stored properly.
That's why fragile implementations keep failing in the same places. They rely on a single country flag, trust a user-entered VAT number, or put exemption logic behind an unreliable dependency without any fallback policy.
The reliable path is narrower than people expect. Model product categories. Separate B2B from B2C early. Validate VAT IDs before applying reverse charge treatment. If validation fails, fail safely. Generate invoices from stored tax facts. And if you don't want to maintain SOAP integrations and normalization logic in-house, use a dedicated validation layer rather than rebuilding tax plumbing as a side project.
If you want a simpler way to validate VAT numbers inside checkout and invoicing flows, TaxID gives developers a REST API for VAT and company ID validation across EU countries and beyond, with structured responses that fit modern billing systems.