Validate at the boundary, not the button
QR payloads, MyKad numbers, phone numbers, and per-bank account formats are all checked before the user reaches a confirmation screen. By the time money is on screen, the inputs are already known-good.
Case · Mobile · Payments
A DuitNow payments client built like money matters: real bank-QR parsing, integer-cent money, and biometrics before every debit.
Payments are unforgiving: a mis-parsed QR, a rounding error, or a debit without consent is a real-world failure, not a bug ticket.
Sole Flutter engineer
A working client in one night — a real CIMB production QR parsed and checksum-verified, integer-cent money end to end, biometrics before every debit, 71 tests.
DuitNow QR and DuitNow Transfer are Malaysia's instant-payment rails, and they punish guesswork: a QR payload has to satisfy EMVCo's tag-length-value structure and a CRC checksum, money has to reconcile to the cent, and no debit should ever happen without the payer's consent. I built a working Flutter client in a single overnight push to hold exactly that bar — not to mock a happy path, but to treat money and identity the way a real wallet has to.
QR payloads, MyKad numbers, phone numbers, and per-bank account formats are all checked before the user reaches a confirmation screen. By the time money is on screen, the inputs are already known-good.
Every fallible path returns a sealed Result / Failure (Freezed) instead of throwing. There is no exception-driven control flow and no untyped `as` casts in the hand-written code (generated Freezed and JSON files excepted), so the compiler forces every error to be handled.
Amounts are integer cents from the moment a value is parsed to the moment it is displayed. Floating-point never touches currency, so totals can't drift by a rounding error.
Biometrics gate every debit, the payment intent travels through a provider rather than route params, and user-facing errors stay deliberately non-revealing — the precise failure reason lives in typed failures and tests, never leaked to the UI.
/// Every EMVCo QR payload ends with a CRC16-CCITT-FALSE checksum
/// over the preceding bytes, including the "6304" tag and length.
/// We recompute it and refuse to parse a code whose checksum is wrong.
int crc16(List<int> data) {
var crc = 0xFFFF; // CCITT-FALSE initial value
for (final byte in data) {
crc ^= byte << 8; // fold next byte into the high byte
for (var i = 0; i < 8; i++) { // then process it one bit at a time
crc = (crc & 0x8000) != 0 // is the top bit set?
? (crc << 1) ^ 0x1021 // yes: shift out, then XOR the polynomial
: crc << 1; // no: just shift
crc &= 0xFFFF; // stay 16-bit (Dart ints are 64-bit)
}
}
return crc;
}
A test-backed DuitNow client built to be checked, not trusted: a real CIMB production QR parsed and checksum-verified, integer-cent math end to end, typed failures on every fallible path, and biometrics before any debit. Built in one overnight push — honest about what's mocked, rigorous about what isn't.