Skip to content

Demo: This is a demo template by Ruji Labs. The business, people, products, prices and reviews on this page are fictional and do not represent any real company.

zahin ooi
menu

Checkout rebuild for Pasarloka

Cut the checkout from five screens to two and the slowest step from 2.8 seconds to 640 milliseconds. Pasarloka: same-day grocery delivery, Klang Valley and Penang.

+11.4%

checkout completion

Client
Pasarloka
Role
Product engineer, checkout squad
Duration
4 months
Shipped
2025.08, v2.4
Stack
TypeScript, Next.js, Node, Redis, PostgreSQL

Problem

Most Pasarloka orders are placed on mid-range Android phones on mobile data, often in a lift or a car park. The checkout called nine services in sequence before it showed a total, and the p95 time to a payable screen was 2.8 seconds on a good day.

Worse, a timeout after payment left people unsure if they had paid. Support handled about 140 duplicate-order tickets a week, and each one needed a manual refund.

Constraints

3 that shaped the design

  • Slot availability and stock change by the minute, so the total cannot be cached for long.
  • Four payment methods with different confirmation timings: card, FPX online banking, two e-wallets.
  • The squad could not change the warehouse system, only call it.

Architecture

scroll sideways to see all of it

Price, slot and stock are fetched in parallel behind one quote. The order is created once, keyed by the quote.
cartquote idapp / web2 screensquote servicep95 640 mspricingpromos, feesslotsper storestockwarehouse apiordersidempotentpayments4 methods

boxes are servicesdouble top rule is a storedashed is async or advisorygreen is what this project built

Code

excerpt, lightly trimmed

The quote id doubles as the idempotency key, so a retried tap returns the same order instead of a second one.

checkout/create-order.tsts
export async function createOrder(quoteId: string, userId: string) {  // One quote can only ever become one order.  const existing = await db.order.findUnique({ where: { quoteId } });  if (existing) return existing;  const quote = await quotes.get(quoteId);  if (!quote || quote.expiresAt < Date.now()) {    throw new CheckoutError("QUOTE_EXPIRED", "Prices changed. Review your basket.");  }  return db.order.create({    data: { quoteId, userId, totalSen: quote.totalSen, slotId: quote.slotId },  });}

Result

measured, not estimated

p95 time to a payable screenwas 2.8 s
640 ms
checkout completion over 8 weeks
+11.4%
duplicate-order ticketswas 140
6 a week
from basket to paidwas 5
2 screens

The quote pattern was later reused for the scheduled weekly basket, which shipped in three weeks instead of the planned two months.

Demo only

This button is part of a demo template. Nothing was sent, booked or charged, and there is no real business behind this page.

Want a page like this for your business?

QR code: Darus Ishak, Ruji Labs contact cardScan or tap to get the contact card of Darus Ishak, Ruji Labs.Open contact card ↗