Grocery Delivery App Development

Build custom app solutions with Scrums.com's expert development team. With an NPS (Net Promoter Score) of 82, Scrums.com crafts cost-effective, custom applications that drive results.

Companies building grocery delivery platforms, dark store operations software, and multi-retailer grocery marketplaces need engineering teams who understand the specific data challenges of perishable inventory, variable slot capacity, and the substitution workflows that separate grocery from standard e-commerce. Scrums.com provides dedicated software engineering teams for grocery delivery platform development, deploying production-ready systems with real-time inventory sync, slot-based scheduling infrastructure, and the event-driven order state machines that grocery operations require.

Product Catalogue, Inventory Sync, and Order Intake Architecture

Grocery platforms manage a significantly more complex product model than standard e-commerce: perishable goods with short expiry windows, weight-variable products sold by approximation (meat, produce), and inventory that changes by the hour across multiple fulfilment locations.

The product catalogue uses a products table (sku, name, category_id, unit_type [EACH | WEIGHT_KG | WEIGHT_G | VOLUME_L]) paired with a product_pricing table with store_id, effective_from/effective_to, and price_per_unit. For weight-variable products, price_per_unit is per kg/g and a tare_weight adjustment is stored in product_weight_config. Prices are versioned: the current price for a store/product combination is always the row with effective_from <= NOW() AND (effective_to IS NULL OR effective_to > NOW()).

Store inventory is managed through a store_inventory table: store_id, product_id, quantity_on_hand, last_updated_at, and restock_threshold. Inventory movements write to a store_inventory_transactions table: RECEIVED | PICKED | WASTED | ADJUSTMENT: never a direct update to quantity_on_hand, which is a materialised view over transactions. Any inventory discrepancy is therefore traceable to a specific transaction.

For real-time inventory sync from warehouse management or POS systems, an inventory_sync_log records each inbound update: source_system_id, raw_payload, ingested_at, and processing_status (RECEIVED / PROCESSED | FAILED). The ingest handler is idempotent on (source_system_id, source_reference_id). Products with quantity_on_hand = 0 or below restock_threshold generate an availability_status flag consumed by the catalogue API: out-of-stock products are excluded from new order item creation automatically.

Order intake uses the same idempotent endpoint pattern: client_idempotency_key with UNIQUE constraint prevents duplicate orders from retries. The order state machine: CART / PLACED / PAYMENT_PROCESSING / CONFIRMED / PICKING / PACKED / OUT_FOR_DELIVERY / DELIVERED | FAILED | CANCELLED. Each transition writes an order_events row.

Slot-Based Delivery Scheduling, Order Batching, and Picker Workflow

Grocery delivery schedules on finite capacity: each delivery slot has a maximum number of orders, and slot availability must be enforced without race conditions.

Slots are stored in a delivery_slots table: store_id, slot_date, slot_start_time, slot_end_time, max_orders, and a zone_id (delivery postcodes/areas served by this slot). Slot bookings write to slot_bookings: slot_id, order_id, booked_at. A slot_availability view computes remaining capacity as max_orders minus COUNT(slot_bookings) per slot. Slot booking uses SELECT ... FOR UPDATE on the slot row to prevent overbooking under concurrent requests. Slot capacity can be adjusted by writing a new slot_capacity_overrides row: no schema change required.

Order batching groups orders by slot and picker zone. The batch_assignment_engine queries confirmed orders for a slot, groups them by zone, and writes pick_batches rows: batch_id, slot_id, zone_id, assigned_picker_id, and a status machine (CREATED / ASSIGNED / IN_PROGRESS / COMPLETED). Each batch has child pick_tasks rows: batch_id, product_id, quantity_required, aisle_location. Aisle locations come from a product_locations config table per store, driving the optimal pick sequence to minimise walk distance.

The picker app reads from pick_tasks ordered by aisle sequence. Each pick action writes a pick_events row: task_id, picker_id, action_type (PICKED | SHORT_PICK | SUBSTITUTED | SKIPPED), quantity_actual, picked_at. SHORT_PICK fires when quantity_actual is less than quantity_required. The substitution workflow then runs. On pack completion, a packing_events row is written and the order transitions to PACKED.

