Skip to content

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.

YAML
# 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.

StageCheck we runGateEvidence kept
StoryChecklist review in refinementA story with a blank item does not enter the sprintAnswered checklist in the ticket
Pull requestUnit tests for each recorded rounding rule and minor unit countMerge blocked on a failed testPull request check results
Pull requestStatic rule that rejects float types on money fieldsMerge blocked on a matchStatic analysis report
Pull requestReviewer confirms an audit event exists for each new stateApproval withheld without the eventReview comment linked to the ticket
Continuous integration (CI)Exact replay, altered-body replay, and parallel requests with one keyBuild fails on a second ledger postingCI run record per commit
CIPartial and full reversal up to the recorded limit, then one unit above itBuild fails if the over-limit reversal postsCI run record per commit
CIContract tests on partner request and response payloadsBuild fails on a contract breakContract verification result
StagingEach recorded failure state forced once against processor and bank simulatorsRelease candidate held on an unhandled stateFailure state run log
StagingReconciliation of the staging run against a synthetic partner settlement fileRelease candidate held on any unexplained breakReconciliation report
StagingAudit log read back for every state change in the runRelease candidate held on a missing fieldAudit 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.

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