微信内置浏览器里的 H5 支付,是站长和开发者最容易卡壳的环节。用户点一个按钮,能不能直接拉起微信收银台、输完密码就付完款,靠的是一套前后端配合的完整链路。这篇教程把易支付JSAPI支付对接的授权目录、后端下单、前端唤起、异步验签、高频报错串成一条可落地的流程,代码都能直接跑,报错按根因讲清楚。

易支付作为支付接口技术服务商,仅提供标准化 API 对接技术服务,资金清算由持牌支付机构完成,开发者对接时应遵守微信支付及相关监管合规要求。

一、前期对接必备准备工作

动手写代码前,先把三件事准备好,能少走一半弯路。「URL 未注册」「无法获取 openid」等报错,根源多在这一步没配齐。

1.1 账号与权限准备

你需要一个已开通 JSAPI 能力的商户账号,以及一个已完成微信认证的公众号。易支付JSAPI接口开发依赖公众号网页授权能力,公众号必须通过认证才能拿到用户身份信息。同时确认商户后台「支付方式」已勾选微信 JSAPI 通道,漏了会返回「通道未开通」。

1.2 域名备案、微信商户平台基础配置

公众号H5 JSAPI对接易支付,域名是绕不过去的一道坎。支付页面必须部署在已备案域名下,并完成两处配置:

  • 公众号网页授权域名:在公众号后台「功能设置」里,把支付页面所在域名填进「网页授权域名」,这一步决定能不能拿到 openid。
  • JSAPI 支付授权目录:在微信商户平台「产品中心 - 开发配置」里,填发起支付页面的上一级目录,以斜杠结尾,这是易支付JSAPI授权目录配置的关键。

这是易支付JSAPI 当前页面URL未注册报错的底层成因:微信唤起收银台前会校验当前页面 URL 是否落在授权目录内,不匹配就拒绝。

JSAPI支付授权目录与网页授权域名配置说明
JSAPI支付授权目录与网页授权域名配置说明

1.3 获取易支付对接基础参数

登录商户后台,在「接口信息」里拿到两个关键值:商户 ID(pid)和通信密钥(key)。pid 标识商户身份,key 用于对请求参数做签名。密钥等同于账户「第二密码」,泄露后别人就能伪造下单请求,务必妥善保管,绝不写进前端页面。

二、易支付JSAPI整体业务时序流程

易支付JSAPI支付完整对接步骤如下:

用户访问支付页面 → 页面引导用户完成公众号网页授权,拿到 openid → 后端用它向易支付发起「创建预支付订单」请求 → 易支付返回一组支付参数 → 前端调用 wx.chooseWXPay 唤起微信收银台 → 用户完成支付 → 易支付向商户配置的 notify_url 发起异步通知 → 商户服务端验签、更新本地订单状态。

贯穿全文的关键原则:前端展示的支付结果只做交互提示,订单最终状态永远以异步通知验签结果为准。
易支付JSAPI支付完整业务时序图
易支付JSAPI支付完整业务时序图

三、后端接口对接实操

后端是易支付JSAPI支付对接的核心,订单创建、签名、回调处理都在这一层。下面按「参数 → 签名 → 代码」顺序拆解。

3.1 创建JSAPI订单请求参数详解

创建预支付订单的核心必填参数如下:

  • pid(必填):商户 ID。
  • type(必填):支付方式,JSAPI 场景固定为 jsapi
  • out_trade_no(必填):商户订单号,需保证唯一。
  • notify_url(必填):异步通知地址,须公网可访问。
  • name(必填):商品名称,展示在收银台。
  • money(必填):金额,单位元,保留两位小数,如 1.00 元。
  • openid(JSAPI 必填):用户在该公众号下的唯一标识,28 位字符。
  • sign(必填):签名,按规则排序后计算。

选填参数如 return_urlparam 按需填写,金额不要用科学计数法。单笔订单金额上限一般由平台风控规则决定,常见为 50000 元以内。

3.2 签名生成、签名校验规范

签名是防篡改的核心。通用规则:把除 sign 外的参数按键名 ASCII 升序排序,拼成 key1=value1&key2=value2,再拼接通信密钥 key,最后做 MD5。三个易踩点:拼接时排除空值参数、编码统一 UTF-8、密钥接在末尾不加分隔符。

3.3 PHP 易支付JSAPI支付示例代码

下面是一段可直接运行的 PHP 示例:

