Trust at the point of decision · Bangkok corridor

1QR: a scan-to-pay app that shows a traveller the real cost

A scan-to-pay app for the ninety seconds a traveller spends in front of a laminated QR code, holding a phone, with a vendor waiting and no idea what the number means.

Paying abroad is mostly guesswork. A code, an amount you half trust, and a real cost you learn four days later from a statement line that reads POS BANGKOK TH 11.90, after the terminal's conversion screen and the issuer's margin have done their quiet work. 1QR inverts the order of operations. It resolves who the code pays before it will show you an amount, so the sticker pasted over a stall's real code is caught by the app rather than by the tourist. It guards the extra zero on the keypad. It states the total in baht as the big number with your own currency beside it, every fee itemised at the mid-market rate, and it names what the card in your pocket would have charged for the same lunch. Payment is a hold, not a tap. And when it lands, the phone turns around: a confirmation in Thai, at the largest size in the app, that the vendor can read from their side of the counter.

1QR · live app Open standalone

Live app: 1QR, scan to pay abroad.

The problem: the price agreed and the amount charged are not the same

Nobody is confused about the food. They are confused about the money, and the confusion is engineered: every party in the chain is paid a little more when the traveller cannot see the arithmetic at the moment of the decision.

A som tam at a Chatuchak stall costs 420 baht. The traveller agrees to 420 baht. What actually leaves the account is 420 baht plus an issuer margin of around 3%, plus a foreign transaction fee, and, if the terminal offered to be helpful and convert to dollars, another 5.5% for the favour. None of it is hidden exactly. It is just disclosed in a place and at a time where it cannot be acted on: on a statement, four days later, as a single line with no rate in it. The gap between the agreed number and the paid number is small enough to shrug at once and large enough to matter across nine days, and it is invisible precisely when a decision could still be made.

The three failures travellers describe

The research that shaped this was not about payment rails. It was about what travellers describe when they talk about being caught out, and the three failures they name are not the same problem in three costumes.

Fraud
The sticker over the code

A printed QR is a piece of paper. Anyone can lay their own over it, and the money goes to a personal account opened last week. The traveller sees the same paper, the same stall, the same amount. Nothing in the flow of a normal payment app ever names the account being paid, so nothing catches this except suspicion.

Slip
The extra zero

Most stall codes carry no amount, so the traveller types it. In a currency where lunch is a four-figure-looking number, a keypad and a hurry produce ฿4,200 for a ฿420 plate. Every app on the market will happily send it. The mistake is not exotic; it is the single most expensive piece of typing in travel.

Margin
The helpful conversion screen

“Would you like to pay in your own currency?” is the only question in the transaction that sounds like a courtesy and behaves like a fee. Saying yes costs about 5.5% on top of an issuer margin already taken. Declining it correctly requires knowing what it is, which is not a reasonable thing to require of someone holding a plate.

The core decision: resolve who is paid before how much

Resolve who before how much. Everything else in 1QR is a consequence of that ordering.

Every scan-to-pay app puts the amount on the first screen after the scan, because the amount is what the user came for. That order is the reason the swapped-sticker scam works: once a number is on screen, the question in the user's head is “is that the right price?” and never “is that the right shop?” 1QR shows the merchant first, and will not draw an amount until the account name behind the code has been checked against the registered business. It costs one screen and about a second and a half. In exchange, the most common street fraud in the corridor becomes a thing the software catches instead of a thing the traveller has to be clever about.

The rest follows. If the app is willing to spend a screen on who is being paid, it can spend one on what is being typed, and one on where every satang of the total goes. And if it is going to claim the total is honest, it has to be willing to say what the alternative would have cost. That is why the comparison against the card in your pocket sits on the payment screen, before the decision, rather than in a marketing page afterwards.

Home · where you are, what is left, what today cost

The resting state of a travel wallet is not a dashboard. It is four facts and one button, sized so they can be read while walking.

The balance is stated in baht, because baht is what the next hour will be priced in, with the dollar equivalent underneath and the rate that produced it named on the same card. Today's spend is measured against a daily plan the traveller set, and it is advisory: it colours and counts, it never blocks. The rate card ticks its own age in seconds, because a quote that says “updated 12 seconds ago” forever is a screenshot, not a quote. Everything else on the screen is a receipt already earned. The one filled control on the page is Scan to pay, and it is the largest touch target in the app.

19:42

Bangkok · Thailand · day 4 of 9

Scan anything. Pay the real number.

Spending balance Mid-market
฿8,420.00
≈ $230.56 USD · converted at 36.52, no markup
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

Scan · a viewfinder that resolves the account behind the code

The scan screen is the only surface in the app that is not on the app's own ground. It is a camera, so it is dark in both themes, and the chrome floats on it.

What matters here is what the status line says while it works. It does not say “reading…” and then jump to a price. It says Reading code, then PromptPay code · resolving the account, then the account's registered name in capitals. The traveller watches the app find out who it is about to pay, which is the fact that will matter in a second and a half, and the amount is not part of the sequence at all.

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

