◆ wport | pm_40

company_pay 金流串接 PRD(橋外生人力銀行)

company_pay 金流串接

來源 doc/feature/pm_40/html/company-pay-subscription-payment-integration.md

company_pay 金流串接 PRD(橋外生人力銀行)

文件入口:company-pay.md

1. 文件資訊

  • 單號:company_pay
  • 類型:新功能(首次導入金流)
  • 範圍:apps/recruitment(橋外生雇主側)+ admin 後台 + packages/http-client + 後端新建 payment 服務
  • 最後更新:2026-06-22
  • 狀態:Draft(前後端對齊中)
  • 主要對象:前端、後端、PM、財務、法務

2. 商業背景與策略

橋外生人力銀行為 wport 平台底下的服務,為外國雇主 + 台灣雇主提供僑外籍學生人才搜尋與媒合服務。

本期定位:B2B 優先 + 先把金流跑通。付費方一律是企業(雇主),求職者不付費;B2C(求職者付費)明確延後到 Phase 2。本期不追求自助化、不追求漂亮的訂閱漏斗,只求以下兩條 B2B 路徑能穩定收到錢:

  1. 企業訂閱(雇主自助) — 雇主自己選方案、自己付款(單一方案、月扣為主)
  2. Admin 自定義收款(業務開單) — 業務談好的客製合約,由 admin 後台建立付款連結寄給客戶

商業模式優先序:B2B(雇主付費)為本期唯一範圍 → B2C(求職者付費,如履歷加值、報告下載)為 Phase 2 → 平台級多服務共用金流為更後期。

未來改版時會擴充訂閱漏斗(試用、升降級、折扣碼、多方案矩陣),本 PRD 預留抽象但不實作。

3. 變更範圍

3.1 In Scope(MVP / 本期金流通)

企業訂閱(自助)

  • 單一方案訂閱購買(月扣 / 年扣,方案數量少、不做矩陣對比)
  • 自動續扣 + 失敗重試(dunning)
  • 訂閱管理頁(查看狀態、取消、更換卡片)
  • 付款失敗通知與更新卡片流程

Admin 自定義收款(業務 / 後台)

  • Admin 後台建立「自定義收款單」(金額、品名、付款人 email、發票資訊)
  • 系統產生付款連結(hosted page)+ 寄 email 給客戶
  • 客戶點連結 → 跳轉金流付款 → 完成
  • Admin 後台查看收款狀態、補寄連結、作廢單據
  • 一次性收款為主(不綁訂閱)

共用基礎

  • 雙軌金流(Stripe + 綠界)
  • 雙發票(台灣電子發票 + 海外 invoice)
  • PaymentProvider 抽象層
  • Webhook 接收與對帳

3.2 Out of Scope(Phase 2 / 改版後)

  • 免費試用期(綁卡或不綁卡)
  • 方案升級 / 降級(proration)
  • 折扣碼 / 優惠券
  • 多幣別顯示(初期僅 TWD)
  • 點數 / 儲值制
  • 一次性購買(單筆職缺、單筆履歷下載)
  • 求職者付費功能
  • 退款自助操作(初期由客服處理)
  • 統編開立紙本發票
  • 行動裝置 RWD(依現有 apps/recruitment 桌機優先策略)
  • Admin 自定義收款支援「分期」或「轉訂閱」

3.3 設計原則:抽象不實作

雖然 Phase 2 功能不做,但抽象層必須預留,避免未來重寫:

  • 後端 PaymentProvider interface 已涵蓋 trial / proration 的 API 形狀
  • 資料庫 subscription 表預留 trial_endpending_plan_change 欄位(允許 NULL)
  • 前端 subscription store 的 type 涵蓋 trialing 狀態(但 UI 不顯示)
  • 訂閱狀態機定義完整 5 個狀態(但本期只會出現 active / past_due / unpaid / canceled)

