PHP JSAPI调支付API实现微信支付功能详解
作者:一本曾经 发布时间:2023-05-29 05:09:13
标签:PHP,JSAPI,微信支付
一、首先我们来填个坑
支付验签失败
这个问题折磨了我两天,官方文档比较含糊不清。各种百度下来的方法试过之后也不尽人意,最后发现问题是没有二次签名
二次签名需要参数(代码会展示在哪里二次签名):
appId: 商户申请的公众号对应的appid(I大写)
nonceStr: 随机字符串(注意是JSAPI下单接口中返回的 nonce_str、不是重新生成)
package: 统一下单接口返回的prepay_id参数值 ,(注意格式prepay_id=wx.....)
signType: 签名类型、(官方文档)仅支持RSA。
(我的签名类型是 HMAC-SHA256 也是可以的,必须和下单使用的签名类型保持一致)
timeStamp:时间戳(这里要把 time() 转成字符串类型)
注明:使用这五个参数生成的 paySign 签名才是需要返给前端的(
)
官方文档实例要计算签名也给我整的蒙圈,最后发现直接将五个必须参数生成的签名返给前端就可以直接调取API了
二、代码示例
1.请求参数配置
$oInput = [
'body' => '测试商品', // 商品说明
'attach' => '测试场景', // 自定义参数:可以用来做回调后场景区分
'out_trade_no' => '测试单号' . time(), // 自定义订单号
'total_fee' => 1 * 100, // 付款金额:记得*100 微信官方是以分为单位
'goods_tag' => '', // 优惠券相关参数
'notify_url' => 'http://...', // 回调通知地址
'trade_type' => 'JSAPI', // 支付方式
'openid' => $openid, // 付款用户openid
// 'profit_sharing' => 'Y', // 是否分账的标识
];
$res = $this->unifiedOrder($oInput); // 这里我调用的统一下单
return $res; // 返给前端带APPID等参数给前端去调用支付
2.统一下单API
public function unifiedOrder($inputObj, $timeOut = 6)
{
$url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
// 首次签名参数
$oValues = [
'body' => $inputObj['body'],// 设置商品或支付单简要描述
'attach' => $inputObj['attach'],// 设置附加数据,用于商户携带订单的自定义数据
'out_trade_no' => $inputObj['out_trade_no'], // 设置商户系统内部的订单号,transaction_id、out_trade_no二选一,如果同时存在优先级:transaction_id> out_trade_no
'total_fee' => $inputObj['total_fee'], // 设置订单总金额,只能为整数,单位:分
'time_start' => date("YmdHis"), // 设置订单生成时间
'time_expire' => date("YmdHis", time() + 600), // 设置订单失效时间
'goods_tag' => $inputObj['goods_tag'], // 设置商品标记,代金券或立减优惠功能的参数
'notify_url' => $inputObj['notify_url'], // 获取接收微信支付异步通知回调地址的值
'trade_type' => $inputObj['trade_type'], // JSAPI,NATIVE,APP
'openid' => $inputObj['openid'], // 用户在商户appid下的唯一标识
//'profit_sharing' => $inputObj['profit_sharing'],// 是否需要分账
'appid' => 'appid', // app_id:替换真实的
'mch_id' => 'mchid', // 商户号:替换真实的
'spbill_create_ip' => $_SERVER['REMOTE_ADDR'], // 终端ip
'nonce_str' => '自定义生成', // 随机32位字符串
'sign_type' => 'HMAC-SHA256', // 签名类型,自行替换
];
// 首次签名
ksort($oValues);
$oValues['sign'] = $this->MakeSign($oValues); // 调用签名
$xml = $this->ToXml($oValues); // 数字转xml类型
$response = self::postXmlCurl($xml, $url, false, $timeOut); // 请求
$result = $this->FromXml($response); // 请求结果从xml转成数组类型
// 二次签名参数
$oResult = [
'appId' => $result['appid'], // 首次请求中的appid
'nonceStr' => $result['nonce_str'], // 首次请求中的nonce_str
'package' => 'prepay_id=' . $result['prepay_id'],// 首次请求中的prepay_id
'signType' => 'HMAC-SHA256', // 跟首次签名中的签名类型参数保持一致
'timeStamp' => (string)(time()),// 时间戳转字符串类型
];
// 二次签名
$oResult['paySign'] = $this->MakeSign($oResult); // 调用签名
$result = json_encode($oResult); // encode数组
return $result; // 直接返回
}
3.MakeSign 签名
/**
* 生成签名
* @param bool $needSignType 是否需要补signtype
* @return 签名,本函数不覆盖sign成员变量,如要设置签名需要调用SetSign方法赋值
*/
public function MakeSign($values, $needSignType = true)
{
if ($needSignType) {
$sSignType = 'HMAC-SHA256'; // 可以在文档开头用枚举定义: 所有签名类型必须一致
}
$sKey = 'key'; // 获取支付参数key
// 签名步骤一:按字典序排序参数
ksort($values);
$string = $this->ToUrlParams($values);
// 签名步骤二:在string后加入KEY
$string = $string . "&key=" . $sKey;
// 签名步骤三:MD5加密或者HMAC-SHA256
if ($sSignType == "MD5") {
$string = md5($string);
} else if ($sSignType == "HMAC-SHA256") {
$string = hash_hmac("sha256", $string, $sKey);
} else {
return "签名类型不支持!";
}
// 签名步骤四:所有字符转为大写
$result = strtoupper($string);
return $result;
}
4.ToXml 数组参数转xml
public function ToXml($values)
{
if (!is_array($values) || count($values) <= 0) {
return "数组数据异常!";
}
$xml = "<xml>";
foreach ($values as $key => $val) {
if (is_numeric($val)) {
$xml .= "<" . $key . ">" . $val . "</" . $key . ">";
} else {
$xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
}
}
$xml .= "</xml>";
return $xml;
}
5.postXmlCurl 发送请求
/**
* 以post方式提交xml到对应的接口url
*
* @param WxPayConfigInterface $config 配置对象
* @param string $xml 需要post的xml数据
* @param string $url url
* @param bool $useCert 是否需要证书,默认不需要
* @param int $second url执行超时时间,默认30s
*/
private function postXmlCurl($xml, $url, $useCert = false, $second = 30)
{
$ch = curl_init();
$curlVersion = curl_version();
$ua = "WXPaySDK/" . self::VERSION . " (" . PHP_OS . ") PHP/" . PHP_VERSION . " CURL/" . $curlVersion['version'] . " " . $aWxpayParam['mchid'];
//设置超时
curl_setopt($ch, CURLOPT_TIMEOUT, $second);
$proxyHost = "0.0.0.0";
$proxyPort = 0;
// 如果有配置代理这里就设置代理
if ($proxyHost != "0.0.0.0" && $proxyPort != 0) {
curl_setopt($ch, CURLOPT_PROXY, $proxyHost);
curl_setopt($ch, CURLOPT_PROXYPORT, $proxyPort);
}
curl_setopt($ch, CURLOPT_URL, $url);
// curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE);
// curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//严格校验
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); //严格校验
curl_setopt($ch, CURLOPT_USERAGENT, $ua);
// 设置header
curl_setopt($ch, CURLOPT_HEADER, FALSE);
// 要求结果为字符串且输出到屏幕上
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
if ($useCert == true) {
// 设置证书
// 使用证书:cert 与 key 分别属于两个.pem文件
// 证书文件请放入服务器的非web目录下
$sslCertPath = 'sslCertPath'; // 证书路径
$sslKeyPath = 'sslKeyPath'; // 证书路径
curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
curl_setopt($ch, CURLOPT_SSLCERT, $sslCertPath);
curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
curl_setopt($ch, CURLOPT_SSLKEY, $sslKeyPath);
}
// post提交方式
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
// 运行curl
$data = curl_exec($ch);
// 返回结果
if ($data) {
curl_close($ch);
return $data;
} else {
$error = curl_errno($ch);
curl_close($ch);
throw new WxPayException("curl出错,错误码:$error");
}
}
6.FromXml 结果xml参数转数组
/**
* 将xml转为array
* @param string $xml
* @throws WxPayException
*/
public function FromXml($xml)
{
if (!$xml) {
return "xml数据异常!";
}
//将XML转为array
//禁止引用外部xml实体
libxml_disable_entity_loader(true);
$res = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
return $res;
}
总结
注意统一下单中五个调用方法别忘了:
getNonceStr:我没贴出来,这个要自己写(0.0)
MakeSign: 这里面的key要记得替换成自己真实的参数
ToXml
postXmlCurl : 注意这里面的证书要改成自己真实的哈
FromXml
来源:https://blog.csdn.net/arlene12345/article/details/127831004


