SheetOpen · BPO Collections
ScopeSagePay to Fiserv, end to end, solo
ConstraintPCI-DSS · live money throughout
Cutovers0 failed
Case study · BPO Collections · FCA regulated
A payment processor migration six people had already started.
I was hired to make a debt collection website accessible, which is a reasonable brief. BPO Collections is FCA regulated and every single person using that site is under financial pressure of one sort or another, so a payment page that locks somebody out isn't a design problem and never was, it's the business failing at the only moment that counts.
Then they asked whether I'd take the payment migration instead. SagePay to Fiserv, on a vendor toolkit old enough to vote, and roughly half a dozen developers had already had a go at it. Not one attempt had ever reached testing. The project changed from the one job to the other.
- SagePay to Fiserv
- Six developers before me
- Never reached testing
- One person, solo
- 80%More mobile payments
- 70%Of those on Apple Pay
- 2,046Lines of C# written
- 0Failed cutovers
Six people had tried it. They asked me. I said yes.
Sheet01 · What was there
Groundbone
EvidenceWhy it stalls
Sourcesrc/data/bpo.ts
01 · What was there
A VB.NET Web Forms estate with a payment layer nobody wanted to touch
299 VB source files, 101 Web Forms pages, 131 SQL scripts, and a payment integration on a vendor toolkit that had stopped being current long before anybody stopped depending on it.
Nobody writes this stack new any more, that's a very different thing from nobody running it. It takes money, it's regulated, and it works, which between the three of them is exactly why it had survived years past the point anybody wanted to go near it. Working software is hard to argue with. Even when you would like to.
The previous attempts are all still sitting in the repository, and that isn't a dig at anybody who made them, because a payment migration is about the worst thing you can hand somebody part time. You can't ship it in pieces. You can't test it properly without the live gateway, and the only way to know you're finished is to take real money off a real card. Six half-migrations is what that gets you.
I hadn't written C# since about 2016. Picked it back up anyway, because the alternative was bolting a modern gateway onto Web Forms with the seams showing, and on a payment page the seams are where the money leaks out.
Sheet02 · What I built
Grounddeeper
EvidenceThe retry that costs money
Sourcesrc/data/bpo.ts
02 · What I built
The processor as a library, not as pages
2,046 lines of C# across a request signing layer, eight gateway operations and twenty request and response types, compiled to a DLL the VB estate calls into.
Keeping it out of the Web Forms pages was the first decision and the one everything else hung off. A gateway call buried in a page lifecycle is a gateway call you can't test, can't reuse, and can't reason about when it goes at three in the morning. As a library it has a surface you can list. Card sale, tokenisation, token sale, transaction lookup, card information lookup, a 3D Secure notification patch, wallet sale and wallet pre-auth.
Every request carries an HMAC-SHA256 signature over the payload, with a timestamp and a client request identifier. The identifier's the part worth explaining, because it's passed back in on a retry so a repeated call gets recognised as the same attempt rather than treated as a second payment. Idempotency on a payment API isn't a nicety. Without it, a timeout that was actually a success takes the money twice, and the person it takes it from is already in arrears.
Digital wallets are the reason the numbers moved. Apple Pay needs a merchant identity certificate and a live validation handshake against a verified domain, and Google Pay needs a merchant profile the gateway has actually approved rather than one that merely exists. Both of them fail in ways the documentation doesn't describe, you find that out on the day.
Fiserv's documentation has a reputation and it earns it. A fair chunk of this build was working out what the gateway actually does by watching what it returns, and then writing that down so the next person doesn't have to. That document is the part I'd keep.
Sheet03 · The decision I would defend hardest
Groundaccent
EvidenceIn the prose
Sourcesrc/data/bpo.ts
03 · The decision I would defend hardest
Gateway credentials came out of config and into the database
One environment switch in application config, and the credentials themselves held server side with exactly one active row per environment.
Payment credentials in a config file are credentials in source control, and in the build artefact, and in every developer's checkout, and in every backup of all three. The usual answer is to transform them at release, which only moves the problem along a bit, because now the transform is the secret and anybody who can publish can read it.
Moving them server side leaves one flag in config saying which environment this deployment is, and the application syncs that on startup. Rotating a key stops being a deployment, it becomes a row.
The constraint that makes it safe is boring and load bearing: exactly one active credential row per environment, verified before cutover. Two active rows isn't an error you find in testing, it's an error you find when half your traffic authenticates and half doesn't.
Sheet04 · How it went live
Groundbone
EvidenceCompletion criteria
Sourcesrc/data/bpo.ts
04 · How it went live
Three runbooks, because the interesting failures are all on the day
A database runbook, an application cutover runbook and a single deployment day checklist, each with its own completion criteria and its own way back.
Writing them wasn't process for the sake of it. A payment cutover has an ordering problem, which is that the database objects and the credentials have to exist before the application starts, or the application throws on construction and the site goes down with a configuration exception rather than a payment error. Splitting the runbooks by server made that ordering impossible to get wrong. Not unlikely. Impossible.
The checklist names the failures I expected and what each one means. Apple returning a 400 about a missing SSL certificate is nearly always the certificate file, or its password, or the application pool identity being unable to read the private key, and knowing that in advance is the difference between ten minutes and a lost afternoon. A specific Google error code in production, with the same code already verified in test, is a merchant enablement problem. Nothing you can fix by editing the code.
Backout was written before it was needed: what triggers it, what to do, and the fact that the database changes stay in place because they were built additively. A rollback plan that requires you to also unpick the schema is a rollback plan nobody executes.
The first twenty four hours had a named watch list. Not a vague instruction to keep an eye on it.
Sheet05 · The original brief, still delivered
Grounddeeper
EvidenceIn the prose
Sourcesrc/data/bpo.ts
05 · The original brief, still delivered
What the law actually says, which isn't what people tell you
There's no UK statute that names WCAG for a private financial firm. There is something with a good deal more teeth than people expect, and it's worth stating precisely rather than reaching for the word compliance and hoping.
The Equality Act 2010 requires service providers to make reasonable adjustments for disabled people, and a website is service provision, so the duty is already sitting there whether anybody has said the word accessibility out loud or not, and it's enforceable through the courts and through the Equality and Human Rights Commission. It never names a version of WCAG. It never names WCAG at all, because a duty isn't a specification.
On top of that, and this is the part specific to a firm like this one, financial services is the only private sector with a conduct regulator sitting above the general law. The FCA's Consumer Duty requires good outcomes for customers, with particular weight on customers in vulnerable circumstances. In March 2025 the FCA published website accessibility improvement as good practice and presenting the same design to every customer regardless of need as poor practice. The FCA Handbook also tells firms to take account of the latest WCAG when designing certain digital promotions.
So the honest position is this. WCAG isn't the law. WCAG is the yardstick the regulator, the courts and the sector all recognise, which makes conforming to it the defensible way to discharge a duty that's very much the law. Building to it isn't gold plating on a debt collection site. Every single person using it is, by definition, under financial pressure, and a meaningful share of them are exactly the customers the Consumer Duty was written about.
That was the brief I was hired for and it didn't stop being the brief when the payment layer turned out to be a bigger job. The platform rebuild ran alongside the migration rather than after it.
Sheet06 · What it did
Groundbone
EvidenceOne number, honestly
Sourcesrc/data/bpo.ts
06 · What it did
Mobile payments up 80% since launch
A solid 80% increase in mobile payments from launch, and 70% of those came directly through Apple Pay, across both one-off payments and continuous payment arrangements.
The mechanism isn't clever and that's rather the point. Somebody in arrears, on a phone, being asked to type a sixteen digit card number and an expiry and a security code and a billing address into a form belonging to a debt collection company, is somebody with several good reasons to put the phone down. Replacing the lot of it with a thumbprint removes the typing and the errors. And a fair amount of the dread.
The same change that raised the number is the change that made the flow usable by somebody who can't fill in a long form accurately. The accessibility brief and the conversion outcome were never in tension. They were the same fix.
SheetClose
Result80% more mobile payments
Of those70% through Apple Pay
Failed cutovers0
Close
What I'd tell anyone taking one of these on
- A payment migration can't be done part time. Six people proved that here before I got near it, and not one of them was bad at their job.
- Write the backout before the deployment. If you write it after, you write it while it's on fire.
- The gateway's documentation is a starting position, not a description. Budget for finding out what it actually does.
- Accessibility and conversion are the same work far more often than either side admits.