2016年8月21日

微信支付 APP 支付方式的php服务器端回调 微信v2版本

微信支付 APP 支付方式的php服务器端处理程序 微信v2版本

对于微信的APP的支付,你是不是也被坑过,对没有错,就是被坑了。问了客服说只能通过微信开放平台申请。后来在打开自己公众帐号 服务号确实发现了如下问题:
weixin-app-dev-1

 

 

 

 

泱泱大国,微信支付在申请的时候就比较严,对服务类的一些支付,本来商品就是虚拟的,所以需要将商品描述的比较详细,服务类的嘛,支付流程是如何的,我们提供什么服务的,操作界面如何等。商品描述140个字,考验你的文本组织能力了。

支付帐号申请审核通过后,收到微信的一封邮件
效果如下:
https://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=3_1
weixin-app-dev-2

 

 

 

 

 

 

 

 

 

 

 

基本上,app支付的流程就是
1、统一下单(由自己的服务器处理)
2、发起支付(客户端)
3、支付成功回调(服务器端)
https://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=9_1
weixin-app-dev-3

 

 

 

 

 

 

这里只说第一点,统一下单程序。统一下单的服务器端处理,就是要生成预支付订单的ID
调试了一下,有一些坑,整理代码如下:

  1. <?php
  2. header("Content-type: text/html; charset=utf-8");
  3. include "../../config.php";
  4. $orderBody = "test_lwxshow_商品";
  5. $tade_no = "abc_" . time();
  6. $total_fee = 1;
  7. $WxPayHelper = new WxPayHelper();
  8. $response = $WxPayHelper->getPrePayOrder($orderBody$tade_no$total_fee);
  9. p_val("---response----");
  10. p_val($response);
  11. p_val("---拿到prepayId再次签名----");
  12. $x = $WxPayHelper->getOrder($response['prepay_id']);
  13. p_val($x);
  14. /**
  15.  * convert xml string to php array - useful to get a serializable value
  16.  *
  17.  * @param string $xmlstr
  18.  * @return array
  19.  * @author Adrien aka Gaarf
  20.  */
  21. class WxPayHelper{
  22.     /*
  23.     配置参数
  24.     */
  25.     var $config = array(
  26.         'appid' => "wx7e26b00000000000",    /*微信开放平台上的应用id*/
  27.         'mch_id' => "1233000000",   /*微信申请成功之后邮件中的商户id*/
  28.         'api_key' => "s6aITei3J3d4UYcCn3k0Mq0000000000",    /*在微信商户平台上自己设定的api密钥 32位*/
  29.         'notify_url' => 'http://mycompany.com/pub_v2/pay/notify.v2.php' /*自定义的回调程序地址id*/
  30.     );
  31.     public function  __construct() {
  32.     }
  33.     //获取预支付订单
  34.     public function getPrePayOrder($body$out_trade_no$total_fee){
  35.         $url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
  36.         $notify_url = $this->config["notify_url"];
  37.         $onoce_str = $this->getRandChar(32);
  38.         $data["appid"] = $this->config["appid"];
  39.         $data["body"] = $body;
  40.         $data["mch_id"] = $this->config['mch_id'];
  41.         $data["nonce_str"] = $onoce_str;
  42.         $data["notify_url"] = $notify_url;
  43.         $data["out_trade_no"] = $out_trade_no;
  44.         $data["spbill_create_ip"] = $this->get_client_ip();
  45.         $data["total_fee"] = $total_fee;
  46.         $data["trade_type"] = "APP";
  47.         $s = $this->getSign($data, false);
  48.         $data["sign"] = $s;
  49.         $xml = $this->arrayToXml($data);
  50.         $response = $this->postXmlCurl($xml$url);
  51.         //将微信返回的结果xml转成数组
  52.         return $this->xmlstr_to_array($response);
  53.     }
  54.     //执行第二次签名,才能返回给客户端使用
  55.     public function getOrder($prepayId){
  56.         $data["appid"] = $this->config["appid"];
  57.         $data["noncestr"] = $this->getRandChar(32);;
  58.         $data["package"] = "Sign=WXPay";
  59.         $data["partnerid"] = $this->config['mch_id'];
  60.         $data["prepayid"] = $prepayId;
  61.         $data["timestamp"] = time();
  62.         $s = $this->getSign($data, false);
  63.         $data["sign"] = $s;
  64.         return $data;
  65.     }
  66.     /*
  67.         生成签名
  68.     */
  69.     function getSign($Obj)
  70.     {
  71.         foreach ($Obj as $k => $v)
  72.         {
  73.             $Parameters[strtolower($k)] = $v;
  74.         }
  75.         //签名步骤一:按字典序排序参数
  76.         ksort($Parameters);
  77.         $String = $this->formatBizQueryParaMap($Parameters, false);
  78.         //echo "【string】 =".$String."</br>";
  79.         //签名步骤二:在string后加入KEY
  80.         $String = $String."&key=".$this->config['api_key'];
  81.         echo "<textarea style='width: 50%; height: 150px;'>$String</textarea> <br />";
  82.         //签名步骤三:MD5加密
  83.         $result_ = strtoupper(md5($String));
  84.         return $result_;
  85.     }
  86.     //获取指定长度的随机字符串
  87.     function getRandChar($length){
  88.        $str = null;
  89.        $strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
  90.        $max = strlen($strPol)-1;
  91.        for($i=0;$i<$length;$i++){
  92.         $str.=$strPol[rand(0,$max)];//rand($min,$max)生成介于min和max两个数之间的一个随机整数
  93.        }
  94.        return $str;
  95.     }
  96.     //数组转xml
  97.     function arrayToXml($arr)
  98.     {
  99.         $xml = "<xml>";
  100.         foreach ($arr as $key=>$val)
  101.         {
  102.              if (is_numeric($val))
  103.              {
  104.                 $xml.="<".$key.">".$val."</".$key.">";
  105.              }
  106.              else
  107.                 $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
  108.         }
  109.         $xml.="</xml>";
  110.         return $xml;
  111.     }
  112.     //post https请求,CURLOPT_POSTFIELDS xml格式
  113.     function postXmlCurl($xml,$url,$second=30)
  114.     {
  115.         //初始化curl        
  116.         $ch = curl_init();
  117.         //超时时间
  118.         curl_setopt($ch,CURLOPT_TIMEOUT,$second);
  119.         //这里设置代理,如果有的话
  120.         //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
  121.         //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
  122.         curl_setopt($ch,CURLOPT_URL, $url);
  123.         curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);
  124.         curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE);
  125.         //设置header
  126.         curl_setopt($ch, CURLOPT_HEADER, FALSE);
  127.         //要求结果为字符串且输出到屏幕上
  128.         curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
  129.         //post提交方式
  130.         curl_setopt($ch, CURLOPT_POST, TRUE);
  131.         curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
  132.         //运行curl
  133.         $data = curl_exec($ch);
  134.         //返回结果
  135.         if($data)
  136.         {
  137.             curl_close($ch);
  138.             return $data;
  139.         }
  140.         else
  141.         {
  142.             $error = curl_errno($ch);
  143.             echo "curl出错,错误码:$error"."<br>";
  144.             echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
  145.             curl_close($ch);
  146.             return false;
  147.         }
  148.     }
  149.     /*
  150.         获取当前服务器的IP
  151.     */
  152.     function get_client_ip()
  153.     {
  154.         if ($_SERVER['REMOTE_ADDR']) {
  155.         $cip = $_SERVER['REMOTE_ADDR'];
  156.         } elseif (getenv("REMOTE_ADDR")) {
  157.         $cip = getenv("REMOTE_ADDR");
  158.         } elseif (getenv("HTTP_CLIENT_IP")) {
  159.         $cip = getenv("HTTP_CLIENT_IP");
  160.         } else {
  161.         $cip = "unknown";
  162.         }
  163.         return $cip;
  164.     }
  165.     //将数组转成uri字符串
  166.     function formatBizQueryParaMap($paraMap$urlencode)
  167.     {
  168.         $buff = "";
  169.         ksort($paraMap);
  170.         foreach ($paraMap as $k => $v)
  171.         {
  172.             if($urlencode)
  173.             {
  174.                $v = urlencode($v);
  175.             }
  176.             $buff .= strtolower($k) . "=" . $v . "&";
  177.         }
  178.         $reqPar;
  179.         if (strlen($buff) > 0)
  180.         {
  181.             $reqPar = substr($buff, 0, strlen($buff)-1);
  182.         }
  183.         return $reqPar;
  184.     }
  185.     /**
  186.     xml转成数组
  187.     */
  188.     function xmlstr_to_array($xmlstr) {
  189.       $doc = new DOMDocument();
  190.       $doc->loadXML($xmlstr);
  191.       return $this->domnode_to_array($doc->documentElement);
  192.     }
  193.     function domnode_to_array($node) {
  194.       $output = array();
  195.       switch ($node->nodeType) {
  196.        case XML_CDATA_SECTION_NODE:
  197.        case XML_TEXT_NODE:
  198.         $output = trim($node->textContent);
  199.        break;
  200.        case XML_ELEMENT_NODE:
  201.         for ($i=0, $m=$node->childNodes->length; $i<$m$i++) {
  202.          $child = $node->childNodes->item($i);
  203.          $v = $this->domnode_to_array($child);
  204.          if(isset($child->tagName)) {
  205.            $t = $child->tagName;
  206.            if(!isset($output[$t])) {
  207.             $output[$t] = array();
  208.            }
  209.            $output[$t][] = $v;
  210.          }
  211.          elseif($v) {
  212.           $output = (string) $v;
  213.          }
  214.         }
  215.         if(is_array($output)) {
  216.          if($node->attributes->length) {
  217.           $a = array();
  218.           foreach($node->attributes as $attrName => $attrNode) {
  219.            $a[$attrName] = (string) $attrNode->value;
  220.           }
  221.           $output['@attributes'] = $a;
  222.          }
  223.          foreach ($output as $t => $v) {
  224.           if(is_array($v) && count($v)==1 && $t!='@attributes') {
  225.            $output[$t] = $v[0];
  226.           }
  227.          }
  228.         }
  229.        break;
  230.       }
  231.       return $output;
  232.     }
  233. }
  234. ?>

注意点:
①post必须支持https,且参数格式必须是xml
②sign签名的参数包括所有$data,除了自己

weixin-app-dev-4

 

 

 

 

 

 

 

 

③$data[“spbill_create_ip”]不能随便设定一个ip地址,不要以为调试方便随便设定,结果返回签名错误坑你没商量。一定要是程序执行时所在的服务器ip地址,所以使用get_client_ip()获取就好。
④api_key是需要自己进入商户平台设定的

weixin-app-dev-5

 

 

 

 

 

 

 

使用随机程序产生32个字符就好了
⑤相当重要的是:返回给各户端发起支付时,还要进行二次签名 $WxPayHelper->getOrder

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*