Case study · Travel payments · Mobile app

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.

1QR · live app Open standalone

Live app: 1QR. Try scanning a code and paying for lunch.

01 · The problem

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.

02 · What travelers told us

Three ways paying abroad goes wrong

When travelers talk about being caught out, they describe three different problems. Each one needs its own fix.

Fraud
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.

Slip
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.

Margin
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.

03 · Constraints

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.

Partners
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.

Pricing
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.

Payments
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.

Outdoors
Direct sun and night markets

Small gray text was readable at a desk and unreadable in a sunny market.

04 · Decisions

Three decisions that shaped it

Each one traded something away to catch a mistake before the money moves.

Decision 1
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.

Decision 2
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.

Decision 3
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.

19:42

Bangkok · Thailand · day 4 of 9

Spending balance Mid-market
฿8,420.00
≈ $230.56 USD · converted at 36.52
Today On plan
฿1,240.00
≈ $33.95 of your $60 daily plan
Rate right nowupdated 14s ago
1 USD = 36.52 THB
The mid-market rate. It is the one you get.

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.

Home Torch

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

0.0sReading code…
0.7sPromptPay · resolving the account
1.5sSOM TAM NUA CO LTD

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.

Before any number Code verified

This code pays Som Tam Nua.

Code paysSOM TAM NUA CO LTDKasikornbank · ···4471
Registered asSom Tam Nua Co LtdThai tax ID 0105558···891
Names matchYes · exactChecked just now
The account name matches the shop
The most common street scam is a sticker pasted over the real code. This is the check that catches it.
1,204 travelers have paid here
Since March 2024. No disputes, no chargebacks, no reports.
You are 40 m from the registered address
A code photographed somewhere else and sent to you would fail this.
Something still feel wrong?
Report this code. Reporting never sends money.

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 shopSom Tam Nua Co LtdRegistered here since March 2024
This code paysP. THANAKORN (personal)Bangkok Bank ···0912 · opened 6 days ago
A personal account, not a business one
Registered stalls are paid into a company account. This one is not.
Opened six days ago
Nine other travelers have reported codes paying this account this week.
What to do now
Ask the vendor to open their own PromptPay code on their phone, and scan that instead. Or pay cash.

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.

Amount Som Tam Nua
฿4,200.00
≈ $115.01 USD
฿4,200 is ten times the usual here.
People pay ฿80 to ฿520 at this stall.
123 456 789 C0

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

฿420.00
≈ $11.50 USDThe amount that leaves your account
Rate held 0:56

Every part of it

To the merchant
Arrives in baht, in about a second
฿420.00≈ $11.50
Exchange rate
Mid-market. No spread, no markup.
36.52THB per USD
1QR fee
Free under ฿1,000
฿0.00
Network fee
PromptPay rail · 1QR covers it
฿0.00
Total
฿420.00≈ $11.50

What the card in your pocket would do

Visa ···4418, in baht
3.0% issuer FX + 0.5% foreign transaction
$11.90
Same card, if the terminal asks "pay in USD?"
Dynamic currency conversion, about 5.5% on top
$12.56
You keep
against the conversion screen · $0.40 against the card alone
$1.06

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.

Reference1QR-8F42-C019
StateSent · awaiting the bank
If it failedReturned in fullAutomatically, within 60 minutes

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.

จ่ายเงินแล้ว
PAYMENT COMPLETE
฿420.00
ส้มตำนัว · ซุ้ม 14
Som Tam Nua · Stall 14
19:42:07 · 4 ก.ย. 2569
1QR-8F42-C019
ธนาคารกสิกรไทย ···4471

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.

Faces
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.

Base size17pxup from 15
Floor12.5pxnothing in the app is smaller
Reading text500 and upno light weights anywhere
Ink
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.

Body on the card12.6 : 1
Secondary7.0 : 1
Uppercase labels5.3 : 1
Color
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.

Dark
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.

05 · Outcome

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.

Handed off

Research, the product design, and live prototypes with every number calculated, so engineers could check any amount on any screen.

Where it went

Into development with the client's engineers, now being rolled out across Southeast Asia.

No numbers yet

My part ended at handoff, before the app was live.

06 · Reflection

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.

KeySign, signing a home offer at the property
Buyers decide right after a showing, but offer tools need a desk and every state has its own forms. KeySign lets the agent write, sign and send a compliant offer from their phone at the property, then open escrow.
Allowance, an agent that pays the bills inside rules you set
A business banking app for the owner's phone, with a desktop for accounts payable. An AI agent pays a company's routine vendor bills, stops and asks when the bank details are new or the amount is far above the usual, and leaves a day to stop any payment before it goes.
How Much Would I Have If, an investment calculator
Answers "how much would I have if I had invested in Apple in 2010" with a page for every asset and year, about 1,070 in all, on real market data with charts anyone can use. Built solo, live at howmuchwouldihaveif.com.