Why and How We Built a Flexible Payments System

Written by Erdem ErbasOct 22, 2025 06:3211 min read
Why and How We Built a Flexible Payments System

Intro

It’s a simple truth of e-commerce: you have to pay for what you want. Whether you’re filling your basket with groceries or ordering a book, a payment is required for us to keep the lights on and the EPVs running. That’s where our Payments Team comes in.

We provide the essential infrastructure that lets our customers pay for their orders. But, as with any system that handles money, it’s not all about taking — we also have to give back. Refunds are a critical part of our job.

Supply chains and logistics aren’t always smooth sailing. Sometimes, we might run out of a sought-after item, or a cucumber might get a bit squashed during transit. If such occasional inconveniences arise, it is our responsibility to execute the correct and timely refunds to our customers.

The Problem of Specificity: One Size Did Not Fit All

When you physically pay for something in a brick-and-mortar store, the experience is essentially the same whether you’re buying a loaf of bread or a TV. That’s because the payment mechanism is decoupled from the actual item you’re purchasing. The act of paying doesn’t change based on what you bought.

We wanted our online payments functionality to feel the same: a consistent, simple experience regardless of what our customers were paying for.

However, as a startup we had to focus our efforts and build our initial system based on the one domain we were dealing with — orders and deliveries and the orchestration of the actions needed in that area, such as initiating refunds or timing the collections of funds in the first place. By doing so the payment system was melded together with the business, sacrificing the flexibility to support any other possible business model.

Our current architecture can handle these specific needs while still providing a consistent user experience regardless of what is being paid for. But it wasn’t always like that


In this article we’ll to go through how our architecture was, and how it evolved over time with the changing landscape of Picnic.

The Tipping Point: Why We Needed a Change

As Picnic grew, so did ambitions and new ideas within the company. Some of these new ventures involved selling different kinds of goods. The big “deal-breaker” moment for the Payments Team came when we introduced a feature allowing customers to tip their lovely runners.

Suddenly, our systems were strained. Since we had been laser-focused on creating an almost perfect payment process for groceries, our architecture had become tightly coupled with the concepts of “orders” and “deliveries.” Tips, however, didn’t fit neatly into either of these boxes.

We realized this was an opportunity to pause and envision the future. How could we build a flexible and resilient payments system that could easily handle any type of good or service we might offer down the road?

The solution was a clear separation: We needed to decouple the logic of the payment transaction itself from the specific business entity it was connected to. This would allow us to maintain a flexible, accessible ‘core’ payment functionality that any new business line could simply plug into.

The Migration: Separating the Core

The first major undertaking was the creation of a Core Payments System to house all generic payment logic — things like initiating a charge or executing a refund on a charged payment.

Ideally, the existing (legacy) system we had would only be responsible for utilizing these Core Payment functionalities based on the business flows.

Alas, the existing system’s data structure did not align with the vision we had for Core Payments, so only one of them could stay alive. That meant legacy payments would have almost no data of their own in the end, and core payments would be the source of truth for the domain of payments. We began painstakingly separating this generic logic from our existing system, leaving behind only the business logic specific triggers for deliveries and orders.

Of course, if only it was that simple! This was a massive migration that required absolute precision.

To remove the limitations of our legacy system, we couldn’t afford any reliance on old, external data. We had to make our new system the definitive source of truth. Our plan focused on a safe, non-disruptive transition:

  1. Syncing All Operations: Every action in the legacy system was immediately mirrored to the new system.
  2. Data Migration: We moved the historical data into the new structure.
  3. Read Switching: We changed existing data points to read from the new system.
  4. Flow Implementation & Backwards Syncing: We built out all necessary payment flows in the new system and — crucially — implemented backwards syncing. This was our safety net, allowing us to revert to the old system quickly if any major issues or bugs arose.
  5. Switching Mechanisms: We’ve introduced basic flags which would manage which implementation (old or new) would be executed for a specific flow, even for a specific payment method, per user. And if we notice any issues these flags would help us switch back to the old implementation.
  6. Write Switching: Finally, we have started to slowly roll out to customers using the mechanism we introduced on the previous step.

