业务处理逻辑:
扫码支付模式二文档: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5
需要用到的API接口
统一下单:
文档地址:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1
查询订单:
文档地址:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_2
关闭订单:
文档地址:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_3
这里的支付的话可以自己写,也可以拿SDK用。
SDK的写法的这里不做说明,因为我有轻微强迫症。所以这里根据自己的需要写的接口
实际开发步骤
1.生成订单(这里不做描述)
2.过滤订单信息(订单过期时间等逻辑)
3.构造订单主体(生成签名)
4.请求微信服务器返回预支付URL
5.用JS或者PHP生成二维码
6.AJAX轮询请求查询订单接口
这里是基于ThinkPHP3.2的写法,需要单独写的可以参考下。
<?php
/**
* Created by PhpStorm.
* User: 隆航
* Date: 2016/12/11 0011
* Time: 17:21
*/
namespace Home\Controller;
use Think\Controller;
class WxpayController extends Controller
{
// 引入微信支付类
public function _initialize()
{
Vendor('Weixinpay.Weixinpay');
}
public function indexAction()
{
echo '404';
}
// 远程请求支付接口的时候ajax返回微信支付的URL地址
public function dowithpayAction(){
if(!IS_POST){
$this->error("非法请求");
}
$weixinpay = new \Weixinpay(C('weixinpay'));
//根据传递的订单号,信息查询 状态为 未支付 微信支付
M()->startTrans();
// todo 查询订单信息
$info = "";
if(!$info){
// 订单信息错误
M()->rollback();
}
// todo 判断订单过期
$order = array(
// 订单主体
'body' => "",//支付时候显示的标题
// 订单金额 单位是分
'total_fee' => "",//注意单位是分
// 商品订单号
'out_trade_no' => "",//商户订单号
// 商品ID 扫码支付必须的参数!
'product_id' => "",//商品ID
// 支付方式 模式2 扫码支付
'trade_type' => 'NATIVE',
// 订单支付结束时间
'time_expire' => date("YmdHis",time()+1800)//过期时间
);
$result = $weixinpay->unifiedOrder($order);
// todo 更新第三方订单号 生成预支付的时候会返回微信订单号 存入数据库
$arr['url'] = urldecode($result['code_url']); //返回URL地址
$arr['out_trade_no'] = $order['out_trade_no']; //返回订单号,查询时用
// todo 记录日志
M()->commit();
$this->ajaxReturn(array('code'=>1,'result'=>$arr));
}
//微信异步地址
public function notifyAction()
{
$wxpay=new \Weixinpay(C('weixinpay'));
$result=$wxpay->notify();
if ($result) {
// 验证成功 修改数据库的订单状态等 $result['out_trade_no']为订单号
// todo 记录日志 ("异步收到订单".$result['out_trade_no']."支付结果,等待验证数据");
if($result['result_code'] == 'SUCCESS'){
// todo 记录日志 ("异步收到订单".$result['out_trade_no']."支付完成通知,更新数据库数据");
M()->startTrans();
// todo 根据订单号查询订单信息
$info = '';
if(!empty($info)){
//订单信息正确,修改订单完成支付
$A = "";
$B = "";
if(false !== $A && false !== $B ){
M()->commit();
// todo 记录日志 ("异步返回订单:".$result['out_trade_no']." 支付完成,且修改数据库完成");
}else{
M()->rollback();
// todo 记录日志 ("异步返回订单:".$result['out_trade_no']." 支付完成,修改数据库时发生错误");
}
}else{
M()->rollback();
// 订单已经处理过 或者不符合要修改的状态 不做改动
// todo 记录日志 ("异步返回订单:".$result['out_trade_no']." 支付完成,订单已经处理过 或者不符合要修改的状态 不做改动");
}
}else{
// todo 记录日志 ("异步收到订单".$result['out_trade_no']."支付失败通知,错误代码:".$result['err_code'].",错误描述:".$result['err_code_des']);
}
}else{
// todo 记录日志 ("异步验证不通过");
}
}
// 查询订单状态
// 因为微信的异步时间没支付宝的即时
public function orderqueryAction($out_trade_no=null){
$out_trade_no = $_POST['out_trade_no'];
//检测必填参数
if(empty($out_trade_no)) {
$this->ajaxReturn(array('code'=>0,'msg'=>'查询订单号不能为空'));
}
$wxpay=new \Weixinpay(C('weixinpay'));
$result=$wxpay->orderquery($out_trade_no);
if($result['return_code']== 'SUCCESS'){
// todo 记录日志 ("查询订单号".$result['out_trade_no']."请求完成");
if($result['result_code'] == 'SUCCESS'){
// todo 记录日志 ("查询订单号".$result['out_trade_no']."请求业务结果完成");
if($result["trade_state"] == "SUCCESS" ){
// todo 记录日志 ("查询订单号".$result['out_trade_no']."完成,订单支付完成.验证数据库数据后修改数据");
M()->startTrans();
// todo 查询订单信息 订单未支付的时候
$info = "";
if(!empty($info)){
$A = "";
$B = "";
if(false !== $A && false !== $B){
M()->commit();
// todo 记录日志 ("查询订单号:".$result['out_trade_no']." 支付完成,且修改数据库完成");
$this->ajaxReturn(array('code'=>1,'msg'=>'支付完成,成功付款'.($result['total_fee']/100).'元',"url"=>"/index.php/xx/payOk/ordersn/".$result['out_trade_no']));
}else{
M()->rollback();
// todo 记录日志 ("查询订单号:".$result['out_trade_no']." 支付完成,修改数据库时发生错误");
$this->ajaxReturn(array('code'=>0,'msg'=>'支付完成,修改数据库时发生错误'));
}
}else{
// 订单已经处理过 或者不符合要修改的状态 不做改动
// todo 记录日志 ("查询订单号:".$result['out_trade_no']." 支付完成,订单已经处理过 或者不符合要修改的状态 不做改动");
M()->rollback();
$this->ajaxReturn(array('code'=>1,'msg'=>'支付完成,成功付款'.($result['total_fee']/100).'元',"url"=>"/index.php/xx/payOk/ordersn/".$result['out_trade_no']));
}
}elseif ($result['trade_state'] == "REFUND"){
// todo 记录日志 ("查询订单号".$result['out_trade_no']."完成,订单转入退款");
$this->ajaxReturn(array('code'=>0,'msg'=>'订单转入退款'));
}elseif ($result['trade_state'] == "NOTPAY"){
// todo 记录日志 ("查询订单号".$result['out_trade_no']."完成,订单未支付");
$this->ajaxReturn(array('code'=>0,'msg'=>'订单未支付'));
}elseif ($result['trade_state'] == "CLOSED"){
// todo 记录日志 ("查询订单号".$result['out_trade_no']."完成,订单已关闭");
$this->ajaxReturn(array('code'=>0,'msg'=>'订单已关闭'));
}elseif ($result['trade_state'] == "USERPAYING"){
// todo 记录日志 ("查询订单号".$result['out_trade_no']."完成,订单正在支付中");
$this->ajaxReturn(array('code'=>0,'msg'=>'订单正在支付中'));
}elseif ($result['trade_state'] == "PAYERROR"){
// todo 记录日志 ("查询订单号".$result['out_trade_no']."完成,订单支付失败");
$this->ajaxReturn(array('code'=>0,'msg'=>'订单支付失败'));
}
}else{
// todo 记录日志 ("查询订单号".$result['out_trade_no']."业务结果失败,错误代码:".$result['err_code'].",错误描述:".$result['err_code_des']);
$this->ajaxReturn(array('code'=>0,'msg'=>'错误代码'.$result['err_code'].",错误描述:".$result['err_code_des']));
}
}else {
if ($result['result_code'] == "ORDERCLOSED") {
// 订单已关闭
// todo 记录日志 ("查询订单:" . $result['out_trade_no'] . " 支付已过期,且修改数据库");
$this->ajaxReturn(array('code'=>0,'msg'=>'该订单已过期'));
}
// todo 记录日志 ("查询订单号" . $result['out_trade_no'] . "请求失败,错误码:".$result['return_msg']);
}
}
}
类库代码:放入ThinkPHP/Library/Vendor/Weixinpay/
<?php
/**
* Created by PhpStorm.
* User: 隆航
* Date: 2016/12/12 0012
* Time: 10:30
*/
class Weixinpay {
// 定义配置项
private $config = array();
// 设置参数
public function __construct($config){
if (empty($config)) {
$this->config = C('weixinpay');
}else{
$this->config = $config;
}
}
// 统一下单
// 传递订单参数
// 手册地址:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1
// body(产品描述)、total_fee(订单金额)、out_trade_no(订单号)、product_id(产品id)、trade_type(类型:JSAPI,NATIVE,APP)
public function unifiedOrder($order){
$weixinpay_config = $this->config;
$config = array(
// 公众账号ID
'appid' => $weixinpay_config['APPID'],
// 商家号
'mch_id' => $weixinpay_config['MCHID'],
// 随机字符串
'nonce_str' => uniqid(),
// 终端IP
'spbill_create_ip' => $_SERVER['REMOTE_ADDR'],
// 异步地址
'notify_url' => $weixinpay_config['NOTIFY_URL']
);
// 合并配置数据和订单数据
$data = array_merge($order,$config);
// 生成签名
$sign = $this->makeSign($data);
$data['sign'] = $sign;
$xml = $this->arrayToXml($data);
$url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
$result = $this->httpPost($xml,$url);
if ($result['return_code'] == 'FAIL') {
die($result['return_msg']);
}
//$result['sign'] = $sign;
//$result['nonce_str']= $config['nonce_str'];
return $result;
}
// 查询订单状态
// 传递商家订单号
// 手册地址 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_2
public function orderQuery($out_trade_no)
{
$weixinpay_config = $this->config;
$data = array(
'appid' => $weixinpay_config['APPID'],
'mch_id' => $weixinpay_config['MCHID'],
'nonce_str' => uniqid(),
'out_trade_no' => $out_trade_no
);
$sign = $this->makeSign($data);
$data['sign'] = $sign;
$xml = $this->arrayToXml($data);
$url = "https://api.mch.weixin.qq.com/pay/orderquery";//接收xml数据的文件
$result = $this->httpPost($xml,$url);
return $result;
}
public function orderClose($out_trade_no)
{
$weixinpay_config = $this->config;
$data = array(
'appid' => $weixinpay_config['APPID'],
'mch_id' => $weixinpay_config['MCHID'],
'out_trade_no' => $out_trade_no,
'nonce_str' => uniqid()
);
$sign = $this->makeSign($data);
$data['sign'] = $sign;
$xml = $this->arrayToXml($data);
$url = "https://api.mch.weixin.qq.com/pay/closeorder ";//接收xml数据的文件
$result = $this->httpPost($xml,$url);
return $result;
}
/**
* CURL 请求
* @param $xml
* @param $url
* @param bool $useCert
* @param int $second
* @return mixed
*/
function httpPost($xml, $url, $useCert = false, $second = 30)
{
$weixinpay_config = $this->config;
$ch = curl_init();
curl_setopt($ch, CURLOPT_TIMEOUT, $second);
curl_setopt($ch, CURLOPT_URL, $url);
// todo 兼容本地 正式请改为 true
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER,false);
// todo 严格校验 兼容本地 正式请不要注释
//curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);
//设置header
$header[] = "Content-type: text/xml";
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_HEADER, false);
//post提交方式
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
if($useCert == true){
curl_setopt($ch, CURLOPT_SSLCERTTYPE,'PEM');
curl_setopt($ch, CURLOPT_SSLCERT, $weixinpay_config['CERT']);
curl_setopt($ch, CURLOPT_SSLKEYTYPE,'PEM');
curl_setopt($ch, CURLOPT_SSLKEY, $weixinpay_config['CERT_KEY']);
}
$data = curl_exec($ch);
if(curl_errno($ch)){
die(curl_error($ch));
}
curl_close($ch);
$result = $this->XmltoArray($data);
return $result;
}
/**
* 验证回调
* @return bool|mixed
*/
public function notify(){
$xml = file_get_contents('php://input', 'r');
$data = $this->XmltoArray($xml);
// 保存原sign
$data_sign = $data['sign'];
// sign不参与签名
unset($data['sign']);
$sign = $this->makeSign($data);
// 判断签名是否正确 判断支付状态
if ($sign === $data_sign && $data['return_code']=='SUCCESS' && $data['result_code']=='SUCCESS') {
$result = $data;
}else{
$result = false;
}
// 返回状态给微信服务器
if ($result) {
$str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
}else{
$str='<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名失败]]></return_msg></xml>';
}
echo $str;
return $result;
}
/**
* 数组转xml
* @param $data
* @return string
* @throws Exception
*/
public function arrayToXml($data){
if(!is_array($data) || count($data) <= 0){
throw new Exception("数组数据异常!");
}
$xml = "<xml>";
foreach ($data as $key=>$val){
if (is_numeric($val)){
$xml.="<".$key.">".$val."</".$key.">";
}else{
$xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
}
}
$xml.="</xml>";
return $xml;
}
/**
* 生成签名
* 手册: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3
* @param $data
* @return string
*/
public function makeSign($data){
// 去空
$data=array_filter($data);
// 字典序排序
ksort($data);
// 转换请求参数
$string = http_build_query($data);
// 编码URL地址
$string = urldecode($string);
$config = $this->config;
// 加入KEY
$string_sign = $string."&key=".$config['KEY'];
// MD5加密
$sign = md5($string_sign);
// 转为大写
$result = strtoupper($sign);
return $result;
}
/**
* 将xml转为array
* @param $xml
* @return mixed
*/
public function xmlToArray($xml){
//禁止引用外部xml实体
libxml_disable_entity_loader(true);
$result= json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
return $result;
}
}
配置文件(这里的配置文件要用 LOAD_EXT_CONFIG 引入,或者直接写在config里面 )
<?php
/**
* Created by PhpStorm.
* User: 隆航
* Date: 2016/12/10 0010
* Time: 17:44
*/
return array(
'weixinpay' => array(
'APPID' => '', // 微信支付APPID
'MCHID' => '', // 微信支付MCHID 商户收款账号
'KEY' => 'v', // 微信支付KEY
//'APPSECRET' => '', // 公众帐号secert (公众号支付专用)
'NOTIFY_URL' => '', // 接收支付状态的连接
'CERT' => getcwd().'/Public/wxcacert/apiclient_cert.pem', //证书签名文件
'CERT_KEY' => getcwd().'/Public/wxcacert/apiclient_key.pem', //证书签名文件
),
);
上一篇: 阿里云短信接口使用...