猜你喜欢
- Python下载Python最新源码,二进制文档,新闻资讯等可以在Python的官网查看到:Python官网:http://www.pyth
- TNS简要介绍与应用 Oracle中TNS的完整定义:transparence Network Substrate透明网络底层,监听服务是它
- '定义变量 Dim cn,rs,Sql Sql = "sel
- 在MySQL中删除数据有两种方式:truncate(截短)属于粗暴型的清空delete属于精细化的删除删除操作如果你需要清空表里的所有数据,
- RabbitMQ 6种工作模式对RabbitMQ 6种工作模式(简单模式、工作模式、订阅模式、路由模式、主题模式、RPC模式)进行场景和参数
- 我们首先来看下全部代码:# -*- coding: cp936 -*- import win32serviceutil import win
- 1、单元测试的几个重要概念(1)Test Case一个Test Case实例是一个测试用例,完整的测试流程包括测试前准备环境的搭建(setU
- 前言:之前博主分享过knockoutJS和BootstrapTable的一些基础用法,都是写基础应用,根本谈不上封装,仅仅是避免了html控
- 示例1:文件打包,上传与校验我们时常做一些文件包分发的工作,实施步骤一般是先压缩打包,在批量上传至目标服务器,最后做一致性校验,本案例通过p
- Python字典的基本用法创建字典:myDict1 = { '薛之谦':'我叫薛之谦', &nb
- 写入txt文件def text_save(filename, data):#filename为写入CSV文件的路径,data为要写入数据列表
- 配置可能会随官方改变,本文仅供参考。1.下载安装GO的包到https://code.google.com/p/go/downloads/li
- 1.新建四个层,放入相应图片,模特层的z-index值设为0。2.把第一个层移到模特身上,找出衣服刚好穿上时层的top和left值,记下来,
- 介绍我们可以使用code-generator 以及controller-tools来进行代码自动生成,通过代码自动生成可以帮我们自动生成 C
- 近几日遇到采集某网页的时候大部分网页OK,少部分网页出现乱码的问题,调试了几日,终于发现了是含有一些非法字符造成的..特此记录1. 在正常情
- 我们有时候会需要在网上查找并下载图片,当数量比较少的时候,点击右键保存,很轻松就可以实现图片的下载,但是有些图片进行了特殊设置,点击右键没有
- 1.Http连接基础Http协议承载了互联网上的主要流量,然而说到传输,还要回归到最基本的网络分层模型TCP/IP。TCP/IP是全球计算机
- 0x00 前言eval是Python用于执行python表达式的一个内置函数,使用eval,可以很方便的将字符串动态执行。比如下列代码:&g
- 1. 问题描述输入一个字符串然后对其进行逆序输出第一种方式:字符串切片第二种方式:使用循环转换然后逆序输出比如:输入字符串'hell
- 背景要做IP地址归属地查询,量比较大,所以想先从网上找到大部分的分配数据,写个蜘蛛程序来抓取入库,以后在程序的运行中不断进行维护、更新、完善