Grocery delivery apps like these are built and delivered by dedicated engineering teams through our mobile app development service.

Substitution Logic, Out-of-Stock Management, and Customer Communication

Substitution is one of the most operationally complex parts of grocery: a substitution the customer dislikes is more damaging than a cancellation. The substitution system must be fast for pickers, configurable by store, and auditable.

The substitution_rules table stores per-product preferences at three levels: CUSTOMER (customer's own saved preferences), STORE (retailer-defined defaults), GLOBAL (category-level fallback). Resolution order is CUSTOMER > STORE > GLOBAL. For each short-picked item, the substitution engine queries the three levels in order and proposes a substitute from the first matching rule. The proposed substitute's availability is checked in real time against store_inventory before presentation.

Customer substitution preferences are stored in customer_substitution_preferences: customer_id, product_id, preference_type (ALWAYS_SUBSTITUTE_WITH | NEVER_SUBSTITUTE | ACCEPT_STORE_CHOICE). Product-level preferences take precedence over category-level preferences.

When a SHORT_PICK fires, the substitution_engine_events table records the proposal: original_product_id, proposed_substitute_id, rule_source, and the picker's decision (ACCEPTED | DECLINED | ALTERNATIVE_CHOSEN). If the customer has configured do-not-substitute, the item is removed and a removal_events row is written. Customer notifications fire via notification_rules config: the customer receives a push or SMS with the substitute proposal and response options (accept/decline/choose_alternative via deep link). Customer responses write back to substitution_responses; the picker sees the response in real time.

Weight capture for variable-weight products: at pick time, the picker enters actual_weight_kg. The order_line_items table stores both estimated_quantity (from order) and actual_quantity (from pick). The final charge is computed from actual_quantity, not the estimate. Weight variance above weight_variance_threshold_pct (config per product category) triggers a customer notification before final charge is applied.

Payment Processing, Promotions, and Grocery Analytics

Grocery payments carry two complexity points that standard e-commerce doesn't: pre-authorisation holds (because the final amount is unknown until picking completes for weight-variable products) and slot-tied refund logic (cancellations before picking vs after picking have different refund policies).

At order placement, a pre-authorisation is placed for estimated_order_total + weight_variance_buffer (configurable percentage, typically 10-15%). The pre_authorisation amount is stored in payment_preauth records. On pack completion, the final charge is computed from actual_quantity picks and the payment is captured at the final_amount. If final_amount is less than preauth_amount, the difference is released via capture_release_events. All payment state changes write to a payment_events child table: the payments table is never directly updated.

Promotions extend the standard promo model with grocery-specific fields: applies_to_category (JSONB array of category IDs for category promotions), requires_loyalty_tier (for tiered loyalty discounts), and product_bundle_rules (JSONB for multi-buy promotions like 3-for-2). Promo redemptions use SELECT ... FOR UPDATE on the max_uses check to prevent overselling under concurrent requests.

Loyalty programmes use a loyalty_ledger: customer_id, event_type (EARNED | REDEEMED | EXPIRED | ADJUSTED), points_delta, reference_order_id, created_at. Current balance = SUM(points_delta) from the ledger: no mutable balance counter. Points per order are computed from a loyalty_earn_rules config table with per-category multipliers and minimum spend thresholds.

Analytics materialised views: orders_by_store_by_hour (slot utilisation heatmap), substitution_rate_by_product (most-substituted SKUs), picker_productivity_kpis (picks per hour, short-pick rate), delivery_kpis (on-time rate, return rate, average delivery time). These feed the operations dashboard without touching live transactional tables.

Frequently Asked Questions

How do you handle inventory sync across multiple store locations in real time?

Each inventory sync inbound event writes to an inventory_sync_log with an idempotency constraint on (source_system_id, source_reference_id). Quantity on hand is a materialised view over a store_inventory_transactions ledger: never a mutable field. Any discrepancy between physical count and system count is traceable to a specific transaction row.

How does your substitution engine work for short-picked items?

The substitution_rules table has three tiers: CUSTOMER preferences, STORE defaults, GLOBAL category fallbacks, resolved in that priority order. The proposed substitute's availability is checked in real time before presentation to the picker. The customer receives a notification with response options; the picker sees the response in real time on the pick app.

How do you prevent slot overbooking under concurrent requests?

Slot booking uses SELECT ... FOR UPDATE on the delivery_slots row. This locks the slot for the duration of the transaction, so concurrent booking attempts queue rather than race. Slot capacity is a configurable field: overrides write a new slot_capacity_overrides row without schema changes.

How are weight-variable product charges handled?

A pre-authorisation is placed at order time for the estimated total plus a configurable weight variance buffer. At pack completion, the final charge is computed from actual picked weights. Variance above a per-category threshold triggers a customer notification before the final charge is captured. The full preauth and capture history is preserved in payment_events.

How long does it take to deploy a grocery delivery platform with Scrums.com?

Scrums.com deploys dedicated engineering teams within 21 days. The platform ships with inventory sync infrastructure, slot scheduling, idempotent order intake, substitution engine scaffolding, and payment pre-authorisation architecture ready for configuration.

Want to Know if Scrums.com is a Good Fit for Your Business?

Get in touch and let us answer all your questions.

Book a Demo

Don't Just Take Our Word for It

Hear from some of our amazing customers who are building with Scrums.com Teams.

"Scrums.com has been a long-term partner of OneCart. You have a great understanding of our business, our culture and have helped us find some real tech rockstars. Our Scrums.com team members are high-impact, hard working, always available, and fun to have around. Thanks a million!"
CTO, OneCart
On-demand marketplace connecting users and top retailers
"The Scrums.com Team is always ready to take my call and assist me with my unique challenges. No problem is to big or small. Great partner, securing strong talent to support our teams."
CIO, Network
Leading digital payments provider
"Finding great developers through Scrums.com is easier than explaining to my mom what I do for a living. Over the past couple of years, their top-tier devs and QAs have plugged seamlessly into Payfast by Network, turbo-charging our sprints without a hitch."
Engineering Manager, PayFast by Network
A secure digital payment processor for online businesses
"Our project was incredibly successful thanks to the guidance and professionalism of the Scrums.com teams. We were supported throughout the robust and purpose-driven process, and clear channels for open communication were established. The Scrums.com team often pre-empted and identified solutions and enhancements to our project, going over and above to make it a success."
CX Expert, Volkswagen Financial Services
Handles insurance, fleet and leasing
"The Scrums.com teams are extremely professional and a pleasure to work with. Open communication channels and commitment to deliver against deadlines ensures successful delivery against requirements. Their willingness to go beyond what is required and technical expertise resulted in a world class product that we are extremely proud to take to market."
Product Manager, BankservAfrica
Africa's largest clearing house
“Scrums.com Team Subscriptions allow us to easily move between tiers and as our needs have evolved, it has been incredibly convenient to adjust the subscription to meet our demands. This flexibility has been a game-changer for our business. Over and above this, one of their key strengths is the amazing team members who have brought passion and creativity to our project, with enthusiasm and commitment. They have been a joy to work with and I look forward to the continued partnership.”
CEO & Co-Founder, Ikue
World's first CDP for telcos
“Since partnering with Scrums.com in 2022, our experience has been nothing short of transformative. From day one, Scrums.com hasn't just been a service provider; they've become an integral part of our team. Despite the physical distance, their presence feels as close and accessible as if they were located in the office next door. This sense of proximity is not just geographical but extends deeply into how they have seamlessly integrated with our company's culture and identity.”
SOS Team, Skole
Helping 60k kids learn, every day
"Scrums.com joined Shout-It-Now on our mission to empower young women in South Africa to reduce the rates of HIV, GBV and unwanted pregnancy. By developing iSHOUT!, an app exclusively for young women, and Chomi, a multilingual GBV chatbot, they have contributed to the critical task of getting information & support to those who need it most. Scrums.com continues to be our collaborative partner on the vital journey."
CX Expert, iShout
Empowering the youth of tomorrow
"Scrums.com has been Aesara Partner's tech provider for the past few years; and with the development support provided by the Scrums.com team, our various platforms have evolved. Throughout the developing journey, Scrums.com has been able to provide us with a team to match our needs for that point in time."
Founder, Aesara Partners
A global transformation practice

Find Related App Types

Omnichannel Retail App

Budgeting App

Food Order Delivery App

Medical app

Energy App

Privacy Protection app