This plan allowed us to operate entirely within our own domain without creating dependencies on other teams, and the backwards syncing gave us a safe way out if anything went wrong.

With the migration to Core payments finalized we have opened up the way to allow other Picnic business domains to start payments. The first thing we enabled with this architecture was the Tip Payments for our lovely runners.

Adapting to business needs

After the migration, we had two main components: Legacy Payments and Core Payments. The Legacy system served as the business interface, triggering processes in the Core Payments system based on business needs.

While creating Core Payments, we had to move some of the delivery/order logic to Core Payments itself. This was because the data structure of Legacy Payments was not suitable to adapt what we’ve enabled with Core Payments. That meant that some queries and also some of the business logic got moved to the Core Payments as well. Our end goal was for Core Payments to be completely domain-agnostic — it should only handle the mechanics of a transaction and never make business decisions on its own.

This led us to the next evolution: Adapters.

If Core Payments is completely unaware of business rules, it can’t decide when to take action. That’s where the Adapters come in. An adapter is an additional layer that knows the specifics of a business entity (like deliveries or tips) and what the current status of its payments is. This knowledge enables it to execute specific business operations by calling the Core Payments system.

Because their function is to adapt our generic payment system to specific business needs, we call them Adapters. They aren’t the source of truth for the data — they are a reflection of an external system that owns the data, tailored to a payments perspective.

This helps us keep the Core Payments completely free of any domain/business specific logic, focus on the payment itself and provide a flexible payments functionality while allowing business adapters to request any kind of payments that would suit their needs. So adding a new business adapter will not make any of our systems more complex, it will be just an adapter on its own with its specific logic.

The First Test: Picnic Family!

Around this time, a major new initiative began to take shape: the Picnic Family. This is our exciting membership program that offers exclusive discounts and benefits to our customers.

Due to their nature, memberships and our grocery orders operate with vastly different business flows. Both require a payment system specialized to handle all the specific needs and complexities of their unique vertical.

Fortunately, we had already begun adapting our architecture to allow exactly this! With Core Payments separated, we had a flexible system that could accommodate the distinct needs from different domains. All that was required was to introduce some additional parameters on the Core Payments side, allowing our adapters to adjust the behavior as necessary. The foundational functionality was ready.

Following that, we simply had to create a dedicated adapter that could communicate with Core Payments. This adapter initiates operations with the correct parameters whenever the flows of the new vertical require it.

And just like that — boom! — we were operational.

As you’re reading this article, we’re likely running our pilot program to see how our customers enjoy the new membership program and making any necessary final fine-tuning adjustments!

The Future: Lightweight, Maintainable, and Scalable

Currently, we are working to move all remaining domain-specific logic and data out of Core Payments and into its own dedicated adapter.

In the end, we will have a clean, two-layered architecture:

  1. XX Payments Adapters: Tailored to the unique payment needs of the business. There can be multiple adapters for different business domains. For example: An adapter responsible for executing operations based on what happens before, during, or after deliveries would be Delivery Payments.
  2. Core Payments: A completely business-agnostic system that fully encapsulates the generic payment logic, enabling different adapters to execute operations as needed.There is another layer of flexibility hidden in here, but that is for another time.

The successful launch of the Picnic Family is the proof that this architecture works. The membership required its own unique payment flows and rules, and we were able to support it through a new, dedicated adapter, without making any complex changes to the central Core Payments system.

This structure ensures that scaling is simple. When a new business line needs a payment system, we only need to create a new Adapter. The logic for each business and the core payment are encapsulated on their own, making every part of our payments system lightweight, easier to maintain, and ready for whatever future opportunities Picnic discovers!


Why and How We Built a Flexible Payments System was originally published in Picnic Engineering on Medium, where people are continuing the conversation by highlighting and responding to this story.

picnic blog job banner image

Want to join Erdem Erbas in finding solutions to interesting problems?