上 Production 完整 Checklist

2026-05-18 · 老闆「上 production 要做的事情整個 codebase 看一看」audit · 共 6 大類 / 30 個切換點

先看這裡 — TL;DR

3 個 critical 必須切才能收真錢:
  • restaurant_settings.newebpay_environment = 'production'(每家餐廳獨立)
  • Supabase Functions env var APNS_ENVIRONMENT = 'production'(全域,影響 wallet push 跟通用 push)
  • Apple Wallet Pass APPLE_PASS_CERTIFICATE 必須是 production cert(非 sandbox)且 UID 跟 pass.com.chefsmate.loyalty 一致
4 個 high 上 production 前要先測: ezPay 電子發票 / Stripe 訂閱 webhook / Resend email 寄件人網域 / TapPay(目前未實際接但已寫 code)

當前老闆兩家餐廳實際 DB 狀態:海邊小巫 newebpay+ezpay 都 sandbox,溪邊小巫沒設定 newebpay/ezpay。

目錄
  1. A. 金流(藍新 / ezPay / TapPay) — per-restaurant DB 設定
  2. B. APNs / Apple Wallet — Supabase Functions env var
  3. C. OAuth(Apple / Google / LINE) — Supabase Functions env var
  4. D. Stripe 訂閱 — Vercel env
  5. E. Hardcoded chefsmate.app URLs
  6. F. iOS App 端 build / cert / TestFlight 注意事項
  7. G. 切換 Production 的完整 deploy 步驟

A. 金流 — Per-restaurant DB 設定(每家餐廳獨立切)

A1. 藍新金流(newebpay) — 訂金 + 退款

使用的 column:restaurant_settings.newebpay_environment('sandbox' / 'production')

檔案 / 模組當前 sandbox URL切 production 後 URL嚴重度
supabase/functions/newebpay-refund/index.ts
(剛 ship v3 sandbox fallback)
ccore.newebpay.com/API/CreditCard/Close core.newebpay.com/API/CreditCard/Close CRITICAL
supabase/functions/newebpay-test-connection/index.ts ccore.newebpay.com/API/QueryTradeInfo core.newebpay.com/API/QueryTradeInfo MEDIUM
web/booking/src/lib/newebpay/crypto.tsgetMpgUrl() ccore.newebpay.com/MPG/mpg_gateway core.newebpay.com/MPG/mpg_gateway CRITICAL
web/booking/src/app/api/newebpay/deposit/create/route.ts settings.newebpay_environment 自動選 URL ✓ LOW
web/booking/src/app/api/newebpay/create/route.ts(訂單付款) settings.newebpay_environment 自動選 URL ✓ LOW

切換步驟

A2. ezPay 電子發票

使用的 column:restaurant_settings.ezpay_environment('sandbox' / 'production')

檔案sandboxproduction
supabase/functions/ezpay-invoice/index.ts cinv.ezpay.com.tw inv.ezpay.com.tw
supabase/functions/ezpay-test-connection/index.ts cinv.ezpay.com.tw inv.ezpay.com.tw
web/booking/src/lib/ezpay/crypto.ts(5 endpoints) cinv.ezpay.com.tw inv.ezpay.com.tw

A3. TapPay 信用卡

使用的 env var(全域,非 per-restaurant):TAPPAY_ENVIRONMENT('sandbox' / 'production')

檔案sandbox URLproduction URL
supabase/functions/tappay-invoice/index.ts sandbox.tappaysdk.com prod.tappaysdk.com
supabase/functions/tappay-payment/index.ts sandbox.tappaysdk.com prod.tappaysdk.com
注意: web/booking/src/app/api/checkout/payments/[paymentId]/settle/route.ts 註解: Adapter 選擇:現金用 CashAdapter;其他方法 MVP 暫時也先用 Cash(等 TapPay/Newebpay config 從 env 進來再切換) — 即 TapPay 雖然有 edge function,但結帳流程實際上還沒接,目前所有非現金都走 Cash adapter。production 上線前需把 settle/reverse routes 真接 TapPay adapter。

B. APNs / Apple Wallet — Supabase Functions 全域 env var

B1. APNs 推播 — 老闆收訂位/取消/付款通知

env var:APNS_ENVIRONMENT = 'sandbox' / 'production'(Supabase Functions secret)

檔案預設值注意
send-push-notification 預設 sandbox 正式上線必切。production token 對 sandbox endpoint 會回 BadDeviceToken。
batch-update-wallet-passes 預設 production OK
update-wallet-pass 預設 production OK
create-wallet-offer 預設 production OK
send-user-silent-push 自動 fallback(sandbox → production) 不用切 — 每張 device token 兩個 endpoint 都試一次,BadDeviceToken 才 fail
send-permission-invalidation 自動 fallback(sandbox → production) 不用切
陷阱(已踩過): debug build / TestFlight 拿到的 APNs token 是 sandbox token,App Store build 是 production token
APNS_ENVIRONMENT 強制單一環境會打壞另一種 build 的 token → 已踩過。建議所有 push 函數都改自動 fallback 那條 path(像 send-user-silent-push 那樣)。

B2. Apple Wallet Pass 簽證 — 會員卡 / 集點卡

env var用途注意
APPLE_PASS_CERTIFICATE Pass signing certificate(PEM) 必須是 production cert,UID 跟 pass.com.chefsmate.loyalty 完全一致。已踩過「UID 不對就靜默失敗」
APPLE_PASS_KEY 對應 private key 同上
APPLE_WWDR_CERTIFICATE Apple WWDR intermediate cert 不分 sandbox/prod,但要用最新版(G4 in 2025+)
APPLE_LOYALTY_PASS_CERTIFICATE
APPLE_LOYALTY_PASS_KEY
替代 wallet cert(舊版?) 確認哪份 cert 實際被用
APNS_TOPIC passTypeIdentifier(預設 pass.com.chefsmate.loyalty) 必須跟 cert UID 一致
Team ID hardcoded 6T8L7SR6YF generate-wallet-pass/index.ts:273 切 team / 換開發者帳號要改

