基于H5的微信支付开发详解

开发 前端
这次总结一下用户在微信内打开网页时,可以调用微信支 付完成下单功能的模块开发,也就是在微信内的H5页面通过jsApi接口实现支付功能。当然了,微信官网上的微信支付开发文档也讲解的很详细,并且有实现 代码可供参考,有的朋友直接看文档就可以自己实现此支付接口的开发了。

这次总结一下用户在微信内打开网页时,可以调用微信支 付完成下单功能的模块开发,也就是在微信内的H5页面通过jsApi接口实现支付功能。当然了,微信官网上的微信支付开发文档也讲解的很详细,并且有实现 代码可供参考,有的朋友直接看文档就可以自己实现此支付接口的开发了。

一、前言

为何我还写一篇微信支付接口的博文呢?第一,我们 必须知道,所谓的工作经验很多都是靠总结出来的,你只有总结了更多知识,积累了更多经验,你才能在该行业中脱颖而出,我个人觉得如今的招聘,很多都需要工 作经验(1年、3年、5年....),其实,工作时间的长久不能衡量一个人技术水平的高低,有的人一年的工作经验能拿3年工作经验的程序猿的工资,有的3 年工作经验的却有可能比别人只有一年工作经验的还低,所以说,总结才能让自己的知识体系,经验深度更牛逼更稳固(虽然写一篇博文挺花费时间的);第二,写 博文分享给大家还是挺有成就感的,首先是能让新手从我分享的博文中能学到东西,并且能快速将博文所讲解的技术运用到实际中来,所以我写的博文基本上能让新 人快速读懂并且容易理解,另外,技术大神的话,看到博文有讲解的不对之处,还可以指出,并且可以交流,何乐而不为呢,我们需要的就是分享和交流。

扯远了,直接进入该主题的详解。

现在的微信支付方式有N种,看下图,有刷卡支付、 公众号支付、扫码支付和APP支付,另外还有支付工具的开发,本博文选择的是公众号支付借口而开发进行讲解,其他几种支付接口开发基本上思路都是一样的, 只要你能看懂我这博文所讲解的基本思路,你基本上也能独自开发其他几个支付接口。

二、思路详解

我们可以拿微信支付接口文档里的业务流程时序图看 看,如下图,基本思路是这样子:首先在后台生成一个链接,展示给用户让用户点击(例如页面上有微信支付的按钮),用户点击按钮后,网站后台会根据订单的相 关信息生成一个支付订单,此时会调用统一下单接口,对微信支付系统发起请求,而微信支付系统受到请求后,会根据请求过来的数据,生成一个 预支付交易会话标识(prepay_id,就是通过这个来识别该订单的),我们的网站收到微信支付系统的响应后,会得到prepay_id,然后通过自己 构造微信支付所需要的参数,接着将支付所需参数返回给客户端,用户此时可能会有一个订单信息页,会有一个按钮,点击支付,此时会调用JSAPI接口对微信 支付系统发起 请求支付,微信支付系统检查了请求的相关合法性之后,就会提示输入密码,用户此时输入密码确认,微信支付系统会对其进行验证,通过的话会返回支付结果,然 后微信跳转会H5页面,这其中有一步是异步通知网站支付结果,我们网站需要对此进行处理(比如说异步支付结果通过后,需要更新数据表或者订单信息,例如标 志用户已支付该订单了,同时也需要更新订单日志,防止用户重复提交订单)。

#p#

三、代码讲解

本次开发环境用的是php5.6 + MySQL + Redis + Linux + Apache,所选用的框架的CI框架(这些环境不一定需要和我的一致,框架也可以自己选择,反正自己稍微修改下代码就能移植过去了)。

微信支付接口的开发代码我已经提前写好了,在这里我对其进行分析讲解,方便大家能轻松理解,当然,假如你有一定的基础,直接看代码就能理清所有流程了,并且我的代码基本上都写上了注释(对于新手来说,这一点比微信文档所提供的代码好一点)。

1、构造一个链接展示给用户

