Lessons from Building commercetools Checkout
The project I get asked about most is commercetools Checkout. It touched product strategy, architecture, security, payments, and coordination across several teams, and the decisions that mattered were rarely only technical.
Today around 100 customers run live on Checkout, and payment providers including Stripe and PayPal/Braintree have built connectors for it themselves. The second fact is the one I care about, for reasons I will get to.
What Checkout is
commercetools is a composable commerce platform. The core APIs already cover carts, products and orders, but historically customers still had to build the checkout experience on top of them.
Checkout is one of the hardest parts of an e-commerce implementation. Addresses, shipping methods, payment methods, authorization, failure handling, order creation, security, compliance, and an integration per payment provider. Every merchant was solving the same problem again.
So the idea was to deliver that last step of the customer journey as a managed product. The merchant adds our browser SDK to their storefront and initializes Checkout. We render the experience inside their site and manage the interaction with the backend.
It covers three use cases:
- Complete checkout, where we manage everything from the cart to order creation.
- Payment only, for merchants with their own checkout who want us to handle payments.
- Express payments, such as Apple Pay or Google Pay from a product page or the cart.
The architecture in four parts
Checkout API and database. Stores the Checkout configuration, and manages the checkout session and its state.
Browser SDK. What merchants integrate into the storefront. They load the SDK and call an initialization function. Checkout opens, usually in an iframe or modal, and talks to the storefront over browser messaging.
Checkout frontend. The customer-facing experience: addresses, shipping options, payment method selection, validation, errors.
Payment connectors. Payments are not implemented inside Checkout. They run as separately deployed connectors.
End to end, a purchase looks like this:
customer clicks checkout
→ SDK initializes the session and opens the Checkout frontend
→ Checkout API loads configuration + cart
→ frontend shows shipping and payment options
→ customer picks a payment method
→ frontend talks to that payment connector
→ connector talks to the PSP
→ authorization result returns to Checkout
→ Checkout creates the order in commercetools
The pivot
The most consequential decision on the project was changing the payment architecture.
We started simpler: payment providers were integrated directly into the Checkout application. It worked, and it taught us a lot about how PSPs actually behave. But the more we talked to customers and looked at the ecosystem, the clearer it became that the model would not scale. There are hundreds of payment providers and a long tail of local payment methods. One team cannot build and maintain all of them. Worse, if most of the team is writing integrations, nobody is improving Checkout.
So we replaced direct integrations with a plugin-style connector architecture.
What I owned
I owned the architecture and interface definition for the connector system, which meant defining the contract between Checkout and external payment integrations.
I also drove the technical alignment with the commercetools Connect team, since Connect became the runtime for deploying connectors. And I pushed for Checkout to move closer to standard commercetools infrastructure instead of running a fully separate environment, which simplified the architecture and cut operating cost.
So the work was not one component. It was product decisions, architecture, infrastructure and cross-team roadmap planning at the same time.
Designing the connector interface
The hard question was where to put the integration boundary.
We first looked at defining the contract only at the backend API level, which would have been much simpler. That fell apart as soon as we went through real PSPs. Many providers and payment methods need provider-specific behaviour in the browser: their own card fields, their own authentication step, their own payment button, their own redirect.
So a connector has two parts:
- The enabler, a frontend component. JavaScript that owns the provider-specific user experience, implementing a standard frontend interface so Checkout can load and drive any provider the same way.
- The processor, a backend API. A standard set of payment operations: authorize, capture, void, refund, and webhook handling.
I designed both by working through several PSPs and several payment method types. The goal was never “support Stripe and PayPal”. It was to find the concepts they share, so the next integration fits without redesigning Checkout.
Where connectors run
A related decision was hosting. One option was to let anyone host a connector anywhere and hand Checkout an endpoint, which would have produced a very open ecosystem. The other was to use commercetools Connect as a managed execution environment.
This was mostly a product and platform decision. My job was to supply the technical picture and the tradeoffs: security, lifecycle management, deployment, configuration, secrets, observability, and how much control we wanted over where connector code runs.
We chose Connect. It gave us control over how connectors are deployed and operated, and a tighter integration with the rest of the platform.
Roadmap and cross-team dependencies
Checkout depended on Cart and Orders, Product Catalog, Connect, Recurring Orders, and Infrastructure. At any point three or four other teams could move our roadmap.
Part of my role was looking ahead at those dependencies. When we planned a feature, I wanted to know whether another team had to deliver something first, and when.
Connect was the most complex dependency, partly because it was also a young product. We needed capabilities that did not exist yet: support for different application types (a connector has a backend API but also frontend JavaScript that needs CDN distribution), logging for integrations, metadata so a connector can describe its capabilities and configuration, and secure secret management for things like PSP API keys.
We could not hand the Connect team a requirements list and wait. We had to design parts of the roadmap together. The question I kept asking in planning was:
If we want to ship feature X six months from now, what has to exist in another platform three months before that?
Asking it early is what let us sequence the work instead of discovering the dependency halfway through.
Executing it
We spent roughly two months planning and validating the new architecture. The original estimate for the implementation was around nine months. We shipped in under six.
It was also not a greenfield rollout. Existing Checkout customers were live on the previous architecture, so we had to migrate them onto connectors without breaking their checkout. Backward compatibility, rollout strategy and migration added most of the real complexity.
The outcome was a change in who builds integrations. Instead of the Checkout team building each one, PSPs and partners build their own. Stripe and PayPal/Braintree did exactly that.
Impact
The benefit was not only supporting more PSPs. It changed where our team’s time went. Before the pivot, every new provider was potentially a significant piece of engineering for us. After it, that work could sit with the PSP or a partner, and we could spend our time on the product.
External providers implementing successfully against our interfaces is the measure I care about. Designing a plugin interface that fits your own implementations is easy. Designing one that another company can implement on its own, without a shared backlog and a standing call, is much harder.
Security and PCI
The original architecture left the door open to building our own card vault and storing sensitive card data, which would have expanded our PCI scope considerably. That is a large part of why Checkout initially ran on separate infrastructure: we did not want Checkout’s compliance requirements to pull the entire commercetools platform into scope.
I was involved in designing that infrastructure on Google Cloud, and we reviewed the architecture with Google Cloud Professional Services with a focus on security and isolation.
Later, when the architecture no longer needed that degree of isolation, I raised the question of whether fully separate infrastructure was still the right call, and pushed for closer integration with standard commercetools infrastructure. That removed complexity and operational cost.
The original decision was correct for the constraints we had. When those constraints changed, it was worth revisiting rather than defending.
Reliability and observability
Checkout sits directly in the purchase flow, so we knew observability would matter. We started with logging standards, including a hard rule about never leaking personally identifiable information or sensitive payment data into logs.
Early on we consciously optimized for delivery speed instead. We had no customers yet, and the first job was proving that the product and the architecture worked at all. Once that was settled and customers started onboarding, the priorities changed and we invested much more in observability, failure handling and production reliability. A lot of that work went into making it obvious where a failure happened: in Checkout, in the connector, or at the PSP.
We watched latency closely for the initial Checkout load, since it affects conversion directly. For the payment operation itself, latency depends on external systems we do not control, so there we focused on correct failure handling, visibility, and making sure the customer understands what is happening.
How I approached the decisions
For the larger calls I started from the business and customer requirement, then worked through the alternatives and their long-term consequences. For connectors the options were:
| Option | Tradeoff |
|---|---|
| Integrations inside Checkout | Simplest to start, does not scale organizationally |
| Backend-only contract | Simpler interface, cannot support many real payment methods |
| Connectors hosted anywhere | Most flexible, weakest lifecycle and security control |
| Connectors on Connect | More constrained, gives managed deployment, secrets and observability |
The aim was not maximum flexibility. It was enough flexibility for the payment ecosystem, in something we could operate reliably as a product.
What I learned
The first lesson came from getting the original payment architecture wrong. We optimized for direct integrations, and only later understood that the ecosystem needed to be open. When you define a platform boundary, validate it early against a wider set of customers, partners and future use cases than the ones in front of you.
The second lesson pulls the other way. We did not try to design a universal payment abstraction before building anything. We looked at real PSPs and real payment methods, found the patterns they shared, and built the smallest interface we believed could carry the ecosystem. Three real integrations told us more about the right abstraction than any amount of design up front.
The third is about organizational architecture. The technically simplest option is not always the best one for the company. PCI scope, team ownership, partner ecosystems, infrastructure cost and roadmap dependencies weigh as much as the software design.
I am proud of Checkout because it started as a fairly narrow idea about payment integrations and became an extensible platform. I owned several of the architectural decisions behind that shift, worked across teams to make the sequencing possible, and we delivered the new architecture well ahead of the original plan while migrating existing customers at the same time.