当前位置:首页 > PHP教程 > php高级应用 > 列表

PHP实现 APP端微信支付功能

发布:smiling 来源: PHP粉丝网  添加日期:2021-10-05 12:22:09 浏览: 评论:0 

前面已经写了手机APP支付宝支付,今天再把手机APP微信支付补上,前期的准备工作在这里就不多说了,可以参考微信支付开发文档,一定要仔细阅读开发文档,可以让你少踩点坑;准备工作完成后就是配置参数,调用统一下单接口,支付后异步回调三部曲啦;

1.我封装好的一个支付类文件,多余的东西都去除掉了,并且把配置参数放到了这个支付类中,只需要修改Weixinpayandroid方法内的几个参数就可以直接复制使用:

  1. class Wxpayandroid 
  2.  //参数配置 
  3.  public $config = array
  4.     'appid' => ""/*微信开放平台上的应用id*/ 
  5.     'mch_id' => ""/*微信申请成功之后邮件中的商户id*/ 
  6.     'api_key' => ""/*在微信商户平台上自己设定的api密钥 32位*/ 
  7.    ); 
  8.  //服务器异步通知页面路径(必填) 
  9.  public $notify_url = ''
  10.  //商户订单号(必填,商户网站订单系统中唯一订单号) 
  11.  public $out_trade_no = ''
  12.  //商品描述(必填,不填则为商品名称) 
  13.  public $body = ''
  14.  //付款金额(必填) 
  15.  public $total_fee = 0; 
  16.  //自定义超时(选填,支持dhmc) 
  17.  public $time_expire = ''
  18.  private $WxPayHelper
  19.  public function Weixinpayandroid($total_fee,$tade_no
  20.  { 
  21.   $this->total_fee = intval($total_fee * 100);//订单的金额 1元 
  22.   $this->out_trade_no = $tade_no;// date('YmdHis') . substr(time(), - 5) . substr(microtime(), 2, 5) . sprintf('%02d', rand(0, 99));//订单号 
  23.   $this->body = 'wxpay';//支付描述信息 
  24.   $this->time_expire = date('YmdHis', time() + 86400);//订单支付的过期时间(eg:一天过期) 
  25.   $this->notify_url = "http://www.ceshi.com/notifyandroid";//异步通知URL(更改支付状态) 
  26.   //数据以JSON的形式返回给APP 
  27.   $app_response = $this->doPay();  
  28.   if (isset($app_response['return_code']) && $app_response['return_code'] == 'FAIL') { 
  29.    $errorCode = 100; 
  30.    $errorMsg = $app_response['return_msg']; 
  31.    $this->echoResult($errorCode$errorMsg); 
  32.   } else { 
  33.    $errorCode = 0; 
  34.    $errorMsg = 'success'
  35.    $responseData = array
  36.     'notify_url' => $this->notify_url, 
  37.     'app_response' => $app_response
  38.    ); 
  39.    $this->echoResult($errorCode$errorMsg$responseData); 
  40.   } 
  41.  } 
  42.  //接口输出 
  43.  function echoResult($errorCode = 0, $errorMsg = 'success'$responseData = array()) 
  44.  { 
  45.   $arr = array
  46.    'errorCode' => $errorCode
  47.    'errorMsg' => $errorMsg
  48.    'responseData' => $responseData
  49.   ); 
  50.    exit(json_encode($arr));  //exit可以正常发送给APP json数据 
  51.   // return json_encode($arr); //在TP5中return这个json数据,APP接收到的是null,无法正常吊起微信支付 
  52.  } 
  53.  function getVerifySign($data$key
  54.  { 
  55.   $String = $this->formatParameters($data, false); 
  56.   //签名步骤二:在string后加入KEY 
  57.   $String = $String . "&key=" . $key
  58.   //签名步骤三:MD5加密 
  59.   $String = md5($String); 
  60.   //签名步骤四:所有字符转为大写 
  61.   $result = strtoupper($String); 
  62.   return $result
  63.  } 
  64.  function formatParameters($paraMap$urlencode
  65.  { 
  66.   $buff = ""
  67.   ksort($paraMap); 
  68.   foreach ($paraMap as $k => $v) { 
  69.    if($k=="sign"){ 
  70.     continue
  71.    } 
  72.    if ($urlencode) { 
  73.     $v = urlencode($v); 
  74.    } 
  75.    $buff .= $k . "=" . $v . "&"
  76.   } 
  77.   $reqPar
  78.   if (strlen($buff) > 0) { 
  79.    $reqPar = substr($buff, 0, strlen($buff) - 1); 
  80.   } 
  81.   return $reqPar
  82.  } 
  83.  /** 
  84.   * 得到签名 
  85.   * @param object $obj 
  86.   * @param string $api_key 
  87.   * @return string 
  88.   */ 
  89.  function getSign($obj$api_key
  90.  { 
  91.   foreach ($obj as $k => $v
  92.   { 
  93.    $Parameters[strtolower($k)] = $v
  94.   } 
  95.   //签名步骤一:按字典序排序参数 
  96.   ksort($Parameters); 
  97.   $String = $this->formatBizQueryParaMap($Parameters, false); 
  98.   //签名步骤二:在string后加入KEY 
  99.   $String = $String."&key=".$api_key
  100.   //签名步骤三:MD5加密 
  101.   $result = strtoupper(md5($String)); 
  102.   return $result
  103.  } 
  104.  /** 
  105.   * 获取指定长度的随机字符串 
  106.   * @param int $length 
  107.   * @return Ambigous <NULL, string> 
  108.   */ 
  109.  function getRandChar($length){ 
  110.   $str = null; 
  111.   $strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"
  112.   $max = strlen($strPol)-1; 
  113.   for($i=0;$i<$length;$i++){ 
  114.    $str.=$strPol[rand(0,$max)];//rand($min,$max)生成介于min和max两个数之间的一个随机整数 
  115.   } 
  116.   return $str
  117.  } 
  118.  /** 
  119.   * 数组转xml 
  120.   * @param array $arr 
  121.   * @return string 
  122.   */ 
  123.  function arrayToXml($arr
  124.  { 
  125.   $xml = "<xml>"
  126.   foreach ($arr as $key=>$val
  127.   { 
  128.     if (is_numeric($val)) 
  129.     { 
  130.     $xml.="<".$key.">".$val."</".$key.">"
  131.     } 
  132.     else 
  133.     $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";  
  134.   } 
  135.   $xml.="</xml>"
  136.   return $xml
  137.  } 
  138.  /** 
  139.   * 以post方式提交xml到对应的接口url 
  140.   * 
  141.   * @param string $xml 需要post的xml数据 
  142.   * @param string $url url 
  143.   * @param bool $useCert 是否需要证书,默认不需要 
  144.   * @param int $second url执行超时时间,默认30s 
  145.   * @throws WxPayException 
  146.   */ 
  147.  function postXmlCurl($xml$url$second=30, $useCert=false, $sslcert_path=''$sslkey_path=''
  148.  { 
  149.   $ch = curl_init(); 
  150.   //设置超时 
  151.   curl_setopt($ch, CURLOPT_TIMEOUT, $second); 
  152.   curl_setopt($ch,CURLOPT_URL, $url); 
  153.   //设置header 
  154.   curl_setopt($ch, CURLOPT_HEADER, FALSE); 
  155.   //要求结果为字符串且输出到屏幕上 
  156.   curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); 
  157.   curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE); 
  158.   curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE); 
  159.   if($useCert == true){ 
  160.    curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE); 
  161.    curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//严格校验 
  162.    //设置证书 
  163.    //使用证书:cert 与 key 分别属于两个.pem文件 
  164.    curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM'); 
  165.    curl_setopt($ch,CURLOPT_SSLCERT, $sslcert_path); 
  166.    curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM'); 
  167.    curl_setopt($ch,CURLOPT_SSLKEY, $sslkey_path); 
  168.   } 
  169.   //post提交方式 
  170.   curl_setopt($ch, CURLOPT_POST, TRUE); 
  171.   curl_setopt($ch, CURLOPT_POSTFIELDS, $xml); 
  172.   //运行curl 
  173.   $data = curl_exec($ch); 
  174.   //返回结果 
  175.   if($data){ 
  176.    curl_close($ch); 
  177.    return $data
  178.   } else { 
  179.    $error = curl_errno($ch); 
  180.    curl_close($ch); 
  181.    return false; 
  182.   } 
  183.  } 
  184.  /** 
  185.   * 获取当前服务器的IP 
  186.   * @return Ambigous <string, unknown> 
  187.   */ 
  188.  function get_client_ip() 
  189.  { 
  190.   if (isset($_SERVER['REMOTE_ADDR'])) { 
  191.    $cip = $_SERVER['REMOTE_ADDR']; 
  192.   } elseif (getenv("REMOTE_ADDR")) { 
  193.    $cip = getenv("REMOTE_ADDR"); 
  194.   } elseif (getenv("HTTP_CLIENT_IP")) { 
  195.    $cip = getenv("HTTP_CLIENT_IP"); 
  196.   } else { 
  197.    $cip = "127.0.0.1"
  198.   } 
  199.   return $cip
  200.  } 
  201.  /** 
  202.   * 将数组转成uri字符串 
  203.   * @param array $paraMap 
  204.   * @param bool $urlencode 
  205.   * @return string 
  206.   */ 
  207.  function formatBizQueryParaMap($paraMap$urlencode
  208.  { 
  209.   $buff = ""
  210.   ksort($paraMap); 
  211.   foreach ($paraMap as $k => $v
  212.   { 
  213.    if($urlencode
  214.    { 
  215.     $v = urlencode($v); 
  216.    } 
  217.    $buff .= strtolower($k) . "=" . $v . "&"
  218.   } 
  219.   $reqPar
  220.   if (strlen($buff) > 0) 
  221.   { 
  222.    $reqPar = substr($buff, 0, strlen($buff)-1); 
  223.   } 
  224.   return $reqPar
  225.  } 
  226.  /** 
  227.   * XML转数组 
  228.   * @param unknown $xml 
  229.   * @return mixed 
  230.   */ 
  231.  function xmlToArray($xml
  232.  { 
  233.   //将XML转为array 
  234.   $array_data = json_decode(json_encode(simplexml_load_string($xml'SimpleXMLElement', LIBXML_NOCDATA)), true); 
  235.   return $array_data
  236.  } 
  237.  public function chkParam() 
  238.  { 
  239.   //用户网站订单号 
  240.   if (emptyempty($this->out_trade_no)) { 
  241.    die('out_trade_no error'); 
  242.   }  
  243.   //商品描述 
  244.   if (emptyempty($this->body)) { 
  245.    die('body error'); 
  246.   } 
  247.   if (emptyempty($this->time_expire)){ 
  248.    die('time_expire error'); 
  249.   } 
  250.   //检测支付金额 
  251.   if (emptyempty($this->total_fee) || !is_numeric($this->total_fee)) { 
  252.    die('total_fee error'); 
  253.   } 
  254.   //异步通知URL 
  255.   if (emptyempty($this->notify_url)) { 
  256.    die('notify_url error'); 
  257.   } 
  258.   if (!preg_match("#^http:\/\/#i"$this->notify_url)) { 
  259.    $this->notify_url = "http://" . $_SERVER['HTTP_HOST'] . $this->notify_url; 
  260.   } 
  261.   return true; 
  262.  } 
  263.  /** 
  264.   * 生成支付(返回给APP) 
  265.   * @return boolean|mixed 
  266.   */ 
  267.  public function doPay() { 
  268.   //检测构造参数 
  269.   $this->chkParam(); 
  270.   return $this->createAppPara(); 
  271.  } 
  272.  /** 
  273.   * APP统一下单 
  274.   */ 
  275.  private function createAppPara() 
  276.  { 
  277.   $url = "https://api.mch.weixin.qq.com/pay/unifiedorder"
  278.   $data["appid"]  = $this->config['appid'];//微信开放平台审核通过的应用APPID 
  279.   $data["body"]   = $this->body;//商品或支付单简要描述 
  280.   $data["mch_id"]  = $this->config['mch_id'];//商户号 
  281.   $data["nonce_str"] = $this->getRandChar(32);//随机字符串 
  282.   $data["notify_url"] = $this->notify_url;//通知地址 
  283.   $data["out_trade_no"] = $this->out_trade_no;//商户订单号 
  284.   $data["spbill_create_ip"] = $this->get_client_ip();//终端IP 
  285.   $data["total_fee"]  = $this->total_fee;//总金额 
  286.   $data["time_expire"]  = $this->time_expire;//交易结束时间 
  287.   $data["trade_type"]  = "APP";//交易类型 
  288.   $data["sign"]    = $this->getSign($data$this->config['api_key']);//签名 
  289.   $xml  = $this->arrayToXml($data); 
  290.   $response = $this->postXmlCurl($xml$url); 
  291.   //将微信返回的结果xml转成数组 
  292.   $responseArr = $this->xmlToArray($response); 
  293.   if(isset($responseArr["return_code"]) && $responseArr["return_code"]=='SUCCESS'){ 
  294.    return $this->getOrder($responseArr['prepay_id']); 
  295.   } 
  296.   return $responseArr
  297.  } 
  298.  /** 
  299.   * 执行第二次签名,才能返回给客户端使用 
  300.   * @param int $prepayId:预支付交易会话标识 
  301.   * @return array 
  302.   */ 
  303.  public function getOrder($prepayId
  304.  { 
  305.   $data["appid"]  = $this->config['appid']; 
  306.   $data["noncestr"] = $this->getRandChar(32); 
  307.   $data["package"] = "Sign=WXPay"
  308.   $data["partnerid"] = $this->config['mch_id']; 
  309.   $data["prepayid"] = $prepayId
  310.   $data["timestamp"] = time(); 
  311.   $data["sign"]  = $this->getSign($data$this->config['api_key']); 
  312.   $data["packagestr"] = "Sign=WXPay"
  313.   return $data
  314.  } 
  315.  /** 
  316.   * 异步通知信息验证 
  317.   * @return boolean|mixed 
  318.   */ 
  319.  public function verifyNotify() 
  320.  { 
  321.   $xml = isset($GLOBALS['HTTP_RAW_POST_DATA']) ? $GLOBALS['HTTP_RAW_POST_DATA'] : '';  
  322.   if(!$xml){ 
  323.    return false; 
  324.   } 
  325.   $wx_back = $this->xmlToArray($xml); 
  326.   if(emptyempty($wx_back)){ 
  327.    return false; 
  328.   } 
  329.   $checkSign = $this->getVerifySign($wx_back$this->config['api_key']);  
  330.   if($checkSign=$wx_back['sign']){ 
  331.    return $wx_back
  332.   }else
  333.    return false; 
  334.   }  
  335.  } 

2.创建控制器定义统一下单接口和支付后的异步回调接口:

  1. //异步通知接口 
  2.  public function notifyandroid() 
  3.  { 
  4.   $wxpayandroid = new \Wxpayandroid;  //实例化微信支付类 
  5.   $verify_result = $wxpayandroid->verifyNotify(); 
  6.   if ($verify_result['return_code']=='SUCCESS' && $verify_result['result_code']=='SUCCESS') { 
  7.     //商户订单号 
  8.     $out_trade_no = $verify_result['out_trade_no']; 
  9.     //交易号 
  10.     $trade_no  = $verify_result['transaction_id']; 
  11.     //交易状态 
  12.     $trade_status = $verify_result['result_code']; 
  13.     //支付金额 
  14.     $total_fee = $verify_result['total_fee']/100; 
  15.     //支付过期时间 
  16.     $pay_date  = $verify_result['time_end']; 
  17.     $order = new Order(); 
  18.     $ret = $order->getOrderN2($out_trade_no); //获取订单信息 
  19.     $total_amount=$ret['money']; 
  20.     if ($total_amount==$total_fee) { 
  21.      // 验证成功 修改数据库的订单状态等 $result['out_trade_no']为订单号 
  22.      //此处写自己的逻辑代码 
  23.     } 
  24.    exit('<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>'); 
  25.   }else
  26.    exit('<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[ERROR]]></return_msg></xml>'); 
  27.   } 
  28.  } 
  29.    
  30.  //调用统一下单接口生成预支付订单并把数据返回给APP 
  31.  public function wxpayandroid(Request $request
  32.  { 
  33.   $param = $request->param(); //接收值 
  34.    
  35.   $tade_no = $param['orderCode']; 
  36.   $order = new Order(); //实例化订单 
  37.   $ret = $order->getOrderN2($tade_no); //查询订单信息 
  38.   $total_fee = $ret['money']; //订单总金额 
  39.     
  40.   $wxpayandroid = new \Wxpayandroid;  //实例化微信支付类 
  41.   $res = $wxpayandroid->Weixinpayandroid($total_fee,$tade_no); //调用weixinpay方法 
  42.     
  43.  } 

封装一个支付类文件,并把配置参数放到支付类内,再定义控制器创建两个方法,这样两步就可以把手机APP微信支付搞定啦。

Tags: PHP微信支付 APP微信支付

分享到: