Payments · mobile

Replacing the foundation under a payment app, while people were paying with it.

Paytec builds payment systems for vending machines. OPTO is the application their customers hold in their hand at the machine. In 2025 it was running on a framework that Microsoft had already stopped supporting — and it could not be allowed to stop working for a single day.

Since
2025
Client
Paytec
Team
2
Status
in production

The product

What OPTO is, and why stability is not cosmetic

OPTO is Paytec's digital payment solution for vending. The phone talks to the machine optically — through the light of its own screen, not NFC and not a QR code — into a small reader fitted to the machine with an optical sensor and a Bluetooth module, no modem and no SIM card. The user holds a balance in the application, tops it up by card or cash, and on suitably equipped machines selects the product from the phone and pays in one gesture. The money side sits with an electronic-money issuer authorised in the European Union.

All of that is Paytec's published description of their own product, and it is on this page for one reason: it sets the standard the software has to meet. An application that is occasionally slow is an annoyance. An application that fails at the moment of payment, in front of a machine, with a balance already loaded, is a support call, a refund and a customer who goes back to coins. Reliability here is not a quality attribute. It is the product.

The situation in 2025

The framework had been dead for a year

Paytec came to us with a working application and a problem that was not yet visible to its users. OPTO was built on Xamarin. Microsoft ended support for Xamarin on 1 May 2024.

An unsupported mobile framework does not announce itself. The application keeps running, keeps taking payments, and keeps looking fine — while three things quietly stop being true. There are no more fixes for defects in the framework itself. There is no support for the behaviour of new operating-system versions, which arrive every autumn whether or not you are ready. And there is the store ratchet: Google Play requires new apps and updates to target Android 15 from 31 August 2025 and Android 16 from 31 August 2026. An application that can no longer be built against a current target is an application that can no longer be published — including the day you urgently need to ship a fix.

That is the real risk in a dead framework, and it is why this was never a modernisation project in the decorative sense. It was a deadline that had already passed.

The support clock behind the OPTO migration A timeline from 2024 to 2029. Xamarin support ended on 1 May 2024. .NET MAUI 9 was released in November 2024 and went out of support on 12 May 2026. .NET MAUI 10 and .NET 10, a long-term support release, arrived in November 2025 and are supported into 2027 and November 2028 respectively. Far Rainbow took over the OPTO codebase in 2025 and moved it onto the supported line. 2024 2025 2026 2027 2028 2029 Xamarin support ended 1 May 2024 — where OPTO was .NET MAUI 9 out of support 12 May 2026 .NET 10 LTS · MAUI 10 supported to November 2028 2025 — we take over the codebase OPTO today sits on the bottom line. It started on the top one.
Dates from Microsoft's published support policies and Google Play's target-API requirements. The point of the picture is the gap between the top line and the bottom one: an application does not move between them by itself.

What we did

Migration first, then everything the migration made possible

We entered a codebase we had not written, for a product we had not designed, with no option to pause it. The brief had two parts and they were not equal: move OPTO from Xamarin to .NET MAUI, and come out the other side more stable than it went in. A migration that lands on a supported framework and a worse crash rate has failed, whatever the release notes say.

The useful thing to understand about this kind of migration is where the work actually is. The business logic — balances, transactions, the rules around them — ports across comparatively cleanly, because it is ordinary code. What does not port itself is the platform layer: how the application talks to the reader, how it behaves when the operating system suspends it, how permissions are requested on each version of each platform, and how all of that differs between a four-year-old Android handset and a current iPhone. In a payment application, that layer is not allowed to be approximately right.

The team was two people for the whole of it: one mobile engineer carrying both iOS and Android on .NET MAUI, and one backend engineer on a PHP service. We mention the size because it is the honest shape of this kind of work. A migration is not made safer by adding people to it; it is made safer by having the same two heads hold the whole system at once.

The instability was not all in the application

This is the part worth taking away from the page. The visible symptom was the application: crashes, freezes, the app being killed in the background, users losing the thread in the middle of a purchase. The obvious conclusion was that the client code was at fault, and some of it was.

But a share of the failures clustered at peak hours — which is a shape, not a coincidence. We reproduced it under load rather than arguing about it, and the cause turned out to be on the other side: the application was losing its connection to the backend when demand concentrated. No amount of work in the mobile code would have fixed that. It was resolved by raising the capacity of the backend, and it is the reason the engagement had a backend engineer on it and not only a mobile one.

