RORK LABJP
TOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the roundTOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the round
Articles/Dev Tools
Dev Tools/2026-03-17Advanced

Build a Full E-Commerce App with Rork Max: Supabase + Stripe + Push Notifications

A step-by-step guide to building a production-grade e-commerce app with Rork Max, Supabase, and Stripe—complete with real-time inventory, checkout, and push notifications.

Rork Max229e-commerceSupabase33Stripe17push notifications11app development40tutorial20

With Rork Max, building a fully functional e-commerce app—something that would typically require a team of engineers—is now achievable by a solo developer. Over this tutorial you build that shopping app from scratch — product catalog, cart, Stripe checkout, order management, and push notifications — and take it all the way to production-ready.

What you'll ship by the end of this tutorial:

  • Product listing and detail screens (with Supabase Storage for images)
  • Cart management (local state synced to Supabase)
  • Stripe checkout (card, Apple Pay, and Google Pay)
  • Order history and status tracking
  • Push notifications for shipping and delivery updates

This tutorial assumes you're familiar with Rork basics. If you're just getting started, check out the Getting Started with Rork guide first.


Prerequisites and Environment Setup

What You'll Need

ToolPurposeCost
Rork MaxApp development & AI generationPaid plan
SupabaseDatabase, auth, storageFree tier available
StripePayment processingTransaction fees only
Expo EASBuild & push notificationsFree tier available

Setting Up Supabase

Create a new Supabase project and run the following SQL to set up your schema. You can ask Rork Max to help you design the tables by prompting: "Help me design a Supabase schema for an e-commerce app."

-- Products table
CREATE TABLE products (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  name TEXT NOT NULL,
  description TEXT,
  price INTEGER NOT NULL, -- in smallest currency unit (e.g., cents)
  stock INTEGER DEFAULT 0,
  image_url TEXT,
  category TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW()
);
 
-- Orders table
CREATE TABLE orders (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
  status TEXT DEFAULT 'pending', -- pending / paid / shipped / delivered
  total_amount INTEGER NOT NULL,
  stripe_payment_intent_id TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW()
);
 
-- Order line items
CREATE TABLE order_items (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  order_id UUID REFERENCES orders(id) ON DELETE CASCADE,
  product_id UUID REFERENCES products(id),
  quantity INTEGER NOT NULL,
  unit_price INTEGER NOT NULL
);
 
-- Enable Row Level Security
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can only view their own orders"
  ON orders FOR SELECT
  USING (auth.uid() = user_id);

Initializing Your Rork Max Project

Open Rork and start a new project with this prompt:

Build an e-commerce app with:
- Product listing, detail, cart, checkout, and profile screens
- Supabase integration (URL: xxx, anon key: xxx)
- React Native / Expo base
- Tab navigation: Home, Cart, Profile

Rork Max will scaffold the entire project. From there, you'll refine specific screens with targeted prompts.


Architecture Overview

How the Pieces Fit Together

[React Native / Expo App]
       ↕ (Supabase JS Client)
[Supabase]
  ├── Auth (email / social login)
  ├── Database (products, orders)
  └── Storage (product images)
       ↕ (API calls)
[Stripe]
  ├── Payment Intents (create charge)
  └── Webhooks (payment confirmation)
       ↕
[Expo Push Notifications]
  └── Triggered on order status changes

State Management Strategy

Cart state is managed with React Context + useReducer, and synced to Supabase for logged-in users. This lets guests add items to a cart before signing up, then seamlessly carry that cart over after authentication.


Step-by-Step Implementation

Step 1: Product Listing Screen

After Rork Max generates the initial screen, refine it with this implementation to properly fetch in-stock products:

// screens/ProductListScreen.tsx
import { useEffect, useState } from 'react';
import {
  FlatList, View, Text, Image,
  TouchableOpacity, StyleSheet, ActivityIndicator
} from 'react-native';
import { supabase } from '../lib/supabase';
 
type Product = {
  id: string;
  name: string;
  price: number;
  image_url: string;
  stock: number;
};
 
export default function ProductListScreen({ navigation }: any) {
  const [products, setProducts] = useState<Product[]>([]);
  const [loading, setLoading] = useState(true);
 
  useEffect(() => {
    fetchProducts();
  }, []);
 
  async function fetchProducts() {
    const { data, error } = await supabase
      .from('products')
      .select('*')
      .gt('stock', 0)        // only show in-stock items
      .order('created_at', { ascending: false });
 
    if (!error && data) setProducts(data);
    setLoading(false);
  }
 
  if (loading) return <ActivityIndicator style={{ flex: 1 }} />;
 
  return (
    <FlatList
      data={products}
      numColumns={2}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => (
        <TouchableOpacity
          style={styles.card}
          onPress={() => navigation.navigate('ProductDetail', { product: item })}
        >
          <Image source={{ uri: item.image_url }} style={styles.image} />
          <Text style={styles.name}>{item.name}</Text>
          <Text style={styles.price}>${(item.price / 100).toFixed(2)}</Text>
        </TouchableOpacity>
      )}
    />
  );
}
 
