PHP,JS开发微信公众号JSAPI支付遇到的那些坑

在之前,写了一下微信公众号支付,现在和大家分享一下经验。

  1. 首先,在开发之前,看一下官方的开发文档:https://pay.weixin.qq.com/wiki/doc/api/index.html 点击选择JSAPI支付

  2. 然后,看一下商户平台的配置信息,微信商户平台地址:
    https://pay.weixin.qq.com/index.php/core/home/login?return_url=%2F
    (1)登录之后,点击产品中心,点击公众号支付,看看是否有公众号支付(没有的,请去申请一下)。
    (2)接下来,开始配置一些信息。
    第一坑
    请确保实际支付时的请求目录与后台配置的目录一致,否则将无法成功唤起微信支付。进行配置支付授权目录:也就是你的支付页面所在的目录,一定是生产环境的,微信不支持 ip +端口 形式的地址 异步通知也不支持,所以测试都需要线上真实环境的域名+支付页面所在目录。
    图片…
    然后,还需要你登录微信公众号平台配置JS接口安全域名。
    登录网址:https://mp.weixin.qq.com
    第二坑
    所谓安全域名,就是你线上服务器上的备案域名,记住一定要提前备案,或者找已经备过案的服务器,进行配置域名和上面提到的JSAPI支付支付目录。
    图片…

  3. 配置完基本信息之后。
    接着,我们需要获取必传参数:appid,mch_id,加密key
    appid: 在微信公众号平台,找到基本配置,名字叫开发者ID(AppID)。
    mch_id:这个东西是你登录商户平台的商户号。去商户平台的个人信息里找就行了。
    key: 微信商户平台(pay.weixin.qq.com)–>账户设置–>API安全–>密钥设置。
    图片…

  4. 再说一下其他的,统一下单必传参数:
    随机字符串:nonce_str,只要不重复就行。
    签名: sign,微信有专门的签名算法
    https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3
    商品描述:body,主要是说,你开发公众号支付,是干什么的。
    商户订单号:out_trade_no ,这个,在后台生成一个唯一的订单号就行。
    如PHP的time(),uniqid()。
    标价金额:total_fee,注意这个数字,单位是
    终端IP:spbill_create_ip,获取用户的终端IP
    通知地址:notify_url,异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
    交易类型:trade_type,公众号支付,选择——>JSAPI -JSAPI支付
    第三坑
    用户标识:openid,“trade_type=JSAPI时(即JSAPI支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识”。(我当时的处理是,在用户刚进入页面的时候,拿‘code’,向微信发送请求,获取openid,没有code,就要先获取code。我在下边代码中会说。)
    参考获取openid的文档:https://mp.weixin.qq.com/wikit=resource/res_main&id=mp1421140842
    现在,所有的必传参数,我们都有了。
    开始上代码:
    后台:我当时写了俩文件。一个配置文件(WeiXinPayConfig):主要是一些上面提到的参数。一个是业务逻辑文件,处理用户下单的操作。
    配置文件(WeiXinPayConfig.php):

const APPID             = '';     //微信公众号appid
const SECRET            = '';     //AppSecret
const MCH_ID            = '';     //微信支付商户号
const KEY               = '';     //自己设置的微信key 
const NOTIFY_URL        = '';     //异步回调地址,需外网可以访问
const TOKEN             = '';     // Token
//config中的log参数
const LEVEL             = '';   
const FILE              = '';
//config中的oauth的参数
const CALLBACK          = '';     //微信公众号appid
//config中的payment的参数
const CERT_PATH         = '';     //证书路径设置,绝对路径
const KEY_PATH          = '';     //密匙文件,绝对路径
//attributes中的参数
const BODY              = '';     //支付后的支付订单信息
const TRADE_TYPE        = 'JSAPI';//唤醒方式
//wcPayParams中的参数
const SIGN_TYPE         = ''      //微信签名方式

上面有些参数你可能比较疑惑,那请你看一下’EasyWeChat’:
https://www.easywechat.com/docs/master/overview
业务逻辑文件(WechatPayController.php):

<?php

namespace App\Http\Controllers\Alipay;

use App\Http\Controllers\Controller;
use App\Model\WeChatPayDatabase;
use config\WeiXinPayConfig;
class WechatPayController  extends Controller
{
    /**微信 支付 下单付款
     * @var null
     */
    protected  $app = null;
    function getPay(Request $request)
    {
        $total_fee    = 1000;                                          //付款金额,单位为分
        $out_trade_no = time();                                        //平台内部订单号
        $phone        = $request->phone;                               //学生电话
        $pay_ways     = $request->pay_ways;                            //学生的支付方式
        $student_id   = $request->student_id;                          //学生学号
        $key          = WeiXinPayConfig::KEY;                          //自己设置的微信key
        $appid        = WeiXinPayConfig::APPID;                        //微信公众号appid
        $secret       = WeiXinPayConfig::SECRET;                       //AppSecret
        $mch_id       = WeiXinPayConfig::MCH_ID;                       //微信支付商户号
        $notify_url   = WeiXinPayConfig::NOTIFY_URL;                   //异步回调地址,需外网可以访问
        $config       = [
           'debug'    => true,                                         // 调试设置为开启
           'app_id'   => $appid,                                       // AppID
           'secret'   => $secret,                                      // AppSecret
           'token'    => WeiXinPayConfig::TOKEN,                       // Token
           'aes_key'  => '',                                           // EncodingAESKey,安全模式下请一定要填写!!!
           'log'      => [                                             //日志
               'level'        => WeiXinPayConfig::LEVEL,
               'permission'   => 0777,
               'file'         => WeiXinPayConfig::FILE,
           ],
           'oauth'   => [                                              //网页授权,获取用户信息
               'scopes'       => ['snsapi_userinfo'],
               'callback'     => WeiXinPayConfig::CALLBACK,
           ],
           'payment' => [
               'merchant_id'  => $mch_id,                              //微信支付商户号
               'key'          => $key,                                 //自己设置的微信key
               'cert_path'    => WeiXinPayConfig::CERT_PATH,           //证书路径设置
               'key_path'     => WeiXinPayConfig::KEY_PATH,            //密匙文件
               'notify_url'   => $notify_url,                          //异步回调地址,需外网可以访问
           ],
        ];
        $this->app        = new Application($config);
        $payment          = $this->app->payment;
        $insert_id        = WeChatPayDatabase::insertstuorder($student_id,$phone,$out_trade_no,$pay_ways); //把订单信息存入数据
        if($insert_id){                                                //如果订单存入成功
            $attributes        = [
                'body'         => WeiXinPayConfig::BODY,               //支付后的支付订单信息
                'total_fee'    => $total_fee,                          //支付金额,以'分'为单位
                'notify_url'   => $notify_url,                         //回调函数
                'out_trade_no' => $out_trade_no,                       //订单号
                'trade_type'   => WeiXinPayConfig::TRADE_TYPE,         //唤醒接口
                'openid'       => session('openId'),                   //从session中取出用户的openid
                'spbill_create_ip'=>$this->getIP()                     //获取用户端口号
            ];
            $order          = new Order($attributes);
            $result         = $payment->prepare($order);               //使用接口完成订单的创建
            $wcPayParams    = [
                "appId"     => $appid,
                "timeStamp" => time(),
                "nonceStr"  => $result['nonce_str'],                   //随机串
                "package"   => "prepay_id=".$result['prepay_id'],      //订单ID
                "signType"  => WeiXinPayConfig::SIGN_TYPE              //微信签名方式
            ];
            $paySign        = $this->MakeSign($wcPayParams);           //生成签名
            $wcPayParams['paySign'] = $paySign;                        //存签名
            $wcPayParams['payId']   = $insert_id;                      //存数据库中的订单ID
            return $this->responseToJson(0,'下单成功',$wcPayParams);
        }else{
            return $this->responseToJson(1,'下单失败');
        }
    }
    /**修改订单状态
     * @param Request $request
     */
    function updateOrder(Request $request)
    {
         WeChatPayDatabase::updateOrders($request->id);
    }

    /**数据返回JSON格式
     * @param int $code
     * @param string $msg
     * @param null $paras
     * @return \Illuminate\Http\JsonResponse
     */
    //所有返回前台的数据,封装成JSON数据格式
    function responseToJson($code = 0, $msg = '', $paras = null) {
        $res["code"] = $code;
        $res["msg"] = $msg;
        $res["result"] = $paras;
        return response()->json($res);
    }

    /**生成微信签名
     * @param $sign
     * @return string
     */
    //微信支付签名
    public function MakeSign($sign){
        //签名步骤一:按字典序排序参数
		ksort($sign);
		$string = $this->ToUrlParams($sign);
		//签名步骤二:在string后加入KEY
		$string = $string . "&key=你自己的KEY";
		//签名步骤三:MD5加密
		$string = md5($string);
		//签名步骤四:所有字符转为大写
		$result = strtoupper($string);
		return $result;
	}

    /**解析生成签名时传来的JSON数据
     * @param $sign
     * @return string
     */
    //把微信支付签名,封装拼接算法
    public function ToUrlParams($sign){
		$buff = "";
		foreach ($sign as $k => $v)
		{
			if($k != "sign" && $v != "" && !is_array($v)){
				$buff .= $k . "=" . $v . "&";
			}
		}
		$buff = trim($buff, "&");
		return $buff;
	 }

    /**获取终端IP
     * @return array|false|string
     */
    //获取用户的终端IP
    function getIP() {
        if (getenv("HTTP_CLIENT_IP"))            //取得用户的IP代码;
            $ip = getenv("HTTP_CLIENT_IP");
        else if(getenv("HTTP_X_FORWARDED_FOR"))  //透过代理服务器取得客户端的真实 IP 地址
            $ip = getenv("HTTP_X_FORWARDED_FOR");
        else if(getenv("REMOTE_ADDR"))           //正在浏览当前页面用户的IP 地址。
            $ip = getenv("REMOTE_ADDR");
        else $ip = "Unknow";
        return $ip;
    }

    /**
     * @param Request $request
     * @return
     */
    //目的:获取用户的openid,首先获取用户的code,然后用code换取openid。
    public function index(Request $request){
        if (strpos($_SERVER['HTTP_USER_AGENT'], 'MicroMessenger') !== false) { //如果是微信浏览器
            if($request->get('code')){                                         //如果有code参数
                $code=$request->get('code');
                $get_token_url="https://api.weixin.qq.com/sns/oauth2/access_token?appid=wx2fffc402a50e03a5&secret=956397f1970f6d1b114a8ac835bc0a77&code=".$code."&grant_type=authorization_code";
                $ch = curl_init();
                curl_setopt($ch,CURLOPT_URL,$get_token_url);
                curl_setopt($ch,CURLOPT_HEADER,0);
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1 );
                curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 100);
                $openid = curl_exec($ch);                                      //拿code换区opeid
                $Id=json_decode($openid);
                session(['openId' => $Id->openid]);                          //存session
                curl_close($ch);
            }else{                        //没有code就先 跳转 然后回调到这里 执行上面的if获取Openid
                return redirect("https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx2fffc402a50e03a5&redirect_uri=你自己的获取'openid的路由'&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect");
            }
        }
        return redirect('/');
    }

    /**支付后回调
     * @return mixed
     */
    public function wechatNotify() {
            $response =$this->app->payment->handleNotify(function($notify, $successful){
            $out_no = $notify->out_trade_no;
            // 使用通知里的 "微信支付订单号" 或者 "商户订单号" 去自己的数据库找到订单
            $order = WeChatPayDatabase::sestuordernum($out_no);
            if (!$order) {                             // 如果订单不存在
                return 'Order not exist.';             // 告诉微信,我已经处理完了,订单没找到,别再通知我了
            }
            if ($successful) {                         // 用户是否支付成功
                WeChatPayDatabase::updateorstatus($out_no);
                return true;
            } else {                                   // 用户支付失败
                return false;
            }
            return true;
        });
        return $response;
    }
}