Merchant · checking the account name against the shop in front of you

Two names, side by side, and a plain sentence about whether they match. That is the entire mechanism, and it is the most valuable screen in the product.

The code carries a bank account. The registry carries a business. 1QR states both, unabbreviated, with the bank and the last four digits, and then says in words whether they are the same party. Underneath, three checks that each name a different way a code can be wrong: whether the account is a company or a personal one, how many travellers have paid it and with what outcome, and whether the phone is standing where the business is registered. The last one matters more than it looks: a code photographed and forwarded to you fails it outright.

And a fourth row that is not a check at all. Report this code is available before any money moves, and the copy says so, because a user who suspects something is being asked to act at exactly the moment they are least sure and most rushed.

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 travellers 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

Swapped code · the stop, and what to do instead

When the two names disagree, the flow ends. There is no amount, no confirm button, and nothing to be talked past.

The stop screen states the disagreement rather than a security abstraction: the shop is a company registered here since March 2024, and this code pays a personal account at a different bank opened six days ago. Both facts are on screen at once, because that pair is the argument and no summary of it is as convincing. The reasons underneath are specific and checkable, including that nine other travellers reported codes paying the same account this week.

The screen ends with a next step, not a warning. Ask the vendor to open their own PromptPay code on their phone and scan that instead, or pay cash. A stop that leaves a hungry person with no way to buy lunch is a stop they will learn to click past, so the design's job is to make refusing this payment cost the traveller nothing.

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 travellers 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

Amount · a keypad, and a guard on the shape of a typo

Most stall codes carry no amount, so typing one is a first-class screen with 46px keys, not a text field squeezed onto another screen.

The interesting part is the guard, and what it is measured against. A flat threshold would be wrong: ฿4,200 is an ordinary dinner somewhere in this city and an absurd one at this stall. So the check is not “is this large” but “is this the shape of an extra zero”: an amount well past what this merchant usually takes, whose tenth lands inside the band people actually pay here. When both are true the app names the specific error, states the band, and offers the corrected figure as one tap.

When the guard appears, the figure it is objecting to shrinks to make room for it. The keypad never moves: it lives in the footer with the action, so no warning can push a digit key off the bottom of the screen at the moment the user most needs to retype.

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

The total · both currencies, every fee, and the counterfactual

This is the screen the product exists to show, so it is set like an argument rather than a summary.

Baht is the big number, because baht is what the vendor said and what the receipt will say. The dollar figure underneath is labelled as what actually leaves the account, not as a decoration. The rate is held for sixty seconds and the chip counts it down for real. When it reaches zero the pay button changes to Get a fresh rate rather than quietly paying at a rate that is no longer the one shown. A held quote that stops being held without saying so is worse than no lock at all.

Then the ledger: what the merchant receives, the rate that produced it, the 1QR fee and the network fee, each with the reason it is what it is. Below that, the part most products would never print. The same lunch on the Visa in your pocket is $11.90. The same lunch if the terminal offers to convert and you accept is $12.56. You are paying $11.50. The figure the screen ends on is what you keep, stated against both, before you decide, not in a summary email afterwards.

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

Confirm · a hold to pay, and losing signal mid payment

Paying is a hold, not a tap. Nine hundred milliseconds: long enough that it cannot happen in a pocket, short enough that it does not feel like a punishment.

Then the screen nobody wants to design. The market is underground, the signal drops between sent and settled, and the traveller is holding a phone that is spinning while a vendor waits. Most apps spin. 1QR lands on one definite state and names it: this payment is sent and unconfirmed, it is not lost, it cannot be sent twice, here is the reference the merchant's own bank will show, and if it failed the money returns in full within the hour.

The corollary is a refusal. 1QR will not start a payment with no signal, and the pay button says so in words rather than greying out. A queued payment is a payment whose outcome the traveller cannot know while standing at the stall, which is the exact condition this product exists to remove. Offline, the honest answer is that you cannot pay yet.

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

Paid · the vendor's half of the receipt

A payment abroad is not finished when the money moves. It is finished when the person on the other side of the counter believes it moved.

Every payment app ends with a receipt written for the payer, in the payer's language, at the payer's reading distance. Then the traveller turns the phone around anyway, and the vendor squints at a screen of English they cannot read, looking for a number and a tick. So 1QR builds that view on purpose. Show the vendor flips the phone into a full-bleed confirmation: a mark first, Thai first, the figure at the largest type size anywhere in the app, the stall's name in Thai script, and the time, reference and bank in a block sized to be read at arm's length across a counter. There are no controls on it except the way back.

The payer's own receipt is a separate, durable thing. It records the rate that was used, the window it was held in, both fee lines, the merchant's tax ID and the bank's confirmation and it is stored on the phone, readable offline, because a receipt that needs 1QR to still exist in order to be worth something is not proof of anything.

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

The vendor view

One amount, one rate, every screen

An app that claims the arithmetic is shown cannot demonstrate itself with hard-coded numbers. So there are none.

