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.
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.
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.
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.
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.
Bangkok · Thailand · day 4 of 9
Scan anything. Pay the real number.
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.
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
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.
This code pays Som Tam Nua.
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 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.
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
Every part of it
What the card in your pocket would do
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.
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.
Som Tam Nua · Stall 14
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.
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.
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.
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 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.