<?php
// 易支付JSAPI支付对接:创建预支付订单示例
$config = array(
    'pid'     => '1000',               // 商户ID,替换成你自己的
    'key'     => 'your_secret_key',     // 通信密钥,务必保密
    'api_url' => 'https://api.xxx.com/', // 接口地址,替换成平台提供值
);
$params = array(
    'pid'          => $config['pid'],
    'type'         => 'jsapi',
    'out_trade_no' => 'ORDER' . date('YmdHis') . mt_rand(1000, 9999),
    'notify_url'   => 'https://yourdomain.com/notify.php',
    'return_url'   => 'https://yourdomain.com/return.php',
    'name'         => '测试商品',
    'money'        => '1.00',
    'openid'       => $openid,          // 由网页授权获得
);
// 生成签名:排除空值,键名升序排序
ksort($params);
$sign_str = '';
foreach ($params as $k => $v) {
    if ($v !== '' && $v !== null) {
        $sign_str .= $k . '=' . $v . '&';
    }
}
$sign_str = rtrim($sign_str, '&') . $config['key'];
$params['sign'] = md5($sign_str);
// 发起请求
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $config['api_url'] . 'submit.php');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
if ($result['code'] == 1) {
    echo json_encode($result); // 返回前端,用于 wx.chooseWXPay
} else {
    echo $result['msg'];
}
?>

实际接入替换 pidkeyapi_url 三个值即可。

四、前端页面唤起JSAPI支付配置

后端下单成功后,前端拿到的参数要交给微信 JSSDK 唤起收银台。这一步是易支付微信网页支付集成里最容易出错的地方,涉及 JSSDK 签名和支付参数签名两层,两者不要混淆。

4.1 引入微信JSSDK,完成wx.config权限注入

先在页面引入微信 JSSDK,再调用 wx.config 注入权限,声明要使用的 chooseWXPay 能力:

<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
<script>
wx.config({
  debug: false,
  appId: '<?= $jsApiConfig["appId"] ?>',
  timestamp: '<?= $jsApiConfig["timestamp"] ?>',
  nonceStr: '<?= $jsApiConfig["nonceStr"] ?>',
  signature: '<?= $jsApiConfig["signature"] ?>',
  jsApiList: ['chooseWXPay']
});
</script>

4.2 chooseWXPay唤起支付完整前端代码

权限注入成功后,把后端返回的支付参数传给 wx.chooseWXPay

wx.ready(function () {
  var payParams = {
    appId: 'wx8888888888888888',
    timeStamp: '1710000000',
    nonceStr: 'abcdefghijklmn',
    package: 'prepay_id=wx20260828000000abcd',
    signType: 'MD5',
    paySign: '后端返回的支付签名'
  };
  wx.chooseWXPay({
    timestamp: payParams.timeStamp,
    nonceStr: payParams.nonceStr,
    package: payParams.package,
    signType: payParams.signType,
    paySign: payParams.paySign,
    success: function () { /* 仅代表用户确认,结果看异步回调 */ },
    cancel: function () { /* 用户主动取消 */ },
    fail: function () { /* 常见原因是授权目录没配对 */ }
  });
});

4.3 前端支付结果处理规范

再次强调:前端 success 回调只是「用户完成密码输入」的交互确认,绝不能作为订单已支付的依据。正确做法是前端等待后端异步通知确认订单状态后再展示「支付成功」,避免用户付了钱、前端却因网络抖动显示失败引发客诉。

前端唤起JSAPI支付链路示意图
前端唤起JSAPI支付链路示意图

五、异步通知notify_url配置与安全验签

异步通知是易支付JSAPI回调通知配置的核心,也是安全防线所在。notify_url 公网可访问,任何人都能向它发请求,不验签就会被伪造的「支付成功」通知骗过。

5.1 回调通知接收基础逻辑

易支付以 POST 方式向 notify_url 发送通知,包含订单号、金额、支付状态等参数。服务端收到后第一步不是急着改订单状态,而是先验签,通过后再处理业务。

5.2 签名校验代码示例,拦截伪造恶意回调

<?php
// 易支付JSAPI支付异步回调验签
$key = 'your_secret_key';
$params = $_POST;
$sign = isset($params['sign']) ? $params['sign'] : '';
unset($params['sign']);
ksort($params);
$sign_str = '';
foreach ($params as $k => $v) {
    if ($v !== '' && $v !== null) {
        $sign_str .= $k . '=' . $v . '&';
    }
}
$sign_str = rtrim($sign_str, '&') . $key;
if (md5($sign_str) !== $sign) {
    exit('fail'); // 验签失败,拒绝
}
$out_trade_no = $params['out_trade_no'];
if ($params['trade_status'] === 'TRADE_SUCCESS') {
    updateOrderStatus($out_trade_no, 'paid');
}
exit('success');
?>

5.3 幂等性处理方案,避免重复回调多次变更订单状态

同一笔订单,易支付可能因网络重试多次推送通知。每次收到都直接改状态,可能触发重复的积分发放、库存扣减等副作用,所以必须做幂等处理

