Stripe Connect 與訂閱計費
同一 Stripe 帳戶上的兩種金流。一個市集向買方收單一次,再將款項分配給多位賣家(Connect,分離收付款轉帳)。一個平台訂閱則離線向那些賣家收取週期性費用(Billing)。兩者共用一個客戶端、一組 Webhook 和一種貨幣型別。
決定一切的核心洞察:**買方的請款與賣家的付款是獨立失敗的。**到結算流程執行時,資金早已流動,因此被拒絕的轉帳絕不能導致請款或 Webhook 失敗,並且必須能在日後重新觸發,而不會重複付款。
何時使用
- 一筆付款需拆分給多位賣家,平台從中抽成。
- 賣家需入門 Connect,並於延遲後付款(託管、準備金、KYC)。
- 平台使用賣家儲存的卡片定期向其收取訂閱費用。
- 關聯帳戶看起來正常,但從未實際收款。
何時不該使用
| 非此情況 | 請改用 |
|---|---|
| 單一賣家結帳,無拆分 | 直接使用 Stripe Checkout / Payment Element |
| PayPal 市集入門 + 付款 | PayPal 技能;僅共享對接部分 |
| 卡片 UI、Payment Element 樣式 | 您的設計系統;此技能為伺服器端 |
架構
買方 ──請款(平台帳戶,transfer_group = orderId)──▶ 平台餘額
│ 結算分配(Webhook 或讀取同步;租約式聲明)
┌────────────┴──┬────────────────────────┬────────────────┐
▼ ▼ ▼ ▼
轉帳:賣家 轉帳:合作夥伴 託管保留 滾動準備金
(source_transaction = 該筆請款) (釋放時間) (% 扣留)
│ │ 釋放託管定時任務
▼ ▼
關聯帳戶餘額 ◀── 轉帳重試 應付餘額 ──▶ 付款(受 KYC/風險控管)
(未入帳部分)
平台 ──(off_session)PaymentIntent(客戶 + 已儲存付款方式)──▶ 訂閱發票
催收:3 次嘗試,每次間隔 3 天,逾期則暫停
關鍵事實
- **分離收付款轉帳,絕不使用目的地扣款。**一筆請款只能有一個
destination;多賣家購物車需要一個transfer_group下的多次轉帳,因此application_fee_amount無法使用。 - 沒有
source_transaction的轉帳會從可用餘額扣除,在請款尚未結算時會被拒絕;對於新平台帳戶來說,每一次轉帳都會如此。這個標誌正是讓資金能在請款結算前順利分配的原因。 - **Stripe 的跨境轉帳僅限於美/加/加/歐盟/瑞士/英國內。**其他地區的平台只能向本國關聯帳戶付款。Stripe 會在轉帳時(而非入門時)強制執行此規則,因此範本會在入門階段套用地區限制。
- **Connect 事件的簽章金鑰與平台事件不同。**需要兩個端點、兩個密鑰,但同一個 URL — 需同時驗證兩者。
- 關聯帳戶的國家/地區不可變更 — 國家錯誤意味著需要重新入門,而非修補。
- 金錢絕不使用浮點數 — 全程使用
numeric(19,4)字串、bigint運算,僅在 SDK 邊界使用最小單位。
硬性規則
**絕不讓失敗的轉帳導致結算失敗。**將該筆記錄為未入帳並繼續處理。請款已成功,因此 Webhook 也必須成功;重試定時任務稍後會補上該筆款項。
**絕不將重複的 Webhook 插入視為「已處理」。**插入操作是一個聲明;若處理途中崩潰而重試,則每個事件都必須完整執行一次。
**絕不在未向 Stripe 確認某轉帳是否已存在的情況下重新發送。**若因回應遺失而記錄為未入帳,一旦 Stripe 的 24 小時等冪窗口到期,就會重複付款。
**絕不依賴在結尾寫入的狀態標誌來獲得結算獨佔性。**兩個呼叫者常同時到達結算;原子性的租約式聲明才能確保庫存和轉帳只執行一次。
**絕不沖銷超過轉帳剩餘額度的資金。**累積的沖銷(先付閘道費,再退款)若超過原始金額,將會被拒絕。
快速入門
- 建立金錢模型 — money.md、data-model.md、store.md。
- 客戶端、雙 Webhook 密鑰、環境變數 — stripe-adapter.md。
- 入門帳戶;先檢查地區規則 — connect-accounts.md。
- 等冪接收事件 — webhooks.md。
- 結算:聲明、分配、託管、準備金 — settlement.md。
- 收斂:重試未入帳部分、調節費用 — reconciliation.md。
- 向租戶計費 — subscriptions.md;使用 operations.md 執行。
根據 adaptation.md 將其適配到您的應用;審計記錄在 provenance.md 中。
適配合約
| 介面 | 技能提供 | 宿主提供 |
|---|---|---|
| 領域實體 | Tenant(賣家組織)、Order、VendorOrder、Partner | 自己的詞彙 |
| 租戶範圍 | 一個伺服器派生的 tenantId | 組織 / 工作區 / 賣家 |
| 授權守衛 | 每個路由的適配器函數簽名 | Clerk、NextAuth、Supabase、自訂 |
| 資料存取 | PaymentsStore 合約 + SQL | 自己的 ORM 或 SDK |
| 審計與通知 | 呼叫點、事件名稱、負載形狀 | 自己的審計表 / outbox |
| 背景任務 | 4 個定時任務及其頻率 | 自己的排程器 |
| 驗證 | 每個路由主體的 schema 形狀 | zod / valibot / yup |
| UI | 無 — 此技能為伺服器端 | 全部 UI |
參考文檔目錄
| 場景 | 觸發關鍵字 | 參考文檔 |
|---|---|---|
| 為何採用此金錢模型 | 分離收付款轉帳、目的地扣款、application_fee_amount、transfer_group、帳簿 | architecture.md |
| 資料表、欄位、RLS | schema、遷移、payment_intents、transfers、escrow_holds、webhook_events | data-model.md |
| 資料存取合約 | store、repository、原子聲明、條件更新、ORM | store.md |
| 金額與四捨五入 | numeric、bigint、最小單位、分配、最大餘數、分 | money.md |
| 客戶端與適配器程式碼 | stripeClient、apiVersion、constructEvent、whsec_、PaymentIntent、refund、payout | stripe-adapter.md |
| 入門、帳戶狀態 | Express、Accounts v2、account.updated、charges_enabled、acct_、country、region | connect-accounts.md |
| 安全接收事件 | 等冪、重複、重播、原始請求主體、簽章、invalid_signature | webhooks.md |
| 分割已付款訂單 | 結算、分配、source_transaction、託管、準備金、單次飛行 | settlement.md |
| 修復未入帳部分 | 重試、未入帳、退避、採用、閘道費、收回、沖銷、額度 | reconciliation.md |
| 週期性平台費用 | SetupIntent、off_session、已儲存卡片、催收、發票、past_due、暫停 | subscriptions.md |
| 設定、環境變數、定時任務、除錯 | STRIPE_SECRET_KEY、webhook 端點、stripe listen、cron、「賣家從未收到款項」 | operations.md |
| 適配到此應用 | adapt、host probe、rename、ORM、auth guard、port | adaptation.md |
| 審計記錄 | provenance、deviation、kept、added、unverified | provenance.md |