1QR: stopping a fake QR code before a traveler pays
In Thailand, you pay at most stalls by scanning a printed QR code, without knowing who it pays or what it will really cost. 1QR checks who the code pays before it shows an amount, catches typing mistakes, and shows the full cost in both currencies before you pay.
Client
A client project through Proof Practice, my consultancy
My role
Product designer and design technologist. I led research and product design, and built the live prototypes
Team
Me and three developers, reporting to a product owner
Status
Handed off for development, now being rolled out across Southeast Asia
Why the app looks different
1QR is used outdoors, so it runs on the two outdoor themes from the Wolf-Rayet design system instead of the usual light and dark. Switch between them with the sun and moon button in the app.
Outdoor light · field-day
For direct sun. Near black on near white, with no gray text, so it stays readable in glare.
Outdoor dark · field-night
For night markets. One dim, warm color, so the screen doesn't spoil your night vision. Each state is named in words, not told apart by red and green.
Live app: 1QR. Try scanning a code and paying for lunch.
The price you agree is not the price you pay
A som tam at a Chatuchak stall costs 420 baht. That is the price the traveler agrees to.
What leaves their account is more: the card's exchange margin, a foreign transaction fee, and sometimes an extra charge for converting to dollars at the terminal. None of it shows up until a statement days later, as a single line with no rate. By then, it's too late to choose differently.
Three ways paying abroad goes wrong
When travelers talk about being caught out, they describe three different problems. Each one needs its own fix.
A fake code stuck over the real one
A QR code is printed on paper, so anyone can stick their own on top. The money goes to their personal account. Normal payment apps never show who the account belongs to, so nothing catches it.
An extra zero
Most stall codes don't include a price, so the traveler types it. In a hurry, ฿420 becomes ฿4,200, and every app will send it.
The "pay in your own currency" question
Card terminals offer to charge you in your own currency. It sounds helpful, but it adds an extra fee on top of what your card already charges. You have to know that to say no.
What the design had to work within
The screens are the easy part. Three of their promises depend on systems outside the app, and the app itself is used outdoors.
Matching accounts to businesses
Checking a code against the shop needs a link from bank account to registered business. The data exists in Thailand, but joining it needs partners.
A real exchange rate
The app promises the mid-market rate with no hidden margin, so its only income is the fee shown on screen.
A payment that can't be sent twice
The app says so on screen, but the payment network underneath, which 1QR doesn't own, has to guarantee it.
Direct sun and night markets
Small gray text was readable at a desk and unreadable in a sunny market.
Three decisions that shaped it
Each one traded something away to catch a mistake before the money moves.
Show who you are paying before the amount
What I chose
Check the account behind the code against the registered business first.
What else I considered
Showing the amount right after the scan, like other payment apps.
Why
Once an amount is on screen, people check the price, not the shop. That is why the fake sticker works.
What I gave up
One extra screen and about a second and a half on every payment.
Catch the extra zero without a fixed limit
What I chose
Flag an amount far above what this stall usually takes, and offer the corrected amount in one tap.
What else I considered
A simple spending limit.
Why
฿4,200 is a normal dinner somewhere else. A fixed limit would block real payments and teach people to ignore it.
What I gave up
Simplicity. The check depends on each stall's payment history.
Outdoor themes instead of light and dark
What I chose
The Wolf-Rayet outdoor themes: near black on white for sun, one dim warm color for night.
What else I considered
The usual light and dark modes.
Why
Those failed in glare, and a bright screen at night spoils your eyes' adjustment to the dark.
What I gave up
Red and green at night. Every state is named in words instead.
Screen 1 · Home
Four things you can read while walking, and one button.
Your balance is shown in baht, with dollars and the exchange rate underneath. Today's spending is compared with a daily budget you set, but it never blocks a payment. The rate shows how old it is, in seconds. Scan to pay is the biggest button in the app.
Bangkok · Thailand · day 4 of 9
Home
Screen 2 · Scan
The camera screen shows the app finding out who the code pays.
While it works, the status line says what it is doing: reading the code, looking up the account, then showing the account's registered name. No amount appears yet.
Point at any PromptPay, Thai QR or 1QR code.
1QR reads who it pays before you see a number.
What the status line says, in order
The viewfinder
Screen 3 · Check the shop
Two names side by side, and a plain sentence saying whether they match.
One name is the account the code pays. The other is the business registered at this stall. Below them are three checks: whether it is a business or a personal account, how other travelers' payments to it went, and whether you are standing where the business is registered. A code someone photographed and sent you fails that last check.
You can report a code here, before any money moves.
This code pays Som Tam Nua.
The merchant check
Screen 4 · A fake code
When the names don't match, the payment stops. There is no amount and no pay button.
The screen says exactly what is wrong: the shop is a company registered since March 2024, but this code pays a personal account opened six days ago at a different bank. It also shows that nine other travelers reported codes paying the same account this week.
It ends with what to do next: ask the vendor to show their own code on their phone, or pay cash. If stopping a payment left people unable to buy lunch, they would learn to ignore it.
Do not pay this code.
The account behind this sticker is not the shop you are standing in.
The stop
Screen 5 · Enter the amount
A full keypad, and a check that catches an extra zero.
The check isn't a simple limit, because ฿4,200 is a normal dinner somewhere else. It looks for an amount well above what this stall usually takes, where a tenth of it would be normal. When that happens, the app says so and offers the corrected amount in one tap. The keypad never moves, so a warning can't push it off the screen.
The keypad guard
Screen 6 · The full cost
The screen the app exists for: the total in both currencies, every fee, and what your card would have charged.
Baht is the big number, because it is the price the vendor said. Dollars show what will actually leave your account. The rate is held for 60 seconds with a visible countdown. When it runs out, the button changes to Get a fresh rate instead of paying at an old rate.
Below that, every fee is listed with the reason for it. Then the comparison: the same lunch costs $11.90 on your usual card, $12.56 if you accept the terminal's currency offer, and $11.50 with 1QR.
You pay, all in
Every part of it
What the card in your pocket would do
The total
Screen 7 · Pay, and losing signal
You pay by holding the button for just under a second, so it can't happen by accident in a pocket.
If the signal drops after the payment is sent, the app doesn't just spin. It says the payment was sent but not yet confirmed, that it can't be sent twice, and gives a reference the vendor's bank will show. If it failed, the money comes back within the hour.
1QR won't start a payment with no signal, and the button says so in words. Otherwise you would be left at the stall not knowing whether you had paid.
The signal went, mid-payment.
This payment is sent and unconfirmed. It is not lost and it cannot be sent twice.
Show the vendor this reference. It is the same one their bank will see.
In flight
Screen 8 · The receipt the vendor can read
A payment isn't finished until the vendor believes it went through.
People turn their phone around to show the vendor, who then has to read English. Show the vendor switches to a confirmation in Thai, with the amount in the largest text in the app, the stall's name, the time and the bank reference.
Your own receipt is separate. It keeps the rate, both fees, the shop's tax ID and the bank's confirmation, and it works offline.
Som Tam Nua · Stall 14
The vendor view
Every number in the app is calculated
Nothing on these screens is typed in by hand. Two values drive all of it: the amount and the exchange rate.
The fees, the dollar total, the card comparison, a split bill, the trip total and the receipt are all worked out from those two. Type a different amount and everything updates, even on screens you haven't opened yet.
That was the only way to check the design holds up at any amount. For example, the 1QR fee goes from free to 0.35% at ฿1,000, and the card comparison gets closer on small amounts.
The outdoor themes, in detail
An earlier version used small gray text on a dark background, and it failed in the sun. So 1QR now uses Wolf-Rayet's outdoor themes, which the system builds for exactly this.
Two typefaces
Archivo for headings, figures and labels. Atkinson Hyperlegible Next for sentences, a typeface designed so similar letters and numbers are hard to confuse. These are the fonts Wolf-Rayet assigns to its outdoor themes.
No gray text in daylight
In the light theme, body text is near black, and labels and supporting text stay dark enough to read in sun. Size and weight show what matters most, not lighter colors, because light gray disappears in sun.
Four colors, each with one meaning
In the light theme, teal is the next step and the only color a button gets. Green means verified or done. Amber means check this before you pay. Red stops a payment.
Every state also has a word. That is what lets the dark theme drop those colors.
The night theme
After dark, a bright screen ruins the eyes' adjustment to the dark. The night theme keeps everything dim and in one warm color, including the buttons and the status colors.
So verified and stop are no longer green and red. They are told apart by their words, which every state in 1QR already has.
What success means: a traveler who never pays a fake code
Nothing was measured while I was on the project. My role ended at handoff, and development is still going on as 1QR is rolled out across Southeast Asia.
Success is a traveler who never pays a fake code. The signs to watch once it is live: fake codes stopped before any money moves, corrected amounts accepted at the keypad, and travelers who come back to pay with it a second time.
Research, the product design, and live prototypes with every number calculated, so engineers could check any amount on any screen.
Into development with the client's engineers, now being rolled out across Southeast Asia.
My part ended at handoff, before the app was live.
How the focus shifted from fees to fraud
1QR started as a way to show the true cost and beat the card. But saving a dollar a day isn't enough to make someone install an app.
Showing who you pay turned out to matter more. Clear pricing is how the app behaves. Catching the fake code is the reason to download it.
More case studies
Three more about seeing the real number before you commit: a home offer signed at the property, a bill the agent holds because the bank details changed, and a savings estimate with its assumptions shown.