Two values are entered: the amount and the mid-market rate. Everything else on every screen is a function of them, computed at paint time: the 1QR fee and the rule that produced it, the dollar estimate, the funding-method surcharge, the card and conversion-screen comparisons, what you keep against each, the per-person share when the bill is split, the running trip total, the hold button's label, and the receipt. Type a different number on the keypad and all of them move together, including the ones two screens away.

That is not showmanship. It is the only way to find out whether the argument holds at every amount rather than at the one the mock was drawn for. The 1QR fee crosses from free to 0.35% at ฿1,000, and the fee row's own explanation changes with it; the card comparison narrows on small amounts, which is worth seeing rather than hiding. An app that can only be honest at ฿420 is a picture.

Type and colour, set for reading in daylight

The previous build of this app set reading text at 12.5px in a mid grey on a near-black ground. It was legible at a desk and gone in a market, which is the only place it is ever used.

Faces
Two faces, one job each

Signika Negative carries every heading, figure and label. It is a humanist face with a tall x-height and open apertures, drawn to be read at a glance. Inter carries running text. Both come from the Wolf-Rayet system this site already ships, self-hosted, so nothing is fetched at render time.

Base size17pxup from 15
Floor12.5pxnothing in the app is smaller
Reading text500 and upno light weights anywhere
Ink
Three inks, and only one of them is for sentences

Reading text is the darkest ink on the card. The second is for secondary sentences and is the lightest any sentence is allowed to be. The third is reserved for uppercase field labels, where weight and tracking separate them from body copy instead of lightness doing the work.

Body on the card12.6 : 1
Secondary7.0 : 1
Uppercase labels5.3 : 1
Colour
Four meanings, no decoration

Teal is the path forward and the only fill an action ever gets, so no screen has two things claiming to be the next step. Green is verified, settled, money kept. Amber is check this before you pay, and is never blocking. Red stops a payment. Nothing is coloured for looks, and no state is signalled by colour alone. Every one of them carries a word.

The consequence is testable: read the app in greyscale and every state is still named.

Dark
Dark is a palette, not a filter

Dark mode reads Wolf-Rayet's interior-dark role set, the same one this site's own dark mode runs on, rather than a second palette invented for the app. Its ink ladder is compressed near white, three near-white steps instead of a fan down into grey, and its status colours all sit at one emission level rather than being daylight greens and ambers reused on a near-black ground.

The palette's accents sit at hue 250 and this product's accent is teal, so the brand is derived on the palette's own ladder at that same emission level, holding 1QR's hue. Level from the palette, hue from the product. It is the same operation KeySign uses for its brass lever.

What would have to be true for this to ship

The interface design is the easy half. Three of the claims on these screens are promises about infrastructure, and a case study that does not name them is selling a mock.

The registry has to exist. “This code pays SOM TAM NUA CO LTD and the shop is registered as Som Tam Nua Co Ltd” requires a resolvable map from bank account to registered business, per corridor. In Thailand the pieces are there (PromptPay proxies, company registration, tax IDs) but joining them is a partnership problem before it is a design one, and the merchant screen degrades honestly without it: it can still name the account, and say plainly that it could not verify the business.

The mid-market rate has to be real. The product's whole claim is that there is no spread. That means the margin is the fee line the user can see, and 1QR's revenue has to survive being printed on the screen where the decision is made. If the honest number cannot carry the business, this design is the wrong one and no amount of typography fixes it.

Idempotency has to be genuine. “It cannot be sent twice” is the strongest sentence in the app and the one most easily written without meaning it. It is a commitment about keys, retries and settlement reconciliation that has to hold on a rail 1QR does not own.

How the positioning moved from fees to fraud

1QR started as a transparency product: show the real total, itemise the fees, beat the card. That is a good pitch and a weak wedge, because a dollar a day is a number travellers shrug at.

What the work found is that resolving who before how much buys something a fee comparison never will. The same second and a half that makes the total honest also catches the swapped sticker, and the swapped sticker is the thing travellers actually talk about. Transparency is the product's ethic; catching the code that pays a personal account opened last week is the reason to install it. The design that serves both is the same design, which is the most useful thing this app has to say.

Explore more work

Three more case studies about showing a person the real number before they commit to it: a home sale closed at the curb, a savings figure with its assumptions on the page, and a voice agent held to a spending limit.

KeySign, closing a home sale in the driveway
A field app for the twenty minutes after a buyer says yes at the curb, from drafted offer to open escrow file. Everything runs locally, so a dead zone is never an error.
How Much Would I Have If, a calculator built to be found
The domain name is the search query, so the product answers it on a statically generated page for every asset and every year. About 1,070 of them, each fast, accessible and honest. Live at howmuchwouldihaveif.com.
Watchtower, supervising a voice agent mid call
Most voice agents ship one human control: a switch to take over the chat. Watchtower puts a supervisor in the beat before the agent acts, with the full picture and a trigger the caller never hears.