All work

Case study · Payments and booking

B3 Academy — never lose a charge

A learning and care platform that sells six kinds of product through one payment pipeline, where a failure after payment must never cost the customer.

The problem

B3 Academy is a herbal-medicine academy in Kuwait. Its platform combines learning with care: courses with quizzes and certificates, printed books and e-books, paid subscriptions that open a group chat, consultations at a clinic or over video, consultation packages and trips. The specification lists 129 use cases.

Six different things can be bought. Each purchase has two halves: the gateway takes the money, then the system delivers, for example by booking sessions with a doctor. The second half can fail after the first has succeeded.

My role

I was the only backend developer and built the whole backend: the customer API and the admin dashboard behind it.

Architecture

  1. Controller
  2. FormRequest
  3. DTO
  4. Service
  5. Resource
Request path.

Payment and invoicing are separate modules. Each gateway is a strategy behind a common interface, and events for paid orders are dispatched only after the database transaction commits.

Key decisions

One pipeline for six products. Every purchase creates a payment transaction that holds a snapshot of what is being bought and an idempotency key, unique per customer. When the gateway reports back, one service routes the result to the right kind of fulfilment.

Do not trust the callback. The callback tells the system that something happened. The system then asks the gateway directly for the payment’s status and acts on that answer.

Record the payment before delivering. This is the decision the title refers to.

  1. Create the transaction with a snapshot and an idempotency key

    A second request with the same key returns the first transaction.

  2. The customer pays at the gateway

  3. Verify the status with the gateway

    The callback only triggers the check.

  4. Record the payment and commit

    From here on, the money is accounted for whatever happens next.

  5. Deliver: book the sessions, grant the access

    If it fails: the payment stays recorded and the order waits for delivery.

  6. A retry with the same key resumes delivery

    It does not charge again.

A failure in delivery never rolls back the record of a payment.

Locks where two people can collide. Buying a consultation package takes a lock per customer and per doctor. The transaction and order rows are locked while they change.

The hard part: a slot taken after checkout

A package books several sessions. Between checkout and delivery, one of those slots can be taken by someone else. Refunding the whole package for one lost slot would be the wrong answer for the customer and for the academy.

The system keeps the purchase, flags the conflicting session, notifies the customer and lets them pick another time. A scheduled command sends reminders until the conflict is resolved.

Outcome

  • About 510 test methods. The package purchase test alone covers a failure after the charge, a retry that resumes, and repeated callbacks.
  • Getting a failing suite back to green (69 failing tests) uncovered six real bugs, among them missing records returned as server errors and a guard for blocked admins that was never applied.
  • Also built: chunked uploads for large files, multi-currency prices with cached rates and three fallback providers, and six scheduled commands.

What I would do next

  • Run the locking tests against MySQL. They pass on SQLite in memory, which cannot prove behaviour under real concurrency.
  • Split the booking service, which became the largest class in the project.
  • Add a pipeline that runs the tests on every merge request.

CV

Download PDF
CV page 1CV page 2