Rork Maxを使えば、従来なら複数の専門エンジニアが必要だったフルスタックのECアプリを、個人でも短期間で構築できます。本チュートリアルでは、商品一覧・カート・決済・注文管理・プッシュ通知をすべて備えた本格的なECアプリを、Rork MaxとSupabase、Stripeを組み合わせてゼロから実装していきます。
完成するアプリの主な機能:
- 商品一覧 / 詳細画面(Supabase Storageの画像表示)
- カート管理(ローカル状態 + Supabase同期)
- Stripe決済(クレジットカード / Apple Pay / Google Pay)
- 注文履歴・ステータス管理
- プッシュ通知(発送・到着通知)
この記事はRork Maxの基本操作を理解している方を対象としています。まだRorkに慣れていない方は Rorkの始め方ガイド を先にご確認ください。
前提知識・環境準備
必要なもの
| ツール | 用途 | 料金 |
|---|---|---|
| Rork Max | アプリ開発・AI生成 | 有料プラン |
| Supabase | DB・認証・ストレージ | 無料枠あり |
| Stripe | 決済処理 | 手数料制 |
| Expo EAS | ビルド・プッシュ通知 | 無料枠あり |
Supabaseプロジェクトのセットアップ
まず Supabase ダッシュボードで新規プロジェクトを作成し、以下のテーブルを作成します。Rork Maxのチャットで「Supabase のテーブル設計を手伝って」と入力すると、AIがSQL生成を補助してくれます。
-- 商品テーブル
CREATE TABLE products (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
price INTEGER NOT NULL, -- 円単位(例: 2980 = ¥2,980)
stock INTEGER DEFAULT 0,
image_url TEXT,
category TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 注文テーブル
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()
);
-- 注文明細テーブル
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
);
-- Row Level Security を有効化
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY "ユーザーは自分の注文のみ参照可能"
ON orders FOR SELECT
USING (auth.uid() = user_id);Rork Maxプロジェクトの初期化
Rorkを開き、新規プロジェクトを作成します。プロンプトには以下のように入力してください:
ECアプリを作りたい。
- 商品一覧・詳細・カート・注文確認・マイページ画面が必要
- Supabase(URL: xxx, anon key: xxx)と連携
- React Native / Expo ベース
- タブナビゲーション(ホーム・カート・マイページ)
Rork MaxのAIがプロジェクト全体のスキャフォールドを生成します。生成後に細部を調整していく方法が最も効率的です。
概念と設計思想
ECアプリのアーキテクチャ
本チュートリアルで構築するアプリは、以下のアーキテクチャを採用しています。
[React Native / Expo アプリ]
↕ (Supabase JS Client)
[Supabase]
├── Auth(メール/SNSログイン)
├── Database(商品・注文データ)
└── Storage(商品画像)
↕ (API呼び出し)
[Stripe]
├── Payment Intent(決済意図の作成)
└── Webhook(決済完了通知)
↕
[Expo Push Notifications]
└── 注文ステータス変更時に通知
状態管理の設計
カート状態はReact Context + useReducerで管理し、ログイン済みの場合はSupabaseのcartsテーブルにも同期します。この設計により、ゲストとログイン後でシームレスにカートを引き継げます。
ステップバイステップ実装
Step 1: 商品一覧画面
Rork MaxのAIに「商品一覧画面を作って」と指示した後、生成されたコードに以下の修正を加えます。
// 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) // 在庫あり商品のみ
.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.toLocaleString()}</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 },
});期待する出力: 2カラムのグリッドに商品が表示され、在庫切れ商品は表示されません。
Step 2: Stripe決済の実装
Stripeの決済はEdge Functionを介して実装します。Rork Maxで「Supabase Edge FunctionでStripe Payment Intentを作成するコードを書いて」と指示すると雛形が生成されます。
// 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. Payment Intent を作成
const paymentIntent = await stripe.paymentIntents.create({
amount, // 円単位の合計金額
currency: currency ?? 'jpy',
automatic_payment_methods: { enabled: true },
metadata: { userId, itemCount: items.length.toString() },
});
// 2. Supabase に注文を仮登録(status: pending)
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. 注文明細を登録
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' }
});
}
});期待する出力: clientSecret と orderId がJSONで返却されます。
フロントエンドではこの clientSecret を使って @stripe/stripe-react-native の confirmPayment() を呼び出します:
// screens/CheckoutScreen.tsx(抜粋)
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
);
// Edge Function 経由で Payment Intent 作成
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();
// Stripe決済フローを起動
const { error } = await confirmPayment(clientSecret, {
paymentMethodType: 'Card',
});
if (error) {
Alert.alert('決済エラー', error.message);
} else {
// 決済成功 → 注文完了画面へ
navigation.navigate('OrderComplete', { orderId });
}
}
// ...
}Step 3: プッシュ通知の実装
注文ステータスが変わったときにプッシュ通知を送る仕組みを実装します。
// 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,
}),
});
// デバイストークンを取得してSupabaseに保存
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;
// ユーザーのプッシュトークンをSupabaseに保存
await supabase
.from('user_push_tokens')
.upsert({ user_id: userId, token, updated_at: new Date().toISOString() });
return token;
}Supabase Edge FunctionのWebhookをStripeと連携させ、決済完了時に注文ステータスをpaidに更新し、Expo Push APIで通知を送る仕組みを構築します。
応用パターン
リアルタイム在庫管理
Supabaseのリアルタイム機能を使うと、他のユーザーが購入した瞬間に在庫数が自動更新されます。
// 在庫変更をリアルタイムで監視
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('お知らせ', 'この商品は売り切れになりました');
}
}
)
.subscribe();
return () => supabase.removeChannel(channel);
}, [product.id]);クーポン・割引コード機能
Stripeのクーポン機能と組み合わせて割引コードを実装できます。Rork Maxに「Stripeクーポンコードを検証するEdge Functionを追加して」と指示すると、検証ロジックを自動生成してくれます。
マルチ通貨対応
海外展開を見据えるなら、ユーザーのロケールに応じて通貨を切り替える仕組みを追加しましょう。Stripeは160以上の通貨に対応しており、currencyパラメータを変更するだけで対応できます。
トラブルシューティング
決済が完了しても注文ステータスが更新されない
原因: StripeのWebhookがSupabase Edge Functionに届いていません。 対処:
- Stripeダッシュボード → Webhooks でエンドポイントURLが正しいか確認
- Edge FunctionのログでWebhookの受信を確認(
supabase functions logs) - Webhook署名検証をスキップしていないか確認(必ず検証すること)
Supabaseのクエリが遅い
原因: インデックスが設定されていません。 対処: 以下のインデックスを追加することで検索速度が大幅に改善します。
-- よく使う検索条件にインデックスを追加
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);プッシュ通知が届かない
確認リスト:
- iOS: APNs証明書がEAS Buildに設定されているか
- Android: FCMのサーバーキーが設定されているか
expo-notificationsのパーミッション取得が完了しているか- デバイストークンがSupabaseに保存されているか(デバッグ: Supabase TableのUI確認)
RLSポリシーエラー(403 Unauthorized)
原因: Row Level Securityが有効なのにポリシーが設定されていません。 対処: 管理者からのみ書き込み可能にしたい場合は、Service Role Keyを使用するEdge Function経由で更新します。絶対にフロントエンドのコードにService Role Keyを含めないこと。
公開・収益化のポイント
App Store / Google Playへの申請
ECアプリをApp Storeに申請する際は、以下の点に特に注意が必要です:
- 外部決済の扱い: App StoreでのデジタルコンテンツはIn-App Purchaseが必須。物理商品の場合はStripeなど外部決済が許可されます。
- プライバシーポリシー: 決済情報を扱うため、必ずプライバシーポリシーのURL提出が必要です。
- カスタマーレビュー機能: 偽レビュー誘導はガイドライン違反のため、
expo-store-reviewを使って適切なタイミングでのみレビュー依頼を表示すること。
アプリ公開の詳細な手順は App Store申請完全ガイド をご参照ください。
収益モデルの設計
ECアプリの収益化には複数のモデルがあります:
- 直販型: 自社商品を直接販売(マージン最大)
- マーケットプレイス型: 出品手数料を収益源にする(Stripe Connectを活用)
- SaaS型: ECプラットフォームとして月額課金(Stripeサブスクリプション)
マーケットプレイス型で複数の出品者を管理する場合は Rorkサブスクリプション収益化ガイド も参考にしてください。
パフォーマンス最適化
ECアプリで最も重要なのは 画像の表示速度です。Supabase Storageに保存した商品画像は、Cloudflare Image Resizingと組み合わせることで、デバイスサイズに最適化された画像を配信できます。詳細は Rork + Cloudflare AIバックエンド構築 をご覧ください。
個人開発者の視点から(実体験メモ)
まとめ
本チュートリアルでは、Rork MaxとSupabase、Stripeを組み合わせてフルスタックECアプリをゼロから構築してきました。要点を整理します:
- アーキテクチャ: Rork Max(フロントエンド)+ Supabase(バックエンド)+ Stripe(決済)の組み合わせが最もコスパが高い
- 認証・RLS: Supabase Authと Row Level Securityで安全なデータアクセスを実装
- 決済: Edge Function経由でPayment Intentを作成し、フロントエンドから
confirmPayment()を呼び出す - プッシュ通知: Expo Push Token をSupabaseに保存し、Webhookトリガーで送信
- リアルタイム: Supabase Realtimeで在庫変更をリアルタイム反映
次のステップとして、売上ダッシュボードの実装やSEO対策(Next.jsへの移行)、AIを使った商品レコメンデーション機能の追加を検討してみましょう。個人開発でのECアプリ収益化については ソロ開発者のアプリビジネス戦略 も参考にしてください。