Production-style Raast transfer screens — beneficiary management with live IBAN validation, limits-aware amounts, skeleton-loading review — with the core payments logic fully unit tested.
Note This is a clean-room demo reimplementation of patterns from my production work as Feature Lead on a Raast payments module (2025). It contains no proprietary code, assets or data — everything here was written from scratch with mock data. Screenshots are from the original app design.
![]() |
![]() |
![]() |
|---|
- Beneficiary list + add-beneficiary — bank picker, IBAN / Raast ID entry with live per-keystroke validation, and a mandatory account-title fetch before a beneficiary can be saved
- Real IBAN validation — full ISO 13616 mod-97 checksum for 24-character PK IBANs, with distinct, user-readable failures (length, country, characters, checksum)
- Title-fetch state machine — the fetch lifecycle (idle → ready → fetching → fetched/failed) is a deterministic state machine, so stale responses and mid-fetch edits are handled by transitions, not view logic
- Transfer amount screen with limits — per-transaction ceiling, daily cap with a usage meter, and balance checks; validation order is part of the tested contract
- Fee calculation & routing — Raast free per SBP policy; IBFT slab (free to Rs 25,000, then Rs 88 + 16% FED) when routing falls back
- Review & confirm with skeleton loading — pulsing skeleton rows while the transfer quote resolves, then explicit confirmation with a submitting state
- Success receipt — reference, channel, fee and settlement details; balance and daily-spend update on completion
RaastTransferTests covers the core logic in isolation (no UI, near-zero latency doubles):
| Suite | What it proves |
|---|---|
IBANValidatorTests |
mod-97 checksum, normalization, typo/transposition detection, bank-code extraction, Raast ID rules |
TitleFetchStateMachineTests |
fetch only from a validated input, stale responses dropped, mid-fetch edits invalidate the request |
AmountRulesTests |
min / per-transaction / daily-cap / balance checks and their ordering; fee slabs and channel routing |
DirectoryServiceTests |
alias resolution (seeded, masked-unknown, unregistered) and transfer submission |
Run with: xcodebuild test -project RaastTransfer.xcodeproj -scheme RaastTransfer -destination 'platform=iOS Simulator,name=iPhone 17'
MVVM with Combine, with the payments rules pulled out of view models into pure, test-first types under Core/. View models are thin adapters: they feed input events into the state machine and delegate every money decision to AmountValidator / FeeCalculator. Services are protocol-backed; the shipped mock directory has configurable latency so the same code path renders loading states in the app and runs instantly in tests.
RaastTransfer/
├── App/
│ └── RaastTransferApp.swift # Entry point + shared services
├── Core/ # Pure, unit-tested payments logic
│ ├── IBANValidator.swift # ISO 13616 mod-97 + alias rules
│ ├── TitleFetchStateMachine.swift # Beneficiary title-fetch lifecycle
│ └── AmountRules.swift # Limits, amount validation, fees
├── Models/
│ └── Models.swift
├── Services/
│ ├── RaastDirectoryService.swift # Protocol + mock directory
│ └── TransferStore.swift # Session state + seed data
├── ViewModels/
│ ├── AddBeneficiaryViewModel.swift
│ └── TransferViewModel.swift # + ReviewViewModel
└── Views/
├── BeneficiaryListView.swift
├── AddBeneficiaryView.swift
├── TransferAmountView.swift
├── ReviewConfirmView.swift # Skeleton-loading states
├── SuccessReceiptView.swift
└── SharedUI.swift
RaastTransferTests/
├── IBANValidatorTests.swift
├── TitleFetchStateMachineTests.swift
├── AmountRulesTests.swift
└── DirectoryServiceTests.swift
Open RaastTransfer.xcodeproj in Xcode 15+ and run. No dependencies, no configuration — all data is mocked locally.
Built by Zaid Mubbasher · Portfolio · LinkedIn


