谈谈那些年微信支付踩过的坑

2017-05-24 Jamling 更多博文 » 博客 » GitHub »

Android QuickAF

原文链接 https://jamling.github.io/2017/05/24/Android-quickaf-wechat-pay/
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。


很早的时候就想写这篇文章了,作为BAT中的一员,还真不想吐槽它,免得被人身攻击。有人说,微信支付很简单嘛,官网有例子,网上也有现成的例子,不过谁用谁知道,本人也是在深入了解之后,真心觉得微信支付里的坑太多,BAT的开发们太敷衍了事,结果给不少的其他开发者带来诸多麻烦。我在这里做个稍全一点的介绍,尽量减少其他同学们掉坑里的概率。

<!-- more -->

在微信上创建你的应用

这里特别强调一下,这一步很重要,不然微信支付集成调试会出现莫名的错误。

1,在注册之前对于Android客户端,需要提供app应用的包名和应用签名(md5值),这两个东西问开发或产品同学要。尽量在注册前提供。另外还需准备一个28x28和108x108的logo图片,问设计或产品同学要。 2,在微信开放平台上注册,注意了,是微信开放平台,不是公众号平台,公众号平台账号不能在开放平台登录。如果已经注册过,请直接登录,登录成功后,点击创建移动应用,创建Android应用时,应用包名应用签名,尽量一次性填对了。应用创建成功后,将得到appid(以wx开头的一串数字)。然后再去申请微信支付(需做开发者认证并缴费) 3,在[微信支付商户平台]注册或登录,申请app支付,申请通过后,将得到商户idmch_id(一串10位数字),然后在账户设置-->API安全-->密钥设置中设置API密钥key(32位的字串)

更多的申请帮助,请参考:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419317780&token=6c7b59a75b08969c15fa41141ee8d88c236f01ab&lang=zh_CN

注:关于微信支付申请,请在开放平台申请(同公众号支付申请流程,公众号支付不在本文讨论范围之内)微信的工作人员审核通过后,会发审核通过的邮件。里面包含微信支付商户号mch_id(一串10位数字)和APPID等信息。然后根据邮件提示或者直接在微信商户平台中-->账户设置-->API安全-->密钥设置中下载api证书并设置API密钥key(32位的字串)

在微信开放平台创建应用成功后,APP支付也申请通过了。请提供给开发同学以下东西:

  • APPID appid(以wx开头的一串数字)
  • 商户id mch_id(一串10位数字)
  • API密钥 key (32位的字串) 对于Android应用,还需要保证应用包名应用签名正确

只要上面的信息正确无误,下面就交给开发同学了。如果是直接使用微信支付sdk的同学,请准备好踩坑吧。

支付SDK和demo

微信支付SDK

SDK从4.0.2开始,已改为使用gradle方式。

dependencies {
   compile 'com.tencent.mm.opensdk:wechat-sdk-android-without-mta:+'
}

以前是直接使用libammsdk.jar,相比起来,算是进步了。

demo

我建议直接忽略官方的demo,不信我的可以直接去Android资源下载下载相关demo