前台:可以参考官方文档,微信内H5调起支付:
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6
我当时用的是VUE。

//向后台发送,进行下单
postpay(){
                let self = this;
                this.$http.post('wechatpay/getpay',{
                    student_id : this.form.student_id,
                    phone      : this.form.phone,
                    pay_ways   : this.pay_ways,
                }).then(
                    function (response) {
                        let data = response.data;
                        if(data.code == 0){
                            self.sicallpay(data.result);
                        }
                        else {
                            self.$message({
                                showClose: true,
                                message: data.msg,
                                type: 'error'
                            });
                        }
                    })
            },
            //
            sicallpay(result){
                if(typeof WeixinJSBridge == 'undefined'){   //WeixinJSBridge内置对象在其他浏览器中无效。
                    if(document.addEventListener()){
                        document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
                    }else if(document.attachEvent) {
                        document.attachEvent('WeixinJSBridgeReady',  onBridgeReady);
                        document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
                    }
                }else {
                    this.onBridgeReady(result);
                }
            },
            onBridgeReady(result){
                let self = this;
                WeixinJSBridge.invoke(
                    'getBrandWCPayRequest', {
                        "appId":result.appId,         //公众号名称,由商户传入   
                        "timeStamp":result.timeStamp, //时间戳,自1970年以来的秒数
                        "nonceStr":result.nonceStr,   //随机串   
                        "package":result.package,
                        "signType":"MD5",             //微信签名方式: 
                        "paySign":result.paySign
                    },
                    function (res) {
                        /**
                        JS API的返回结果get_brand_wcpay_request:ok仅在用户成功完成支付时返回。
                        *具体的其他返回结果,参考官方文档
                        */
                        if (res.err_msg == "get_brand_wcpay_request:ok") {
                            self.updateOrders(result.payId);
                        } else {
                            self.$message({
                                showClose: true,
                                message: '支付失败,无法报名',
                                type: 'error'
                            });
                        }
                    }
                );
            },
            updateOrders(orderid){
           /**
             * 这里写你自己的业务逻辑
             */
            },
        }

第四坑
前台唤醒微信支付,需要注意,要兼容IOS系统。上面的前台写的,只适用于android。
兼容苹果系统,我后面会补上。
到这里,微信公众号JSAPI支付,基本已经完成了。
喜欢,请留言,点赞!!!!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章