美文网首页后端开发iOSiOS
微信App支付全解析

微信App支付全解析

作者: Tsy远 | 来源:发表于2016-07-08 16:22 被阅读23900次

    简单介绍了微信移动支付的申请、接入、使用、确认支付结果等相关流程

    0 系列文章

    系列一 微信App支付全解析
    系列二 支付宝App支付全解析
    系列三 微信公众号支付全解析
    系列四 微信扫码支付全解析
    系列五 支付宝即时到账支付全解析
    系列六 微信退款全解析
    系列七 支付宝退款全解析
    系列八 支付宝开放平台支付更新升级全解析

    1 申请

    申请步骤直接参考官方文档

    主要2个大块:

    1. 申请开通开放平台
    2. 申请支付开通商户平台

    全部申请通过后,获取支付必须的参数如下:

    1.1 AppID和AppSecret

    开放平台创建的应用唯一标识。
    登录微信开放平台,进入应用详情可查看AppID和AppSecret。

    Paste_Image.png Paste_Image.png

    1.2 mch_id

    微信支付申请完成之后,微信商户平台会给你的邮箱发通知邮件,里面包含开通支付的商户信息

    Paste_Image.png

    1.3 API秘钥

    即商户支付秘钥,主要负责处理通信相关参数加密。登陆微信商户平台(账号密码在微信商户平台发来的邮件里)
    点击左侧的「账户设置 - API 安全」(第一次登陆会让你安装操作证书,请先安装操作证书)。点击设置密钥,设置自己的密钥。

    Paste_Image.png

    1.4 商户证书

    用于退款等一些需要证书验证的接口使用。在微信商户平台点击「账户中心 - API 安全」,点击「下载证书」

    Paste_Image.png

    证书下载后,打开压缩包会看到「apiclient_cert.pem」和「apiclient_key.pem」和rootca.pem证书。

    2 接入流程

    参考接入文档

    主要几个步骤:

    1. 统一下单(放在服务端,需要加密参数)
    2. 生成支付参数(放在服务端,需要生成签名)
    3. 调用客户端SDK发起支付
    4. 服务端异步接收支付结果

    2.1 统一下单

    $appid = "";  //你的appid
    $mch_id = "";  //商户id
    $wx_api_key = "";    //商户api秘钥
    $out_trade_no = "";  //自己业务系统生成的交易no,可以唯一标识
    $client_ip = "";  //客户端ip
    $notify_url = "";    //接收支付结果通知url
    
    $UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";  //统一下单地址
    
    $data = array();
    $data['appid'] = $appid; 
    $data['mch_id'] =$mch_id;
    $data['nonce_str'] = randomStr(20);  //随机20位字符串
    $data['body'] = "微信移动支付测试";
    $data['detail'] = "微信移动支付测试";
    $data['out_trade_no'] = $out_trade_no;    
    $data['total_fee'] = 1;  //注意 单位是分
    $data['spbill_create_ip'] = $client_ip;
    $data['notify_url'] = $notify_url;
    $data['trade_type'] = "APP";  //交易类型
    $data['sign'] =sign($data, $wx_api_key);    //签名
    
    //转为xml格式
    $xml_str = arrayToXmlStr($data); 
    
    //发送请求 使用封装好的curl_post
    $result = curl_post($UNIFIED_ORDER_URL, $xml_str);
    
    //解析得到的值
    $get_data = simplexml_load_string($raw_data, 'SimpleXMLElement', LIBXML_NOCDATA);
    $get_para = array();
    $get_sign = "";
    foreach ($get_data->children() as $child) 
    {    
        if($child->getName() == 'sign') {        
            $get_sign = strval($child);    
        } else {        
            $get_para[strval($child->getName())] = strval($child);    
        }
    }
    
    if($get_para['return_code'] !== "SUCCESS") {
        //return code fail
    }
    
    //验证签名
    if(!verifySign($get_sign, $get_para, $wx_api_key)) {
        //验证签名非法
    }
    
    //可以自行处理解析获得的参数
    //todo...
    

    一些函数:

    /**
     * array转成xml str
     * @param $arr
     */
    public static function arrayToXmlStr($arr) {    
        $xml_data = new \SimpleXMLElement("<xml></xml>");    
        Func::arrayToXml($arr, $xml_data);    
        return $xml_data->asXML();
    }
    
    /**
     * 生成指定长度的随机字符串(包含大写英文字母, 小写英文字母, 数字)
     * @param $length int 需要生成的字符串的长度
     * @return string 包含 大小写英文字母 和 数字 的随机字符串
     */
    public static function randomStr($length){    
        //生成一个包含 大写英文字母, 小写英文字母, 数字 的数组    
        $arr = array_merge(range(0, 9), range('a', 'z'), range('A', 'Z'));    
        $str = '';    
        $arr_len = count($arr);    
        for ($i = 0; $i < $length; $i++)    {        
            $rand = mt_rand(0, $arr_len-1);        
            $str.=$arr[$rand];    
        }    
        return $str;
    }
    
    /**
     * 微信签名
     * @param $para mixed 带签名参数数组
     * @param $wx_key string wxkey
     */
    public static function sign($para, $wx_key) {    
        $unsign_str = Func::createLinkString(Func::argSort($para)) . "&key=" . $wx_key;    
        $sign = strtoupper(md5($unsign_str));    
        return $sign;
    }
    
    /**
     * 微信签名验证
     * @param $sign
     * @param $para
     * @param $wx_key
     * @return false-验证失败 true-验证成功
     */
    public static function verifySign($sign, $para, $wx_key) {    
        $unsign_str = Func::createLinkString(Func::argSort($para)) . "&key=" . $wx_key;    
        $sign_str = strtoupper(md5($unsign_str));    
        if($sign === $sign_str) {        
        return true;    
        }    
        return false;
    }
    
    

    2.2 生成支付参数

    客户端需要的支付参数是带签名的,所以最好支付参数也在服务端生成后,jsondecode后传入客户端即可直接调用

    //生成支付参数
    $data = array();
    $data['appid'] = $appid; 
    $data['mch_id'] =$mch_id;
    $data['prepayid'] = $prepayid;    //刚才统一下单生成的prepayid
    $data['package'] = "Sign=WXPay";
    $data['noncestr'] = randomStr(20);
    $data['timestamp'] = time();
    $data['sign'] =sign($data, $wx_api_key);
    
    $pay_param = json_encode($data);
    

    3. 调用支付

    3.1 Android

    注:微信支付在开放平台中填入应用对应的包名和签名,并且测试时要签名打包,不然支付失败

    可以直接参考调用我二次封装过的Android SDK。
    Github地址:https://github.com/tsy12321/PayAndroid

    3.2 iOS

    二次封装过的iOS SDK。
    Github地址:https://github.com/tsy12321/PayiOS

    4 异步结果通知

    注:尤其要注意通知结果验证成功后要能正确处理重复通知,放置多次发货造成资金损失

    $raw_data = $GLOBALS["HTTP_RAW_POST_DATA"];
    
    $get_data = simplexml_load_string($raw_data, 'SimpleXMLElement', LIBXML_NOCDATA);
    $get_para = array();
    $get_sign = "";
    foreach ($get_data->children() as $child) 
    {    
        if($child->getName() == 'sign') {        
            $get_sign = strval($child);    
        } else {        
            $get_para[strval($child->getName())] = strval($child);    
        }
    }
    
    if($get_para['return_code'] !== "SUCCESS") {
        //return code fail
        die("<xml><return_code><![CDATA[FAIL]]></return_code></xml>");
    }
    
    //验证签名
    if(!verifySign($get_sign, $get_para, $wx_api_key)) {
        //验证签名非法
        //todo
        die("<xml><return_code><![CDATA[FAIL]]></return_code></xml>");
    }
    
    //在这其实通知已经接受成功 可以返回成功告诉微信不用再次通知了
    echo("<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>");
    
    //业务状态码判断
    if ($get_para['result_code'] !== 'SUCCESS') {       
        //状态码错误
        //支付错误 更改订单状态 记录log等 
        //...
    }
    
    //支付成功 更改订单状态 记录log等 
    //todo
    
    

    5 其他

    1. 客户端收到同步支付结果后建议一段时间内轮询检查服务端,获取服务端的结果,支付最终状态以服务端为准

    结尾

    更多文章关注我的公众号


    我的公众号

    相关文章

      网友评论

      • 涂鸦男人:微信支付的 一个appid是不是只能一个app使用?
        Tsy远:如果APP支付就避免不了
        涂鸦男人:@Tsy远 支付宝也是这样吗? 如果集团下面想做一个聚合支付,如果是微信支付,是不是要创建多个appid?
        Tsy远:对的 跟包名绑定
      • d8ceff4eefc6:为什么总关键步骤确一带而过呢?设置API密钥需要32位的字符串,这要怎么设置呢? 网上随便搜索一个生成器随便生成吗? 这个步骤很关键 希望大神能详细说明下!:pray:
      • 8324a7eda376:不错不错,收藏了。

        推荐下,源码圈 300 胖友的书单整理:http://t.cn/R0Uflld


        12a033ef755a:写的不错,谢谢博主;已收藏~
      • 张憨憨:哥们你做过 微信支付商户平台,企业向个人付款吗?
        Tsy远:@张憨憨 企业付款流程比较简单。就是调用一个接口请求付款给某人,同步返回成功还是失败。
        https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_1
        通过openid获取转给谁,可选参数可以验证姓名之类的。
        张憨憨:@Tsy远 你能给我说下流程吗 我刚做不太懂流程 我们的需求是微信提现功能
        Tsy远:企业付款也做过的。文章就没写了
      • 2fee47c3a6eb:代码不完整啊,怎么会有那么多人点赞啊
      • 6253b868282a:IOS提示支付验证签名失败,安卓提示-1,这是咋回事呢?
      • 5c753f1ff42e:额,回复到支付宝的那篇里了..

        感觉统一下单的代码还有有问题啊
        curl_post()方法没有找到。。

        //发送请求 使用封装好的curl_post
        $result = curl_post($UNIFIED_ORDER_URL, $xml_str);

        //解析得到的值
        $get_data = simplexml_load_string($raw_data, 'SimpleXMLElement', LIBXML_NOCDATA);
        $raw_data应该换成$result ?
        Tsy远:@24牽手歲月 恩。这不是完整的代码。我只是把核心片段取出来的。比如arrayToXml 可以上网找一个实现方法,还有curl_post也是的。
        $raw_data应该换成$result ,笔误
      • hhuua:你好,我们有一个需求,根据用户订单的不同,是要往不同的账户里面转账的,支付宝可以通过同一个partner下不同的sellerID达到这个功能吗,微信呢?微信好像没有这方面的支持。请问有没有什么解决方法。
        Tsy远:你可以看下微信的企业转账。看是不是你们需要的啊
      • 6c11260fb35d:请问怎么使用? 不是 eclipse 工程。使用 gradlew.bat 打开了构建几个小时还没有完呀。
      • 凤鸣游子:关于同步, 异步的还是不太清楚. ...
        Tsy远:@阿水哥哥
        微信客户端支付完成后会返回给你客户端一个支付结果。
        同时微信的服务端会主动调用你服务端的接口发送支付结果通知。
        逻辑处理应该是你服务端接收到支付结果后处理,比如修改订单状态,发货等等。不能依赖客户端的返回结果认为支付成功,是不可靠的,微信的文档也是这么建议的。
        你前后端的时序可以这样。客户端支付完成收到支付结果后,在一定时间内不断轮询查询服务端订单的状态有没有修改。(比如5s内每s查询一次)这样以服务端的交易状态为准
        凤鸣游子:@Tsy远 你说的我们客户端接收同步的返回结果是由微信客户端回调时候返回的吗? 另外, 如果最后以服务端通知为准, 它是在哪里需要做逻辑处理吗? 因为我理解的是, 支付完成之后回调客户端, 然后通知支付结果, 这里就基本结束了.
        Tsy远:同步就是客户端一支付完会返回一个结果。但是因为这个结果是客户端返回的不可靠。
        异步就是你支付完第三方会发post数据给你的服务端。由于是被动获取无法准确掌握时间,所以是异步。
        最后的支付结果要以服务端通知的结果为准
      • 凤鸣游子:看不懂啊
      • 宇宙只有巴掌大:问题是服务端用他获取的预支付id 已经做了签名 返回给我 结果我直接用你封装的Android支付adk 直接用服务端给得7个值吊起支付 但是 提示 error_code -1
      • 宇宙只有巴掌大:大神你这里的 pay_param 参数需要服务端做二次签名吗?
        Tsy远:@阿水哥哥 证书在退款的时候要用。支付的时候不需要。
        可以去看退款解析那篇文章
        凤鸣游子:@宇宙只有巴掌大 你们知道那个证书怎么使用啊?
        Tsy远:要的。根据微信文档要的参数后再用微信提供的签名方式签名。也是客户端的一个字段
      • 宇宙只有巴掌大:pay_param是如何生成的?
      • 聪葱忙忘:为什么同样是在异步结果通知,微信需要verifySign验证签名,而支付宝不用呢?
        Tsy远:支付宝有验证的。仔细看下代码哈
      • thisfeng:看到这个系列,真的是良心奉献啊!都是最新
      • 小楼东风:你github上的iOS版的支付Demo下载不下来,发我一份至820811266@qq.com邮箱内,谢谢!
      • 57316a0da9c5:不明觉厉
      • 风之丨旅人:这是java吗?
        Tsy远:@javakam php
      • c1d645454ef1:写的不错……
        Tsy远:@NULLPO 就是把代码放到回调接口啊。然后把一些验证签名的放到公共方法
        c1d645454ef1:@Tsy远 支付接口,异步通知,你是怎么封装的?
        Tsy远:@NULLPO :smiley:
      • Jeff_Jie_Xu:(⊙o⊙)…
      • 72a3651cffee:支付宝就够了😓
        Tsy远:@呼倫貝爾的斑馬 😆
      • vvvei:自己app里面的零钱,提现到微信怎么做啊?
        Tsy远:@vvvei AndroidManifest里面的权限?
        vvvei:@Tsy远 app要申请什么权限嘛?
        Tsy远:@vvvei 那个应该是企业转账。
      • 幻凌风:不说的别的,直接赞!
        Tsy远:@幻凌风 :cat::cat:
      • 72acacef5aab:也可以官方的时序图,很清晰

      本文标题:微信App支付全解析

      本文链接:https://www.haomeiwen.com/subject/ulkrjttx.html