JST --:--:--

Akbar

フードオーダープラットフォーム

akbar.co.jpで動くアクバルの公式オンライン注文。たまプラーザ・あざみ野周辺のお客様が、お店から直接デリバリーやテイクアウトを注文し、オンラインで支払えます。

サイトを見る arrow_outward
Akbar's ordering site homepage in a desktop browser
9:41
Akbar's ordering site homepage on a phone
課題

今や多くの人がデリバリーアプリで料理を注文しますが、アプリは注文ごとに大きな手数料を取ります。電話注文は時間がかかり、誰かが電話の前にいなければなりません。

主な特長

9:41
Akbar ordering on a phone: choose delivery or takeout
1

デリバリーもテイクアウトもワンタップで

お客様はご自宅へのデリバリーか店頭受け取りを選び、スマホからメニューを見て注文できます。電話を待つことも、聞き間違いもありません。

9:41
Akbar menu on a phone: curries with photos and prices
2

お店と同じメニューをそのまま

カレー・セット・ナンを写真と価格つきでタブごとに掲載。検索や絞り込みもでき、辛さやトッピングも選べます。ランチセットはランチの時間だけ表示されます。

Akbar staff dashboard with active orders, income and popular dishes
3

お店のすべてを1つのダッシュボードで

上部に進行中の注文・本日の注文数・本日の売上を表示し、日・週・月ごとの売上グラフと人気メニューも並びます。下には最近の注文が内容・支払い方法・状況つきで一覧になり、新しい注文は入った瞬間に表示されます。

Pause new orders setting in the Akbar dashboard
4

忙しい夜のための一時停止

キッチンが混み合ったら、新規注文の一時停止や配達時間帯の締め切りができ、ワンクリックで再開できます。お客様にはいつも正しい受付状況が表示されます。

お店にとって変わること

電話番が不要に

注文は内容も支払いも確定した状態で届くので、電話を受けて注文を取るためだけの人手が要らなくなります。

手数料を手元に

デリバリーアプリは注文ごとに大きな手数料を取ります。アクバルの公式サイトでの注文ならその手数料がかからず、売上がより多くお店に残ります。

電話よりかんたん

今はスマホで料理を注文するのが当たり前。アクバルにも、誰かが電話に出るのを待たずに、いつでも直接注文できます。

ひと目でわかる

プロジェクト概要

クライアント
横浜市のたまプラーザ・あざみ野エリアのインド料理店、アクバル。
担当
お客様向けの注文サイトからスタッフ用ダッシュボード、決済、サーバーまで、デザインと開発をすべて担当しました。現在も保守を続けています。
納品したもの
  • デリバリー・テイクアウトの注文サイト
  • SMSコードで電話番号を確認する会員登録
  • オンラインのカード決済と代金引換
  • 注文・売上・人気メニューをリアルタイムで確認できるスタッフ用ダッシュボード
  • 写真・トッピング・売り切れ設定つきのメニュー管理
  • サーバー構築・ホスティング・HTTPS対応
状況
akbar.co.jpで公開中。実際のご注文を受け付けています。

飲食店を経営していて、アプリを通さずに直接注文を受けたい方は、飲食店のウェブサイト制作(英語のページ)もご覧ください。

開発者向けの技術情報

使用技術・アーキテクチャ・作り方について。

スタック

バックエンド
Python 3 Flask SQLAlchemy Flask-Migrate Flask-Login Flask-WTF Flask-Mail
フロントエンド
Jinja2 Vanilla JS Vanilla CSS HTML5 Stripe.js
リアルタイム
Server-Sent Events (SSE) Background Thread
認証・決済
Twilio Verify Stripe PBKDF2-SHA256 CSRF (Flask-WTF)
インフラ
AWS EC2 Nginx Gunicorn Cloudflare D1 Cloudflare Workers Cloudflare R2 Certbot / SSL
Integrations
Zoho Mail (SMTP) Nager.Date API boto3 Pillow pandas pytz
バックエンド
Python Flask
フロントエンド
HTML5 CSS3 JavaScript
認証・決済
Stripe (オンライン決済) Twilio (SMS認証)
インフラ・保存先
AWS EC2 (クラウドサーバー) Cloudflare D1 (データベース) Cloudflare R2 (画像保存)