<?php
function updateOrderStatus($out_trade_no, $status) {
    $order = findOrderByNo($out_trade_no);
    if ($order['status'] === 'paid') {
        return; // 已处理过,直接返回
    }
    $updated = updateOrderIfStatus($out_trade_no, 'unpaid', $status);
    if ($updated) {
        grantUserBenefits($order['user_id']); // 仅状态真正变化时执行一次
    }
}
?>

核心是「先查状态、再原子更新、状态真正变化时才触发业务」,平台重推多少次,业务逻辑都只执行一次。

5.4 return_url同步跳转页面说明

return_url 是用户支付完成后浏览器跳转的页面,用于展示结果。它与 notify_url 的区别:return_url 是「给人看的」,由浏览器跳转,不可靠;notify_url 是「给机器验的」,由服务器发起,才是订单状态的权威来源。

JSAPI支付异步回调验签与幂等处理示意图
JSAPI支付异步回调验签与幂等处理示意图

六、易支付JSAPI高频报错问题汇总

下面四个报错,占据易支付JSAPI支付对接排错工作的九成,逐个讲根因和正确解法。

6.1 当前页面的URL未注册:根因+正确配置方案

报错「当前页面的URL未注册」几乎只指向一个原因:发起支付页面的 URL 不在微信商户平台配置的「JSAPI 支付授权目录」范围内。例如支付页面是 https://yourdomain.com/pay/index.html,授权目录就填 https://yourdomain.com/pay/,必须以斜杠结尾。

高频细节:授权目录修改后有缓存延迟,微信侧一般需几分钟到半小时才生效。刚改完就测试仍报「URL 未注册」,先等一会儿再试。

6.2 异步回调接收不到通知:防火墙、内网地址、公网访问校验排查

回调收不到,按三层排查:一、notify_url 必须是公网可访问地址,内网 IP、localhost 一律不行;二、检查防火墙或安全组是否放行回调来源 IP;三、用 curl 从外部模拟 POST 验证接口能通。结合平台「回调日志」判断:已推送但收不到是网络层问题,推送失败多半是 notify_url 配错。

6.3 签名校验失败:参数排序、编码格式、密钥填写错误排查

签名对不上,按顺序查三处:参数是否严格按键名 ASCII 升序排序;编码是否统一 UTF-8(尤其是中文商品名);密钥是否与后台一致、是否带进首尾空格。自检法:把拼好的签名字符串打印出来,和平台文档示例逐字符比对。

6.4 无法获取OpenID:公众号授权回调域名配置错误修复

易支付JSAPI支付如何获取openid,最常见问题是公众号「网页授权域名」没配或配错,须与实际发起授权域名一致。另一个原因是 scope 用错,应按微信 OAuth2.0 流程选用 snsapi_basesnsapi_userinfo 换取 openid。

七、上线前安全自查清单

正式上线前,逐条过一遍这份清单,能挡住大部分安全事故:

  • 密钥禁止硬编码写入前端页面:key 只存在于服务端。
  • 回调接口做好鉴权、幂等处理:验签 + 幂等,缺一不可。
  • JSAPI授权目录精细化配置:不推荐直接填根目录,精确到发起支付页面的目录。
  • 测试环境完整联调后切换正式通道:先用测试参数跑通全流程,再切生产密钥。

FAQ 常见问答

下面是开发者高频咨询的几个问题,统一解答。

Q1:易支付JSAPI和Native支付区别是什么?如何选择适用场景?

核心区别在「支付入口」:JSAPI 面向微信内置浏览器,用户在公众号或 H5 页面点按钮直接拉起收银台;Native 则生成二维码,用户用「扫一扫」完成支付。公众号菜单、H5 商城选 JSAPI;PC 网站、线下收银台选 Native。

Q2:配置JSAPI授权目录后,多久正式生效?

微信侧一般几分钟到半小时内生效,存在缓存延迟。建议配置后等 10 到 30 分钟再测试,若仍报「URL 未注册」,先核对目录格式。微信授权目录最多可配置 5 个,超出需先清理旧的。

Q3:JSAPI支付对接是否必须公众号AppID?

是的。JSAPI 依赖公众号网页授权获取 openid,而 openid 是 JSAPI 下单必填参数,所以必须有已认证的公众号及其 AppID,否则无法走 JSAPI 通道。

Q4:支付成功,异步回调没有触发该如何排查?

按「地址可达性 → 网络放行 → 验签结果」三层排查:确认 notify_url 公网可访问、防火墙放行、回调日志里平台是否推送成功。已推送但收不到,查服务器日志;收到但验签失败,逐项核对参数排序。