Skip to content

Audlis 的创作者收益系统基于 Stripe Connect Express 实现。当买家购买使用商业授权(commercial license)设计的商品时,设计创作者获得分成(默认 70%),每月自动结算到创作者的 Stripe Connect 账户。


整体流程

买家下单 (Stripe Checkout)

    ▼ 订单付款成功
Stripe Webhook → 记录版税 (royalty_ledger, state='accrued')

    ▼ 14 天退款窗口期过后 (cron 每小时检查)
版税成熟 (state='accrued' → 'eligible')

    ▼ 每月 1 日 00:00 UTC (cron 自动触发)
月度结算 (runPayoutSweep)
    │  ├─ 汇总每个创作者的 eligible 余额
    │  ├─ 跳过 < $20 的账户
    │  ├─ 跳过未完成 Stripe onboarding 的创作者
    │  ├─ stripe.transfers.create → 转账到创作者 Connect 账户
    │  └─ 发送邮件通知 (Resend)


版税标记已付 (state='eligible' → 'paid')

    ▼ Stripe 自行处理
创作者银行账户到账 (通常 1-3 个工作日)

用户指南

1. 设置收款(Stripe Connect Onboarding)

入口:登录 → Account → Earnings 标签页 → "Set up payouts" 按钮

步骤

  1. 点击 "Set up payouts"

    • 系统会在 Stripe 创建一个 Express 类型的 Connect 账户
    • 你会被重定向到 Stripe 的托管 onboarding 页面(connect.stripe.com/setup/e/...
  2. 在 Stripe Express 页面完成信息填写

    • 这就是 "Sign in to Express" 页面——Stripe 托管的身份验证和银行账户设置
    • 你需要填写:邮箱、手机号、身份信息、银行账户
    • Stripe(不是 Audlis)负责 KYC 验证——Audlis 看不到你填的敏感信息
  3. 完成后自动跳回 Audlis

    • Stripe 将你重定向到 /account?onboarding=complete#earnings
    • EarningsCenter 自动轮询 /api/account/payouts/status 获取最新状态
    • 状态从 pending 变为 active(Stripe 验证通过后,通常几分钟到几天)
  4. 如果 30 分钟内没填完

    • Stripe 链接过期,你会被重定向到 /account?onboarding=refresh#earnings
    • EarningsCenter 自动生成新的 onboarding 链接并重新跳转

2. 收款状态说明

状态含义前端显示
none未设置收款显示 "Set up payouts" 按钮
pending已开始但未完成验证显示 "Verification in progress" + "Continue setup"
restrictedStripe 需要更多信息红色卡片 + 待办事项列表
active完全可用绿色卡片 "Stripe Connect linked"

3. 收益状态说明

每笔版税记录有 5 个状态:

状态含义颜色
accrued订单已付款,但在 14 天退款窗口期内黄色
eligible退款窗口已过,等待月度结算绿色
paid已包含在某次月度打款中蓝色
refunded订单在打款前被退款,版税取消灰色
void管理员作废(如欺诈订单)红色

4. Earnings 仪表板

访问 /account#earnings 可看到:

  • 当前余额accrued + eligible 的总和(即将到手的钱)
    • 拆分显示:Pending(退款窗口期内)+ Eligible(下次结算包含)
  • 已付总额:所有 paid 状态的版税
  • 终身总收入accrued + eligible + paid
  • 已退款:被退款的版税总额
  • 联邦传播(如果启用了联邦身份):粉丝数、被点赞数、被转发数
  • Top 收益设计:收益最高的 5 个设计
  • 最近活动:最近 50 条版税流水记录

开发者指南

技术架构

组件文件职责
Onboardingsrc/lib/stripe-connect.ts创建 Connect 账户 + 生成 onboarding 链接
Onboarding APIsrc/pages/api/account/payouts/onboard.tsPOST 入口
状态检查 APIsrc/pages/api/account/payouts/status.tsGET 当前 onboarding 状态
版税累计src/lib/royalty.tsaccrueRoyalty / getEarningsSummary
月度结算src/lib/payouts.tsrunPayoutSweep
邮件通知src/lib/payout-mailer.tssendPayoutNotification
Stripe Webhooksrc/pages/api/webhooks/stripe.ts订单付款 → 累计版税;account.updated → 同步状态
Cron Workercron-worker/index.ts每小时成熟版税;每月 1 日结算
前端 UIsrc/components/EarningsCenter.tsx完整仪表板
返回/刷新页src/pages/account/payouts/{return,refresh}.astroStripe 重定向中转

数据库表

creator_payout_accounts — 创作者的 Stripe Connect 账户状态

类型说明
user_idTEXT PK创作者 user id
stripe_account_idTEXTStripe acct_xxx
onboarding_stateTEXTnone / pending / active / restricted
payout_methodTEXTstripe_express
last_synced_atINTEGER上次从 Stripe 同步的时间

creator_payouts — 每次月度结算记录

类型说明
idTEXT PKpay_xxx(用作 Stripe 幂等键)
creator_user_idTEXT创作者
period_start / period_endINTEGER结算周期范围
gross_centsINTEGER结算总额(分)
net_centsINTEGER实际转账金额(= gross,手续费当前为 0)
stripe_transfer_idTEXTStripe tr_xxx
stateTEXTpending / sent / failed

royalty_ledger — 版税流水(每笔一行)

类型说明
idTEXT PKroy_xxx
creator_user_idTEXT设计创作者(快照)
design_idTEXT设计 ID
order_id / order_item_idTEXT订单 + 行项(order_item_id UNIQUE 幂等)
royalty_rate_bpsINTEGER版税率快照(7000 = 70%)
gross_centsINTEGER订单行小计(分)
creator_centsINTEGER创作者分成 = floor(gross * bps / 10000)
platform_centsINTEGER平台分成 = gross - creator
stateTEXTaccrued / eligible / paid / refunded / void
eligible_atINTEGER退款窗口到期时间(accrued→eligible)
paid_at / payout_idINTEGER / TEXT付款时间和关联的 payout 记录

配置项(app_config 表)

Key默认值说明
royalty_creator_bps_default7000创作者默认分成比例(7000 bps = 70%)
royalty_refund_window_days14退款窗口天数(版税在此期间保持 accrued)
royalty_min_payout_cents2000最低结算金额(2000 分 = $20)

这些值可以在 admin SiteSettings 中修改,无需重新部署。

Stripe API 调用方式

Onboarding 路径使用原生 fetch()(不用 Stripe SDK):

stripe-connect.ts → stripeFetchRaw()
  POST https://api.stripe.com/v1/accounts      (创建 Connect 账户)
  POST https://api.stripe.com/v1/account_links  (生成 onboarding 链接)

原因:Stripe Node SDK 的 HTTP 层在 Cloudflare Workers 中有兼容性问题(502 超时)。原生 fetch 避免了这个问题。

Webhook 和月度结算使用 Stripe SDK(import Stripe from 'stripe'),因为这些路径在 Workers 中正常工作。

幂等性保障

  1. 版税累计royalty_ledgerUNIQUE(order_item_id) 约束防止重复累计
  2. 月度结算stripe.transfers.create 使用 idempotencyKey: payoutId,重试不会重复转账
  3. 结算后更新UPDATE ... WHERE state='eligible' 确保已付行不会被重复包含

自购不产生版税

reuse.creator_user_id === buyerUserId 时,webhook 跳过版税累计——创作者购买自己的设计不产生分成。

退款处理

  • 订单在打款前退款 → reverseRoyaltiesForOrderaccrued/eligible 行翻转为 refunded
  • 订单在打款后退款 → 版税已 paid,不可逆转(平台承担损失)
  • 这就是 14 天退款窗口的设计目的:确保退款先于结算

管理员手动触发结算

POST /api/admin/payouts/run

管理员可手动触发结算(测试或补救部分失败的 cron run)。

Cron 调度

任务频率说明
maturePendingRoyalties每小时accruedeligible(退款窗口到期)
runPayoutSweep每月 1 日 00:00 UTC汇总 + 转账 + 邮件通知

邮件通知

结算成功后通过 Resend 发送邮件:

  • 发件人:Audlis <noreply@shop.audlis.com>
  • 主题:Audlis · $X.XX payout sent(英文)/ Audlis · 已为你结算 $X.XX(中文)
  • 内容:结算金额、周期、Audlis Payout ID、Stripe Transfer ID
  • 链接到 /account/earnings 查看详情
  • 语言根据用户的 user_locale_{userId} 配置决定

Synergy Books · SYS-BOOK Series