中国领先的IT技术网站
|
|

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

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

作者:kelly来源:博客园|2015-11-10 11:38

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

一、前言

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

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

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

二、思路详解

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

三、代码讲解

本次开发环境用的是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的微信支付接口开发详解就讲到这里,如果你认真理清博文里所讲解的思路,自己基本上也可以尝试开发此接口了,同时只要会了这个,你也基本上可以开发二维码支付,刷卡支付等等的支付接口。

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

控制器: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端都分别对应有。所以整一个支付系统模块还算比较完整,后期有时间我会总结和分享下其他的支付接口开发的博文,希望有兴趣的博友可以关注下哦!!

本次分享和总结就到此

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

 
 

 
 
 

【编辑推荐】

  1. 进度落后不是开发者的错,工作流程可能才是凶手!
  2. 75份开发者、设计师必备的速查表
  3. 使用RelProxy提高Java开发效率
  4. Swift语言已迈出侵蚀Android应用开发的第一步_移动·开发技术周刊
  5. 开发者的故事:他们在上演游戏的破釜沉舟
  6. 开发者,速度远比你以为的重要
【责任编辑:wangxueyan TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

热门职位+更多

读 书 +更多

网管员必读——超级网管经验谈

本书是一本以示例形式直接面向应用的网络管理图书。书中以大量示例和大量实用网络管理与故障排除经验介绍了当前网络管理工作的各主要方面。...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊
× 四周年庆  签到赢手机