4. 非範圍與假設

  • 假設:公司已具備台灣商業登記,可申請綠界商家與 Stripe Taiwan 帳號
  • 假設:所有收款進台灣公司戶頭,Stripe 收外幣自動換 TWD(接受約 1% 匯損)
  • 假設:初期不開海外公司,海外營收達 USD 50,000/月再評估
  • 假設:橋外生求職者不付費,付費角色僅雇主

5. 用戶角色與場景

5.1 角色

角色說明付款方式
海外雇主在台灣以外註冊的企業Stripe(信用卡、支援 3DS)
台灣雇主在台灣註冊的企業綠界(信用卡、ATM、超商)
橋外生求職者外籍學生不付費
wport 業務跟客戶談客製合約的內部人員透過 admin 後台開單
wport 客服 / 財務後台處理退款、查詢、開發票補發透過 admin 後台操作

5.2 核心場景

訂閱類(自助)

  1. 海外雇主首次訂閱:選方案 → Stripe Checkout → 立即扣款 → 收到英文 invoice email
  2. 台灣雇主首次訂閱:選方案 → 綠界結帳 → 立即扣款 → 收到電子發票
  3. 續扣失敗:第 1/3/5/7 天自動重試 → 同時寄 email 提醒更新卡片 → 第 7 天仍失敗則暫停服務
  4. 取消訂閱:本期到期後生效,不退費

Admin 自定義收款類: 5. 業務開單收款(一次性):業務談好 NT$200,000 客製方案 → admin 後台填單(金額、品名、客戶 email、發票資訊、選擇金流)→ 系統產生 hosted 付款連結 → email 寄給客戶 → 客戶點連結付款 → 完成後自動開發票 6. 補寄付款連結:客戶弄丟 email → admin 後台找到該筆單據 → 點「補寄」→ 系統重寄相同連結 7. 作廢收款單:客戶決定不買 / 談新條件 → admin 後台作廢未付款單據(已付款的不可作廢,須走退款流程) 8. 查詢與對帳:財務在 admin 後台查特定日期區間的收款明細,匯出 CSV

6. 現況對齊(Project Impact Snapshot)

6.1 Frontend(已存在)

  • apps/recruitment 已有 Vite SPA + PrimeVue + Zod 架構
  • packages/http-client 提供 axios wrapper,已有 token 管理
  • packages/auth-session 提供登入狀態,需與訂閱狀態解耦(訂閱過期 ≠ 登入過期)
  • packages/i18n 已建立 zh-TW / en-US 對應,本次需新增 payment.* 命名空間