const styles = StyleSheet.create({
  card: { flex: 1, margin: 8, backgroundColor: '#fff', borderRadius: 12,
          overflow: 'hidden', elevation: 2 },
  image: { width: '100%', aspectRatio: 1 },
  name:  { fontSize: 14, fontWeight: '600', padding: 8 },
  price: { fontSize: 16, color: '#e53e3e', paddingHorizontal: 8, paddingBottom: 8 },
});

Expected output: A two-column grid of in-stock products. Tapping any card navigates to the product detail screen.

Step 2: Stripe Checkout

Payment processing is handled server-side via a Supabase Edge Function. Prompt Rork Max with: "Create a Supabase Edge Function that creates a Stripe Payment Intent and saves a pending order to the database."

// supabase/functions/create-payment-intent/index.ts
import Stripe from 'npm:stripe@14.21.0';
import { createClient } from 'npm:@supabase/supabase-js@2';
 
const stripe = new Stripe(Deno.env.get('STRIPE_SECRET_KEY')!);
 
Deno.serve(async (req) => {
  const { amount, currency, items, userId } = await req.json();
 
  try {
    // 1. Create a Payment Intent
    const paymentIntent = await stripe.paymentIntents.create({
      amount,
      currency: currency ?? 'usd',
      automatic_payment_methods: { enabled: true },
      metadata: { userId, itemCount: items.length.toString() },
    });
 
    // 2. Save a pending order to Supabase
    const supabase = createClient(
      Deno.env.get('SUPABASE_URL')!,
      Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
    );
    const { data: order } = await supabase
      .from('orders')
      .insert({
        user_id: userId,
        total_amount: amount,
        status: 'pending',
        stripe_payment_intent_id: paymentIntent.id,
      })
      .select()
      .single();
 
    // 3. Save order line items
    const orderItems = items.map((item: any) => ({
      order_id: order.id,
      product_id: item.productId,
      quantity: item.quantity,
      unit_price: item.price,
    }));
    await supabase.from('order_items').insert(orderItems);
 
    return new Response(JSON.stringify({
      clientSecret: paymentIntent.client_secret,
      orderId: order.id,
    }), { headers: { 'Content-Type': 'application/json' } });
 
  } catch (error) {
    return new Response(JSON.stringify({ error: error.message }), {
      status: 400,
      headers: { 'Content-Type': 'application/json' }
    });
  }
});

Expected output: Returns { clientSecret, orderId } which the app uses to present the Stripe payment sheet.

On the client side:

// screens/CheckoutScreen.tsx (excerpt)
import { useStripe } from '@stripe/stripe-react-native';
 
export default function CheckoutScreen({ cartItems }: any) {
  const { confirmPayment } = useStripe();
 
  async function handlePay() {
    const totalAmount = cartItems.reduce(
      (sum: number, item: any) => sum + item.price * item.quantity, 0
    );
 
    const res = await fetch(`${SUPABASE_URL}/functions/v1/create-payment-intent`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json',
                 'Authorization': `Bearer ${session.access_token}` },
      body: JSON.stringify({ amount: totalAmount, items: cartItems,
                             userId: session.user.id }),
    });
    const { clientSecret, orderId } = await res.json();
 
    const { error } = await confirmPayment(clientSecret, {
      paymentMethodType: 'Card',
    });
 
    if (error) {
      Alert.alert('Payment failed', error.message);
    } else {
      navigation.navigate('OrderComplete', { orderId });
    }
  }
  // ...
}

Step 3: Push Notifications

Register device tokens and send push notifications when order status changes.

// lib/notifications.ts
import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
import { supabase } from './supabase';
 
Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true,
    shouldPlaySound: true,
    shouldSetBadge: false,
  }),
});
 
export async function registerPushToken(userId: string) {
  if (!Device.isDevice) return;
 
  const { status } = await Notifications.requestPermissionsAsync();
  if (status !== 'granted') return;
 
  const token = (await Notifications.getExpoPushTokenAsync({
    projectId: 'YOUR_EXPO_PROJECT_ID',
  })).data;
 
  // Persist the token in Supabase
  await supabase
    .from('user_push_tokens')
    .upsert({ user_id: userId, token, updated_at: new Date().toISOString() });
 
  return token;
}

A Stripe Webhook handler in a Supabase Edge Function listens for payment_intent.succeeded, updates the order status to paid, and fires an Expo push notification to the user.


Advanced Patterns

Real-Time Inventory Updates

Supabase Realtime lets you push stock changes to the app the moment another user completes a purchase:

