数字商品与会员交付
把订单、权益和访问控制分开设计,让付款以后真的拿得到东西。含摆摊自己的付费墙实现和七项交付验收。
收款成功不等于交易完成。
用户买的是结果:文件能下载、课程能打开、会员页面能访问、积分能使用。支付只是交付流程的触发器,不是终点。很多项目的第一批售后全部来自这一段——钱收到了,东西没到。
快速版
- 先写清你卖的权益是什么(一次购买 / 期限会员 / 余额),再选技术。
- 订单和权益分两张表存,不要用"最近一笔订单"判断权限。
- 访问控制必须在服务端,前端隐藏不算门禁。
- 交付必须可恢复:换设备、清缓存、回调晚到都要能拿到。
- 退款和撤销规则在购买前就写在页面上。
- 用一笔真实订单跑完七项验收,任何一步需要你手改数据库就是没做完。
先定义卖的是什么
不同商品需要不同的权益模型。搞混这一步,后面所有代码都要重写。
| 商品 | 权益记录 | 常见失效方式 |
|---|---|---|
| 文件或模板 | 一次购买记录 + 下载权限 | 退款、商品下架 |
| 付费文章 | 用户与内容的访问关系 | 退款、人工撤销 |
| 期限会员 | 会员截止时间 | 到期、退款 |
| 积分或次数 | 可用余额 + 流水 | 消耗、退款冲正 |
| 人工服务 | 订单 + 履约状态 | 取消、完成、退款 |
先把权益写清楚,再选交付技术。 不要让支付平台的商品模型反过来决定你的业务——平台的"商品"概念通常比你的权益模型粗糙得多。
订单和权益必须分开
这是这一页最重要的一条。
- 订单回答"发生了什么交易":谁、什么时候、付了多少、状态是什么。它是历史,不该被修改。
- 权益回答"这个用户现在能做什么":会员到期时间、剩余积分、可访问的内容。它是当前状态,会被多种事件改变。
一个用户可能续费多次、用兑换码开通、被人工赠送、退款后撤销。如果页面直接判断"最近一笔订单是否成功",这些情况全都会出错。
摆摊的做法:支付订单和会员有效期分两处保存。订单结算成功后延长会员时间,而不是覆盖。带来两个直接好处:
- 重复回调不会重复延长(幂等)
- 会员到期不需要回头修改历史订单
订单表:order_id, user_id, amount, status, created_at ← 只追加,不改
权益表:user_id, membership_expires_at ← 随事件更新
结算成功 → expires_at = max(now, expires_at) + 有效期max(now, expires_at) 这一下处理的是两种情况:还没过期的续费要接着算,已经过期的重新从今天算。
访问控制必须在服务端
只在页面上隐藏正文、模糊文字,或者靠前端请求判断会员状态,都不是门禁。用户仍然能从 HTML 源码、接口响应或搜索引擎缓存里拿到内容。
摆摊把会员内容放在独立路由里,每次请求先在服务端读会话和会员状态:
- 有权限 → 服务端渲染正文
- 无权限 → 只返回标题、摘要和付费墙
正文不会先发到浏览器再隐藏。这个区别可以直接验证:
# 用未登录的身份抓一次会员页,搜正文里的任意一句话
curl -s https://你的域名/members/某篇 | grep "正文里的某句话"
# 期望:什么都搜不到如果这条命令能搜到内容,你的付费墙是假的。这是唯一可靠的自查方式,比人眼看页面可信。
顺带一个工程上的提醒:会员路由需要动态渲染(读会话),公开文档可以全静态。把两者放在不同的路由段里,各自配置渲染策略,比在一个页面里做条件判断干净得多。
交付要能恢复
用户会付款后关掉页面、换设备登录、清理浏览器缓存,回调也可能晚到几分钟。所以权益必须绑定账号或可靠的购买凭证,并且提供恢复入口:
- 登录后能看到已购内容
- 能重新查询订单状态
- 下载失败能再次获取
- 联系支持时能用订单号定位
"支付成功页只显示一次下载链接"看起来最省事,实际上会制造大量售后——而每一个都要你手工处理。
退款和撤销要提前想
数字商品无法像实体商品一样真正收回,所以规则必须在购买前说清楚。至少定义这五条:
| 要定义的 | 不定义的后果 |
|---|---|
| 退款后是否立即失去访问权 | 用户退款后仍能用,或申诉说"我还没看完" |
| 已消耗的积分怎么处理 | 用完再退款,等于白送 |
| 已下载的文件是否还能收回 | 技术上不能,写清楚避免争议 |
| 期限会员按整单撤销还是按剩余时间 | 用了 11 个月要求全额退款 |
| 人工赠送权益由谁操作、是否留记录 | 对不上账,也查不出是谁给的 |
什么时候不该自建
如果你还不确定有没有人买,先别做交付系统。
现成的内容付费平台、发卡寄售平台、二手交易平台的自动发货,都自带支付、库存、订单查询和售后记录。用它们卖一版,比先写一套后台快得多。
自建的时机是这三件事至少发生一件:
- 你需要用户的联系方式来做产品迭代(平台不给)
- 商品形态被平台限制住了
- 已经有稳定复购,客服成本值得用系统降下来
选平台的部分见收款通道怎么选的方案二。
常见卡点
| 卡在哪 | 可能原因 | 试试这样 |
|---|---|---|
| 付了钱但没开通权限 | 回调没收到,或没做主动查单兜底 | 见支付接入的必测场景 |
| 重复回调导致会员被延长两次 | 发放不幂等 | 按订单号去重,用 max(now, expires_at) 累加 |
| 换设备就没有权益了 | 权益绑在浏览器(cookie/localStorage)上 | 权益必须绑账号 |
| 会员内容能被搜到 | 前端隐藏而非服务端拦截 | 用上面的 curl 自查,改成服务端渲染分支 |
| 退款后用户还能访问 | 退款没有触发权益撤销 | 退款回调也要改权益,不只是改订单状态 |
| 每天都要手工帮人恢复权益 | 没有恢复入口 | 加"已购内容"页和订单号查询 |
验收标准
用一笔真实订单依次跑完:
- 支付成功后,不需要人工刷新就能看到权益
- 换浏览器登录同一账号,权益仍然在
- 未登录和非会员的响应里不含受限正文(用 curl 验证)
- 重复支付回调不重复发货
- 历史订单能追踪到对应的权益变化
- 退款、到期、人工赠送都有明确状态和记录
- 下载或内容服务出故障时可以重试
本章动作
从一笔真实订单出发,依次测试:付款 → 关闭页面 → 换设备登录 → 重复触发回调 → 权益到期。
如果任何一步需要你直接改数据库,交付链路就还没有完成。 这一句是唯一的判断标准。
下一步
- 支付接入——订单状态机、回调验签、幂等发放
- 支付合规、退款与争议——退款规则和争议处理
- 发票、税务与交易台账——订单、资金、交付三方对账