The other thing that changed for users is unglamorous and matters every single time: the application now opens faster from cold. At a vending machine, with somebody waiting behind you, the seconds before the first screen appear are the whole experience.

Two OPTO screens: the sign-in screen with the OPTO logo and the strapline At the Speed of Light, and the balance screen showing the wallet total, the number of purchases and the dates of the last top-up and the last purchase

OPTO as its users see it, from Paytec's published store listing. The migration deliberately did not change this: the brief was to replace what the application stands on, not what it looks like.

The second phase came after the first had proved itself: Paytec asked us to carry on, and we moved the application from .NET 9 to .NET 10 and improved parts of its behaviour along the way. That upgrade looks like housekeeping and is not. A .NET MAUI application runs on two clocks, and the shorter one is easy to miss: .NET MAUI 9 went out of support on 12 May 2026, six months before .NET 8 and .NET 9 themselves reach end of support on 10 November 2026. .NET 10 is a long-term support release, carried to November 2028. Moving when we did was the difference between planning an upgrade and being forced into one.

Today we maintain the generation that is in production while building the next one alongside it. The shipped application stays supported and shippable throughout — which is the only arrangement that works when the thing you are replacing is also the thing taking the money. The next generation will get a page of its own once it is in people's hands; we would rather write that one afterwards than announce it now.

The pattern

Four things this engagement is evidence of

01 · Inherited code

We were not the original authors and the original authors were not available. Taking over somebody else's production codebase — reading it, stabilising it, then changing its foundation — is a distinct skill from starting clean, and it is most of what modernisation work actually consists of.

02 · We looked past the obvious culprit

The symptom was in the application; a share of the cause was in the backend, and only showed itself under peak load. Finding that required reproducing it rather than guessing, and a team that could work on both sides of the wire.

03 · We are still here

The migration was 2025. The .NET 10 upgrade came after it, because the client asked us to carry on. We now maintain the current generation and are building the next. Continuing an engagement is a harder thing to win than starting one.

04 · The clock is public

Every date on this page can be checked against Microsoft's and Google's own published policies. We would rather make the argument from documents you can verify than from adjectives you cannot.

Questions

What people ask about work like this

Why migrate rather than rewrite from scratch?

Because the expensive part of a payment application is not its screens — it is the years of accumulated behaviour around money, devices and edge cases that nobody has written down. A rewrite throws that away and rediscovers it in production, in front of paying users. A migration keeps the business logic and replaces the foundation underneath it, which is slower to plan and very much faster to finish safely.

What actually happens to an app on a framework that is out of support?

Nothing, at first — which is the dangerous part. It keeps running and keeps selling. What stops is everything around it: no fixes for defects in the framework, no support for new operating-system behaviour, and no way to meet the app stores' annual target-API requirements. Google Play requires new apps and updates to target Android 16 from 31 August 2026. An application that cannot be rebuilt against a current target eventually cannot be published at all — and the first time that matters is usually the day you urgently need to ship a fix.

If there were no metrics, how do you know it got better?

Honestly: not as firmly as we would like. The application carried no analytics and no crash reporting when we inherited it, so there is no before-and-after number — and inventing one would be worse than having none. What we have is that the crash, freeze and out-of-memory reports coming from the client and their users stopped arriving, and that the peak-hour failures we reproduced under load no longer reproduce. We would rather tell you that plainly than publish a percentage we cannot source.

Is .NET MAUI ready for production payments?

In our experience, yes — with the caveat that the platform-specific layer is where the work really lives. Business logic ports across cleanly. Device communication, background behaviour, permissions and the differences between iOS and Android versions do not port themselves, and they are exactly the parts a payment flow cannot be approximately right about. Budget the project around that layer, not around the screens.

Does a migration like this interrupt the service?

It should not, and that is a planning decision rather than a technical one. The released application stays supported and shippable throughout, so an urgent fix can still go out while the migration is in progress. We have been maintaining the shipped generation of OPTO alongside the development of the next one for exactly that reason.

Who owns the result?

The client. We work in the client's repository, the code is theirs from the first commit, and the handover is written into the contract at the start rather than negotiated at the end.

We still have a Xamarin application. How urgent is it?

Xamarin stopped being supported on 1 May 2024. If your application is still shipping, it is doing so on a framework that has received no updates for over two years, and the store deadlines are public and annual. The useful first step is small: a few days of assessment that establishes what ports mechanically, what has to be rewritten, and what the realistic range is. You can take that document to any supplier.