以下是我踩过的坑:

  • 支付Demo在另外一个单独的project中,尽管sdk demo与pay demo非常类似,搞不懂为啥不合为一个demo project。我昨天在微信网站下载的支付demo,今天在微信网站上就找不着了。昨天下载的支付demo,看版本是v3(sdk demo已经是5.0.2了),还是eclipse工程,不过导入到eclipse之后,编译不通过。sdk中原来的com.tencent.mm.sdk包换成了com.tencent.mm.opensdk,demo,导致src代码一片红。
  • 支付Demo中的服务端下单接口地址(http://wxpay.weixin.qq.com/pub_v2/app/app_pay.php?plat=android) 不能访问,就算修复了编译错误,结果仍然运行不了。 然后我看了下其它的文档,sdk换了,但是相关的文档并没有更新,尤其是混淆配置,直接影响到联调,这也是一个隐藏的坑。

统一下单

统一下单是商户系统(客户端或服务端)向微信支付后台发送请求,以拿到预支付交易会话标识(prepayid)等信息。关于接口的请求与响应信息,请参考统一下单接口描述。文档写得还是蛮全的,不过,实际上如何,我就只能呵呵了。请看下面

  • 接口的请求与响应都是xml格式,xml的解析不太方便,微信仅支持这个格式。算是小坑。
  • 接口文档中定义了一个NOT_UTF8的错误码,似乎微信要求的请求编码为utf-8,不过使用官方demo的Util.httpPost时,如果参数中有中文,还需将请求的xml转为ISO-8859-1编码的字符串才行。不然,微信直接返回给你一个空字符串,保证让你找不着北。
  • 接口文档中定义了一个LACK_PARAMS的错误码,指的是如果必填的参数为空,会返回此错误码,可事实上呢,有一次我把total_fee参数搞混了,写成了支付宝的参数名,结果接口返回一个空串,结果让我一顿好找。
  • Android从6.0开始,删除了apache的http组件,于是乎我把Util中的apache http组件换成了HttpUrlConnection,关于请求头,解析都和原来保持一致,结果返回个签名错误。要不是我换http组件之前可以成功下单,我几乎就信了它,真的去查签名了(事实上根本不是签名错误好吧)。后来我修改http的请求头,尝试各种请求方式与编码,结果还是返回签名错误,这让我很是折腾,一度曾想改回apache的http client。最后,我把请求的xml转为utf-8编码,然后HttpUrlConnection全都默认设置,这才下单成功。(PS,这里顺带讲一个HttpUrlConnection的坑,设置请求方法为post,可是debug中看到的HttpUrlConnection对象,请求方法仍为get,当初还以为是这个导致的,后来才知道HttpUrlConnection真正的实现是在delegate中)

总结一下,统一下单的响应、错误码和错误描述比较混乱,成功响应还好,一旦出现问题,准让你摸不清东南西北。

签名这块,请参考签名算法,注意按字母升序组织请求参数。我用的是有序的TreeMap保存参数并做签名。没掉坑里。这里附上微信提供的签名检验工具

调用支付接口

这一步是客户端拿到prepayid之后,向微信发送PayReq。请求参数有7个(详细参考微信sdk中的PayReq类,其中partnerId填商户IDmch_idpackageValue固定填Sign=WXPaytimestamp为北京时间戳,单位为秒。其它的顾名思义,对号入座即可),缺一不可。这里呢,也有坑

  • PayResp(响应)定义了errCode和errStr,只要支付失败,errCode都为-1,errStr永远为null,所以,具体的错误原因:签名错误、未注册APPID、项目设置APPID不正确、注册的APPID与设置的不匹配、其他异常等,一个一个查吧。
  • 统一下单接口不返回timestamp,如果是我们自己设置一个,那么需要对请求重新签名,统一下单返回的sign就不能用了。不然调用支付接口肯定返回-1。
  • 如果接口调用失败,返回-1,是应用签名(apk的签名)不正确(比如debug和release版本使用的不同签名)导致的,那么需要清空微信缓存,才能支付成功。不过,我可不敢随便清微信缓存,我建议可以将手机重启,如果仍然不行,再清微信的缓存。

libammsdk.jar冲突

因微信支付和分享是在同一个jar中,所以如果使用了第三方的分享sdk,极有可能会出现jar冲突。即使是相同模块中的jar完全一致也不行。解决的办法是只保留一个模块中的libammsdk.jar然后clen build。

建议

  • apk的包名,签名在工程初始化的时候就确定好,debug和release使用同一个签名。
  • 微信开放平台在创建应用前,确认好apk包名和签名。
  • 如果是服务端下单,确保sign值是对的(需要再次签名的再签一次)。客户端拿到七个参数后,可以直接支付。
  • 建议在服务端下单,这样更安全。
  • 使用第三方的支付sdk,避免入坑。

af-pay

最后推荐一个Android上的支付sdk:af-pay,github地址为:https://github.com/Jamling/af-pay af-pay是一个Android平台上支付库,支持支付宝和微信,使用af-pay可以让支付变得更简单,并且支持服务端与服务单下单。示例代码如下:

private void doWxpay(String orderInfo) {
    final Activity activity = this;
    // 获取支付类
    Wxpay wxpay = Wxpay.getInstance(activity);
    // 设置支付回调监听
    wxpay.setPayListener(new Wxpay.PayListener() {
        @Override
        public void onPaySuccess(BaseResp resp) {
            showToast(activity, "支付成功");
        }

        @Override
        public void onPayCanceled(BaseResp resp) {
            showToast(activity, "支付取消");
        }

        @Override
        public void onPayFailure(BaseResp resp) {
            showToast(activity, "支付失败");
        }
    });
    // 这里是服务端下单,内容是统一下单返回的xml
    if (!TextUtils.isEmpty(orderInfo)) {
        PayReq req = OrderInfoUtil.getPayReq(orderInfo);
        wxpay.pay(req);
    }
    else { // 客户端下单
        Wxpay.DEBUG = true; // 开启日志
        // API密钥,在微信商户平台设置
        Wxpay.Config.api_key = "32位的字串";
        // APPID,在微信开放平台创建应用后生成
        Wxpay.Config.app_id = "wx...";
        // 商户ID,注册商户平台后生成
        Wxpay.Config.mch_id = "14...";
        // 支付结果异步通知接口,由后台开发提供
        Wxpay.Config.notify_url = "http://www.ieclipse.cn/app/pay/wxpay_notify.do";
        // 创建统一下单异步任务
        Wxpay.DefaultOrderTask task = new Wxpay.DefaultOrderTask(wxpay);
        // 这个商户订单号,由后台返回,在这里随便生成一个
        String outTradeNo = OrderInfoUtil2_0.genOutTradeNo();
        // 设置统一下单的请求参数
        task.setParams(OrderInfoUtil.buildOrderParamMap(outTradeNo, "测试支付", "", "1", null, null, null));
        task.execute();
    }
}

附上完整的支付过程中的日志 完整日志

如果是服务端下单,客户端只需生成PayReq对象,然后调用wxpay.pay(PayReq)即可。af-pay已经配置好回调的WXPayActivity和Receiver。详细信息请访问Github。

关于

QuickAF是一个Android平台上的app快速开发框架,欢迎读者在github star或fork。本人写作水平有限,欢迎广大读者指正,如有问题,可与我直接联系或在我的官方博客中给出评论。

参考

QuickAF: https://github.com/Jamling/QuickAF 微信支付开发文档:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_1