Payment Flow — Case Study · Mirsha Balderas
01Role & Scope

A reality check,
not a spec.

After v0.1 mapped the ideal vendor payment lifecycle, engineering flagged a hard constraint: when a payment goes on hold, the reason lives in three disconnected backend systems that don’t share a format. A unified, machine-readable reason code — the thing that makes the perfect Hold state possible — is a ~3-month reconciliation project.

Rather than wait a quarter, v0.2 designs around the wall. It separates the expensive part (a precise reason with a one-click fix) from a cheap question hiding underneath: even without a clean reason, do we already know which of the three systems raised the hold? IIf yes, we can already answer some of the most important questions around the Hold state: who is handling it, whether action is required, and what happens next.

  • Feasibility Review Design partner
  • State Mapping Lifecycle
  • Designing Around Limits Backend reality
  • Phased Delivery Now → later
  • Trust & Guardrails Payments
02Primary Path

The happy path is unchanged.

The constraint only touches the Hold state. From approved work to paid, the lifecycle is unaffected — so the redesign leaves it exactly where it was.

Approved work → paid ↓

Entry
Work Approved
“Your task passed review.”
system queues
State 1
Payment Queued
“Scheduled — next payout DD Mon.”
payout runs
State 2
Processing
“Payment on its way.”
provider confirms
Terminal ✓
Paid
“€X paid to ····1234. Receipt →”
03The Constraint

Why “show the reason” isn’t free.

Any of three systems can independently put a payment on hold — and they don’t share a reason format. Today all three collapse into one generic “On Hold”.

Three systems, three formats, no shared reason code ↓

Backend A
Tax / Compliance
Missing tax form, verification, sanctions/KYC check. Its own status codes.
Backend B
Banking / Provider
Invalid bank details, failed transfer, currency/threshold rules. Provider-specific errors.
Backend C
Ops / Work Approval
Quality dispute, manual review, contract issue. Internal ticket references.
no shared reason code
→ one generic “On Hold”

The expensive part is a unified reason code with a specific fix (all three systems reconciled — the ~3-month project). But there’s a cheap question hiding underneath: even without a clean reason, do we already know which system raised the hold? If yes, we can ship a more informative Hold state without waiting for full reconciliation.

04The Hold State, Phased

Design around the wall,
don’t wait for it.

Same component, two levels of detail. Phase 0 ships on a single flag; Phase 1 layers the precise reason on top once the backend work lands, using the same component structure to minimise redesign.

Phase 0 — ship now~weeks

Depends only on knowing which system flagged the hold.

Vendor sees
Payment on hold
  • With: our Tax & Compliance team
  • “Action required: Yes
  • Next step: Check your compliance requirements
  • Next update: within 3 business days
↳ or: “No action needed — we’ll update you by DD Mon”
Kills most of the anxiety. Gives the vendor more context without requiring the exact reason: who owns the hold, whether action is required, and when to expect an update.
Phase 1 — targetafter ~3-mo project

Depends on the unified reason service reconciling A + B + C.

Vendor sees
On hold — specific reason
  • Reason: W-8BEN tax form expired on 12 Mar
  • Fix: Re-submit form → auto-releases on validation
  • Est. release: ~24h after re-submission
  • Status: live, no waiting for a human
↳ enables self-service resolution where the backend supports it
The ideal. Precise reason + one-click fix + live status — earned by the backend work, not before it.
05Sequencing

So nobody’s blocked
waiting on the 3 months.

Ship Phase 0, measure its effect, and use the evidence to decide whether the reconciliation project earns the roadmap.

Now → Weeks
Ship Phase 0 hold state
Route by system + action-needed + next-update. Instrument it: Tag payment-hold tickets so we can measure whether support contact changes after launch.
In Parallel
Quantify & lobby
Use ticket data to size the impact and help Product decide if the reconciliation project deserves the roadmap.
~3 Months
Layer in Phase 1
When the unified reason service lands, upgrade the same state to specific reason + one-click fix, building on the same Hold-state structure.
06Cheap vs. Expensive

What’s cheap,
what’s a project.

The split that lets engineering make the call — Several useful vendor-facing signals sit in the lower-cost rows.

Capability Depends on Cost Ships
Which system holds it A/B/C already expose some flag cheap Phase 0
Action-needed? Y/N One boolean per system cheap Phase 0
“Next update by” date SLA per hold type (a rule) medium Phase 0
Specific reason code Reconciling A + B + C 3-mo Phase 1
One-click auto-release Reason service + write-back 3-mo Phase 1

The one question that decides Phase 0: can each system already tell us that it’s holding a payment — even if not cleanly why? That flag is the key dependency that makes a smaller Phase 0 possible.

07The Bet

Reduce uncertainty now,
layer in precision later.

3
Disconnected hold systems
1
Key signal enables Phase 0
~3mo
Estimated reconciliation project
2
Delivery Phases
Why phase it instead of waiting. The perfect Hold state needs three months of backend work. The target Hold state depends on backend reconciliation. A smaller Phase 0 depends on fewer signals and can be delivered earlier. It gives vendors clearer information while giving Product a measurable first step before committing to the larger backend investment.
⚠ Guardrail — where I would NOT move fast This is a payment flow, so Phase 0 only shows what we can stand behind. A vague-but-true “with our Tax team, no action needed yet” is safe. A specific-but-guessed reason is not — in payments, a confident wrong answer is worse than an honest vague one.
// Keep exploring Next Case