Juan Pablo Gallegos → Work → Bulkify
Bulkify
Modeling checkout as an intention, not a transaction.
What changed before the long read
The case is about giving a high-ticket marketplace a checkout model that matched how buyers actually commit.
Context
Bulkify is a B2B platform for listing and purchasing high-value industrial machinery — tractors, excavators, and industrial presses sold by regional dealers across Argentina. The platform handles the full transaction lifecycle: listing, discovery, negotiation, checkout, and delivery confirmation.
The backend was already structured around Hexagonal Architecture and Domain-Driven Design. Bounded contexts, aggregates, domain events, and ports/adapters were all established practice when I joined as a freelance backend engineer.
Problem
The existing checkout assumed a single atomic payment: a buyer pays the full amount and receives an order confirmation. This model broke for the platform's core use case.
Industrial equipment buyers typically place a deposit to reserve a machine weeks before taking delivery — with the remaining balance due on handoff. The old checkout had no concept of partial commitment, staged payment, or deferred confirmation. Operationally, this meant sales teams were managing payment stages in spreadsheets outside the system.
Constraints
The architecture was already committed to DDD. New work had to fit cleanly into bounded contexts, aggregate design, and domain events — no procedural shortcuts or direct DB calls across context boundaries.
Mercado Pago was the payment provider. Its webhook model distinguishes deposit pre-authorizations from captured payments, and its API surface changes with some regularity. The existing Order bounded context could not be broken — Checkout had to be entirely additive.
The engagement was five months with no guaranteed renewal, which put real pressure on handoff quality: the documentation had to be enough for another engineer to own Checkout confidently.
Role
I owned the entire Checkout bounded context end to end: domain modeling, application services, REST API contract, Mercado Pago integration, and domain event bus wiring to downstream contexts — Inventory (to hold stock on deposit) and Notifications (to trigger buyer confirmation emails).
I also wrote the integration test suite for the checkout state machine, covering every valid state transition and every attempt to reach an invalid one.
System shape
Checkout is modeled as an "intention to purchase" aggregate — a record of a buyer's commitment, entirely separate from the Order that governs delivery and logistics. A Checkout transitions through five explicit states:
Each transition is triggered by a domain event, not a direct mutation. The aggregate enforces its own invariants: a deposit cannot be refunded once the balance is locked; a checkout cannot be fulfilled until payment is complete. The Mercado Pago surface is wrapped entirely behind a payment port/adapter — the domain never imports the payment SDK directly.
Key decisions
Checkout and Order have different lifecycles and different invariants. Checkout is about payment commitment; Order is about fulfillment and logistics. Collapsing them forces impossible states — a fulfilled order with an unpaid deposit, or a paid checkout with no delivery record.
Boolean flags like isPaid, hasDeposit, isComplete create 2ⁿ possible states, most of them invalid. An explicit state machine makes illegal transitions impossible by construction and makes every valid path testable in isolation.
CheckoutDepositReceived and CheckoutFullyPaid are published to an internal event bus. Inventory and Notifications subscribe independently. Checkout stays unaware of its consumers, making it independently testable and deployable.
Trade-offs
Gained
- Illegal payment states are impossible by construction
- Checkout, Order, and Payment are independently deployable
- Every state transition has a focused, isolated unit test
- Mercado Pago API changes absorbed in one adapter file
Cost
- Higher upfront complexity: two aggregates instead of one
- More infrastructure: event bus, webhook handler, adapter layer
- Higher onboarding cost for engineers unfamiliar with DDD
Outcome
The deposit payment system shipped and is live in production. Buyers place deposits to reserve machines, receive automated confirmation emails, and settle the balance before delivery. No payment integrity incidents since launch. The Checkout bounded context was handed off with full test coverage and an architecture decision record.