这里我们提前需要知道一个点,那就是请求统一下单接口需要微信用户的openid(详情可看这https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1),而获取openid需要先获取code(详情可看这微信登录接口),所以我们需要构造一个获取code的URL:

  1. Wxpay.php文件: 
  2. <?php 
  3. defined('BASEPATH') OR exit('No direct script access allowed'); 
  4.  
  5. class Wxpay extends MY_Controller { 
  6.     public function __construct() { 
  7.         parent::__construct(); 
  8.         $this->load->model('wxpay_model'); 
  9.         //$this->load->model('wxpay'); 
  10.          
  11.     } 
  12.   
  13.     public function index() { 
  14.         //微信支付 
  15.         $this->smarty['wxPayUrl'] = $this->wxpay_model->retWxPayUrl(); 
  16.         $this->displayView('wxpay/index.tpl'); 
  17.     } 

在这先看看model里所写的几个类:model里有几个类:微信支付类、统一下单接口类、响应型接口基类、请求型接口基类、所有接口基类、配置类。为何要分那么多类而不在一个类里实现所有的方法的,因为,这样看起来代码逻辑清晰,哪个类该干嘛就干嘛。

这里我直接附上model的代码了,里面基本上每一个类每一个方法甚至每一行代码都会有解释的了,这里我就不对其展开一句句分析了:

 

  1. <?php 
  2. defined('BASEPATH') OR exit('No direct script access allowed'); 
  3.  
  4. class Wxpay_model extends CI_Model { 
  5.     public function __construct() { 
  6.         parent::__construct(); 
  7.     } 
  8.      
  9.     /** 
  10.      * 返回可以获得微信code的URL (用以获取openid) 
  11.      * @return [type] [description] 
  12.      */ 
  13.     public function retWxPayUrl() { 
  14.         $jsApi = new JsApi_handle(); 
  15.         return $jsApi->createOauthUrlForCode(); 
  16.     } 
  17.   
  18.     /** 
  19.      * 微信jsapi点击支付 
  20.      * @param  [type] $data [description] 
  21.      * @return [type]       [description] 
  22.      */ 
  23.     public function wxPayJsApi($data) { 
  24.         $jsApi = new JsApi_handle(); 
  25.         //统一下单接口所需数据 
  26.         $payData = $this->returnData($data); 
  27.         //获取code码,用以获取openid 
  28.         $code = $_GET['code']; 
  29.         $jsApi->setCode($code); 
  30.         //通过code获取openid 
  31.         $openid = $jsApi->getOpenId(); 
  32.          
  33.         $unifiedOrderResult = null
  34.         if ($openid != null) { 
  35.             //取得统一下单接口返回的数据 
  36.             $unifiedOrderResult = $this->getResult($payData, 'JSAPI', $openid); 
  37.             //获取订单接口状态 
  38.             $returnMessage = $this->returnMessage($unifiedOrder, 'prepay_id'); 
  39.             if ($returnMessage['resultCode']) { 
  40.                 $jsApi->setPrepayId($retuenMessage['resultField']); 
  41.                 //取得wxjsapi接口所需要的数据 
  42.                 $returnMessage['resultData'] = $jsApi->getParams(); 
  43.             }  
  44.  
  45.             return $returnMessage; 
  46.         } 
  47.     } 
  48.  
  49.     /** 
  50.      * 统一下单接口所需要的数据 
  51.      * @param  [type] $data [description] 
  52.      * @return [type]       [description] 
  53.      */ 
  54.     public function returnData($data) { 
  55.         $payData['sn'] = $data['sn']; 
  56.         $payData['body'] = $data['goods_name']; 
  57.         $payData['out_trade_no'] = $data['order_no']; 
  58.         $payData['total_fee'] = $data['fee']; 
  59.         $payData['attach'] = $data['attach']; 
  60.  
  61.         return $payData; 
  62.     } 
  63.  
  64.     /** 
  65.      * 返回统一下单接口结果 (参考https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1) 
  66.      * @param  [type] $payData    [description] 
  67.      * @param  [type] $trade_type [description] 
  68.      * @param  [type] $openid     [description] 
  69.      * @return [type]             [description] 
  70.      */ 
  71.     public function getResult($payData, $trade_type, $openid = null) { 
  72.         $unifiedOrder = new UnifiedOrder_handle(); 
  73.  
  74.         if ($opneid != null) { 
  75.             $unifiedOrder->setParam('openid', $openid); 
  76.         } 
  77.         $unifiedOrder->setParam('body', $payData['body']);  //商品描述 
  78.         $unifiedOrder->setParam('out_trade_no', $payData['out_trade_no']); //商户订单号 
  79.         $unifiedOrder->setParam('total_fee', $payData['total_fee']);    //总金额 
  80.         $unifiedOrder->setParam('attach', $payData['attach']);  //附加数据 
  81.         $unifiedOrder->setParam('notify_url', base_url('/Wxpay/pay_callback'));//通知地址 
  82.         $unifiedOrder->setParam('trade_type', $trade_type); //交易类型 
  83.  
  84.         //非必填参数,商户可根据实际情况选填 
  85.         //$unifiedOrder->setParam("sub_mch_id","XXXX");//子商户号 
  86.         //$unifiedOrder->setParam("device_info","XXXX");//设备号 
  87.         //$unifiedOrder->setParam("time_start","XXXX");//交易起始时间 
  88.         //$unifiedOrder->setParam("time_expire","XXXX");//交易结束时间 
  89.         //$unifiedOrder->setParam("goods_tag","XXXX");//商品标记 
  90.         //$unifiedOrder->setParam("product_id","XXXX");//商品ID 
  91.          
  92.         return $unifiedOrder->getResult(); 
  93.     } 
  94.  
  95.     /** 
  96.      * 返回微信订单状态 
  97.      */ 
  98.     public function returnMessage($unifiedOrderResult,$field){ 
  99.         $arrMessage=array("resultCode"=>0,"resultType"=>"获取错误","resultMsg"=>"该字段为空"); 
  100.         if($unifiedOrderResult==null){ 
  101.             $arrMessage["resultType"]="未获取权限"
  102.             $arrMessage["resultMsg"]="请重新打开页面"
  103.         }elseif ($unifiedOrderResult["return_code"] == "FAIL"
  104.         { 
  105.             $arrMessage["resultType"]="网络错误"
  106.             $arrMessage["resultMsg"]=$unifiedOrderResult['return_msg']; 
  107.         } 
  108.         elseif($unifiedOrderResult["result_code"] == "FAIL"
  109.         { 
  110.             $arrMessage["resultType"]="订单错误"
  111.             $arrMessage["resultMsg"]=$unifiedOrderResult['err_code_des']; 
  112.         } 
  113.         elseif($unifiedOrderResult[$field] != NULL) 
  114.         { 
  115.             $arrMessage["resultCode"]=1
  116.             $arrMessage["resultType"]="生成订单"
  117.             $arrMessage["resultMsg"]="OK"
  118.             $arrMessage["resultField"] = $unifiedOrderResult[$field]; 
  119.         } 
  120.         return $arrMessage; 
  121.     } 
  122.  
  123.     /** 
  124.      * 微信回调接口返回  验证签名并回应微信 
  125.      * @param  [type] $xml [description] 
  126.      * @return [type]      [description] 
  127.      */ 
  128.     public function wxPayNotify($xml) { 
  129.         $notify = new Wxpay_server(); 
  130.         $notify->saveData($xml); 
  131.         //验证签名,并回复微信 
  132.         //对后台通知交互时,如果微信收到商户的应答不是成功或者超时,微信认为通知失败 
  133.         //微信会通过一定的策略(如30分钟共8次),定期重新发起通知 
  134.         if ($notify->checkSign() == false) { 
  135.             $notify->setReturnParameter("return_code","FAIL");//返回状态码 
  136.             $notify->setReturnParameter("return_msg","签名失败");//返回信息 
  137.         } else { 
  138.             $notify->checkSign=TRUE; 
  139.             $notify->setReturnParameter("return_code","SUCCESS");//设置返回码 
  140.         } 
  141.  
  142.         return $notify; 
  143.     } 
  144.  
  145. /** 
  146. * JSAPI支付——H5网页端调起支付接口 
  147. */ 
  148. class JsApi_handle extends JsApi_common { 
  149.     public $code;//code码,用以获取openid 
  150.     public $openid;//用户的openid 
  151.     public $parameters;//jsapi参数,格式为json 
  152.     public $prepay_id;//使用统一支付接口得到的预支付id 
  153.     public $curl_timeout;//curl超时时间 
  154.  
  155.     function __construct() 
  156.     { 
  157.         //设置curl超时时间 
  158.         $this->curl_timeout = WxPayConf::CURL_TIMEOUT; 
  159.     } 
  160.  
  161.     /** 
  162.      * 生成获取code的URL 
  163.      * @return [type] [description] 
  164.      */ 
  165.     public function createOauthUrlForCode() { 
  166.         //重定向URL 
  167.         $redirectUrl = "http://www.itcen.cn/wxpay/confirm/".$orderId."?showwxpaytitle=1"
  168.         $urlParams['appid'] = WxPayConf::APPID; 
  169.         $urlParams['redirect_uri'] = $redirectUrl; 
  170.         $urlParams['response_type'] = 'code'
  171.         $urlParams['scope'] = 'snsapi_base'
  172.         $urlParams['state'] = "STATE"."#wechat_redirect"
  173.         //拼接字符串 
  174.         $queryString = $this->ToUrlParams($urlParams, false); 
  175.         return "https://open.weixin.qq.com/connect/oauth2/authorize?".$queryString; 
  176.     } 
  177.  
  178.     /** 
  179.      * 设置code 
  180.      * @param [type] $code [description] 
  181.      */ 
  182.     public function setCode($code) { 
  183.         $this->code = $code; 
  184.     } 
  185.  
  186.     /** 
  187.      *  作用:设置prepay_id 
  188.      */ 
  189.     public function setPrepayId($prepayId) 
  190.     { 
  191.         $this->prepay_id = $prepayId; 
  192.     } 
  193.  
  194.     /** 
  195.      *  作用:获取jsapi的参数 
  196.      */ 
  197.     public function getParams() 
  198.     { 
  199.         $jsApiObj["appId"] = WxPayConf::APPID; 
  200.         $timeStamp = time(); 
  201.         $jsApiObj["timeStamp"] = "$timeStamp"
  202.         $jsApiObj["nonceStr"] = $this->createNoncestr(); 
  203.         $jsApiObj["package"] = "prepay_id=$this->prepay_id"
  204.         $jsApiObj["signType"] = "MD5"
  205.         $jsApiObj["paySign"] = $this->getSign($jsApiObj); 
  206.         $this->parameters = json_encode($jsApiObj); 
  207.  
  208.         return $this->parameters; 
  209.     } 
  210.  
  211.     /** 
  212.      * 通过curl 向微信提交code 用以获取openid 
  213.      * @return [type] [description] 
  214.      */ 
  215.     public function getOpenId() { 
  216.         //创建openid 的链接 
  217.         $url = $this->createOauthUrlForOpenid(); 
  218.         //初始化 
  219.         $ch = curl_init(); 
  220.         curl_setopt($ch, CURL_TIMEOUT, $this->curl_timeout); 
  221.         curl_setopt($ch, CURL_URL, $url); 
  222.         curl_setopt($ch, CURL_SSL_VERIFYPEER, FALSE); 
  223.         curl_setopt($ch, CURL_SSL_VERIFYHOST, FALSE); 
  224.         curl_setopt($ch, CURL_HEADER, FALSE); 
  225.         curl_setopt($ch, CURL_RETURNTRANSFER, TRUE); 
  226.         //执行curl 
  227.         $res = curl_exec($ch); 
  228.         curl_close($ch); 
  229.         //取出openid 
  230.         $data = json_decode($res); 
  231.         if (isset($data['openid'])) { 
  232.             $this->openid = $data['openid']; 
  233.         } else { 
  234.             return null
  235.         } 
  236.  
  237.         return $this->openid; 
  238.  
  239.     } 
  240.  
  241.     /** 
  242.      * 生成可以获取openid 的URL 
  243.      * @return [type] [description] 
  244.      */ 
  245.     public function createOauthUrlForOpenid() { 
  246.         $urlParams['appid'] = WxPayConf::APPID; 
  247.         $urlParams['secret'] = WxPayConf::APPSECRET; 
  248.         $urlParams['code'] = $this->code; 
  249.         $urlParams['grant_type'] = "authorization_code"
  250.         $queryString = $this->ToUrlParams($urlParams, false); 
  251.         return "https://api.weixin.qq.com/sns/oauth2/access_token?".$queryString; 
  252.     } 
  253.  
  254. /** 
  255.  * 统一下单接口类 
  256.  */ 
  257. class UnifiedOrder_handle extends Wxpay_client_handle { 
  258.     public function __construct() { 
  259.         //设置接口链接 
  260.         $this->url = "https://api.mch.weixin.qq.com/pay/unifiedorder"
  261.         //设置curl超时时间 
  262.         $this->curl_timeout = WxPayConf::CURL_TIMEOUT; 
  263.     } 
  264.  
  265.  
  266. /** 
  267.  * 响应型接口基类 
  268.  */ 
  269. class Wxpay_server_handle extends JsApi_common{ 
  270.     public $data; //接收到的数据,类型为关联数组 
  271.     public $returnParams;   //返回参数,类型为关联数组 
  272.  
  273.     /** 
  274.      * 将微信请求的xml转换成关联数组 
  275.      * @param  [type] $xml [description] 
  276.      * @return [type]      [description] 
  277.      */ 
  278.     public function saveData($xml) { 
  279.         $this->data = $this->xmlToArray($xml);  
  280.     } 
  281.  
  282.  
  283.     /** 
  284.      * 验证签名 
  285.      * @return [type] [description] 
  286.      */ 
  287.     public function checkSign() { 
  288.         $tmpData = $this->data; 
  289.         unset($temData['sign']); 
  290.         $sign = $this->getSign($tmpData); 
  291.         if ($this->data['sign'] == $sign) { 
  292.             return true
  293.         } 
  294.         return false
  295.     } 
  296.  
  297.  
  298.     /** 
  299.      * 设置返回微信的xml数据 
  300.      */ 
  301.     function setReturnParameter($parameter, $parameterValue) 
  302.     { 
  303.         $this->returnParameters[$this->trimString($parameter)] = $this->trimString($parameterValue); 
  304.     } 
  305.  
  306.     /** 
  307.      * 将xml数据返回微信 
  308.      */ 
  309.     function returnXml() 
  310.     { 
  311.         $returnXml = $this->createXml(); 
  312.         return $returnXml; 
  313.     } 
  314.  
  315.  
  316. /** 
  317.  * 请求型接口的基类 
  318.  */ 
  319. class Wxpay_client_handle extends JsApi_common{ 
  320.     public $params; //请求参数,类型为关联数组 
  321.     public $response; //微信返回的响应 
  322.     public $result; //返回参数,类型类关联数组 
  323.     public $url; //接口链接 
  324.     public $curl_timeout; //curl超时时间 
  325.  
  326.     /** 
  327.      * 设置请求参数 
  328.      * @param [type] $param      [description] 
  329.      * @param [type] $paramValue [description] 
  330.      */ 
  331.     public function setParam($param, $paramValue) { 
  332.         $this->params[$this->tirmString($param)] = $this->trimString($paramValue); 
  333.     } 
  334.  
  335.     /** 
  336.      * 获取结果,默认不使用证书 
  337.      * @return [type] [description] 
  338.      */ 
  339.     public function getResult() { 
  340.         $this->postxml();  
  341.         $this->result = $this->xmlToArray($this->response); 
  342.  
  343.         return $this->result; 
  344.     } 
  345.  
  346.     /** 
  347.      * post请求xml 
  348.      * @return [type] [description] 
  349.      */ 
  350.     public function postxml() { 
  351.         $xml = $this->createXml(); 
  352.         $this->response = $this->postXmlCurl($xml, $this->curl, $this->curl_timeout); 
  353.  
  354.         return $this->response; 
  355.     } 
  356.  
  357.     public function createXml() { 
  358.         $this->params['appid'] = WxPayConf::APPID; //公众号ID 
  359.         $this->params['mch_id'] = WxPayConf::MCHID; //商户号 
  360.         $this->params['nonce_str'] = $this->createNoncestr();   //随机字符串 
  361.         $this->params['sign'] = $this->getSign($this->params);  //签名 
  362.          
  363.         return $this->arrayToXml($this->params);  
  364.     } 
  365.  
  366.      
  367.  
  368.  
  369. /** 
  370.  * 所有接口的基类 
  371.  */ 
  372. class JsApi_common { 
  373.     function __construct() { 
  374.  
  375.     } 
  376.  
  377.     public function trimString($value) { 
  378.         $ret = null
  379.         if (null != $value) { 
  380.             $ret = trim($value); 
  381.             if (strlen($ret) == 0) { 
  382.                 $ret = null
  383.             } 
  384.         }  
  385.         return $ret; 
  386.     } 
  387.  
  388.     /** 
  389.      * 产生随机字符串,不长于32位 
  390.      * @param  integer $length [description] 
  391.      * @return [type]          [description] 
  392.      */ 
  393.     public function createNoncestr($length = 32) { 
  394.         $chars = "abcdefghijklmnopqrstuvwxyz0123456789"
  395.         $str = ''
  396.         for ($i = 0; $i < $length; $i++) { 
  397.             $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1); 
  398.         } 
  399.  
  400.         return $str; 
  401.     } 
  402.  
  403.     /** 
  404.      * 格式化参数 拼接字符串,签名过程需要使用 
  405.      * @param [type] $urlParams     [description] 
  406.      * @param [type] $needUrlencode [description] 
  407.      */ 
  408.     public function ToUrlParams($urlParams, $needUrlencode) { 
  409.         $buff = ""
  410.         ksort($urlParams); 
  411.  
  412.         foreach ($urlParams as $k => $v) { 
  413.             if($needUrlencode) $v = urlencode($v); 
  414.             $buff .= $k .'='. $v .'&'
  415.         } 
  416.  
  417.         $reqString = ''
  418.         if (strlen($buff) > 0) { 
  419.             $reqString = substr($buff, 0, strlen($buff) - 1); 
  420.         } 
  421.  
  422.         return $reqString; 
  423.     } 
  424.  
  425.     /** 
  426.      * 生成签名 
  427.      * @param  [type] $params [description] 
  428.      * @return [type]         [description] 
  429.      */ 
  430.     public function getSign($obj) { 
  431.         foreach ($obj as $k => $v) { 
  432.             $params[$k] = $v; 
  433.         } 
  434.         //签名步骤一:按字典序排序参数 
  435.         ksort($params); 
  436.         $str = $this->ToUrlParams($params, false);   
  437.         //签名步骤二:在$str后加入key 
  438.         $str = $str."$key=".WxPayConf::KEY; 
  439.         //签名步骤三:md5加密 
  440.         $str = md5($str); 
  441.         //签名步骤四:所有字符转为大写 
  442.         $result = strtoupper($str); 
  443.  
  444.         return $result; 
  445.     } 
  446.  
  447.     /** 
  448.      * array转xml 
  449.      * @param  [type] $arr [description] 
  450.      * @return [type]      [description] 
  451.      */ 
  452.     public function arrayToXml($arr) { 
  453.         $xml = "<xml>"
  454.         foreach ($arr as $k => $v) { 
  455.             if (is_numeric($val)) { 
  456.                 $xml .= "<".$key.">".$key."</".$key.">"
  457.             } else { 
  458.                 $xml .= "<".$key."><![CDATA[".$val."]]></".$key.">"
  459.             } 
  460.         } 
  461.         $xml .= "</xml>"
  462.         return $xml; 
  463.     } 
  464.  
  465.     /** 
  466.      * 将xml转为array 
  467.      * @param  [type] $xml [description] 
  468.      * @return [type]      [description] 
  469.      */ 
  470.     public function xmlToArray($xml) { 
  471.         $arr = json_decode(json_encode(simplexml_load_string($xml, 'SinpleXMLElement', LIBXML_NOCDATA)), true); 
  472.  
  473.         return $arr; 
  474.     } 
  475.  
  476.     /** 
  477.      * 以post方式提交xml到对应的接口 
  478.      * @param  [type]  $xml    [description] 
  479.      * @param  [type]  $url    [description] 
  480.      * @param  integer $second [description] 
  481.      * @return [type]          [description] 
  482.      */ 
  483.     public function postXmlCurl($xml, $url, $second = 30) { 
  484.         //初始化curl 
  485.         $ch = curl_init(); 
  486.         //设置超时 
  487.         curl_setopt($ch, CURL_TIMEOUT, $second); 
  488.         curl_setopt($ch, CURL_URL, $url); 
  489.         //这里设置代理,如果有的话 
  490.         //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8'); 
  491.         //curl_setopt($ch,CURLOPT_PROXYPORT, 8080); 
  492.         curl_setopt($ch, CURL_SSL_VERIFYHOST, FALSE); 
  493.         curl_setopt($ch, CURL_SSL_VERIFYPEER, FALSE); 
  494.         //设置header 
  495.         curl_setopt($ch, CURL_HEADER, FALSE); 
  496.         //要求结果为字符串且输出到屏幕上 
  497.         curl_setopt($ch, CURL_RETURNTRANSFER, TRUE); 
  498.         //以post方式提交 
  499.         curl_setopt($ch, CURL_POST, TRUE); 
  500.         curl_setopt($ch, CURL_POSTFIELDS, $xml); 
  501.         //执行curl 
  502.         $res = curl_exec($ch); 
  503.  
  504.         if ($res) { 
  505.             curl_close($ch); 
  506.             return $res; 
  507.         } else { 
  508.             $error = curl_errno($ch); 
  509.             echo "curl出错,错误码:$error"."<br>"
  510.             echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>"
  511.             curl_close($ch); 
  512.             return false
  513.         } 
  514.     } 
  515.  
  516. /** 
  517.  * 配置类 
  518.  */ 
  519. class WxPayConf { 
  520.     //微信公众号身份的唯一标识。 
  521.     const APPID = 'wx654a22c6423213b7'
  522.     //受理商ID,身份标识 
  523.     const MCHID = '10043241'
  524.     const MCHNAME = 'KellyCen的博客'
  525.      
  526.     //商户支付密钥Key。 
  527.     const KEY = '0000000000000000000000000000000'
  528.     //JSAPI接口中获取openid 
  529.     const APPSECRET = '000000000000000000000000000'
  530.  
  531.     //证书路径,注意应该填写绝对路径 
  532.     const SSLCERT_PATH = '/home/WxPayCacert/apiclient_cert.pem'
  533.     const SSLKEY_PATH = '/home/WxPayCacert/apiclient_key.pem'
  534.     const SSLCA_PATH = '/home/WxPayCacert/rootca.pem'
  535.  
  536.     //本例程通过curl使用HTTP POST方法,此处可修改其超时时间,默认为30秒 
  537.     const CURL_TIMEOUT = 30
  538.  
  539. Wxpay_model.php 

 获取到code的URL后,将其分配到页面去,让用户去点击,用户进行点击后,就会从微信服务器获取到code,然后回调到redirect_uri所指的地址去。

2、获取到code后,会回调到redirect_uri所指向的地址去,这里是到了/Wxpay/confirm/,看看这个confirm方法是打算干嘛的

  1. /** 
  2.      * 手机端微信支付,此处是授权获取到code时的回调地址 
  3.      * @param  [type] $orderId 订单编号id 
  4.      * @return [type]          [description] 
  5.      */  
  6.     public function confirm($orderId) { 
  7.         //先确认用户是否登录 
  8.         $this->ensureLogin(); 
  9.         //通过订单编号获取订单数据 
  10.         $order = $this->wxpay_model->get($orderId); 
  11.         //验证订单是否是当前用户 
  12.         $this->_verifyUser($order); 
  13.  
  14.         //取得支付所需要的订单数据 
  15.         $orderData = $this->returnOrderData[$orderId]; 
  16.         //取得jsApi所需要的数据 
  17.         $wxJsApiData = $this->wxpay_model->wxPayJsApi($orderData); 
  18.         //将数据分配到模板去,在js里使用 
  19.         $this->smartyData['wxJsApiData'] = json_encode($wxJsApiData, JSON_UNESCAPED_UNICODE); 
  20.         $this->smartyData['order'] = $orderData; 
  21.         $this->displayView('wxpay/confirm.tpl'); 
  22.          
  23.     } 

这一步开始去取JSAPI支付接口所需要的数据了,这一步算是最主要的一步,这里还会调用统一下单接口获取到prepay_id,我们跳到

$this->wxpay_model->wxPayJsApi($orderData) 看看:

  1. /** 
  2.      * 微信jsapi点击支付 
  3.      * @param  [type] $data [description] 
  4.      * @return [type]       [description] 
  5.      */ 
  6.     public function wxPayJsApi($data) { 
  7.         $jsApi = new JsApi_handle(); 
  8.         //统一下单接口所需数据 
  9.         $payData = $this->returnData($data); 
  10.         //获取code码,用以获取openid 
  11.         $code = $_GET['code']; 
  12.         $jsApi->setCode($code); 
  13.         //通过code获取openid 
  14.         $openid = $jsApi->getOpenId(); 
  15.          
  16.         $unifiedOrderResult = null
  17.         if ($openid != null) { 
  18.             //取得统一下单接口返回的数据 
  19.             $unifiedOrderResult = $this->getResult($payData, 'JSAPI', $openid); 
  20.             //获取订单接口状态 
  21.             $returnMessage = $this->returnMessage($unifiedOrder, 'prepay_id'); 
  22.             if ($returnMessage['resultCode']) { 
  23.                 $jsApi->setPrepayId($retuenMessage['resultField']); 
  24.                 //取得wxjsapi接口所需要的数据 
  25.                 $returnMessage['resultData'] = $jsApi->getParams(); 
  26.             }  
  27.  
  28.             return $returnMessage; 
  29.         } 
  30.     } 

这里首先是取得下单接口所需要的数据;

接着获取到code码,通过code码获取到openid;

然后调用统一下单接口,取得下单接口的响应数据,即prepay_id;

最后取得微信支付JSAPI所需要的数据。

这就是上面这个方法所要做的事情,取到数据后,会将数据分配到模板里,然后根据官方文档所给的参考格式将其放在js里,如下面的代码:

  1. <!doctype html>   
  2. <html>   
  3. <head lang="zh-CN">   
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
  5. <!-- Make sure that we can test against real IE8 --> 
  6. <meta http-equiv="X-UA-Compatible" content="IE=8" /> 
  7. <title></title> 
  8. </head> 
  9. <body> 
  10.  
  11. <a href="javascript:callpay();" id="btnOrder">点击支付</a> 
  12. </body>   
  13. <script type="text/javascript"
  14.     //将数据付给js变量 
  15.     var wxJsApiData = {$wxJsApiData}; 
  16.     function onBridgeReady() 
  17.         { 
  18.             //格式参考官方文档 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6 
  19.             WeixinJSBridge.invoke( 
  20.                 'getBrandWCPayRequest'
  21.                 $.parseJSON(wxJsApiData.resultData), 
  22.                 function(res){ 
  23.                     if(res.err_msg == "get_brand_wcpay_request:ok" ){ 
  24.                         window.location.href="/wxpay/paysuccess/"+{$order.sn};  
  25.                     } 
  26.  
  27.                 }  
  28.             ); 
  29.         } 
  30.         function callpay() 
  31.         {  
  32.             if(!wxJsApiData.resultCode){ 
  33.                 alert(wxJsApiData.resultType+","+wxJsApiData.resultMsg+"!"); 
  34.                 return false
  35.             } 
  36.             if (typeof WeixinJSBridge == "undefined"){ 
  37.                 if( document.addEventListener ){ 
  38.                     document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false); 
  39.                 }else if (document.attachEvent){ 
  40.                     document.attachEvent('WeixinJSBridgeReady', onBridgeReady); 
  41.                     document.attachEvent('onWeixinJSBridgeReady', onBridgeReady); 
  42.                 } 
  43.             }else
  44.                 onBridgeReady(); 
  45.             } 
  46.         } 
  47. </script> 
  48. </html> 

3、此时用户只需要点击支付,就可以开始进入支付界面了,接着就是输入密码,确认,最后会提示支付成功,紧接着网站会提供一个支付成功跳转页面。类似微信文档里所提供的图片这样,这里我就直接截取文档里的案例图了:

4、这里还有一步,就是微信支付系统会异步通知网站后台用户的支付结果。在获取统一下单数据时,我们指定了一个通知地址,在model里可以找到

支付成功后,微信支付系统会将支付结果异步发送到此地址上/Wxpay/pay_callback/ ,我们来看一下这个方法

  1. /** 
  2.      * 支付回调接口 
  3.      * @return [type] [description] 
  4.      */ 
  5.     public function pay_callback() { 
  6.         $postData = ''
  7.         if (file_get_contents("php://input")) { 
  8.             $postData = file_get_contents("php://input"); 
  9.         } else { 
  10.             return
  11.         } 
  12.         $payInfo = array(); 
  13.         $notify = $this->wxpay_model->wxPayNotify($postData); 
  14.  
  15.         if ($notify->checkSign == TRUE) { 
  16.             if ($notify->data['return_code'] == 'FAIL') { 
  17.                 $payInfo['status'] = FALSE; 
  18.                 $payInfo['msg'] = '通信出错'
  19.             } elseif ($notify->data['result_code'] == 'FAIL') { 
  20.                 $payInfo['status'] = FALSE; 
  21.                 $payInfo['msg'] = '业务出错'
  22.             } else { 
  23.                 $payInfo['status'] = TRUE; 
  24.                 $payInfo['msg'] = '支付成功'
  25.                 $payInfo['sn']=substr($notify->data['out_trade_no'],8); 
  26.                 $payInfo['order_no'] = $notify->data['out_trade_no']; 
  27.                 $payInfo['platform_no']=$notify->data['transaction_id']; 
  28.                 $payInfo['attach']=$notify->data['attach']; 
  29.                 $payInfo['fee']=$notify->data['cash_fee']; 
  30.                 $payInfo['currency']=$notify->data['fee_type']; 
  31.                 $payInfo['user_sign']=$notify->data['openid']; 
  32.             } 
  33.         } 
  34.         $returnXml = $notify->returnXml(); 
  35.  
  36.         echo $returnXml; 
  37.  
  38.         $this->load->library('RedisCache'); 
  39.         if($payInfo['status']){ 
  40.            //这里要记录到日志处理(略) 
  41.             $this->model->order->onPaySuccess($payInfo['sn'], $payInfo['order_no'], $payInfo['platform_no'],'', $payInfo['user_sign'], $payInfo); 
  42.             $this->redis->RedisCache->set('order:payNo:'.$payInfo['order_no'],'OK',5000); 
  43.         }else
  44.            //这里要记录到日志处理(略) 
  45.             $this->model->order->onPayFailure($payInfo['sn'], $payInfo['order_no'], $payInfo['platform_no'],'', $payInfo['user_sign'], $payInfo, '订单支付失败 ['.$payInfo['msg'].']'); 
  46.         } 
  47.     } 

这方法就是对支付是否成功,对网站的支付相关逻辑进行后续处理,例如假如支付失败,就需要记录日志里说明此次交易失败,或者是做某一些逻辑处理,而支付成功又该如何做处理,等等。

这里我们就分析下这个方法 $this->wxpay_model->wxPayNotify($postData); 对异步返回的数据进行安全性校验,例如验证签名,看看model里的这个方法:

  1. /** 
  2.      * 微信回调接口返回  验证签名并回应微信 
  3.      * @param  [type] $xml [description] 
  4.      * @return [type]      [description] 
  5.      */ 
  6.     public function wxPayNotify($xml) { 
  7.         $notify = new Wxpay_server(); 
  8.         $notify->saveData($xml); 
  9.         //验证签名,并回复微信 
  10.         //对后台通知交互时,如果微信收到商户的应答不是成功或者超时,微信认为通知失败 
  11.         //微信会通过一定的策略(如30分钟共8次),定期重新发起通知 
  12.         if ($notify->checkSign() == false) { 
  13.             $notify->setReturnParameter("return_code","FAIL");//返回状态码 
  14.             $notify->setReturnParameter("return_msg","签名失败");//返回信息 
  15.         } else { 
  16.             $notify->checkSign=TRUE; 
  17.             $notify->setReturnParameter("return_code","SUCCESS");//设置返回码 
  18.         } 
  19.  
  20.         return $notify; 
  21.     } 

如果验证通过,则就开始进行交易成功或者失败时所要做的逻辑处理了,这逻辑处理的代码我就不写了,因为每一个网站的处理方式都不一样,我这里是这样处理的,我把思路写下,方便不懂的朋友可以按着我的思路去完善后续的处理:首先是查看数据库里的订单日志表,看这笔交易之前是否已经交易过了,交易过就不用再更新数据表了,如果没交易过,就会将之前存在redis的订单数据给取出来,再将这些数据插入到订单日志表里,差不多就这样处理。

好了,基于H5的微信支付接口开发详解就讲到这里,如果你认真理清博文里所讲解的思路,自己基本上也可以尝试开发此接口了,同时只要会了这个,你也基本上可以开发二维码支付,刷卡支付等等的支付接口。

#p#

这里我附上此次开发中的完整代码供大家阅读:

控制器:Wxpay.php

  1. <?php 
  2. defined('BASEPATH') OR exit('No direct script access allowed'); 
  3.  
  4. class Wxpay extends MY_Controller { 
  5.     public function __construct() { 
  6.         parent::__construct(); 
  7.         $this->load->model('wxpay_model'); 
  8.         //$this->load->model('wxpay'); 
  9.          
  10.     } 
  11.   
  12.     public function index() { 
  13.         //微信支付 
  14.         $this->smarty['wxPayUrl'] = $this->wxpay_model->retWxPayUrl(); 
  15.         $this->displayView('wxpay/index.tpl'); 
  16.     } 
  17.  
  18.     /** 
  19.      * 手机端微信支付,此处是授权获取到code时的回调地址 
  20.      * @param  [type] $orderId 订单编号id 
  21.      * @return [type]          [description] 
  22.      */  
  23.     public function confirm($orderId) { 
  24.         //先确认用户是否登录 
  25.         $this->ensureLogin(); 
  26.         //通过订单编号获取订单数据 
  27.         $order = $this->wxpay_model->get($orderId); 
  28.         //验证订单是否是当前用户 
  29.         $this->_verifyUser($order); 
  30.  
  31.         //取得支付所需要的订单数据 
  32.         $orderData = $this->returnOrderData[$orderId]; 
  33.         //取得jsApi所需要的数据 
  34.         $wxJsApiData = $this->wxpay_model->wxPayJsApi($orderData); 
  35.         //将数据分配到模板去,在js里使用 
  36.         $this->smartyData['wxJsApiData'] = json_encode($wxJsApiData, JSON_UNESCAPED_UNICODE); 
  37.         $this->smartyData['order'] = $orderData
  38.         $this->displayView('wxpay/confirm.tpl'); 
  39.          
  40.     } 
  41.     /** 
  42.      * 支付回调接口 
  43.      * @return [type] [description] 
  44.      */ 
  45.     public function pay_callback() { 
  46.         $postData = ''
  47.         if (file_get_contents("php://input")) { 
  48.             $postData = file_get_contents("php://input"); 
  49.         } else { 
  50.             return
  51.         } 
  52.         $payInfo = array(); 
  53.         $notify = $this->wxpay_model->wxPayNotify($postData); 
  54.  
  55.         if ($notify->checkSign == TRUE) { 
  56.             if ($notify->data['return_code'] == 'FAIL') { 
  57.                 $payInfo['status'] = FALSE; 
  58.                 $payInfo['msg'] = '通信出错'
  59.             } elseif ($notify->data['result_code'] == 'FAIL') { 
  60.                 $payInfo['status'] = FALSE; 
  61.                 $payInfo['msg'] = '业务出错'
  62.             } else { 
  63.                 $payInfo['status'] = TRUE; 
  64.                 $payInfo['msg'] = '支付成功'
  65.                 $payInfo['sn']=substr($notify->data['out_trade_no'],8); 
  66.                 $payInfo['order_no'] = $notify->data['out_trade_no']; 
  67.                 $payInfo['platform_no']=$notify->data['transaction_id']; 
  68.                 $payInfo['attach']=$notify->data['attach']; 
  69.                 $payInfo['fee']=$notify->data['cash_fee']; 
  70.                 $payInfo['currency']=$notify->data['fee_type']; 
  71.                 $payInfo['user_sign']=$notify->data['openid']; 
  72.             } 
  73.         } 
  74.         $returnXml = $notify->returnXml(); 
  75.  
  76.         echo $returnXml
  77.  
  78.         $this->load->library('RedisCache'); 
  79.         if($payInfo['status']){ 
  80.            //这里要记录到日志处理(略) 
  81.             $this->model->order->onPaySuccess($payInfo['sn'], $payInfo['order_no'], $payInfo['platform_no'],''$payInfo['user_sign'], $payInfo); 
  82.             $this->redis->RedisCache->set('order:payNo:'.$payInfo['order_no'],'OK',5000); 
  83.         }else
  84.            //这里要记录到日志处理(略) 
  85.             $this->model->order->onPayFailure($payInfo['sn'], $payInfo['order_no'], $payInfo['platform_no'],''$payInfo['user_sign'], $payInfo'订单支付失败 ['.$payInfo['msg'].']'); 
  86.         } 
  87.     } 
  88.  
  89.     /** 
  90.      * 返回支付所需要的数据 
  91.      * @param  [type] $orderId 订单号 
  92.      * @param  string $data    订单数据,当$data数据存在时刷新$orderData缓存,因为订单号不唯一 
  93.      * @return [type]          [description] 
  94.      */ 
  95.     public function returnOrderData($orderId$data = '') { 
  96.         //获取订单数据 
  97.         $order = $this->wxpay_model->get($orderId); 
  98.         if (0 === count($order)) return false; 
  99.         if (emptyempty($data)) { 
  100.             $this->load->library('RedisCache'); 
  101.             //取得缓存在redis的订单数据 
  102.             $orderData = $this->rediscache->getJson("order:orderData:".$orderId); 
  103.             if (emptyempty($orderData)) { 
  104.                 //如果redis里没有,则直接读数据库取 
  105.                 $this->load->model('order_model'); 
  106.                 $order = $this->order_model->get($orderId); 
  107.                 if (0 === count($order)) { 
  108.                     return false; 
  109.                 } 
  110.                 $data = $order
  111.             } else { 
  112.                 //如果redis里面有的话,直接返回数据 
  113.                 return $orderData
  114.             } 
  115.         } 
  116.   
  117.         //支付前缓存所需要的数据 
  118.         $orderData['id'] = $data['id']; 
  119.         $orderData['fee'] = $data['fee']; 
  120.  
  121.         //支付平台需要的数据 
  122.         $orderData['user_id'] = $data['user_id']; 
  123.         $orderData['sn'] = $data['cn']; 
  124.         //这是唯一编号 
  125.         $orderData['order_no'] = substr(md5($data['sn'].$data['fee']), 8, 8).$data['sn']; 
  126.         $orderData['fee'] = $data['fee']; 
  127.         $orderData['time'] = $data['time']; 
  128.         $orderData['goods_name'] = $data['goods_name']; 
  129.         $orderData['attach'] = $data['attach']; 
  130.  
  131.         //将数据缓存到redis里面 
  132.         $this->rediscache->set("order:orderData:".$orderId$orderData, 3600*24); 
  133.         //做个标识缓存到redis,用以判断该订单是否已经支付了 
  134.         $this->rediscache->set("order:payNo:".$orderData['order_no'], "NO", 3600*24); 
  135.  
  136.         return $orderData
  137.     } 
  138.  
  139.     private function _verifyUser($order) { 
  140.         if (emptyempty($order)) show_404(); 
  141.         if (0 === count($order)) show_404(); 
  142.         //判断订单表里的用户id是否是当前登录者的id 
  143.         if ($order['user_id'] == $this->uid) return
  144.         show_error('只能查看自己的订单'); 
  145.     } 
  146.  
  147.  
  148. 控制器:Wxpay.php 

模型:Wxpay_model.php

  1. <?php 
  2. defined('BASEPATH') OR exit('No direct script access allowed'); 
  3.  
  4. class Wxpay_model extends CI_Model { 
  5.     public function __construct() { 
  6.         parent::__construct(); 
  7.     } 
  8.      
  9.     /** 
  10.      * 返回可以获得微信code的URL (用以获取openid) 
  11.      * @return [type] [description] 
  12.      */ 
  13.     public function retWxPayUrl() { 
  14.         $jsApi = new JsApi_handle(); 
  15.         return $jsApi->createOauthUrlForCode(); 
  16.     } 
  17.   
  18.     /** 
  19.      * 微信jsapi点击支付 
  20.      * @param  [type] $data [description] 
  21.      * @return [type]       [description] 
  22.      */ 
  23.     public function wxPayJsApi($data) { 
  24.         $jsApi = new JsApi_handle(); 
  25.         //统一下单接口所需数据 
  26.         $payData = $this->returnData($data); 
  27.         //获取code码,用以获取openid 
  28.         $code = $_GET['code']; 
  29.         $jsApi->setCode($code); 
  30.         //通过code获取openid 
  31.         $openid = $jsApi->getOpenId(); 
  32.          
  33.         $unifiedOrderResult = null
  34.         if ($openid != null) { 
  35.             //取得统一下单接口返回的数据 
  36.             $unifiedOrderResult = $this->getResult($payData, 'JSAPI', $openid); 
  37.             //获取订单接口状态 
  38.             $returnMessage = $this->returnMessage($unifiedOrder, 'prepay_id'); 
  39.             if ($returnMessage['resultCode']) { 
  40.                 $jsApi->setPrepayId($retuenMessage['resultField']); 
  41.                 //取得wxjsapi接口所需要的数据 
  42.                 $returnMessage['resultData'] = $jsApi->getParams(); 
  43.             }  
  44.  
  45.             return $returnMessage; 
  46.         } 
  47.     } 
  48.  
  49.     /** 
  50.      * 统一下单接口所需要的数据 
  51.      * @param  [type] $data [description] 
  52.      * @return [type]       [description] 
  53.      */ 
  54.     public function returnData($data) { 
  55.         $payData['sn'] = $data['sn']; 
  56.         $payData['body'] = $data['goods_name']; 
  57.         $payData['out_trade_no'] = $data['order_no']; 
  58.         $payData['total_fee'] = $data['fee']; 
  59.         $payData['attach'] = $data['attach']; 
  60.  
  61.         return $payData; 
  62.     } 
  63.  
  64.     /** 
  65.      * 返回统一下单接口结果 (参考https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1) 
  66.      * @param  [type] $payData    [description] 
  67.      * @param  [type] $trade_type [description] 
  68.      * @param  [type] $openid     [description] 
  69.      * @return [type]             [description] 
  70.      */ 
  71.     public function getResult($payData, $trade_type, $openid = null) { 
  72.         $unifiedOrder = new UnifiedOrder_handle(); 
  73.  
  74.         if ($opneid != null) { 
  75.             $unifiedOrder->setParam('openid', $openid); 
  76.         } 
  77.         $unifiedOrder->setParam('body', $payData['body']);  //商品描述 
  78.         $unifiedOrder->setParam('out_trade_no', $payData['out_trade_no']); //商户订单号 
  79.         $unifiedOrder->setParam('total_fee', $payData['total_fee']);    //总金额 
  80.         $unifiedOrder->setParam('attach', $payData['attach']);  //附加数据 
  81.         $unifiedOrder->setParam('notify_url', base_url('/Wxpay/pay_callback'));//通知地址 
  82.         $unifiedOrder->setParam('trade_type', $trade_type); //交易类型 
  83.  
  84.         //非必填参数,商户可根据实际情况选填 
  85.         //$unifiedOrder->setParam("sub_mch_id","XXXX");//子商户号 
  86.         //$unifiedOrder->setParam("device_info","XXXX");//设备号 
  87.         //$unifiedOrder->setParam("time_start","XXXX");//交易起始时间 
  88.         //$unifiedOrder->setParam("time_expire","XXXX");//交易结束时间 
  89.         //$unifiedOrder->setParam("goods_tag","XXXX");//商品标记 
  90.         //$unifiedOrder->setParam("product_id","XXXX");//商品ID 
  91.          
  92.         return $unifiedOrder->getResult(); 
  93.     } 
  94.  
  95.     /** 
  96.      * 返回微信订单状态 
  97.      */ 
  98.     public function returnMessage($unifiedOrderResult,$field){ 
  99.         $arrMessage=array("resultCode"=>0,"resultType"=>"获取错误","resultMsg"=>"该字段为空"); 
  100.         if($unifiedOrderResult==null){ 
  101.             $arrMessage["resultType"]="未获取权限"
  102.             $arrMessage["resultMsg"]="请重新打开页面"
  103.         }elseif ($unifiedOrderResult["return_code"] == "FAIL"
  104.         { 
  105.             $arrMessage["resultType"]="网络错误"
  106.             $arrMessage["resultMsg"]=$unifiedOrderResult['return_msg']; 
  107.         } 
  108.         elseif($unifiedOrderResult["result_code"] == "FAIL"
  109.         { 
  110.             $arrMessage["resultType"]="订单错误"
  111.             $arrMessage["resultMsg"]=$unifiedOrderResult['err_code_des']; 
  112.         } 
  113.         elseif($unifiedOrderResult[$field] != NULL) 
  114.         { 
  115.             $arrMessage["resultCode"]=1
  116.             $arrMessage["resultType"]="生成订单"
  117.             $arrMessage["resultMsg"]="OK"
  118.             $arrMessage["resultField"] = $unifiedOrderResult[$field]; 
  119.         } 
  120.         return $arrMessage; 
  121.     } 
  122.  
  123.     /** 
  124.      * 微信回调接口返回  验证签名并回应微信 
  125.      * @param  [type] $xml [description] 
  126.      * @return [type]      [description] 
  127.      */ 
  128.     public function wxPayNotify($xml) { 
  129.         $notify = new Wxpay_server(); 
  130.         $notify->saveData($xml); 
  131.         //验证签名,并回复微信 
  132.         //对后台通知交互时,如果微信收到商户的应答不是成功或者超时,微信认为通知失败 
  133.         //微信会通过一定的策略(如30分钟共8次),定期重新发起通知 
  134.         if ($notify->checkSign() == false) { 
  135.             $notify->setReturnParameter("return_code","FAIL");//返回状态码 
  136.             $notify->setReturnParameter("return_msg","签名失败");//返回信息 
  137.         } else { 
  138.             $notify->checkSign=TRUE; 
  139.             $notify->setReturnParameter("return_code","SUCCESS");//设置返回码 
  140.         } 
  141.  
  142.         return $notify; 
  143.     } 
  144.  
  145. /** 
  146. * JSAPI支付——H5网页端调起支付接口 
  147. */ 
  148. class JsApi_handle extends JsApi_common { 
  149.     public $code;//code码,用以获取openid 
  150.     public $openid;//用户的openid 
  151.     public $parameters;//jsapi参数,格式为json 
  152.     public $prepay_id;//使用统一支付接口得到的预支付id 
  153.     public $curl_timeout;//curl超时时间 
  154.  
  155.     function __construct() 
  156.     { 
  157.         //设置curl超时时间 
  158.         $this->curl_timeout = WxPayConf::CURL_TIMEOUT; 
  159.     } 
  160.  
  161.     /** 
  162.      * 生成获取code的URL 
  163.      * @return [type] [description] 
  164.      */ 
  165.     public function createOauthUrlForCode() { 
  166.         //重定向URL 
  167.         $redirectUrl = "http://www.itcen.cn/wxpay/confirm/".$orderId."?showwxpaytitle=1"
  168.         $urlParams['appid'] = WxPayConf::APPID; 
  169.         $urlParams['redirect_uri'] = $redirectUrl; 
  170.         $urlParams['response_type'] = 'code'
  171.         $urlParams['scope'] = 'snsapi_base'
  172.         $urlParams['state'] = "STATE"."#wechat_redirect"
  173.         //拼接字符串 
  174.         $queryString = $this->ToUrlParams($urlParams, false); 
  175.         return "https://open.weixin.qq.com/connect/oauth2/authorize?".$queryString; 
  176.     } 
  177.  
  178.     /** 
  179.      * 设置code 
  180.      * @param [type] $code [description] 
  181.      */ 
  182.     public function setCode($code) { 
  183.         $this->code = $code; 
  184.     } 
  185.  
  186.     /** 
  187.      *  作用:设置prepay_id 
  188.      */ 
  189.     public function setPrepayId($prepayId) 
  190.     { 
  191.         $this->prepay_id = $prepayId; 
  192.     } 
  193.  
  194.     /** 
  195.      *  作用:获取jsapi的参数 
  196.      */ 
  197.     public function getParams() 
  198.     { 
  199.         $jsApiObj["appId"] = WxPayConf::APPID; 
  200.         $timeStamp = time(); 
  201.         $jsApiObj["timeStamp"] = "$timeStamp"
  202.         $jsApiObj["nonceStr"] = $this->createNoncestr(); 
  203.         $jsApiObj["package"] = "prepay_id=$this->prepay_id"
  204.         $jsApiObj["signType"] = "MD5"
  205.         $jsApiObj["paySign"] = $this->getSign($jsApiObj); 
  206.         $this->parameters = json_encode($jsApiObj); 
  207.  
  208.         return $this->parameters; 
  209.     } 
  210.  
  211.     /** 
  212.      * 通过curl 向微信提交code 用以获取openid 
  213.      * @return [type] [description] 
  214.      */ 
  215.     public function getOpenId() { 
  216.         //创建openid 的链接 
  217.         $url = $this->createOauthUrlForOpenid(); 
  218.         //初始化 
  219.         $ch = curl_init(); 
  220.         curl_setopt($ch, CURL_TIMEOUT, $this->curl_timeout); 
  221.         curl_setopt($ch, CURL_URL, $url); 
  222.         curl_setopt($ch, CURL_SSL_VERIFYPEER, FALSE); 
  223.         curl_setopt($ch, CURL_SSL_VERIFYHOST, FALSE); 
  224.         curl_setopt($ch, CURL_HEADER, FALSE); 
  225.         curl_setopt($ch, CURL_RETURNTRANSFER, TRUE); 
  226.         //执行curl 
  227.         $res = curl_exec($ch); 
  228.         curl_close($ch); 
  229.         //取出openid 
  230.         $data = json_decode($res); 
  231.         if (isset($data['openid'])) { 
  232.             $this->openid = $data['openid']; 
  233.         } else { 
  234.             return null
  235.         } 
  236.  
  237.         return $this->openid; 
  238.  
  239.     } 
  240.  
  241.     /** 
  242.      * 生成可以获取openid 的URL 
  243.      * @return [type] [description] 
  244.      */ 
  245.     public function createOauthUrlForOpenid() { 
  246.         $urlParams['appid'] = WxPayConf::APPID; 
  247.         $urlParams['secret'] = WxPayConf::APPSECRET; 
  248.         $urlParams['code'] = $this->code; 
  249.         $urlParams['grant_type'] = "authorization_code"
  250.         $queryString = $this->ToUrlParams($urlParams, false); 
  251.         return "https://api.weixin.qq.com/sns/oauth2/access_token?".$queryString; 
  252.     } 
  253.  
  254. /** 
  255.  * 统一下单接口类 
  256.  */ 
  257. class UnifiedOrder_handle extends Wxpay_client_handle { 
  258.     public function __construct() { 
  259.         //设置接口链接 
  260.         $this->url = "https://api.mch.weixin.qq.com/pay/unifiedorder"
  261.         //设置curl超时时间 
  262.         $this->curl_timeout = WxPayConf::CURL_TIMEOUT; 
  263.     } 
  264.  
  265.  
  266. /** 
  267.  * 响应型接口基类 
  268.  */ 
  269. class Wxpay_server_handle extends JsApi_common{ 
  270.     public $data; //接收到的数据,类型为关联数组 
  271.     public $returnParams;   //返回参数,类型为关联数组 
  272.  
  273.     /** 
  274.      * 将微信请求的xml转换成关联数组 
  275.      * @param  [type] $xml [description] 
  276.      * @return [type]      [description] 
  277.      */ 
  278.     public function saveData($xml) { 
  279.         $this->data = $this->xmlToArray($xml);  
  280.     } 
  281.  
  282.  
  283.     /** 
  284.      * 验证签名 
  285.      * @return [type] [description] 
  286.      */ 
  287.     public function checkSign() { 
  288.         $tmpData = $this->data; 
  289.         unset($temData['sign']); 
  290.         $sign = $this->getSign($tmpData); 
  291.         if ($this->data['sign'] == $sign) { 
  292.             return true
  293.         } 
  294.         return false
  295.     } 
  296.  
  297.  
  298.     /** 
  299.      * 设置返回微信的xml数据 
  300.      */ 
  301.     function setReturnParameter($parameter, $parameterValue) 
  302.     { 
  303.         $this->returnParameters[$this->trimString($parameter)] = $this->trimString($parameterValue); 
  304.     } 
  305.  
  306.     /** 
  307.      * 将xml数据返回微信 
  308.      */ 
  309.     function returnXml() 
  310.     { 
  311.         $returnXml = $this->createXml(); 
  312.         return $returnXml; 
  313.     } 
  314.  
  315.  
  316. /** 
  317.  * 请求型接口的基类 
  318.  */ 
  319. class Wxpay_client_handle extends JsApi_common{ 
  320.     public $params; //请求参数,类型为关联数组 
  321.     public $response; //微信返回的响应 
  322.     public $result; //返回参数,类型类关联数组 
  323.     public $url; //接口链接 
  324.     public $curl_timeout; //curl超时时间 
  325.  
  326.     /** 
  327.      * 设置请求参数 
  328.      * @param [type] $param      [description] 
  329.      * @param [type] $paramValue [description] 
  330.      */ 
  331.     public function setParam($param, $paramValue) { 
  332.         $this->params[$this->tirmString($param)] = $this->trimString($paramValue); 
  333.     } 
  334.  
  335.     /** 
  336.      * 获取结果,默认不使用证书 
  337.      * @return [type] [description] 
  338.      */ 
  339.     public function getResult() { 
  340.         $this->postxml();  
  341.         $this->result = $this->xmlToArray($this->response); 
  342.  
  343.         return $this->result; 
  344.     } 
  345.  
  346.     /** 
  347.      * post请求xml 
  348.      * @return [type] [description] 
  349.      */ 
  350.     public function postxml() { 
  351.         $xml = $this->createXml(); 
  352.         $this->response = $this->postXmlCurl($xml, $this->curl, $this->curl_timeout); 
  353.  
  354.         return $this->response; 
  355.     } 
  356.  
  357.     public function createXml() { 
  358.         $this->params['appid'] = WxPayConf::APPID; //公众号ID 
  359.         $this->params['mch_id'] = WxPayConf::MCHID; //商户号 
  360.         $this->params['nonce_str'] = $this->createNoncestr();   //随机字符串 
  361.         $this->params['sign'] = $this->getSign($this->params);  //签名 
  362.          
  363.         return $this->arrayToXml($this->params);  
  364.     } 
  365.  
  366.      
  367.  
  368.  
  369. /** 
  370.  * 所有接口的基类 
  371.  */ 
  372. class JsApi_common { 
  373.     function __construct() { 
  374.  
  375.     } 
  376.  
  377.     public function trimString($value) { 
  378.         $ret = null
  379.         if (null != $value) { 
  380.             $ret = trim($value); 
  381.             if (strlen($ret) == 0) { 
  382.                 $ret = null
  383.             } 
  384.         }  
  385.         return $ret; 
  386.     } 
  387.  
  388.     /** 
  389.      * 产生随机字符串,不长于32位 
  390.      * @param  integer $length [description] 
  391.      * @return [type]          [description] 
  392.      */ 
  393.     public function createNoncestr($length = 32) { 
  394.         $chars = "abcdefghijklmnopqrstuvwxyz0123456789"
  395.         $str = ''
  396.         for ($i = 0; $i < $length; $i++) { 
  397.             $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1); 
  398.         } 
  399.  
  400.         return $str; 
  401.     } 
  402.  
  403.     /** 
  404.      * 格式化参数 拼接字符串,签名过程需要使用 
  405.      * @param [type] $urlParams     [description] 
  406.      * @param [type] $needUrlencode [description] 
  407.      */ 
  408.     public function ToUrlParams($urlParams, $needUrlencode) { 
  409.         $buff = ""
  410.         ksort($urlParams); 
  411.  
  412.         foreach ($urlParams as $k => $v) { 
  413.             if($needUrlencode) $v = urlencode($v); 
  414.             $buff .= $k .'='. $v .'&'
  415.         } 
  416.  
  417.         $reqString = ''
  418.         if (strlen($buff) > 0) { 
  419.             $reqString = substr($buff, 0, strlen($buff) - 1); 
  420.         } 
  421.  
  422.         return $reqString; 
  423.     } 
  424.  
  425.     /** 
  426.      * 生成签名 
  427.      * @param  [type] $params [description] 
  428.      * @return [type]         [description] 
  429.      */ 
  430.     public function getSign($obj) { 
  431.         foreach ($obj as $k => $v) { 
  432.             $params[$k] = $v; 
  433.         } 
  434.         //签名步骤一:按字典序排序参数 
  435.         ksort($params); 
  436.         $str = $this->ToUrlParams($params, false);   
  437.         //签名步骤二:在$str后加入key 
  438.         $str = $str."$key=".WxPayConf::KEY; 
  439.         //签名步骤三:md5加密 
  440.         $str = md5($str); 
  441.         //签名步骤四:所有字符转为大写 
  442.         $result = strtoupper($str); 
  443.  
  444.         return $result; 
  445.     } 
  446.  
  447.     /** 
  448.      * array转xml 
  449.      * @param  [type] $arr [description] 
  450.      * @return [type]      [description] 
  451.      */ 
  452.     public function arrayToXml($arr) { 
  453.         $xml = "<xml>"
  454.         foreach ($arr as $k => $v) { 
  455.             if (is_numeric($val)) { 
  456.                 $xml .= "<".$key.">".$key."</".$key.">"
  457.             } else { 
  458.                 $xml .= "<".$key."><![CDATA[".$val."]]></".$key.">"
  459.             } 
  460.         } 
  461.         $xml .= "</xml>"
  462.         return $xml; 
  463.     } 
  464.  
  465.     /** 
  466.      * 将xml转为array 
  467.      * @param  [type] $xml [description] 
  468.      * @return [type]      [description] 
  469.      */ 
  470.     public function xmlToArray($xml) { 
  471.         $arr = json_decode(json_encode(simplexml_load_string($xml, 'SinpleXMLElement', LIBXML_NOCDATA)), true); 
  472.  
  473.         return $arr; 
  474.     } 
  475.  
  476.     /** 
  477.      * 以post方式提交xml到对应的接口 
  478.      * @param  [type]  $xml    [description] 
  479.      * @param  [type]  $url    [description] 
  480.      * @param  integer $second [description] 
  481.      * @return [type]          [description] 
  482.      */ 
  483.     public function postXmlCurl($xml, $url, $second = 30) { 
  484.         //初始化curl 
  485.         $ch = curl_init(); 
  486.         //设置超时 
  487.         curl_setopt($ch, CURL_TIMEOUT, $second); 
  488.         curl_setopt($ch, CURL_URL, $url); 
  489.         //这里设置代理,如果有的话 
  490.         //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8'); 
  491.         //curl_setopt($ch,CURLOPT_PROXYPORT, 8080); 
  492.         curl_setopt($ch, CURL_SSL_VERIFYHOST, FALSE); 
  493.         curl_setopt($ch, CURL_SSL_VERIFYPEER, FALSE); 
  494.         //设置header 
  495.         curl_setopt($ch, CURL_HEADER, FALSE); 
  496.         //要求结果为字符串且输出到屏幕上 
  497.         curl_setopt($ch, CURL_RETURNTRANSFER, TRUE); 
  498.         //以post方式提交 
  499.         curl_setopt($ch, CURL_POST, TRUE); 
  500.         curl_setopt($ch, CURL_POSTFIELDS, $xml); 
  501.         //执行curl 
  502.         $res = curl_exec($ch); 
  503.  
  504.         if ($res) { 
  505.             curl_close($ch); 
  506.             return $res; 
  507.         } else { 
  508.             $error = curl_errno($ch); 
  509.             echo "curl出错,错误码:$error"."<br>"
  510.             echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>"
  511.             curl_close($ch); 
  512.             return false
  513.         } 
  514.     } 
  515.  
  516. /** 
  517.  * 配置类 
  518.  */ 
  519. class WxPayConf { 
  520.     //微信公众号身份的唯一标识。 
  521.     const APPID = 'wx654a22c6423213b7'
  522.     //受理商ID,身份标识 
  523.     const MCHID = '10043241'
  524.     const MCHNAME = 'KellyCen的博客'
  525.      
  526.     //商户支付密钥Key。 
  527.     const KEY = '0000000000000000000000000000000'
  528.     //JSAPI接口中获取openid 
  529.     const APPSECRET = '000000000000000000000000000'
  530.  
  531.     //证书路径,注意应该填写绝对路径 
  532.     const SSLCERT_PATH = '/home/WxPayCacert/apiclient_cert.pem'
  533.     const SSLKEY_PATH = '/home/WxPayCacert/apiclient_key.pem'
  534.     const SSLCA_PATH = '/home/WxPayCacert/rootca.pem'
  535.  
  536.     //本例程通过curl使用HTTP POST方法,此处可修改其超时时间,默认为30秒 
  537.     const CURL_TIMEOUT = 30
  538.  
  539. 模型:Wxpay_model.php 

视图:index.tpl

  1. <!doctype html>   
  2. <html>   
  3. <head lang="zh-CN">   
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
  5. <!-- Make sure that we can test against real IE8 --> 
  6. <meta http-equiv="X-UA-Compatible" content="IE=8" /> 
  7. <title></title> 
  8. </head> 
  9. <body> 
  10.  
  11. <a href="{$wxPayUrl}">微信支付</a> 
  12. </body>   
  13. </html> 
  14.  
  15. 视图:index.tpl 

 视图:confirm.tpl

  1. <!doctype html>   
  2. <html>   
  3. <head lang="zh-CN">   
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
  5. <!-- Make sure that we can test against real IE8 --> 
  6. <meta http-equiv="X-UA-Compatible" content="IE=8" /> 
  7. <title></title> 
  8. </head> 
  9. <body> 
  10.  
  11. <a href="javascript:callpay();" id="btnOrder">点击支付</a> 
  12. </body>   
  13. <script type="text/javascript"
  14.     //将数据付给js变量 
  15.     var wxJsApiData = {$wxJsApiData}; 
  16.     function onBridgeReady() 
  17.         { 
  18.             //格式参考官方文档 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6 
  19.             WeixinJSBridge.invoke( 
  20.                 'getBrandWCPayRequest'
  21.                 $.parseJSON(wxJsApiData.resultData), 
  22.                 function(res){ 
  23.                     if(res.err_msg == "get_brand_wcpay_request:ok" ){ 
  24.                         window.location.href="/wxpay/paysuccess/"+{$order.sn};  
  25.                     } 
  26.  
  27.                 }  
  28.             ); 
  29.         } 
  30.         function callpay() 
  31.         {  
  32.             if(!wxJsApiData.resultCode){ 
  33.                 alert(wxJsApiData.resultType+","+wxJsApiData.resultMsg+"!"); 
  34.                 return false
  35.             } 
  36.             if (typeof WeixinJSBridge == "undefined"){ 
  37.                 if( document.addEventListener ){ 
  38.                     document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false); 
  39.                 }else if (document.attachEvent){ 
  40.                     document.attachEvent('WeixinJSBridgeReady', onBridgeReady); 
  41.                     document.attachEvent('onWeixinJSBridgeReady', onBridgeReady); 
  42.                 } 
  43.             }else
  44.                 onBridgeReady(); 
  45.             } 
  46.         } 
  47. </script> 
  48. </html> 
  49.  
  50. 视图:confirm.tpl 

里面所用到的一些自定义函数可以在我上一篇博文里找找,那里已经提供了代码参考了

现在我们的线上项目是微信支付,支付宝支付,网银支付信用卡支付的功能都有,并且PC和WAP端都分别对应有。所以整一个支付系统模块还算比较完整,后期有时间我会总结和分享下其他的支付接口开发的博文,希望有兴趣的博友可以关注下哦!!

本次分享和总结就到此

如果此博文中有哪里讲得让人难以理解,欢迎留言交流,若有讲解错的地方欢迎指出。

 
 

 

 
 
 

 

责任编辑:王雪燕 来源: 博客园
相关推荐

2018-08-09 09:16:24

Vue微信签名失败

2020-07-21 14:00:51

App小程序前端

2016-06-30 13:46:59

向日葵H5

2018-08-01 15:49:51

AndroidH5通信

2020-06-04 16:57:07

移动开发互联网实践

2015-12-11 15:35:33

光合资本

2016-10-25 17:52:56

H5APP教材

2016-03-04 10:29:51

微信支付源码

2022-03-28 07:52:31

H5小游戏开发教程页面基础布局

2015-02-13 10:20:15

微信

2022-09-27 07:30:16

H5益智游戏引擎

2019-12-05 14:50:26

微信H5前端

2020-08-05 15:04:13

微信支付宝移动应用

2017-01-04 18:09:23

Android微信支付快速实现

2020-04-09 16:00:13

5G消息微信支付宝

2013-11-28 11:15:43

微信支付宝支付战争

2022-03-24 08:33:58

小游戏项目cmdvue3

2020-12-03 18:07:07

H5主题用户

2021-06-18 10:30:57

H5微信

2020-12-13 12:14:45

H5开发H5-Dooring
点赞
收藏

51CTO技术栈公众号