アーキテクチャ

account_tree

Request path

Browser → Nginx (reverse proxy, HTTPS, rate limiting, static caching) → Gunicorn (2–6 worker processes on port 8000) → Flask (Blueprints: main, auth, admin, homepage) → Cloudflare D1 via a custom SQLAlchemy dialect over HTTP  |  Cloudflare R2 for media  |  External APIs (Stripe, Twilio, Zoho, Nager.Date).

Nginx has a special configuration for the SSE endpoint (/order_stream): buffering is disabled and HTTP/1.1 is enforced so the stream reaches the admin browser without delay.

stream

Real-time via Server-Sent Events

The admin browser holds a persistent HTTP connection to /order_stream. Flask streams today's orders as JSON every few seconds using SSE: no WebSocket, no Socket.IO. A background threading.Thread runs every 60 seconds and auto-marks delivery orders as delivered after 30 minutes in ready state, preventing orders from getting stuck.

payments

Stripe payment flow

Flask creates a Stripe PaymentIntent. Stripe.js collects card details in the browser: the card number never touches the Flask server. A unique reference UUID per order prevents duplicate charges on network retries. Stripe commission (3.6%) is calculated and tracked separately in the revenue dashboard. Admin cancellations trigger automatic stripe.Refund.create() calls.

phone_iphone

Phone-based authentication

