付费内容的正确实现:为什么前端隐藏一定被绕过,以及异步通知的五个边界

阅读约 5 分钟1,413 字

「付费阅读」看起来简单——把内容藏起来,付了钱再显示。但真正做到不被绕过、不误伤、能离线验证,需要处理不少边界。

一、绝对不能只在前端隐藏

最常见的错误做法:

<!-- ❌ 危险:源码里全文可见,F12 就绕过了 -->
<div style="display:none">
    <?php echo $付费内容; ?>
</div>

正确做法是服务端不输出未付费内容。付费判断必须在 PHP 层完成,且早于 HTML 生成。

二、判断「已付费」的可靠来源

顺序很重要:

  1. 数据库订单状态 —— 最可靠
  2. 服务端 session / 用户 meta —— 次之
  3. 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
喜欢就支持一下吧
点赞10 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容