上个月有个做独立站的哥们,把易支付的接口文档打印出来贴墙上,研究了两天,最后跑来问我一句话:"这文档每个字我都认识,连在一起我就不知道从哪下手了。"

我太理解这种感受了。支付接口的文档,从来不是写给第一次对接的人看的。它默认你已经知道"签名是干嘛的""回调是干嘛的",只管把参数往那一列,剩下的你自己悟。

所以这篇我不照搬文档。我把对接易支付 API 接口这件事,拆成"一张参数表 + 一套签名算法 + 一段能直接跑的 PHP 代码",你照着顺序走,半小时能把第一笔订单跑通。文末还有三个我亲眼见过的翻车点,每个都能卡你半天。

先别写代码,把这张参数表看明白

对接接口最忌讳的,是上来就抄代码。代码抄得再对,参数含义没搞懂,出了错你连排查方向都没有。

下面是支付下单接口(/api/submit.php)的核心参数。我把"必填 / 选填 / 谁最容易填错"都标出来了:

参数含义必填最容易错在哪
pid商户 ID,后台开通商户后分配不是你的登录账号,是独立的一串数字
type支付方式:alipay / wxpay / qqpay写错大小写、写成中文"支付宝"
out_trade_no商户订单号,你自己生成的同一商户号下不能重复,否则会被判重复下单
notify_url异步通知地址,支付成功后平台回调这里必须是公网能访问的 HTTPS 地址
return_url同步跳转地址,用户付完跳回这里别和 notify_url 写反了,功能完全不同
name商品名称,展示给用户看别带特殊符号,有些通道会拦截
money支付金额,单位是元一定要用字符串传,别用浮点数(下面细说)
signMD5 签名,防篡改拼接顺序错了,签名必错
sign_type签名类型,固定 MD5漏传这个字段,直接报签名错误

这张表你截图存一下。后面每一步出错,回来对着这张表查,十有八九能找到原因。

签名算法:接口对接真正的分水岭

参数表是固定的,签名算法是灵活的,也是绝大多数人最容易出错的地方。

签名的本质,是防止有人在你下单的过程中把金额改了。比如你本来要收 10 块,中间被人截获改成 0.01,如果没有签名,平台根本发现不了。

易支付的签名规则是这样的:把所有请求参数(sign 除外)按参数名的 ASCII 码从小到大排序,拼成 key1=value1&key2=value2 的字符串,末尾再拼上商户密钥,最后对整串做 MD5

这里有两个细节,文档里写了但很多人没当回事:

第一,排序是按参数名的 ASCII 码,不是按你写代码的顺序。你代码里先写 pid 再写 money,排序后可能 money 排在 pid 前面。这就是为什么你不能手动拼字符串,必须用程序排序。

第二,密钥是拼在最后,格式是 &key=你的密钥,不是拼接在某个参数值里。这个 & 和 key= 都不能少。

写成代码,核心就三行:

// 1. 按参数名排序(sign、sign_type 都不参与签名)
ksort($params);

// 2. 拼成 key1=value1&key2=value2 的字符串
$paramStr = urldecode(http_build_query($params));

// 3. 末尾拼密钥,算 MD5
$sign = md5($paramStr . $merchantKey);

注意第二行的 urldecode。很多人漏掉这一步,http_build_query 会把中文和特殊字符转成百分号编码,直接拿去算 MD5,得出来的签名和平台对不上。这个坑我在 支付回调sign签名:MD5和RSA到底有什么区别、该用哪个 里也提过,签名这事,细节决定成败。

一段能直接跑的 PHP 代码

别急着自己从头写。先把下面这段完整代码跑通,看到支付页面跳出来了,再改成你自己的业务逻辑。

<?php

// 你的商户信息(后台开通商户后能拿到)
$pid = '1001';                    // 商户 ID
$merchantKey = '你的商户密钥';      // 商户密钥,后台查看

