php 开发微信app支付接口
【准备工作】
在准备着手开发之前呢,我建议大家先去查阅一下微信的 APP支付开发者文档 ,对微信支付开发的流程有一个系统的了解。
我这里为大家准备了一张交互时序图,以便大家随时查看:
商户系统和微信支付系统主要交互说明:
- 用户在商户APP中选择商品,提交订单,选择微信支付。
- 商户后台收到用户支付单,调用微信支付统一下单接口。参见 【统一下单API】
- 统一下单接口返回正常的prepay_id,再按签名规范重新生成签名后,将数据传输给APP。参与签名的字段名为appId,partnerId,prepayId,nonceStr,timeStamp,package。注意:package的值格式为Sign=WXPay
- 商户APP调起微信支付。api参见本章节 【app端开发步骤说明】
- 商户后台接收支付通知。api参见 【支付结果通知API】
- 商户后台查询支付结果。api参见 【查询订单API】
【着手开发】
由于我们是做服务端,因此我们更关注服务端数据的处理,因此,跳过第一步。不过我们还是要先来模拟一些订单数据:
点击步骤2中的统一下单API的链接,我们可以看到我们请求接口时需要向其传输的一些参数,包括应用ID、商户号、设备号等等,我们只需向其传输必填项即可,选填数据可以根据自己的实际需求来决定。
appid 和 mch_id 分别去到微信开放平台和微信商户平台中获取,nonce_str (随机字符串) 很随意了,不长于32位就好。
/*** 生成随机数并返回*/
private function getNonceStr() {$code = "";for ($i=0; $i > 10; $i++) { $code .= mt_rand(1000); //获取随机数}$nonceStrTemp = md5($code);$nonce_str = mb_substr($nonceStrTemp, 5,37); //MD5加密后截取32位字符return $nonce_str;
}
notify_url(通知地址)是接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数。例如:'https://pay.weixin.qq.com/wxpay/pay.action'
接下来是微信支付中最为关键的步骤之一:签名,微信支付整个流程下来一共要经过三次签名。
上图所示的是微信签名的算法说明,特别要注意图上我所标识出来的关键点。根据微信官方的签名算法,我编写了下面的方法:
/*** 获取参数签名;* @param Array 要传递的参数数组* @return String 通过计算得到的签名;*/
private function getSign($params) {ksort($params); //将参数数组按照参数名ASCII码从小到大排序foreach ($params as $key => $item) {if (!empty($item)) { //剔除参数值为空的参数$newArr[] = $key.'='.$item; // 整合新的参数数组}}$stringA = implode("&", $newArr); //使用 & 符号连接参数$stringSignTemp = $stringA."&key=".$this->key; //拼接key// key是在商户平台API安全里自己设置的$stringSignTemp = MD5($stringSignTemp); //将字符串进行MD5加密$sign = strtoupper($stringSignTemp); //将所有字符转换为大写return $sign;
}
注意:key的值长度不能超过32位。
这里,我们最好编写一个类文件来包含这些方法,比如上面我们获取签名的方法会重复调用很多次,写在类方法里能减少耦合,并且方便多次调用。
那么我编写了一个微信支付的类文件(我会在文章的最末尾将源码提供给大家参考),该类在实例化的同时会初始化一些固定数据,例如appid 、 mch_id 等
/*** 构造函数,初始化成员变量* @param String $appid 商户的应用ID* @param Int $mch_id 商户编号* @param String $key 秘钥*/
// 将构造函数设置为私有,禁止用户实例化该类
private function __construct($appid, $mch_id, $key) {if (is_string($appid) && is_string($mch_id)) {$this->appid = $appid;$this->mch_id = $mch_id;$this->key = $key;}
}/*** 获取微信支付类实例* 该类使用单例模式* @return WeEncryption 本类实例*/
public static function getInstance() {if(self::$instance == null) {self::$instance = new Self(APPID, MCHID, APP_KEY);}return self::$instance;
}
再调用 WeEncryption::setNotifyUrl($url) 方法来设置异步通知回调地址:
/*** 设置通知地址* @param String $url 通知地址;*/
public function setNotifyUrl($url) {if (is_string($url)) {$this->notify_url = $url;}
}
直到现在,我们所有需要向统一下单接口传输的数据已经全部准备完毕了,接下来就该向微信请求数据了。
首先我们先将要发送的数据拼装成xml格式:
/*** 拼装请求的数据* @return String 拼装完成的数据*/
private function setSendData($data) {$this->sTpl = "<xml><appid><![CDATA[%s]]></appid><body><![CDATA[%s]]></body><mch_id><![CDATA[%s]]></mch_id><nonce_str><![CDATA[%s]]></nonce_str><notify_url><![CDATA[%s]]></notify_url><out_trade_no><![CDATA[%s]]></out_trade_no><spbill_create_ip><![CDATA[%s]]></spbill_create_ip><total_fee><![CDATA[%d]]></total_fee><trade_type><![CDATA[%s]]></trade_type><sign><![CDATA[%s]]></sign></xml>"; //xml数据模板$nonce_str = $this->getNonceStr(); //调用随机字符串生成方法获取随机字符串$data['appid'] = $this->appid;$data['mch_id'] = $this->mch_id;$data['nonce_str'] = $nonce_str;$data['notify_url'] = $this->notify_url;$data['trade_type'] = $this->trade_type; //将参与签名的数据保存到数组// 注意:以上几个参数是追加到$data中的,$data中应该同时包含开发文档中要求必填的剔除sign以外的所有数据$sign = $this->getSign($data); //获取签名$data = sprintf($this->sTpl, $this->appid, $data['body'], $this->mch_id, $nonce_str, $this->notify_url, $data['out_trade_no'], $data['spbill_create_ip'], $data['total_fee'], $this->trade_type, $sign);//生成xml数据格式return $data;
}
特别注意:
- xml数据要使用<![CDATA[]]>注释包裹
到此,我们的准备工作已经完毕,可以开始向统一下单接口发起请求了:
/*** 发送下单请求;* @param Curl $curl 请求资源句柄* @return mixed 请求返回数据*/
public function sendRequest(Curl $curl, $data) {$data = $this->setSendData($data); //获取要发送的数据$url = "https://api.mch.weixin.qq.com/pay/unifiedorder";$curl->setUrl($url); //设置请求地址$content = $curl->execute(true, 'POST', $data); //执行该请求return $content; //返回请求到的数据
}
以上示例代码中包含了一个Curl类,是一个数据请求工具类,不了解的小伙伴可以百度查一下。该工具主要是帮助我们发送请求用的,稍后我会在文章的最后将该类文件的源码跟微信支付类一起展示给大家。
我们在客户端代码中实例化该工具类,调用 WeEncryption::sendRequest(Curl $curl, $data) 方法请求下单接口:
$curl = new Curl(); //实例化传输类;
$xml_data = $encpt->sendRequest($curl, $data); //发送请求
我们已经向下单接口发送请求,如果请求成功,微信会向我们返回一些数据
好的,此时我们开始第三步 —— 二次签名。
我们重点关注一下返回数据中的 prepay_id,该参数是微信生成的预支付回话标识,用于后续接口调用中使用,该值有效期为2小时。
在第三步中我们得知:
- 统一下单接口返回正常的prepay_id,再按签名规范重新生成签名后,将数据传输给APP。
- 参与签名的字段名为appId,partnerId,prepayId,nonceStr,timeStamp,package。
上一步我们向微信发送请求后,我们将返回的数据保存到了变量$xml_data中,接下来,我们根据上一步微信返回的数据判断上一次的请求是否成功:
$postObj = $encpt->xmlToObject($xml_data); //解析返回数据if ($postObj === false) {echo 'FAIL';exit; // 如果解析的结果为false,终止程序
}if ($postObj->return_code == 'FAIL') {echo $postObj->return_msg; // 如果微信返回错误码为FAIL,则代表请求失败,返回失败信息;
} else {//如果上一次请求成功,那么我们将返回的数据重新拼装,进行第二次签名$resignData = array('appid' => $postObj->appid,'partnerId' => $postObj->mch_id,'prepayId' => $postObj->prepay_id,'nonceStr' => $postObj->nonce_str,'timeStamp' => time(),'package' => 'Sign=WXPay');//二次签名;$sign = $encpt->getClientPay($resignData);echo $sign;
}
上述代码中,我们先调用了 WeEncryption::xmlToObject($xml_data) 方法解析返回数据:
/*** 解析xml文档,转化为对象* @author 栗荣发 2016-09-20* @param String $xmlStr xml文档* @return Object 返回Obj对象*/
public function xmlToObject($xmlStr) {if (!is_string($xmlStr) || empty($xmlStr)) {return false;}// 由于解析xml的时候,即使被解析的变量为空,依然不会报错,会返回一个空的对象,所以,我们这里做了处理,当被解析的变量不是字符串,或者该变量为空,直接返回false$postObj = simplexml_load_string($xmlStr, 'SimpleXMLElement', LIBXML_NOCDATA);$postObj = json_decode(json_encode($postObj));//将xml数据转换成对象返回return $postObj;
}
如果返回数据无误,接着将重新参与签名的数据拼装好,进行二次签名,在这里我需要提醒一下大家:
- package的值为Sign=WXPay不变
- 时间戳使用time()获取就好
- mch_id 即为 partnerId
- 其他数据可以使用微信返回的数据,也可以自己写
- 最重要的一点,看下图:
图中微信说参与签名的字段包含这些,我圈起来的变量是大小写结合的,但实际上,二次签名的时候所有的变量都是小写的,否则会提示签名错误(这一点坑了我好久)。
最后调用 WeEncryption::getClientPay($data) 重新生成签名
/*** 获取客户端支付信息* @author 栗荣发 2016-09-18* @param Array $data 参与签名的信息数组* @return String 签名字符串*/
public function getClientPay($data) {$sign = $this->getSign($data); // 生成签名并返回return $sign;
}
将重新生成的签名传输给 APP 客户端。
返回的时候,要将sign,appId,partnerId,prepayId,nonceStr,timeStamp,package 这七个值一起返回个 APP 客户端。
【验签】
完成前面我们讲解的过程之后,APP客户端已经可以调起微信的支付界面进行支付了,但是整个过程还没有完成。为了用户资金的安全起见,防止数据被篡改,我们要对微信返回过来的数据进行验证。
还记得我们上一次向微信发送请求的时候,我们填写了一个 notify_url 的参数吗?当APP客户端请求支付成功后,微信会发起一个并行操作:
- 向APP客户端返回支付状态
- 向商户后台服务器返回支付结果
我们先来获取一下微信向商户后台服务器返回的结果:
/*** 接收支付结果通知参数* @return Object 返回结果对象;*/
public function getNotifyData() {$postXml = $GLOBALS["HTTP_RAW_POST_DATA"]; // 接受通知参数;if (empty($postXml)) {return false;}$postObj = $this->xmlToObject($postXml); // 调用解析方法,将xml数据解析成对象if ($postObj === false) {return false;}if (!empty($postObj->return_code)) {if ($postObj->return_code == 'FAIL') {return false;}}return $postObj; // 返回结果对象;
}
然后我们在客户端代码中接收一下:
$obj = $encpt->getNotifyData(); // 接收数据对象
然后重新拼装数据准备第三次签名:
if ($obj) {$data = array('appid' => $obj->appid,'mch_id' => $obj->mch_id,'nonce_str' => $obj->nonce_str,'result_code' => $obj->result_code,'openid' => $obj->openid,'trade_type' => $obj->trade_type,'bank_type' => $obj->bank_type,'total_fee' => $obj->total_fee,'cash_fee' => $obj->cash_fee,'transaction_id' => $obj->transaction_id,'out_trade_no' => $obj->out_trade_no,'time_end' => $obj->time_end);// 拼装数据进行第三次签名$sign = $encpt->getSign($data); // 获取签名/** 将签名得到的sign值和微信传过来的sign值进行比对,如果一致,则证明数据是微信返回的。 */if ($sign == $obj->sign) {$reply = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";echo $reply; // 向微信后台返回结果。exit;}
}
你以为到这里整个流程就结束了?看起来是可以了,支付已经完成了,而且我们也已经向微信发送了成功消息,还有什么要做的吗?
答案是肯定的,接下来APP客户端还会我们发起请求查询实际结果。
试想:如果遇到突发情况,我们的服务器没有接收到来自微信的通知消息,那我们没有返回给微信任何消息,结果是失败的,但是APP客户端却收到了微信返回的支付成功的通知,遇见这种情况我们该怎么办?
因此,当支付流程结束后,我们的APP客户端依然要向我们发起一个请求,查询实际的订单状态,此时我们需要客户端将订单号传递给我们,然后我们使用订单号,继续向微信发起请求:
/*** 查询订单状态* @param Curl $curl 工具类* @param string $out_trade_no 订单号* @return xml 订单查询结果*/
public function queryOrder(Curl $curl, $out_trade_no) {$nonce_str = $this->getNonceStr();$data = array('appid' => $this->appid,'mch_id' => $this->mch_id,'out_trade_no' => $out_trade_no,'nonce_str' => $nonce_str);$sign = $this->getSign($data);$xml_data = '<xml><appid>%s</appid><mch_id>%s</mch_id><nonce_str>%s</nonce_str><out_trade_no>%s</out_trade_no><sign>%s</sign></xml>';$xml_data = sprintf($xml_data, $this->appid, $this->mch_id, $nonce_str, $out_trade_no, $sign);$url = "https://api.mch.weixin.qq.com/pay/orderquery";$curl->setUrl($url);$content = $curl->execute(true, 'POST', $xml_data);return $content;
}
获取到查询结果后,我们可以根据微信的返回值来判断实际的支付结果。
在这一步,我们也可以在确保成功后,将订单的信息保存到数据库。
作者:Elvis_Li
链接:http://www.jianshu.com/p/a5ddd19e01a3
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
php 开发微信app支付接口相关推荐
- android微信支付都需要什么意思,Android开发微信APP支付功能的要点小结
基本概念 包名值得是你APP的包,在创建工程时候设置的,需要在微信支付平台上面设置. 签名指的是你生成APK时候所用的签名文件的md5,去掉:全部小写,需要在微信支付平台上面设置. 调试阶段,签名文件 ...
- JAVA微信APP支付接口整合
2019独角兽企业重金招聘Python工程师标准>>> 上次我们看到了支付宝的APP支付工具,那么这次就来封装封装微信的APP支付;如果已经清楚了支付宝的支付流程,那么微信支付也和它 ...
- java app微信支付接口_JAVA微信APP支付接口整合
上次我们看到了支付宝的APP支付工具,那么这次就来封装封装微信的APP支付;如果已经清楚了支付宝的支付流程,那么微信支付也和它大同小异了,当然这其中肯定是有各种变化的: 首先让我把微信支付文档的官网贴 ...
- java 记录微信app支付接口实现
```java [原文链接(侵删)](https://www.cnblogs.com/godwhisper/p/6880060.html) 一.工具类ConstantUtil.javapublic c ...
- 微信APP支付开发(java)关联openId和appID不匹配问题
@ 微信打包APP,支付接口后台包这个错误,openId和appID不匹配 #之前一直做 H5 小程序方面 今天做App 简直闹心死了 微信app支付与小程序支付有所差别(切记不要传openid)上代 ...
- 微信APP支付-Java后台实现
微信APP支付-Java后台实现 转自:http://blog.csdn.net/walle167/article/details/50957503 1 注册微信开放者平台 开发者平台地址:https ...
- 微信app支付服务端开发记录
微信APP支付服务端 调用接口需要注意事项: 1.签名:需要全部参数参加签名,空值去掉.(实际传递了什么参数需要,就根据实际参数进行签名) 2.签名参数:appid是申请支付功能的app对于的ID,k ...
- JAVA微信扫码支付及微信App支付开发(模式二)完整功能实现
一,准备工作 事前申请一个商家版的微信公众号(目前微信支付只有商家版公众号可开通),然后开通微信支付功能,并做相应的配置. 申请开通微信公众号和开通微信支付需要等待审核,一般都5个工作日左右.开通成功 ...
- 微信APP支付功能开发
前期准备工作 1. 微信各平台功能认识 1.1 微信开放平台: 支持移动应用,公众号的开发,创建应用并得到APPID,使你的应用支持微信支付. 1.2 微信公众平台: 微信小程序,服务号,订阅号的开发 ...
- 微信app支付功能-服务端的实现-python3版
微信app支付功能-服务端的实现-python3版 一:需求说明 二:微信app支付处理流程 三:所需依赖 3.1 支付配置 四:接口开发 4.1 创建订单接口 4.2 微信异步回调接口 4.3 订单 ...
最新文章
- 前一千页CVE 对应影响产品信息 JSON文件格式转成HTML
- EfficientNet 测试
- hdu1.3.4 排序
- js 层 分页显示选择用户名
- WPF中的触发器(Trigger)
- kali破解WiFi时wlan0没有变wlan0mon_黑客入门干货:黑客使用 Aircrack-ng 破解 Wi-Fi 密码
- 使用string定义一个变量如何输出
- 和菜鸟一起学linux内核源码之启动篇
- Java对接(顺丰、京东、跨越、EMS、DHL、FedEx、UPS)七大快递
- 京东商城网上购物登录
- 如何确定网站的关键词呢
- UG/NX 装配克隆
- 逆向学习实战之--替换哈罗单车图片
- D2D第一个程序详解
- django项目启动加载训练的模型报错OSError: Unable to open file (unable to open file: name = ‘model/model_weigh完美解决
- win10查看计算机管理员,win10系统下怎么获取administrator管理员权限
- 对龙邱科技TC264库的理解
- 个性化lightswitch登录屏幕(附源码)
- DeepSORT C++版的一个bug
- Atitit外包优缺点 提升开发效率 外包模式 1.一般来说外包优点 1.1.更加方便快捷 时间成本降低了 1.2.会导致 经济成本高,,时间成本降低了, 2.缺点 2.1.成本高 2.2.
热门文章
- ecu故障现象_发动机各传感器故障现象总结
- 5G和4G有那些区别
- android在wifi和4G网络都可以使用的情况下,设置每次请求使用的网络类型
- js中的splice方法使用,删除数组中的最大最小值
- Windows Server 2008打印服务器安装与配置
- Python 断言的使用
- 64 The HTTP header line [ssl-client-cert:12330x11] does not conform to RFC 7230 and has been ignored
- PS CC2018替换/修改图片上的文字内容
- 中国半导体仍然弱小 产业生态体系亟需完善
- Word文档图标变成空白如何恢复