useEffect(() => {
  const channel = supabase
    .channel('product-stock')
    .on(
      'postgres_changes',
      { event: 'UPDATE', schema: 'public', table: 'products',
        filter: `id=eq.${product.id}` },
      (payload) => {
        setStock(payload.new.stock);
        if (payload.new.stock === 0) {
          Alert.alert('Sold out', 'This item just sold out.');
        }
      }
    )
    .subscribe();
 
  return () => supabase.removeChannel(channel);
}, [product.id]);

Discount Codes with Stripe Coupons

Stripe's Coupon API makes discount codes straightforward. Prompt Rork Max: "Add an Edge Function that validates a Stripe coupon code and returns the discount amount." The AI will generate the server-side validation logic and help you wire it into the checkout screen.

Multi-Currency Support

For international markets, change the currency parameter in the Payment Intent call based on the user's locale. Stripe supports 135+ currencies, so no additional integration is required.


Troubleshooting

Order status isn't updating after a successful payment

Cause: Stripe Webhook isn't reaching your Supabase Edge Function endpoint. Fix:

  1. In the Stripe Dashboard → Webhooks, verify the endpoint URL is correct.
  2. Check Edge Function logs with supabase functions logs.
  3. Make sure you're verifying the Stripe webhook signature on every request.

Supabase queries feel slow

Cause: Missing database indexes. Fix: Add indexes for the most common query patterns:

CREATE INDEX idx_products_category ON products(category);
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_status ON orders(status);

Push notifications not being delivered

Checklist:

  • iOS: Is your APNs certificate configured in EAS Build?
  • Android: Is your FCM server key set in your Expo project?
  • Has the user granted notification permission?
  • Is the push token saved in Supabase? (Check via the Supabase Table Editor)

RLS policy blocking requests (403 error)

Cause: Row Level Security is enabled but no policy exists for that operation. Fix: For admin-only write operations (like updating order status), use a Service Role Key inside an Edge Function—never in client-side code. Exposing the Service Role Key in the app is a critical security vulnerability.


Publishing and Monetization

App Store and Google Play Submission

Key points when submitting an e-commerce app:

  1. In-App Purchase rules: Physical goods can use external payment processors like Stripe. Digital goods (subscriptions, virtual items) must use Apple/Google's in-app purchase systems.
  2. Privacy policy: Required for any app handling payment information.
  3. Review prompts: Use expo-store-review at appropriate moments—never in response to a user action that was negative.

For a complete App Store submission walkthrough, see App Store Publishing Guide.

Monetization Models

Three common e-commerce monetization models and how to implement them with Rork Max:

  • Direct sales: Sell your own products with maximum margin (this tutorial's approach)
  • Marketplace: Charge listing or transaction fees using Stripe Connect
  • SaaS platform: License the shop infrastructure as a monthly subscription using Stripe Billing

For the subscription approach, see Rork Subscription App Monetization.

Performance Optimization

Product image loading speed is critical for e-commerce conversion rates. Store images in Supabase Storage and serve them through Cloudflare Image Resizing for device-optimized delivery. This alone can cut image transfer size by 60–80%. See Rork + Cloudflare AI Backend for the full setup.


Summary

In this tutorial, you built a production-grade e-commerce app using Rork Max, Supabase, and Stripe. Here's a quick recap:

  • Architecture: Rork Max (frontend) + Supabase (backend) + Stripe (payments) is the most cost-effective stack for solo developers.
  • Security: Row Level Security and Webhook signature verification are non-negotiable.
  • Checkout flow: Create a Payment Intent server-side, then call confirmPayment() on the client.
  • Push notifications: Store Expo Push Tokens in Supabase and trigger them via Stripe Webhooks.
  • Real-time: Supabase Realtime gives you live inventory updates with minimal extra code.

Next steps to consider: an admin sales dashboard, SEO optimization with Next.js, and AI-powered product recommendations. For solo developer business strategy, see Solo Developer App Business.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Dev Tools2026-04-12
to Building Marketplace App Payments with Rork and Stripe Connect
A comprehensive guide to integrating Stripe Connect into a Rork-built marketplace app. Covers seller account onboarding, destination charges, platform fee distribution, Webhook handling, and launch compliance.
Dev Tools2026-03-19
Building a Full-Featured EdTech Learning App with Rork Max: Courses, Progress Tracking & Stripe Monetization
An advanced guide to building a production-ready EdTech app with Rork Max. Covers Supabase for course & progress management, Stripe for paywalls, offline downloads, and App Store publishing.
Getting Started2026-03-17
Build a Habit Tracker App with Rork Max — Push Notifications & Home Screen Widgets
A complete Rork Max tutorial for building a habit tracker app with push notification reminders and home screen widgets — no coding required. Perfect for beginners.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →