A shift left testing approach for fintech: the checklist we run on money-moving stories
A shift left testing approach for fintech moves money checks into the story, before code exists. We review every money-moving story against a nine-item checklist: amounts in minor units, rounding rule, currency, idempotency, failure states, reversal, reconciliation hook, regulatory trigger, and audit log. Pull request, continuous integration, and staging checks then test the recorded answers.
Adrian Voss, Lead QA engineer, payments and acquiringPublished Updated
What shift left means, from the 2001 article to the ISTQB syllabus
Larry Smith named the idea in Dr. Dobb's Journal in September 2001: "Shift-left testing is how I refer to a better way of integrating the quality assurance (QA) and development parts of a software project." His argument was organisational: QA joins development from the specification onward instead of waiting for a finished build.
The ISTQB Foundation Level syllabus v4.0.1, dated 15 September 2024, defines it in section 2.1.5: "Shift left basically suggests that testing should be done earlier (e.g., not waiting for code to be implemented or for components to be integrated), but it does not mean that testing later in the SDLC should be neglected." The first good practice it lists is "Reviewing the specification from the perspective of testers. These review activities on specifications often find potential defects, such as ambiguities, incompleteness, and inconsistencies." The syllabus also states the trade: shift left "might result in extra training, effort and/or costs earlier in the process but is expected to save efforts and/or costs later in the process."
For a fintech team, the specification is the user story. A story that moves money carries decisions that a test cannot recover after release: which unit an amount is in, who gets the remainder, what a retry does, and which rule starts a clock.
Why money-moving stories fail review, with three sourced examples
Minor units differ by currency. The ISO 4217 list published by SIX on 17 September 2026 gives JPY 0 minor units, EUR 2, and BHD 3. A story that says "send the amount in cents" produces a service that multiplies by 100 for every currency, so a JPY 5000 charge leaves as 500000. Our tie and scale tests are in the banker's rounding testing guide.
Idempotency has no finished standard. The IETF draft on the Idempotency-Key header, revision 07 of 15 October 2025, says that on reuse of a key with a different request payload "the resource SHOULD reply with a HTTP 422 status code with body containing a link pointing to relevant documentation." The IETF datatracker showed the draft as expired when we checked it on 22 September 2026; revision 07 expired on 18 April 2026 without becoming an RFC. Duplicate handling is therefore whatever the story decides, and an undecided story ships a duplicate charge. The replay and concurrency tests are in the payment idempotency testing guide.
Failure paths start regulatory clocks. Under 12 CFR 1005.11(c)(1), eCFR current as of 18 September 2026, "A financial institution shall investigate promptly and, except as otherwise provided in this paragraph (c), shall determine whether an error occurred within 10 business days of receiving a notice of error." Paragraph (c)(2) allows up to 45 days if the institution provisionally credits the consumer's account within 10 business days, subject to the conditions in that paragraph. A consumer transfer story without a failure path that feeds error handling leaves that deadline without an owner. Our reading of Regulation E test scope is on the Regulation E page.
The requirements review checklist we use on money-moving stories
This checklist is our practice, not a requirement of any standard. We run it in refinement, before estimation, with product, engineering, and QA present.
# Requirements review checklist for money-moving stories (FintechAssuranceLabs practice)
# Pass rule: every item answered in the story. "n/a" only with a written reason.
checklist:
- id: amount-units
ask: "In which unit does each amount travel and get stored?"
story_must_state: "Integer minor units for every API field, event, and column, plus the ISO 4217 minor unit count of each currency in scope."
fails_when: "Any amount is a decimal or float, or a currency with 0 or 3 minor units is in scope and not named."
- id: rounding-rule
ask: "Where does the flow round, to how many places, and by which rule?"
story_must_state: "Each rounding point, the rule (half up, half even, or down), and which party receives the remainder of a split."
fails_when: "The story says 'round to cents' without a rule or a rounding point."
- id: currency
ask: "Which currencies can this flow carry, and where does conversion happen?"
story_must_state: "The currency list, the rate source with its timestamp, and whether fees apply before or after conversion."
fails_when: "The currency is implied by the account, the card, or the market."
- id: idempotency
ask: "What happens when the same request arrives twice?"
story_must_state: "The key, its scope and retention window, the reply to an exact replay, and the reply to a reused key with a different body."
fails_when: "Retry behaviour is left to the client or to the processor SDK."
- id: failure-states
ask: "Which states can the money end in besides success?"
story_must_state: "Every pending and terminal state, including a timeout after the partner approved, with the balance effect of each state."
fails_when: "The story has only success and a generic error."
- id: reversal
ask: "How is the movement undone, fully and partially?"
story_must_state: "The refund, reversal, or return path, the limit (original amount minus earlier refunds), and who bears fees on the reversal."
fails_when: "Reversal is deferred to a later story that has no ticket."
- id: reconciliation-hook
ask: "Which external record will this movement be matched against?"
story_must_state: "The reference carried end to end, the partner file or report it appears in, and the expected date of that record."
fails_when: "The ledger entry and the partner record share no reference."
- id: regulatory-trigger
ask: "Does any outcome of this flow start a regulatory clock, notice, or disclosure?"
story_must_state: "The rule and section, the event that starts it, and the deadline, or 'none' with the reason."
fails_when: "A consumer transfer failure has no path into error handling."
- id: audit-log
ask: "Which record proves who changed what amount, and when?"
story_must_state: "For each state change: event name, actor, before and after values in minor units, reference, and a UTC timestamp."
fails_when: "The only trace is an application debug log."Per-stage checks from story to pull request to CI to staging
Each checklist answer becomes a test at a later stage. The evidence column is what we keep per release.
| Stage | Check we run | Gate | Evidence kept |
|---|---|---|---|
| Story | Checklist review in refinement | A story with a blank item does not enter the sprint | Answered checklist in the ticket |
| Pull request | Unit tests for each recorded rounding rule and minor unit count | Merge blocked on a failed test | Pull request check results |
| Pull request | Static rule that rejects float types on money fields | Merge blocked on a match | Static analysis report |
| Pull request | Reviewer confirms an audit event exists for each new state | Approval withheld without the event | Review comment linked to the ticket |
| Continuous integration (CI) | Exact replay, altered-body replay, and parallel requests with one key | Build fails on a second ledger posting | CI run record per commit |
| CI | Partial and full reversal up to the recorded limit, then one unit above it | Build fails if the over-limit reversal posts | CI run record per commit |
| CI | Contract tests on partner request and response payloads | Build fails on a contract break | Contract verification result |
| Staging | Each recorded failure state forced once against processor and bank simulators | Release candidate held on an unhandled state | Failure state run log |
| Staging | Reconciliation of the staging run against a synthetic partner settlement file | Release candidate held on any unexplained break | Reconciliation report |
| Staging | Audit log read back for every state change in the run | Release candidate held on a missing field | Audit log extract |
The CI rows are where automated regression suites run on every commit. The staging reconciliation row follows the break-injection method in the ledger reconciliation testing guide.
To start, add the YAML block to the ticket template for any story that creates, changes, or reverses a balance, and review the next sprint's money-moving stories against it before estimation. Each blank item found there is a defect caught before code.
Sources
- Larry Smith, Shift-Left Testing, Dr. Dobb's Journal, September 2001 (archived copy at jacobfilipp.com, capture date not shown, read 2026-09-22),
- ISTQB Certified Tester Foundation Level Syllabus v4.0.1, section 2.1.5 Shift Left,
- SIX, ISO 4217 currency code list one (XML), published 17 September 2026,
- IETF, The Idempotency-Key HTTP Header Field, draft-ietf-httpapi-idempotency-key-header-07,
- eCFR, 12 CFR 1005.11 Procedures for resolving errors (Regulation E), up to date as of 18 September 2026,
Related
Guides
Segments
Scope your testing
Send FintechAssuranceLabs your product, segment, money flows, and the standards that need evidence, and get a proposed test scope with its deliverables.
Scope your testing