6.2 Frontend(需新增)

  • 新路由:/billing/plans/billing/checkout/:planId/billing/subscription/billing/invoices/billing/payment-method
  • 新 API 模組:src/api/payment/index.ts
  • 新狀態 store:src/stores/subscription.ts(Pinia,使用 packages/store-core
  • 新元件:PlanCardSubscriptionStatusBannerInvoiceListPaymentMethodCardTrialCountdown
  • 新 guard:路由前置守衛,訂閱過期時導向方案頁但不登出

6.3 Backend(尚未開工,本 PRD 同步定義契約)

  • 新建 payment-service(建議獨立微服務,因 webhook 流量與業務 API 解耦較安全)
  • 整合 Stripe SDK 與綠界 SDK
  • 整合 ezPay(或綠界)電子發票
  • Webhook 端點接收 Stripe / 綠界事件
  • 訂閱狀態與發票記錄 DB schema
  • Dunning 流程排程(cron 或 queue)
  • 對帳作業(每日比對金流方與本地紀錄)

7. 收費模式設計

7.1 訂閱方案(範例,最終由 PM 定價)

方案月費 (TWD)年費 (TWD)主要權限
Basic3,00030,000月可發布 5 個職缺,搜尋 50 份履歷
Pro8,00080,000月可發布 20 個職缺,搜尋 200 份履歷,AI 推薦
Enterprise客製客製走 admin 自定義收款

設計原則

  • 年費為月費的 10 倍(等於送 2 個月,業界慣例)
  • 本期不做免費試用(Phase 2 再加),訂閱即扣款
  • 不做自助升降級(Phase 2 再加),客戶要換方案請聯絡客服 → 客服走 admin 自定義收款 / 退款
  • Enterprise 一律走 admin 自定義收款

7.2 Admin 自定義收款

業務 / 客服在後台手動建立的收款單,欄位如下:

欄位必填說明
收款品名顯示在發票與付款頁面(如「橋外生人才搜尋 1 年方案」)
金額整數 TWD(初期不支援外幣,海外客戶仍收 TWD 走 Stripe 換匯)
客戶 email寄付款連結用
客戶公司名開發票用
統編條件台灣公司必填,海外免填
客戶國別決定走 Stripe(非 TW)或綠界(TW)
備註內部用,不顯示給客戶
到期日付款連結失效日(預設 7 天)
關聯帳號若指定,付款成功後該帳號開通對應權限(否則僅收款不開通,由業務手動處理)

8. 金流選型與分流邏輯

8.1 分流規則

使用者選方案後 →
  判斷使用者帳戶屬性 company.country
    ├─ country === 'TW'    → 走綠界(含台灣電子發票)
    ├─ country !== 'TW'    → 走 Stripe(英文 invoice)
    └─ 未填 country        → 結帳前強制要求填寫

8.2 為何雙軌

需求Stripe綠界
海外信用卡支援✅ 強❌ 拒付率高
台灣電子發票❌ 無法✅ 內建
訂閱續扣(recurring)✅ 內建完整✅ 定期定額
Proration(升降級按比例)✅ 內建❌ 需自行計算
Smart Retries✅ 內建❌ 需自行實作
Webhook 穩定性✅ 高⚠️ 需重試機制
ATM / 超商付款❌ 無✅ 有(但訂閱不適用)

結論:海外用 Stripe、台灣用綠界,雙軌不可避免。

8.3 抽象層設計

後端必須抽象 PaymentProvider interface,前端 API 對外只暴露統一介面,不暴露 Stripe / 綠界差異:

interface PaymentProvider {
  // 訂閱
  createSubscription(input: CreateSubscriptionInput): Promise<SubscriptionResult>
  cancelSubscription(subscriptionId: string, mode: 'immediate' | 'at_period_end'): Promise<void>
  updatePaymentMethod(customerId: string): Promise<PaymentMethodUpdateUrl>

  // 一次性收款(admin 自定義單)
  createOneTimeCharge(input: CreateChargeInput): Promise<ChargeResult>  // 回傳 hosted url
  voidCharge(chargeId: string): Promise<void>  // 作廢未付款單

  // 共用
  retrieveInvoice(invoiceId: string): Promise<Invoice>

  // Phase 2 預留(不實作)
  // changePlan(subscriptionId: string, newPlanId: string): Promise<ChangePlanResult>
  // createTrialSubscription(...)
}

前端不該知道「這筆是 Stripe 還是綠界」。

9. 功能流程

9.1 首次訂閱(自助)

flowchart TD
    A[雇主進入方案頁 /billing/plans] --> B{已登入?}
    B -->|否| C[導向登入]
    B -->|是| D[選擇方案]
    D --> E{填寫公司國別}
    E -->|TW| F[綠界結帳流程]
    E -->|非 TW| G[Stripe Checkout]
    F --> H[後端建立綠界訂單 + 跳轉]
    G --> I[後端建立 Stripe Checkout Session + 跳轉]
    H --> J[綠界 hosted page 付款]
    I --> K[Stripe hosted page 付款]
    J --> L[Webhook: payment_succeeded]
    K --> L
    L --> M[後端建立 subscription 記錄 status=active]
    M --> N[開立發票 + 寄通知 email]
    N --> O[前端導回 /billing/subscription?status=success]

9.2 Admin 自定義收款

flowchart TD
    A[業務談妥客製方案] --> B[admin 後台 /admin/billing/charges/new]
    B --> C[填寫: 品名/金額/email/國別/到期日]
    C --> D{選擇金流}
    D -->|TW 客戶| E[後端建立綠界一次性訂單]
    D -->|海外客戶| F[後端建立 Stripe Payment Link]
    E --> G[產生付款連結 + 寄 email 給客戶]
    F --> G
    G --> H[客戶收到 email 點連結]
    H --> I[Stripe / 綠界 hosted page 付款]
    I --> J[Webhook: payment_succeeded]
    J --> K[後端更新收款單狀態 → paid]
    K --> L[自動開發票]
    L --> M{有關聯帳號?}
    M -->|是| N[自動開通對應權限]
    M -->|否| O[僅收款,由業務手動處理]
    L --> P[email 通知客戶付款成功 + 發票]

狀態機(自定義收款單):

draft → pending → paid

           expired (到期未付)

            voided (admin 主動作廢)

9.3 [Phase 2] 試用、升降級

本期不實作,預留章節避免漏掉。詳細設計參見舊版 v0.1 草稿(已從本 PRD 移除)。

9.4 續扣失敗(Dunning)

flowchart TD
    A[扣款失敗] --> B[第 0 天: 寄 email + 站內通知]
    B --> C{Stripe Smart Retries 排程}
    C --> D[第 3 天自動重試]
    D -->|成功| Z[恢復 active]
    D -->|失敗| E[第 5 天自動重試]
    E -->|成功| Z
    E -->|失敗| F[第 7 天最後重試]
    F -->|成功| Z
    F -->|失敗| G[status: active → past_due → unpaid]
    G --> H[暫停服務但保留帳號 30 天]
    H --> I[期間使用者可主動更新卡片觸發重試]
    I --> J{30 天內更新?}
    J -->|是| Z
    J -->|否| K[status: unpaid → canceled]

10. 前端頁面清單

10.1 雇主側(apps/recruitment

路由頁面名稱用途
/billing/plans方案選擇頁方案卡片、月/年切換、行動按鈕
/billing/checkout/:planId結帳跳轉頁確認資訊、呼叫後端取得 Stripe/綠界跳轉 URL
/billing/checkout/success付款成功頁顯示訂閱開始、導引到下一步
/billing/checkout/cancel付款取消頁用戶取消 hosted page 時導回
/billing/subscription訂閱管理頁目前方案、續期日、取消按鈕
/billing/payment-method付款方式頁顯示卡片末四碼、更換卡片按鈕
/billing/invoices發票/收據列表歷史扣款、下載發票 PDF
/billing/pay/:token自定義收款付款頁客戶從 email 點進來,顯示金額品名後跳金流 hosted page

10.2 Admin 後台(路由依現有 admin 架構而定)

路由頁面名稱用途
/admin/billing/charges自定義收款單列表篩選狀態、金額區間、客戶、日期
/admin/billing/charges/new建立收款單§7.2 的所有欄位
/admin/billing/charges/:id收款單詳情狀態、付款記錄、補寄、作廢、退款(連結客服 SOP)
/admin/billing/subscriptions所有訂閱列表雇主訂閱狀態總覽,可手動取消、查看歷史
/admin/billing/invoices所有發票列表全平台發票,可重發、查問題
/admin/billing/reconciliation對帳頁顯示對帳差異、可手動標記處理

Admin 後台位置:本 PRD 不指定 admin 後台的程式碼位置,請後端 / 全端團隊評估是放在現有後台、獨立 admin app、或 retool 等低程式碼工具。前端工作量可大可小,取決於這個決定。

10.3 共用元件(雇主側)

  • <PlanCard :plan /> — 方案卡片
  • <SubscriptionStatusBanner /> — 全站頂部訂閱狀態提醒(付款失敗、未付款)
  • <InvoiceListItem :invoice /> — 發票列表項
  • <PaymentMethodCard :method /> — 卡片資訊顯示
  • <CancelSubscriptionDialog /> — 取消訂閱確認對話框
  • <CustomChargePayPanel :token /> — 自定義收款付款頁主面板(顯示金額、品名、客戶資訊確認)

10.4 路由守衛

// router/guards/subscription.ts
beforeEach((to, from) => {
  const sub = useSubscriptionStore()

  if (to.meta.requiresActiveSubscription) {
    if (sub.status === 'unpaid' || sub.status === 'canceled') {
      return { path: '/billing/plans', query: { reason: 'subscription_required' } }
    }
    if (sub.status === 'past_due') {
      return { path: '/billing/payment-method', query: { reason: 'update_required' } }
    }
  }
})

注意

  • 訂閱狀態與登入狀態解耦。訂閱過期不可登出使用者,否則無法引導回方案頁。
  • /billing/pay/:token 路徑不需要登入(客戶可能不是平台用戶),純粹用 token 驗證。

11. API 契約(前後端介面)

所有 API 對前端統一介面,內部 provider 差異由後端封裝。

11.1 取得方案列表

GET /api/v1/billing/plans
Response:
{
  "plans": [
    {
      "id": "basic-monthly",
      "name": "Basic",
      "interval": "month",
      "price": { "amount": 3000, "currency": "TWD" },
      "features": ["5 job posts/month", "50 resume search"],
      "trial_days": 14
    },
    ...
  ]
}

11.2 建立 Checkout Session

POST /api/v1/billing/checkout
Body:
{
  "plan_id": "basic-monthly",
  "success_url": "https://wport.me/recruitment/billing/checkout/success",
  "cancel_url": "https://wport.me/recruitment/billing/checkout/cancel"
}
Response:
{
  "checkout_url": "https://checkout.stripe.com/c/pay/cs_xxx",
  "provider": "stripe",  // 或 "ecpay"
  "session_id": "cs_xxx"
}

前端拿到 checkout_urlwindow.location.href = checkout_url 跳轉。不可開新分頁(iOS Safari 會擋)。

11.3 取得目前訂閱

GET /api/v1/billing/subscription
Response:
{
  "id": "sub_xxx",
  "status": "trialing" | "active" | "past_due" | "unpaid" | "canceled",
  "plan": { "id": "basic-monthly", "name": "Basic", ... },
  "current_period_start": "2026-06-22T00:00:00Z",
  "current_period_end": "2026-07-22T00:00:00Z",
  "trial_end": "2026-07-06T00:00:00Z" | null,
  "cancel_at_period_end": false,
  "pending_plan_change": null | { "plan_id": "pro-monthly", "effective_at": "..." },
  "payment_method": {
    "type": "card",
    "brand": "visa",
    "last4": "4242",
    "exp_month": 12,
    "exp_year": 2027
  }
}

11.4 [Phase 2] 變更方案

本期不實作,預留 API path:POST /api/v1/billing/subscription/change-plan

11.5 取消訂閱

POST /api/v1/billing/subscription/cancel
Body:
{
  "mode": "at_period_end" | "immediate"  // 預設 at_period_end
}
Response:
{
  "status": "canceled" | "active",  // immediate=canceled, at_period_end=active 但 cancel_at_period_end=true
  "ends_at": "2026-07-22T00:00:00Z"
}

11.6 更新付款方式

POST /api/v1/billing/payment-method/update-session
Response:
{
  "update_url": "https://billing.stripe.com/p/session/xxx",
  "provider": "stripe"
}

前端跳轉到 update_url,使用者更新完後跳回 /billing/payment-method?status=updated

11.7 發票列表

GET /api/v1/billing/invoices?page=1&limit=20
Response:
{
  "invoices": [
    {
      "id": "inv_xxx",
      "number": "INV-2026-0001",
      "amount": 3000,
      "currency": "TWD",
      "status": "paid",
      "issued_at": "2026-06-22T00:00:00Z",
      "pdf_url": "https://...",
      "invoice_type": "ecpay_tax_invoice" | "stripe_receipt",
      "tax_invoice_number": "AB12345678"  // 僅綠界電子發票
    }
  ],
  "total": 12
}

11.8 自定義收款 API(雇主側 + admin 後台)

客戶付款頁(公開,token 驗證)

GET /api/v1/billing/charges/by-token/:token
Response:
{
  "id": "ch_xxx",
  "status": "pending" | "paid" | "expired" | "voided",
  "description": "橋外生人才搜尋 1 年方案",
  "amount": 200000,
  "currency": "TWD",
  "customer_email": "ceo@example.com",
  "customer_company": "Example Inc.",
  "expires_at": "2026-06-29T23:59:59Z"
}

POST /api/v1/billing/charges/by-token/:token/checkout
Response:
{
  "checkout_url": "https://checkout.stripe.com/c/pay/cs_xxx",
  "provider": "stripe" | "ecpay"
}

Admin 後台

POST /api/v1/admin/billing/charges
Body:
{
  "description": "橋外生人才搜尋 1 年方案",
  "amount": 200000,
  "currency": "TWD",
  "customer_email": "ceo@example.com",
  "customer_company": "Example Inc.",
  "customer_tax_id": "12345678",  // 統編,可選
  "customer_country": "TW",
  "internal_note": "業務 Alice,2026 Q3 案",
  "expires_at": "2026-06-29T23:59:59Z",
  "linked_account_id": "user_xxx"  // 可選
}
Response:
{
  "id": "ch_xxx",
  "pay_url": "https://pay.wport.me/abc123token",
  "status": "pending",
  ...
}

GET /api/v1/admin/billing/charges?status=&from=&to=&q=
POST /api/v1/admin/billing/charges/:id/resend-email
POST /api/v1/admin/billing/charges/:id/void

11.9 錯誤碼

CodeHTTP說明前端處理
subscription_already_exists409已有 active 訂閱導向訂閱管理頁
plan_not_found404方案不存在導回方案頁
payment_method_required402需先設定付款方式跳付款方式頁
provider_error502Stripe/綠界回應錯誤顯示通用錯誤,請使用者稍後再試
country_required400未填公司國別跳出國別選擇對話框
charge_expired410自定義收款單已過期顯示「連結已過期,請聯絡業務重新開立」
charge_already_paid409自定義收款單已付款顯示「此單據已完成付款」
charge_voided410自定義收款單已作廢顯示「此單據已作廢」

12. 訂閱狀態機

            ┌──────────┐
   建立 →   │  active  │  ←──┐
            └────┬─────┘     │ 重試成功 / 更新卡片
                 │ 扣款失敗  │
                 ▼            │
            ┌──────────┐     │
            │ past_due │─────┘
            └────┬─────┘
                 │ 7 天重試全部失敗

            ┌──────────┐
            │  unpaid  │
            └────┬─────┘
                 │ 30 天未補繳 / 主動取消

            ┌──────────┐
            │ canceled │  (終態)
            └──────────┘

(預留: trialing 狀態,Phase 2 使用)

前端對應行為

  • trialing → 預留,本期不會出現
  • active → 正常使用
  • past_due → 顯示紅色 banner「付款失敗,請更新卡片」,功能仍可用(grace period)
  • unpaid → 唯讀模式,所有付費功能擋下,僅可進入 /billing/*
  • canceled → 同 unpaid,但可重新訂閱

13. [Phase 2] 試用期邏輯

本期不實作。Phase 2 規劃時需決定:

  • 綁卡 vs 不綁卡試用(建議綁卡,業界轉換率與詐騙風險平衡)
  • 試用天數(建議 14 天)
  • 是否限制同一公司只能試用一次(後端 has_used_trial flag)

14. [Phase 2] 升降級 Proration

本期不實作,客戶需要換方案請走客服 → admin 自定義收款 / 退款流程。Phase 2 規劃時參考:

  • Stripe subscription.update 內建 proration_behavior: 'create_prorations'
  • 綠界需後端自行計算剩餘天數差額
  • UI 需在對話框顯示「將立即扣款 NT$X」或「將於 YYYY-MM-DD 起以新方案扣款」

15. 發票處理

15.1 綠界電子發票(台灣雇主)

  • 後端在 payment_succeeded webhook 中呼叫綠界發票 API
  • 開立兩聯式(個人)或三聯式(公司,需統編)
  • 雇主在訂閱前必須填寫:公司名、統編、發票寄送 email
  • 發票 PDF 由綠界產生,後端儲存 URL

15.2 Stripe Invoice(海外雇主)

  • Stripe 自動產生 PDF invoice,email 給使用者
  • 內容包含公司名、地址、金額、稅務說明
  • 後端只需儲存 invoice ID 與 PDF URL,不需自行渲染

15.3 統一前端呈現

/billing/invoices 頁面對前端只看到統一格式,不暴露 provider 差異:

type Invoice = {
  id: string
  number: string
  amount: number
  currency: string
  status: 'paid' | 'open' | 'void'
  issued_at: string
  pdf_url: string
  invoice_type: 'ecpay_tax_invoice' | 'stripe_receipt'
  tax_invoice_number?: string  // 僅綠界
}

16. Webhook 與資料同步(後端責任,前端無感)

16.1 Stripe Webhook 必須處理的事件

事件動作
checkout.session.completed訂閱:建立 subscription;一次性:更新 charge 為 paid
customer.subscription.created設定 status
customer.subscription.updated同步 status
customer.subscription.deletedstatus → canceled
invoice.payment_succeeded開立發票、寄通知
invoice.payment_failed進入 dunning
payment_intent.succeeded一次性收款付款成功(admin 自定義單)
customer.subscription.trial_will_end[Phase 2] 寄試用即將結束 email

16.2 綠界 Webhook

綠界 webhook 穩定性較差,後端必須:

  • 接收後立即回 1|OK(綠界規範)
  • 用 message queue 非同步處理
  • 同一筆訂單冪等處理(避免重複)
  • 每日跑對帳排程比對綠界後台與本地 DB

16.3 對帳機制

  • 每日 02:00 拉前一天 Stripe / 綠界交易,比對本地 DB
  • 不一致時寄 alert 給工程與財務
  • 對帳結果存 payment_reconciliation table

17. 安全與合規(必讀)

17.1 PCI DSS

  • 前端絕對不能收集卡號
  • 卡號輸入一律走 Stripe Checkout(hosted page)或綠界 hosted page
  • 若未來需要嵌入式體驗,必須使用 Stripe Elements / 綠界 SDK(卡號送到第三方 iframe,不經過本站伺服器)
  • 違反 PCI DSS 罰款最高每月 USD 100,000,且銀行可終止商戶帳號

17.2 機密管理

機密存放位置前端可見?
Stripe Secret Key後端環境變數
Stripe Publishable Key後端配置 / 前端 build-time 環境變數
綠界 HashKey / HashIV後端環境變數
Webhook Signing Secret後端環境變數

17.3 Webhook 簽章驗證

所有 webhook 必須驗證簽章,否則攻擊者可偽造 webhook 觸發業務邏輯。

17.4 退款政策

  • 退款一律由客服後台處理,前端不提供使用者自助退款
  • 退款規則由 PM 與法務確認後寫入 ToS

18. 監控與告警

18.1 關鍵指標

指標告警閾值
Checkout 成功率< 80% 連續 1 小時
Webhook 處理成功率< 99% 連續 1 小時
續扣失敗率> 5% 單日
Stripe / 綠界 API 5xx 率> 1% 連續 15 分鐘
對帳差異筆數> 0 即告警

18.2 Logs

  • 所有金流相關請求與 webhook 必須記錄完整 payload(注意脫敏卡號)
  • 保留至少 1 年供稽核

19. 里程碑與時程(3 個月,目標「金流通」)

Sprint 1(Week 1-2):規劃與帳號準備

  • 前後端對齊本 PRD
  • 申請 Stripe Taiwan 商戶帳號
  • 申請綠界商戶帳號
  • 申請電子發票(ezPay 或綠界)
  • DB schema 設計(含 Phase 2 預留欄位)
  • PaymentProvider interface 定義
  • Admin 後台位置決策(既有後台 / 新 app / 低程式碼)

Sprint 2-3(Week 3-6):後端核心

  • PaymentProvider 抽象層(訂閱 + 一次性收款)
  • Stripe 整合(checkout、subscription、payment link、webhook)
  • 綠界整合(定期定額、一次性訂單、webhook)
  • 電子發票整合
  • 訂閱狀態機與 DB
  • 自定義收款單 CRUD + 寄信
  • 前後端 API 串接

Sprint 4(Week 7-8):雇主側前端

  • 路由與 store 建置
  • 方案頁、結帳頁、訂閱管理頁
  • 發票列表、付款方式管理
  • 自定義收款付款頁(/billing/pay/:token
  • 路由守衛、狀態 banner
  • i18n(zh-TW、en-US)

Sprint 5(Week 9-10):Admin 後台

  • 自定義收款單列表、建立、詳情頁
  • 訂閱列表、發票列表
  • 對帳頁
  • 補寄、作廢、查詢功能

Sprint 6(Week 11-12):Dunning、對帳、上線準備

  • Dunning 流程(後端)
  • 對帳排程(後端)
  • 監控告警
  • 客服 SOP 文件(退款、補單、查詢)
  • 法務審 ToS / 退款政策
  • E2E 測試(Cypress)
  • Beta 內測

20. 風險與緩解

風險影響緩解
綠界 webhook 不穩訂閱狀態不同步對帳排程 + 主動 polling
Stripe Taiwan 申請被拒海外金流卡關提早申請(4 週緩衝),備案改 PayPal
法律 ToS 未準備好上線延期Sprint 1 同步啟動法務作業
PCI DSS 不熟導致誤收卡號巨額罰款Code review 強制檢查卡號相關欄位
海外雇主拒付 (chargeback)收入損失 + 罰款啟用 Stripe Radar 風控
匯損超出預期毛利下降監控匯率,必要時啟動雙幣戶頭

21. Open Questions(待確認)

  • PM:實際方案定價與權限對應(本 PRD 為範例)
  • PM:是否需要年費自動續訂提前通知(GDPR / 加州法規要求)
  • 法務:ToS 與退款政策草稿
  • 財務:發票抬頭、稅務處理(是否要報營業稅)
  • 老闆:台灣公司商業登記是否已完成
  • 後端:是否獨立 payment-service 微服務 vs 併入既有 monolith
  • 設計師:完整 Figma 流程(方案頁、結帳頁、訂閱管理頁)
  • 客服:退款 SOP 與後台需求

22. 參考資料


前端團隊 TL;DR

雇主側做 §10.1 的 8 個頁面、串 §11 的 API、掛上 §12 的訂閱狀態機(簡化版,無 trialing)、路由守衛照 §10.4 寫。Admin 後台做 §10.2 的 6 個頁面(或評估用低程式碼工具)。所有卡號相關 UI 絕對交給 Stripe / 綠界 hosted page,自己不要收。試用 / 升降級本期不做,但 store 與 type 預留欄位。預估前端工作量約 4 個 sprint(雇主側 2 + admin 2)。

後端團隊 TL;DR

PaymentProvider 抽象(§8.3)涵蓋訂閱 + 一次性收款、Webhook(§16)、訂閱狀態機(§12)、Dunning(§9.4)、對帳(§16.3)、電子發票整合(§15)、自定義收款單系統(§7.2 + §9.2 + §11.8)。預估後端工作量約 6 個 sprint,建議獨立 payment-service 微服務。

本期 MVP 不做但要預留的事

  • 試用期(DB 預留 trial_end 欄位、PaymentProvider 預留 method、subscription type 預留 trialing 狀態)
  • 升降級(DB 預留 pending_plan_change、PaymentProvider 預留 changePlan method)
  • 折扣碼、多幣別、點數、自助退款

版本歷史

  • v0.2(2026-06-22):縮減 MVP 範圍,移除試用 / 升降級到 Phase 2,新增 admin 自定義收款
  • v0.1(2026-06-22):初版,含完整訂閱漏斗(試用、升降級、proration)