// 下单参数
$params = [
    'pid'          => $pid,
    'type'         => 'alipay',                        // 支付方式
    'out_trade_no' => 'ORDER' . time(),                // 商户订单号
    'notify_url'   => 'https://你的域名/notify.php',    // 异步通知
    'return_url'   => 'https://你的域名/return.php',    // 同步跳转
    'name'         => '测试商品',
    'money'        => '0.01',                          // 金额用字符串
];

// 生成签名
ksort($params);                                          // 按参数名排序
$paramStr = urldecode(http_build_query($params));        // 拼字符串
$params['sign'] = md5($paramStr . $merchantKey);         // 算 MD5
$params['sign_type'] = 'MD5';

// 发起请求
$apiUrl = 'https://xxkkfz.cn/api/submit.php';
$ch = curl_init($apiUrl);
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);

// 响应是 JSON,里面有支付跳转链接
$result = json_decode($response, true);
if (isset($result['url'])) {
    header('Location: ' . $result['url']);
    exit;
}

echo $response;

你的商户密钥你的域名 换成自己的,直接跑。正常情况下,浏览器会跳到一个支付页面,那就是对接成功了。

如果没成功,看返回的 JSON 里 msg 字段写了什么。九成的情况是"签名错误",那问题就出在上面的排序和拼接上,回去对着签名算法一节逐行检查。

回调验签:别让订单状态成了摆设

支付页面跳出来了,只是走完了一半。更关键的是 notify_url 这个异步通知——它是平台告诉你"钱真的到账了"的唯一可靠信号。

这里有个原则必须记住:永远不要相信同步跳转页(return_url)来判断支付成功。用户付完钱,可能直接关掉浏览器,根本不会跳回来。只有异步通知到了,才是真正的支付成功。

收到通知后,你要做两件事:

第一,验签。平台发过来的通知里也带一个 sign,你要用同样的算法,把通知里的参数(sign 除外)排序拼接,算一遍 MD5,和传来的 sign 比对。对得上,说明这条通知真的是平台发的,不是有人伪造。

第二,处理完后返回 success。平台收到这个字符串,才认为通知送达,否则会反复重试。

<?php
// notify.php —— 异步通知处理
$data = $_POST;

$sign = $data['sign'];
unset($data['sign']);
unset($data['sign_type']);

ksort($data);
$paramStr = urldecode(http_build_query($data));
$checkSign = md5($paramStr . $merchantKey);

if ($checkSign !== $sign) {
    exit('fail');   // 验签失败,可能是伪造
}

// 验签通过,处理你的业务
$outTradeNo = $data['out_trade_no'];
$tradeStatus = $data['trade_status'];
if ($tradeStatus === 'TRADE_SUCCESS') {
    // 更新订单为已支付
}

echo 'success';   // 必须返回这个

这段代码是整个对接里最不能省的一步。我见过有人图省事,回调过来直接改订单状态不验签,结果被人伪造回调刷了单。

三个能卡你半天的翻车点

最后说三个真实的坑。每个都是我或者身边的人真实遇到过的,写出来帮你省时间。

金额一定要用字符串传

PHP 里 '0.01'0.01 看起来一样,但如果是 19.90 这种带小数的金额,用浮点数传,底层二进制表示是有误差的,签名算出来可能对不上。所以 money 这个字段,务必用字符串。

密钥里的换行符

有些商户后台复制密钥的时候,会多带一个看不见的换行符。密钥末尾多了个 \n,签出来的 MD5 永远对不上,你还查不出原因。这个坑我在 易支付支付失败排查实录:藏在密钥里的换行符 里写过完整排查过程,建议一并看一眼。

notify_url 必须公网能访问

很多人本地测试时,把 notify_url 填成 localhost 或者内网 IP。平台回调的时候,是从公网主动访问你这个地址的,它根本摸不到你的 localhost。本地测试回调,得用内网穿透工具,或者直接部署到线上服务器再测。

对接易支付 API 接口,说穿了就三件事:参数填对、签名算对、回调验签。这三件事都不难,难的是没人把它们串起来讲清楚。希望这篇能让你少走一点弯路。