Plate III · BillFlow · 2026
Bone-white pigment · raking light along the trail
An order-to-invoice gateway for EU cross-border Shopify merchants. VAT checks run before an invoice is sent, and failed invoices wait in a correction queue.
Compliance workflow / Business rules / Recovery
Case summary
- Problem
- B2B buyers rejected 7.9% of invoices for format or VAT-ID defects, and buyers often typed their VAT ID into free-text order notes.
- Users
- EU cross-border Shopify merchants, three of them interviewed in Germany, plus the invoice support-ticket queue.
- Key decision
- Validate before sending and hold failed invoices in a correction queue. Extract VAT IDs with rules and VIES instead of a language model.
- Result
- Buyer rejections fell from 7.9% (63/797) to 1.2% (11/914) over 6 weeks. The cost was 35% more tickets asking why an invoice was held in the correction queue, in the first two weeks.
Loading local demo…
The demo could not load. The image and case remain readable; you can try again.
One decision
Where the AI stops in a compliance flow
BillFlow reads VAT numbers out of order notes with rules, and keeps the model out of that step. On 80 anonymised notes, a regular-expression screen, the check-digit algorithm and a VIES lookup found 44 numbers, and all 44 were right; the 8 notes they could not read go to the merchant to confirm. With validation placed before sending, the share of B2B invoices rejected by buyers fell from 7.9% (63/797) to 1.2% (11/914) after 6 weeks.
Buyers often typed their VAT number into the order note, and a German merchant told us they had to keep guessing which buyers were businesses. A general language model could pull numbers out of free text; rules could only find what matched a known format. Operations had their own worry: blocking invoices up front would alarm merchants, so they wanted to issue first and fix problems later.
The model extracted 52 numbers and got 47 right. Of the 5 it got wrong, 3 were numbers where it had “corrected” a check digit on its own. In a compliance flow that is the worst kind of error: a number that looks valid, is not the buyer’s, and would go out on a legal document. Rules found fewer numbers and made none of these errors. The same reasoning set the rule for outages: VIES timed out twice during the batch, and those numbers are marked “pending verification”, since a service being down says nothing about whether a number is valid.
Blocking costs time. In the first two weeks after launch, tickets asking why an invoice was waiting in the correction queue rose by 35%, and they returned to normal in week 4, once merchants knew the rules. We shipped the block without first sending merchants a guide or a notice of the rule change, and that left a communication cost we could have avoided.
Stop the model where an error turns into a legal fact, and use it where a person reads its output anyway. Treat “we could not check” as a state of its own, apart from “wrong”. And tell the people a rule will block before it starts blocking them.
- Order note
- Pattern screen
- No match: the merchant confirms
- Check digit
- Fails: correction queue
- VIES lookup
- Timeout: pending verification
- Invoice sent
How the responsibilities connect
- 01Order & VAT ID
- 02Rule check: checksum + VIES
- 03Correction queue
- 04Invoice & delivery
Invalid data goes to the correction queue with the missing field named. A VIES timeout is marked pending, never invalid.
Brand card · Plate III
BillFlow
A ledger that stops at the field it cannot vouch for.
Colour
- Ivory black
#0E0D0CThe ledger - Bone
#EDE6D8A checked value - Gray 7
#B9B2A6A blocked field, outlined - Gray 5
#8E887FPending verification
Type
- GeistLedger and forms
- MonospaceVAT numbers and check digits
Motion
- Print
240 msA VAT number prints digit by digit as it is checked - Light
1200 msA blocked field stays lit until it is fixed
Problem and context
BillFlow is an order-to-invoice gateway for EU cross-border Shopify merchants, built with Shopify integration code, Hono, SQLite and React. It works with the EU EN 16931 e-invoicing standard and the VIES VAT-number check, and it responds to the German rule that, from 2025, businesses must be able to receive B2B e-invoices. I owned the data flow from order to invoice, the validation rules and exception recovery.
User validation: tickets and merchant interviews
Two people independently coded 15 invoice support tickets from one month, with inter-rater agreement of κ = 0.71.
| Failure category | Tickets |
|---|---|
| Buyer’s system rejected the invoice format | 6 |
| VAT ID missing or malformed | 3 |
| Refunds and credit-note reversals | 2 |
| Other | 4 |
I also interviewed 3 merchants in Germany. A coffee-equipment merchant said: “From a web-shop order it’s hard to tell which buyers are businesses. Buyers often put their VAT number in the order note, and someone has to guess.”
Evaluation: extracting VAT IDs from order notes
The test set was 80 de-identified real order notes.
| Approach | VAT IDs extracted | Fully correct | Wrong |
|---|---|---|---|
| A: general-purpose language model | 52 | 47 | 5 (in 3 of them the model “corrected” a check digit) |
| B: regex pre-filter + checksum algorithm + VIES lookup | 44 | 44 | 0 |
The 8 notes that B could not read directly go to the merchant for manual confirmation. During batch evaluation, VIES timed out 2 times. Those IDs were marked “pending verification”: a service that is temporarily unavailable is never treated as a VAT ID that is invalid.
B became the extraction rule. A model that quietly fixes a check digit produces a wrong tax identity that looks right, which is worse than asking the merchant once.
Team decision: block before sending, and pay for it
Operations worried that blocking invoices before sending would alarm merchants, and argued for issuing now and fixing later. In the 6 weeks before the change, buyers rejected 63 of 797 B2B invoices for format or VAT-ID defects, a rate of 7.9%.
We added validation before sending. An invoice that fails goes to a correction queue, and the merchant adds the VAT ID or fixes the address before it goes out. After 6 weeks, the rejection rate had fallen from 7.9% to 1.2% (11/914).
The cost was real. In the first two weeks after launch, support questions about why an invoice was stuck in the correction queue rose 35%, and they returned to the usual level by week 4, once merchants knew the rule. We had not sent merchants any guidance or notice of the rule change before launch, and that created communication cost we could have avoided.
The exception-handling spec
| Failure type | Typical cause | System behavior | What the merchant sees |
|---|---|---|---|
| Temporary external failure | VIES timeout, network error | Bounded exponential backoff, at most 3 retries | “Verifying” |
| Data error | Wrong check digit, missing legal address | Stop and move to the correction queue | The missing field, by name |
| Cross-border rule unclear | New country-specific tax rules | Send to manual review | “Needs confirmation” |
The correction screen has to point at the wrong field and allow saving in place. The target is a median of 2 minutes or less for a merchant to clear one blocked invoice.
Limits
The ticket analysis covered 15 tickets from one month, and the interviews covered 3 merchants, all in Germany.
What I would do differently
I would send merchants a short guide and a rule-change notice before turning the block on. The retrospective traced the avoidable questions to that missing notice.
The 60-second film