「付费阅读」看起来简单——把内容藏起来,付了钱再显示。但真正做到不被绕过、不误伤、能离线验证,需要处理不少边界。
一、绝对不能只在前端隐藏
最常见的错误做法:
<!-- ❌ 危险:源码里全文可见,F12 就绕过了 -->
<div style="display:none">
<?php echo $付费内容; ?>
</div>
正确做法是服务端不输出未付费内容。付费判断必须在 PHP 层完成,且早于 HTML 生成。
二、判断「已付费」的可靠来源
顺序很重要:
- 数据库订单状态 —— 最可靠
- 服务端 session / 用户 meta —— 次之
- URL 参数或 Cookie —— 不可信,可伪造
同步跳转(return_url)回来的参数只能用于提示,不能作为已付费依据。必须查订单表。
三、异步通知才是真相
支付平台的回调分两种:
| 类型 | 触发 | 可信度 |
|---|---|---|
| 同步跳转 return_url | 用户浏览器返回 | ❌ 可能伪造、可能丢失 |
| 异步通知 notify_url | 平台服务器直连你的服务器 | ✅ 唯一可信来源 |
⚠️ 用户关掉浏览器,同步跳转就不会发生,但异步通知仍然会到。
附:异步通知处理骨架
<?php
/**
* 支付异步通知处理
* 关键:验签 → 查单 → 幂等 → 记账 → 返回 success
*/
function handle_payment_notify()
{
// ① 取出原始参数(注意:不要做任何过滤,验签要用原始值)
$params = $_POST;
// ② 验签 —— 不过就立刻拒绝
if (!verify_signature($params)) {
error_log('[PAY] 验签失败');
exit('fail');
}
// ③ 校验交易状态
if (($params['trade_status'] ?? '') !== 'TRADE_SUCCESS') {
exit('success'); // 非成功状态也要应答 success,避免重复通知
}
$order_no = $params['out_trade_no'];
// ④ 幂等:同一订单只处理一次
if (get_order_status($order_no) === 'paid') {
exit('success');
}
// ⑤ 二次核验金额(防止篡改)
$order = get_order($order_no);
if (abs($order['amount'] - (float) $params['total_amount']) > 0.01) {
error_log('[PAY] 金额不符');
exit('fail');
}
// ⑥ 记账
update_order_status($order_no, 'paid', $params);
// ⑦ 必须返回 success,否则平台会一直重试
echo 'success';
exit;
}
附:五个必须检查的边界
- 重复通知 —— 平台可能发多次,处理必须幂等
- 金额篡改 —— 不能只信通知里的金额,要和自己的订单比对
- 订单不存在 —— 收到未知订单号应记录并返回 fail
- 应答超时 —— 处理要快(< 5 秒),重活丢队列
- 并发 —— 两笔通知同时到达要加锁
附:测试方法
# 本地模拟通知(需先构造合法签名)
curl -X POST https://你的站点/pay/notify
-d "out_trade_no=TEST001"
-d "trade_status=TRADE_SUCCESS"
-d "total_amount=0.01"
-d "sign=..."
# 预期:
# · 首次 -> success,订单变为 paid
# · 重复 -> success,订单状态不变(幂等)
# · 改签名 -> fail
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END
暂无评论内容