Customers register with a Japanese phone number (formatted to E.164) and password. Flask calls the Twilio Verify API to send a 6-digit SMS OTP. After the customer confirms the code, is_phone_verified is set to True and they are logged in. Sessions are signed cookies (Flask-Login's default) lasting 60 days. Returning customers log in with phone + password only: no SMS needed.

manage_accounts

Four-tier user system

Four distinct user types, each with separate login flows and route-level decorators: Customers (phone + password + Twilio OTP on first registration), Admins (email + password), Delivery boys (PIN: simplified login for delivery staff), and a Super admin (4-digit passcode giving full system control including menu management, site blocking, and user management).

cloud_upload

Media and storage

Menu item images and PDF menus are stored in Cloudflare R2, accessed through R2's S3-compatible API via boto3 in utils/s3_utils.py (the same upload code that ran against AWS S3 before the migration, since R2 speaks the identical API). The Super Admin can drag-and-drop reorder categories and items: position is updated via AJAX. Admins can toggle item availability between available, sold out today, and sold out.

filter_alt

Smart menu filtering

The lunch category is hidden on weekends by default. The app queries the Nager.Date API to detect Japanese national holidays and adjusts lunch visibility accordingly. Admins can override this toggle manually. All categories, items, and add-ons are loaded in a single optimised database query using eager loading for speed.

mail

Spam-filtered contact form

The public contact form detects whether a message contains Japanese characters via regex. If the message is Japanese, it is sent via Zoho Mail SMTP (smtp.zoho.in). If English-only, a success response is shown but no email is sent: a deliberate spam filter to block automated English-language bots while staying functional for real Japanese customers.

Key flows

person_add

Customer registration

  1. 1Customer enters name, Japanese phone number, password
  2. 2Server formats phone to E.164 (+81…), checks for duplicates
  3. 3Account created; Twilio Verify API sends 6-digit SMS
  4. 4Customer enters the OTP on the verify screen
  5. 5Flask validates code via Twilio Checks API
  6. 6Phone verified → customer logged in, redirected to menu
shopping_cart_checkout

Checkout & payment

  1. 1Customer selects delivery or pickup, time slot, payment method
  2. 2For card/PayPay: Flask creates a Stripe PaymentIntent
  3. 3Stripe.js handles card input in the browser: details never reach Flask
  4. 4On success: browser calls /store_order_details, Flask saves order and clears cart
  5. 5For cash on delivery: order saved immediately, no Stripe call
  6. 6Customer redirected to payment success page
receipt_long

Admin order management

  1. 1Admin opens /admin/orders; browser connects to SSE stream
  2. 2New orders appear live without page refresh
  3. 3Admin clicks status buttons → AJAX updates database
  4. 4Status flow: pending → preparing → ready → delivered / completed / cancelled
  5. 5Background thread auto-delivers after 30 min in ready state
  6. 6Cancellation triggers automatic Stripe refund
restaurant_menu

Menu management

  1. 1Super admin logs in at /control-center with 4-digit passcode
  2. 2Adds, edits, or deletes categories and menu items
  3. 3Images uploaded to Cloudflare R2 via boto3
  4. 4Add-on groups (spice levels, drinks) configured per category or item
  5. 5Drag-and-drop reordering updates position field via AJAX
  6. 6Item availability toggled: available / sold out today / sold out

技術的なハイライト

cloud

本番クラウドデプロイ

AWS EC2にNginxリバースプロキシとGunicorn(2〜6ワーカー)でデプロイ。CertbotでHTTPS対応。メニュー画像はCloudflare R2に保存。akbar.co.jpで実際のお客様に公開中。

swap_horiz

エッジDBへの無停止移行

本番データベースをAWS RDS PostgreSQLからCloudflare D1へ移行。D1はCloudflare Workersランタイム内からしかアクセスできないため、D1の手前にHTTPプロキシとして動作する小さなCloudflare Workerと通信するカスタムSQLAlchemyダイアレクト(d1+http)を実装。FlaskアプリのORMコード自体は変更不要でした。画像保存もAWS S3からCloudflare R2へ同様に移行(同じS3互換APIのため)。

Data model

Table What it stores
CustomerLogin Phone number (unique), name, email, hashed password, address fields, verification status, order count
Admin Restaurant admin accounts: email, PBKDF2-SHA256 hashed password (310,000 iterations), passcode
SuperAdmin Top-level admin: email and 4-digit passcode. One row expected
DeliveryBoy Delivery staff: name and PIN for simplified login
Category Food categories with position ordering, optional time restriction, and spicy level flag
MenuItem Individual items: name, description, price, image URL, availability, spice options, position, order count
AddonCategory Add-on groups (e.g. "Spice Level", "Drink Choice") scoped per category or per item
AddonItem Individual add-on choices (e.g. "Mild", "Medium Hot") with price. Belongs to an AddonCategory
Order Placed order: links to customer, status, total, payment method, Stripe payment_intent_id, delivery method, scheduled time
OrderItem Items within an order: quantity, price at time of order, spicy level, special instructions, add-ons stored as JSON
Settings Restaurant settings: name, address, phone, opening hours, admin email. One row
TodayCurry / MonthlyCurry CMS data: today's curry name and the monthly special (name, image URL, price)

Deployment & security

Production stack

  • AWS EC2 instance (Ubuntu)
  • Nginx: reverse proxy, rate limiting, HTTPS
  • Gunicorn: 2–6 workers, internal port 8000
  • Cloudflare D1: all data, via a custom SQLAlchemy dialect talking HTTP to a Cloudflare Worker proxy (D1 isn't reachable outside the Workers runtime)
  • Certbot: SSL certificate with auto-renewal script
  • Cloudflare R2: images and PDFs, S3-compatible API
  • Domain: akbar.co.jp (confirmed in nginx.conf)

Authentication

  • Customers: phone + password (PBKDF2-SHA256, 310,000 iterations)
  • Admins: email + password, same hash algorithm
  • Super admin: 4-digit passcode
  • Delivery boys: PIN number
  • Sessions are signed cookies (Flask default), last 60 days
  • CSRF protection via Flask-WTF on all forms

Payment security

  • Card numbers never reach the Flask server
  • Stripe PaymentIntent API (industry standard approach)
  • Unique reference UUID per order prevents duplicate charges
  • Stripe commission (3.6%) tracked separately in dashboard
  • Auto-refund on cancellation via Stripe Refunds API

Environment variables

  • SECRET_KEY (Flask session signing)
  • STRIPE_SECRET_KEY, STRIPE_PUBLISHABLE_KEY
  • R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_ACCOUNT_ID, R2_BUCKET_NAME, R2_PUBLIC_URL
  • TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_VERIFY_SERVICE_ID
  • MAIL_USERNAME, MAIL_PASSWORD (Zoho SMTP)

このようなサイトをお考えですか?

オンライン注文、予約、ポイント機能を備えた飲食店のウェブサイトを制作しています。注文はお店に直接届きます。