Skip to content
All work

CurryDash: subscriptions and on-demand in one marketplace

A food marketplace that combines meal-kit subscriptions with on-demand orders, built on Laravel, Flutter and Cloud Run.

Status
Status: Built, not launched
Role
Full-stack engineering
Credit
Built at CoralShades
Stack
  • Laravel
  • MySQL
  • Redis
  • Flutter
  • Stripe
  • Google Cloud

At CoralShades, I built CurryDash: a marketplace for Sri Lankan home cooks and small restaurants in Australia. It runs two ordering models side by side. Customers can subscribe to a weekly curry pack, or order on demand. Both draw on the same vendor catalogue and delivery windows. CurryDash is built. It has not launched.

Case-study film made with CoralShades. Listings, orders and names are illustrations.

The problem

Home cooks already sell curry packs through social media groups and chat threads. That works while the circle is small. It stops working when demand grows: there is no way to find new cooks, payment is a bank transfer, and nobody can track an order.

A home cook lifting the lid off a steaming pot in a home kitchen.
Generated imagery from the case-study film.

The big delivery apps don't fit either. A cook who makes a batch every Friday needs customers to commit in advance. On-demand delivery alone can't plan a batch. So the platform had to run both models at once:

  • A subscription engine that creates orders on a weekly, fortnightly or monthly cycle
  • Real-time on-demand ordering, with live status for the customer
  • Split payments, so the platform commission comes off before the vendor payout
  • Menus built the way these cooks sell: a pack price, then choices for protein, spice level and sides
  • Separate sign-in and permissions for customers, vendors and admins

How it fits together

Two order paths, one platform

Subscriptions and on-demand orders share the vendor catalogue but run their own state machines.Select a step to see what it does.

Connections: Flutter apps to Laravel API; Laravel API to Subscription engine (subscription orders); Laravel API to On-demand orders (on-demand orders); Subscription engine to Stripe Connect; On-demand orders to Stripe Connect; Laravel API to MySQL and Redis; Cloud Run to Laravel API (hosts).

Two order models

The two order types share a catalogue, but almost nothing else. Their states, triggers and side effects differ.

  • Subscription: draft, confirmed, preparing, packed, delivered
  • On demand: pending, accepted, preparing, dispatched, delivered
One on-demand order, from the buyer's pick to on its way. A loop from the case-study film; listings and names are illustrations.

An early design used one state machine with a type flag. I dropped it, because every transition filled up with conditions. Instead there is a base Order model with the shared fields, and two subtypes, each with its own state machine, listeners and jobs. Notifications, payments and reviews point at either type through Laravel's polymorphic relations. Two code paths to maintain, but each one is short and testable on its own.

Packs, not menu items

Vendors don't sell single dishes. They sell configurable packs, so the catalogue is a three-level tree: package, configuration, option.

Code
Package (Family Curry Pack, $45)
├── Configuration: "Choose your protein" (min 2, max 3)
│   ├── Option: Chicken Curry (+$0)
│   ├── Option: Fish Curry (+$3)
│   └── Option: Prawn Curry (+$5)
├── Configuration: "Select spice level" (min 1, max 1)
│   └── Options: Mild | Medium | Hot
└── Configuration: "Add sides" (min 0, max 4)
    └── Options: Dhal | Sambol | Papadum | Roti

The price is the pack price plus the selected options. Building this tree from day one avoided a schema migration later; a generic "item plus add-on" model would not have held it.

Overhead view of a kraft takeaway box with rice, curry, sambol and a papadum.
A weekly curry pack. Generated imagery from the case-study film.

What a vendor does to go live

  1. 1RegisterBusiness profile and food safety documents, uploaded through the self-service portal.
  2. 2Connect payoutsLink a bank account through Stripe Connect.
  3. 3Build packsSet up packages, configurations and options, plus delivery days.
  4. 4Take ordersSubscription volume shows first, so the week's batch can be planned. Live on-demand orders sit below it.

Payments

When an on-demand order is confirmed, Stripe Connect splits the platform commission from the vendor payout in the same transaction.

php
public function createOrderPaymentIntent(
    StripeClient $stripe,
    OnDemandOrder $order,
): PaymentIntent {
    $vendor = $order->vendor;
    $totalCents = (int) ($order->total_amount * 100);
    $commissionCents = (int) ($totalCents * $this->platformCommissionRate);

    return $stripe->paymentIntents->create([
        'amount'                 => $totalCents,
        'currency'               => 'aud',
        'application_fee_amount' => $commissionCents,
        'transfer_data'          => [
            'destination' => $vendor->stripe_connect_account_id,
        ],
        'metadata'               => [
            'order_id'   => $order->id,
            'order_type' => 'on_demand',
        ],
    ]);
}

What I'd keep

A monolith was the right call for a small team. Laravel's service providers and domain repositories give each area its own boundary without separate deployments, and any one area can be split out later if it needs to scale on its own.

The hardest design work was the vendor dashboard. A home cook thinks in batches; a restaurant thinks in live orders. The dashboard puts the subscription forecast at the top and the live queue below, clearly separated.

Need a marketplace or multi-vendor platform?

Split payments, subscriptions and live orders are a lot to hold in one system. If that's your problem, book a call.

Book a call (opens in a new tab)

wihithat@gmail.com

  • Status: In developmentRaava's own product

    An offline-first app for small Sri Lankan pharmacies: invoice-photo receiving, price-guarded billing and batch stock. Phase 1 is built and in testing.

    Evidence: Phase 1 built; user acceptance testing pending; no pilot yet.

    Stack: React · Vite · TypeScript · Tailwind CSS

  • Status: Design stage

    A design-stage restaurant system for Sri Lankan venues: mobile ordering, a kitchen display and an admin dashboard, built to keep running through outages.

    Evidence: Screen designs for three surfaces and a local-first sync design

    Stack: Flutter · React · Next.js · Supabase