C. OAuth(Apple / Google / LINE) — Supabase Functions env var

影響 functions:oauth-link-merge / line-link-merge

env var用途備註
APPLE_SERVICES_ID Sign In with Apple 的 Services ID Apple Developer Portal 設定的 Service ID(non-app)
APPLE_SIGNIN_KEY_ID Sign In with Apple JWT key ID 同 Apple Developer Portal Keys
APPLE_SIGNIN_PRIVATE_KEY Sign In with Apple JWT private key(PEM) 10 個月會過期,production 上線要排程 rotate
APPLE_TEAM_ID Apple team ID 跟 hardcoded 6T8L7SR6YF 必須一致
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET Google OAuth Google Cloud Console 建立 production OAuth credentials;callback URL 加 chefsmate.app domain
LINE_CHANNEL_ID / LINE_CHANNEL_SECRET LINE Login LINE Developers Console 設 production channel + callback URL
GOOGLE_WALLET_ISSUER_ID
GOOGLE_WALLET_SERVICE_ACCOUNT_JSON
Google Wallet pass 申請 Google Pay & Wallet Console issuer account
CWA_API_KEY 中央氣象局 API 金鑰 免費申請,production 直接用就好

D. Stripe 訂閱(餐廳老闆訂閱 ChefsMate)

env var當前範例production
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY pk_test_xxx(來自 .env.local.example) pk_live_xxx
STRIPE_SECRET_KEY sk_test_xxx sk_live_xxx
STRIPE_WEBHOOK_SECRET whsec_test_xxx whsec_live_xxx(Stripe Dashboard → Webhooks → 對應 production endpoint 才會生)

E. Hardcoded chefsmate.app URLs

如果未來換 domain(例:chefsmate.tw),這些都要改:

檔案用途
supabase/functions/batch-update-wallet-passes/index.ts (×4)Wallet pass 內的連結(checkin / menu / booking / stamp image)
supabase/functions/send-deposit-received/index.ts BASE_URLemail 內連結
supabase/functions/send-reservation-confirmed/index.ts BASE_URLemail 內連結
supabase/functions/send-modification-squeeze-result/index.tsemail 連客人訂位頁
supabase/functions/send-cancellation-email/index.ts FROM_EMAIL寄件人 noreply@chefsmate.app
supabase/functions/send-staff-invitation-email/index.ts員工邀請信內連結 + APP_STORE_URL_PLACEHOLDER
supabase/functions/update-wallet-pass/index.tsstamp image URL
supabase/functions/generate-wallet-pass/index.tsPass body 內所有 URL
Apps/ChefsMatePOS/.../*.swift (10+ 處)POS 打 web API base URL

F. iOS App build / TestFlight / App Store 注意事項

項目當前狀態切 production 要做
Bundle ID com.ciro.ChefsMate 確認 Apple Developer Portal app ID + Provisioning Profile
APNS_TOPIC (push 用的 Bundle ID) 預設 com.ciro.ChefsMate 對應每個 app 的 Bundle ID(POS / Customer / Booking 各別)
APNs Auth Key (.p8) APNS_KEY_P8 env var 跟 Apple Developer Portal 一致的 production key
Sign in with Apple — Service ID APPLE_SERVICES_ID 確認對應 production domain
App Universal Links /.well-known/apple-app-site-association(vercel.json 有設 header) 內容要列 production app IDs

G. 切換 Production 的完整 deploy 步驟(建議順序)

  1. Domain:確認 chefsmate.app(或新 domain)DNS 指向 Vercel
  2. Vercel Env:Production scope 設 Stripe live keys、Resend API key、Supabase URL/key
  3. Supabase Functions secrets:
    • APNS_ENVIRONMENT=production
    • APNS_KEY_P8/ID/TEAM_ID = production
    • APPLE_PASS_* = production wallet cert
    • OAuth secrets(Apple / Google / LINE)= production app credentials
    • RESEND_API_KEY = live key, FROM_EMAIL = 已驗證網域
    • PUBLIC_WEB_BASE=https://chefsmate.app
  4. 金流(per-restaurant):每家上線餐廳分別在 POS 後台填 production 憑證 + 切環境
  5. Stripe Dashboard:Live mode webhook endpoint + 建 live mode prices,寫進 subscription_plans
  6. iOS app:Archive → App Store Connect 上傳;同時更新 TestFlight
  7. Wallet:確認 production cert UID 跟 pass.com.chefsmate.loyalty 一致(已踩過陷阱)
  8. 實測 smoke test:
    • 客人線上付訂金 1 元 → 收 push → POS 看到「訂金已收」
    • 客人取消 → POS 收 push + UI 自動刷新
    • POS 同意退款 → 藍新後台「信用卡請退款查詢」看到記錄(這次沙箱看不到,production 才能驗)
    • 餐廳老闆訂閱 1 個月最便宜方案 → webhook + 訂閱狀態
    • 客人加 Wallet 集點卡 → 更新一次 → 卡片 reload
已知陷阱(memory 已記):
  • Vercel Hobby plan cron 規定每天最多 1 次 → 已有 vercel.json 只設 1 cron(/api/credit/cron/expire-overdue 03:00),不要加
  • Apple Wallet UID 跟 passTypeIdentifier 不對會「靜默失敗」
  • Apple Sign In key 9 個月過期
  • Resend domain 沒驗證會被當垃圾信
ChefsMate Audit · 2026-05-18 · 老闆 dogfood 觸發