微信支付 JSAPI 统一下单接口调用指南
在获取用户 openid 后,下一步是调用微信支付统一下单接口生成预支付交易单。该接口是微信支付的核心入口,适用于除付款码支付外的所有场景。
接口地址
https://api.mch.weixin.qq.com/pay/unifiedorder https://api2.mch.weixin.qq.com/pay/unifiedorder(备用)
必备请求参数(JSAPI 场景)
| 参数名 | 说明 |
|---|---|
| appid | 微信公众号或小程序的 AppID |
| mch_id | 微信支付分配的商户号(10位数字) |
| nonce_str | 随机字符串,用于增强签名安全性 |
| sign | 通过签名算法生成的签名字符串 |
| sign_type | 签名算法类型:MD5 或 HMAC-SHA256 |
| body | 商品或订单描述 |
| out_trade_no | 商户系统内部唯一订单号 |
| total_fee | 订单总金额,单位为分(整数) |
| spbill_create_ip | 发起支付请求的客户端 IP 地址 |
| notify_url | 支付结果异步通知回调地址 |
| trade_type | 交易类型,JSAPI 场景固定为 "JSAPI" |
| openid | 微信用户在商户 appid 下的唯一标识 |
| device_info | 设备信息,非必填,PC网页或公众号支付建议传 "WEB" |
签名计算流程
- 过滤空值参数,排除 sign 字段
- 按参数名 ASCII 码升序排列
- 拼接为 URL 键值对格式:
key1=value1&key2=value2... - 末尾追加
&key=商户API密钥 - 执行 MD5 或 HMAC-SHA256 运算,结果转大写
微信提供在线签名验证工具,可选择签名类型为 XML 格式进行校验。
签名实现代码(PHP)
<?php
class PaymentSigner
{
/**
* 生成请求签名
* @param array $params 待签名参数集合
* @param string $apiKey 商户API密钥
* @param bool $useSha256 是否使用HMAC-SHA256算法
* @return string 大写签名字符串
*/
public static function createSign(array $params, string $apiKey, bool $useSha256 = false): string
{
// 过滤空值并排序
$filtered = self::filterAndSort($params);
// 构建待签名字符串
$stringToSign = self::buildQueryString($filtered);
$stringToSign .= "&key=" . $apiKey;
// 执行签名运算
if ($useSha256) {
$signature = hash_hmac('sha256', $stringToSign, $apiKey);
} else {
$signature = md5($stringToSign);
}
return strtoupper($signature);
}
/**
* 过滤空值并按ASCII排序
*/
private static function filterAndSort(array $data): array
{
$result = [];
foreach ($data as $field => $value) {
if ($field !== 'sign' && $value !== '' && !is_array($value)) {
$result[$field] = $value;
}
}
ksort($result);
return $result;
}
/**
* 构建URL编码格式的查询字符串
*/
private static function buildQueryString(array $data): string
{
$pairs = [];
foreach ($data as $k => $v) {
$pairs[] = $k . '=' . $v;
}
return implode('&', $pairs);
}
}
组装XML请求数据
<?php
class XmlBuilder
{
/**
* 将数组转换为微信支付XML格式
*/
public static function fromArray(array $data): string
{
$xml = "<xml>\n";
foreach ($data as $tag => $value) {
$xml .= "<{$tag}>{$value}</{$tag}>\n";
}
$xml .= "</xml>";
return $xml;
}
}
生成的XML示例:
<xml>
<appid>wxd678efh567hg6787</appid>
<mch_id>1230000109</mch_id>
<device_info>WEB</device_info>
<nonce_str>5K8264ILTKCH16CQ2502SI8ZNMTM67VS</nonce_str>
<body>腾讯充值中心-QQ会员充值</body>
<out_trade_no>20150806125346</out_trade_no>
<total_fee>888</total_fee>
<spbill_create_ip>123.12.12.123</spbill_create_ip>
<notify_url>https://example.com/wxpay/notify</notify_url>
<trade_type>JSAPI</trade_type>
<openid>oUpF8uMuAJO_M2pxb1Q9zNjWeS6o</openid>
<sign>C380BEC2BFD727A4B6845133519F3AD6</sign>
</xml>
HTTP请求发送
<?php
class HttpClient
{
/**
* 发送POST请求至微信支付接口
*/
public static function postXml(string $url, string $xmlBody, int $timeout = 30): string|false
{
$handler = curl_init();
curl_setopt_array($handler, [
CURLOPT_URL => $url,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $xmlBody,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => false,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
]);
$response = curl_exec($handler);
if ($response === false) {
$errCode = curl_errno($handler);
curl_close($handler);
error_log("CURL请求失败,错误码: {$errCode}");
return false;
}
curl_close($handler);
return $response;
}
}
响应结果解析
接口返回XML数据,关键字段说明:
| 字段 | 含义 |
|---|---|
| return_code | 通信标识:SUCCESS/FAIL |
| return_msg | 通信错误信息 |
| result_code | 业务结果:SUCCESS/FAIL |
| prepay_id | 预支付交易会话标识(调起支付必需) |
| trade_type | 交易类型 |
成功响应示例:
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<appid><![CDATA[wxd678efh567hg6787]]></appid>
<mch_id><![CDATA[1230000109]]></mch_id>
<nonce_str><![CDATA[SMIpZtHhPPyTezlY]]></nonce_str>
<sign><![CDATA[7215B1BAB1C08A4887AAB0ECB2A60447]]></sign>
<result_code><![CDATA[SUCCESS]]></result_code>
<prepay_id><![CDATA[wx201410272009395522657a690399285100]]></prepay_id>
<trade_type><![CDATA[JSAPI]]></trade_type>
</xml>
获取到 prepay_id 后,即可用于前端调起微信支付。