zoukankan      html  css  js  c++  java
  • 微信APP支付

    第一次做微信支付记录一下: 

    1. 使用企业执照申请, 获得APPID, mch_id, key 等    (https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_3   微信支付文档里有时序图)
    2. 流程很简单:  用户下单后,  后端生成订单,  然后调用统一下单api,  微信支付系统会生成预付单, 并返回给商户服务器,   支付成功后,微信会通过notify_url返回支付结果给商户后端. 后端可以进一步处理.
    3. 统一下单api:
      接口地址:https://api.mch.weixin.qq.com/pay/unifiedorder
      请求参数: APPID, mch_id .......(大约10个左右必填参数)
      转换成XML数据格式, 通过curl 请求.
      如果微信返回成功标识, 还需要对参数进行二次签名

    4. 成功会返回几个参数, 其中包括但不限于: prepay_id, appid, mch_id   (把返回的参数进行二次签名返回给前端, 调起支付)
    5. $xml = file_get_contents('php://input');  //PHP版本大于5.6     $xml = $GLOBALS['HTTP_RAW_POST_DATA'];  //PHP版本小于5.6  //用来接收微信通知的数据,
    6. 遇到的错误: 发送请求, 微信返回 "XML格式错误", 开始不知道怎么排错. 后来在网上看到了一个例子: 使用  $xml = htmlspecialchars($xml); //把XML格式数据显示出来, 看看XML数据有没有少什么参数.  还有一些要注意的地方: 比如说XML编码要求是utf-8的.(我这里默认是utf-8, 不用设置)
    7. 签名失败: 可能的原因很多, 我遇到的有: (1) 因为key 填写的错误(要保证这个是设置秘钥的那个key)  (2) 因为微信文档说了参数值为空的不参与签名,需要过滤掉, 但是total_fee为0的也被过滤掉了, 所以少了这个参数导致签名失败, 解决办法是total_fee为0的就不要下单了. 
    8.  libxml_disable_entity_loader(true); //修复XXE漏洞       参考: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=23_5
    9. 我使用yii框架做支付遇到的问题: yii配置的路由是默认格式, 因为最开始没有配置美化的URL. 所以导致一个 notify_url 的问题,  事情是这样的: 微信文档里说notify_url  是不能带有参数的, 但是yii默认的URL是 ?r=moduleID/controllerID/actionID 的形式, 使用这个微信是不能通知结果的, 但是如果改成美化的URL, 之前的接口都得变, 后端没什么, 前端就得改改改了. 为了前端不改改改. 只能后端想想办法, 后来的解决办法就是: 在项目的web目录下创建一个PHP文件, 这个是可以直接访问的, 比如web/wxpay.php ,那么就可以通过https://hostName/web/wxpay.php 访问到. (这里注意,域名配置的时候, 配置到项目的根目录 , 如果配置到了index.php,那这个办法就用不了了,只能使用rewrite或者美化URL或者别的方法了)
      $_GET['r'] = 'course/wx-pay/test';    require __DIR__ . '/index.php';
      //在wxpay.php 添加这行代码,微信访问这个文件,在转发到处理结果的控制器方法里

      这里成功了, 紧接着又出现了新的问题, 接受不到微信的通知, 查看nginx/error.log 没发现什么错误, 查看nginx/access.log 发现微信请求报400, 原因:  微信是通过post 请求的, 但是yii 框架接受post请求,会验证csrf, 而微信是么有这个参数的, 所以需要关闭csrf验证机制,只在某个控制器里关闭csrf验证(局部禁止csrf验证), 在/course/wxpay控制器里写下一行代码

      public $enableCsrfValidation = false;   //关闭csrf验证  在控制器中加入这行代码就可以关闭csrf验证, 就能接收微信支付通知结果啦
    10. 能够接受到微信的结果通知了, 但是我给微信返回结果,微信没有接收到, 导致微信一直给我发支付通知, 找原因, 发现是因为签名出现了错误, 微信通知结果的参数里有sign, 是为了验证用的. 把这个参数也进行签名了, 所以导致验证失败, 没执行应答微信的逻辑, 只好重新写一个签名的代码
      ksort($arr);
              $buff = '';
              foreach ($arr as $k => $v){
                  if($k != 'sign'){
                      $buff .= $k . '=' . $v . '&';
                  }
              }
              $stringSignTemp = $buff . 'key=vrjwwwikogt1zs1i0ih3rp2mmayw24';//key为证书密钥
              $sign = strtoupper(md5($stringSignTemp));
      View Code
    11. 特别注意: 保存微信参数的时候, 出现了500, 后来发现因为微信返回的time_end是14位的字符串, 而我数据库设置的是int(11), 所以导致服务器错误, 总结: 有时候服务器500, 找问题的时候不能只看代码,忽略了数据库, 极有可能是数据库设置字段约束的时候, 存储字节长度,类型等等有问题.
    12. 附上代码(只供参考, 不能直接使用)
        1 public $enableCsrfValidation = false; //关闭csrf验证(yii接收微信post请求时做的处理)
        2 /**
        3      * 接收用户下单数据
        4      * @param string $token : 用来验证
        5      * @param int $store_id : 区分快应用
        6      * @param int $cid : 课程id
        7      * @param string $phone 用户账号
        8      * @return mixed data
        9      */
       10     public function actionOrder($store_id=null, $cid=null, $phone=null)
       11     {
       12         if($store_id == null){
       13             $data = [
       14                 'code'=>1,
       15                 'msg'=> 'store_id不能为空',
       16                 'data'=>[],
       17             ];
       18             return $data;
       19         }
       20         if($cid == null){
       21             $data = [
       22                 'code'=>1,
       23                 'msg'=> 'cid不能为空',
       24                 'data'=>[],
       25             ];
       26             return $data;
       27         }
       28         if($cid == null){
       29             $data = [
       30                 'code'=>1,
       31                 'msg'=> '请传一个价格',
       32                 'data'=>[],
       33             ];
       34             return $data;
       35         }
       36         $quick_app_id = Store::findOne($store_id)['wechat_app_id']; //根据store_id查找quick_app_id,获取指定的APPID等信息
       37         if(empty($quick_app_id)){
       38             $data = [
       39                 'code'=>1,
       40                 'msg'=> '快应用不存在,请确认store_id参数正确并且后台快应用配置成功',
       41                 'data'=>[],
       42             ];
       43             return $data;
       44         }
       45         //根据id查询课程信息
       46         $course_info = Course::find()->select('fee')->where(['id'=>$cid,])->one(); //防止用户修改付款价格
       47         if(!$course_info){
       48             $data = [
       49                 'code'=>1,
       50                 'msg'=> '没有数据, 请确认cid参数是否正确',
       51                 'data'=>[],
       52             ];
       53             return $data;
       54         }
       55         //生成随机字符串(下面的才是统一下单代码)
       56         $rand = mt_rand(1000,9999).time();
       57         $nonce_str = strval($rand);
       58         $ip = $_SERVER['REMOTE_ADDR'];  //客户端IP
       59         $out_trade_no = date('YmdHis').'-'.date('si-Hm-dY');
       60         $wxpay = WxPay::findOne($quick_app_id);
       61         $wxpay->notify_url = 'https://'.$_SERVER['HTTP_HOST'].'/web/wx-pay.php';
       62         $wxpay->nonce_str = $nonce_str;
       63         //$wxpay->trade_type = 'APP';
       64         $wxpay->total_fee = $course_info['fee'];
       65         $wxpay->spbill_create_ip = $ip;
       66         $wxpay->time_start = date('YmdHis');
       67         $wxpay->time_expire = date('YmdHis', time()+3600);
       68         $wxpay->body = $wxpay->name.'-购买课程';  //需传入应用市场上的APP名字-实际商品名称
       69         $wxpay->out_trade_no = $out_trade_no;
       70         //把订单信息保存到数据库
       71         $order = new Order();
       72         $order->out_trade_no = $out_trade_no;
       73         $order->total_fee = $wxpay->total_fee;
       74         $order->order_time = time();
       75         $order->body = $wxpay->body;
       76         $order->store_id = $store_id;
       77         $order->phone = $phone; //根据phone 和 store_id就可以定位用户了
       78         $order->cid = $cid;
       79         if(!$order->save()){
       80             // 把错误信息添加到log表中保存
       81         }
       82         $dataXML = $wxpay->UniformOrder();
       83         $arr = (array)simplexml_load_string($dataXML, 'SimpleXMLElement', LIBXML_NOCDATA);
       84         //print_r($arr);
       85         if($arr['return_code'] == 'SUCCESS' && $arr['result_code'] == 'SUCCESS'){
       86             //需要进行二次签名
       87             $twoSign['appid'] = $arr['appid'];
       88             $twoSign['partnerid'] = $arr['mch_id'];
       89             $twoSign['prepayid'] = $arr['prepay_id'];
       90             $twoSign['noncestr'] = $arr['nonce_str'];
       91             $twoSign['package'] = 'Sign=WXPay';  //目前固定值
       92             $twoSign['timestamp'] = time();
       93             $twoSign['sign'] = $wxpay->getSign($twoSign, $wxpay->key);
       94             $data = [
       95                 'code' => 0,
       96                 'msg' => 'success',
       97                 'data' => $twoSign,
       98             ];
       99             return $data;
      100         }else{
      101             //fail
      102             $data = [
      103                 'code' => 1,
      104                 'msg' => 'fail',
      105                 'data' => ['errmsg'=>$arr],
      106             ];
      107             return $data;
      108         }
      109     }
      View Code
      /**
           * 微信回调
           */
          public function actionNotify()
          {
              libxml_disable_entity_loader(true); //修复XXE漏洞
      
              $postStr = file_get_contents('php://input'); //php raw data , require php version > 5.6
              $postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
              $arr = json_decode(json_encode($postObj), true); #对象转成数组
              //处理一下key匹配对应的快应用问题, 通过订单号查询订单, 在订单表有store_id,根据store_id可以查询到对应的快应用配置的key
              $order = Order::findOne(['out_trade_no'=>$arr['out_trade_no']]);
              if(!$order){
                  return false;
              }
              $store = Store::findOne($order->store_id);
              if(!$store){
                  return false;
              }
              $wxpay = WxPay::findOne($store->wechat_app_id);
              if(!$wxpay){
                  return false;
              }
              if($arr['return_code'] != 'SUCCESS'){
                  echo '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
                  return ;
              }
              //签名
              ksort($arr);
              $buff = '';
              foreach ($arr as $k => $v){
                  if($k != 'sign'){
                      $buff .= $k . '=' . $v . '&';
                  }
              }
              $stringSignTemp = $buff . "key={$wxpay->key}";//key为证书密钥
              //$stringSignTemp = $buff . "key=vrjwwwidkogt1zs1i0ih3rp2mmayw24j";//key为证书密钥
              $sign = strtoupper(md5($stringSignTemp));
              if($sign == $arr['sign']){
                  //验证签名成功, 处理商户订单逻辑, 注意需要给微信返回接收信息成功的通知  signature successfully
                  $session = Yii::$app->session;
                  $session->set('notify', $arr['return_code']);
                  //先把结果写在文件里, 看一下结果
                  //$paylog = Yii::$app->basePath.'/web/paylog.txt';
                  //$str_arr = var_export($arr, true);
                  //$res = file_put_contents($paylog, $str_arr, FILE_APPEND);
                  //$order = Order::findOne(['out_trade_no'=>$arr['out_trade_no']]);//上面查询了
                  $order->is_pay = 1;
                  $order->pay_time = $arr['time_end']; //数据库里这个存储的是int11,而微信返回的是字符串14位
                  $order->transaction_id = $arr['transaction_id'];
                  $order->openid = $arr['openid'];
                  $order->save();
                  //用户支付成功了, 把课程添加到用户订阅课程里user_course表
                  $r = (new UserCourse())->addSubscribe($order->phone, $order->cid);// 是这一行的原因吗
                  if(!$order->save()){
                      return false;
                  }
                  return '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
              }else{
                  //fail
                  return '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
              }
          }
      View Code

      参考: https://www.jianshu.com/p/52bcadca67bc

  • 相关阅读:
    update set from 语句用法
    SQL WITH AS用法
    SQL插入数据的多种方式
    本地临时表“#” 与 全局临时表 "##"
    sql常用语句
    关于mybatis转义SQL动态执行语句变慢的原因
    关于SQL中OUTER APPLY 得用法
    AspNetPager控件的简单使用
    JQuery常用的案例
    JQuery的学习笔记
  • 原文地址:https://www.cnblogs.com/bneglect/p/11461182.html
Copyright © 2011-2022 走看看