一、背景介绍
作为一名Android开发,从最初的跌跌撞撞到现在小有所悟,这其中经历过的辛酸苦辣也是一种痛并快乐着的过程。在这一个过程中,不断的在工作中、在网络上向各位前辈朋友学习,一次一次的充实了自己,学到了新东西,解决了新问题,内心甚是感激。同时在这一过程中,也慢慢积累了自己的一些经验,趁着有了一点空余时间,拿出来分享一下,希望能帮助到有需要的朋友。因为本人技术能力有限,如文章有欠缺不妥之处,还望指正。
最近的开发工作又重新接入了一次微信支付,终于完成了从开放平台的账号设置到APP端、服务器端的代码编写都有参与的一个历程,算是理清了微信支付的一个整体流程,并且对如何避免入坑以及错误扫雷有了比较大的认知。记得上一次的接入还是一年前,那时的主要角色只限于APP端的开发工作,虽说也是APP端的主导开发,但是并不能从全局从整体去参与,所以部分流程和概念还是一个似懂非懂的一个状态(最终APP接入微信支付是成功的,但也是知其然不知所以然)。故而在此写下一遍文章以记录所知所感,期以做个人备忘及新人参考之用。
这篇文章的主要目的不是介绍如何接入微信支付快速完成代码开发,我相信这个问题的答案在微信的官方开发文档以及网络上都已有了先例,而且也写得很好。如果你是刚刚接触微信支付接入并且没有阅读过官方开发文档,那么你在看这篇文章时会有很多困惑;但是如果你已经在接入的过程当中,并且对官方开发文档比较熟悉,那么本文中的某些部分能够给你一种豁然开朗的感觉。正如标题所言,本文的目的在于说明微信支付的分工合作,重点了解从【微信支付开放平台的APP信息设置】到【服务器后台获取预支付id信息】再到【APP端拿到服务器信息后发起支付请求】直到最后【服务器和客户端得到支付成功通知】的这样一个过程,在这一过程中,将会在适当的位置以Tips的形式插入需要注意的地方,期望能够作为错误扫雷手册参考。因为本文针对的是开发者,特别是Android开发者,可能账号申请及APP设置环节、服务器后台处理流程环节大部分人没有去参与过,本着查漏补缺的原则,所以会对这两个环节进行更为详细的描述,而对APP接入代码编写环节主要做一个理论上的描述,不涉及具体的代码实现,如有兴趣可以留言进行交流。
二、分工合作流程
2.1 账号申请和APP设置
2.1.1 账号申请
打开微信支付开放平台登录地址,按照微信支付申请接入流程,选择一个接入场景进行接入。例如APP支付的接入流程是:
1、注册开放平台账号
2、认证开发者资质
3、创建APP并提交审核(Tips:设置好APP的keystore签名)
4、提交资料申请微信支付
5、开户成功,登录商户平台进行验证
6、在线签署协议
7、启动设计和开发
2.1.2 APP设置
微信支付申请审核通过后,商户在申请资料填写的邮箱中将收取到由微信支付小助手发送的邮件,此邮件包含了开发时需要使用的支付账户信息内容。
收到邮件后,需要登录微信商户平台设置API支付密钥。
Tips:可以通过微信公众平台接口调试工具对微信支付的账号以及API密钥进行有效性验证。具体方法是:
1、打开微信公众平台接口调试工具页面,接口选择【自定义】选项
2、填入微信支付必填参数及其参数值(注意:大小写敏感)以及商户Key(即API密钥)
3、点击生成签名,就可以得到统一下单接口参数数据
4、将统一下单接口参数数据填入一个POST请求工具(例如Chrome插件Postman),向统一下单接口地址提交一个POST请求,如果能够正常得到预支付id,那么说明微信支付账号和API密钥有效(Tips:除了appid、mch_id、trade_type、商户Key,其他参数值都可以随便填,只要符合字段类型定义就行,不会影响到最终获取预支付id结果。例如notify_url参数如果还没有自己的通知回调页面,可以填入http://www.baidu.com之类的都行)。否则请返回之前的步骤,检查支付账号是否开通APP支付功能以及是否设置了API密钥。
至此,微信支付账号的准备工作完成,可以开始进行下一步的代码开发工作了。
2.2 代码开发流程
准备好了微信支付账号,并确认账号的支付权限和API密钥设置无误后,现在可以开始进行代码开发了。上图是微信官方文档中的微信支付业务流程图,从流程图中可以看到代码开发分服务器端和APP端,服务器端负责支付前的订单生成、数据获取和支付后的订单状态更新,APP端负责发起支付请求,简言之,服务器负责支付前期、后期,APP则负责中期。
2.2.1 服务器接口开发
服务器接口的前期工作是:
1、生成内部订单,调用统一下单接口,获取到预支付id信息
服务器根据APP发送过来的商品id和用户id信息,在数据库中生成一条内部订单,然后利用订单信息、微信支付账号信息生成统一下单接口参数,调用统一下单接口获取得到预支付id。必填的接口参数如图。
固定部分为每次调用接口都固定不变的参数(不是值全部写死,而是每个订单每次调用都不变),trade_type的值APP表明接入的是APP支付(如果是JSAPI支付,则填入JSAPI),notify_url则是支付回调页面,这个页面属于后期工作中的支付结果监听部分内容(Tips:该回调页面应该能够外网访问,否则微信无法调用进行支付通知,这个在真正进行支付调试的时候要特别注意),spbill_create_ip可以直接填入服务器的外网IP地址(Tips:它的值是服务器内网或者外网地址,又或者是其他任意有效的IP地址,都不会影响获取预支付id的结果,但是建议还是填入服务器外网IP地址)。
账号部分为所申请的微信支付账号信息部分,这部分主要提供appid和mch_id(Tips:appid和mch_id是一一对应的关系,不可以错开使用,一个appid必须对应他在账号申请时签署的商户mch_id,否则无法成功获取预支付id信息)。appid和mch_id可以在后台配置表中进行配置,一般情况下同一个APP每次调用统一下单接口时不会改变这两个值,但也不排除针对不同订单,其收款对象不同的这样一个需求,所以在此处可以根据业务需求来进行账号信息配置。
商品部分内容为所要支付的商品信息,out_trade_no为服务器生成的内部订单id(Tips:该id会在支付回调页面中返回,用以更新该订单状态,所以需要保证该id在内部订单系统中的唯一性)。body字段为商品描述部分,用于在微信支付确认中显示(Tips:该字段的类型定义是String(128),所以字段长度不要超过128字符)。total_fee字段则是商品的价格,其单位为分,类型为int,例如 total_fee = 666,则默认表明¥6.66。
验证部分内容提供了安全性保障。nonce_str字段为一个随机字符串,目的就是为了增加随机性,让签名后的sign字段无法预料,使得通过拦截进行破解变得不可能。(Tips:nonce_str可以是16位随机字符串,也可以是32位随机字符串,或者其他少于等于32位的随机字符串都可以,nonce_str字段在每次进行签名前都应该重新生成而不是沿用原来的,否则就失去了它的意义。它的字符长度、字符类型集合、是否签名前重新生成还是沿用都不会影响到最后获取预支付id的成功与否,但是必须保证加入签名获得sign字段时的nonce_str与传给微信的nonce_str参数两者的值是一样的。在获得预支付id结果后,加入签名获得sign字段时的nonce_str与返回给前端的结果参数nonce_str也要一致,否则会出现签名错误问题)。sign字段是整个参数的一个关键,它保证了参数传输和接收的安全性,保证这些参数从发送方到接收方的传输过程中,没有被修改过。它的生成规则在官方文档里面的说明中已经约定,所有传递过去的参数,除了sign参数字段,其他参数字段都要按照参数名进行ASCII码字典序从小到大排序拼接成一串,然后在字符串末尾拼接API密钥,最终得到一串拼接了API密钥的参数链字符串,对该参数链字符串进行MD5加密成32位大写字母字符串,即得到了最终的sign字段值。试想一下,如果有个黑客想要通过拦截修改的方式,将原本价格为100元的商品修改为0.01元,即把 total_fee = 10000 改成 total_fee = 1 ,然后发送给了微信,那么微信在验证过程把参数拼接成参数链进行签名后得到的sign字段已经和参数中原有的sign字段不一样了,就会提示签名错误异常,不接受该请求。而黑客如果想在修改了价格后再重新伪造sign字段来通过微信验证,那么他就必须知道API密钥,但该密钥只有服务器端和微信端才有,并不通过网络进行传播,所以黑客也就无计可施了(Tips:所以API密钥一定要妥善保管在服务器端,不能通过网络传播给APP客户端,更不能内置在APP客户端)。
2、请求统一下单接口获得数据并处理
服务器端生成了参数后,调用统一下单接口获得了接口返回的包含预支付id的结果,这时需要做两个操作:
(1)对接口返回结果进行签名验证
从前面的sign字段分析中已经提到了,该字段保证了数据传输接收的安全性,所以在拿到接口返回数据时,要对该字段进行验证,将所有参数剔除sign字段后按顺序拼接成带API密钥的参数链字符串,然后通过MD5方法进行签名,得到了新的32位大写MD5字符串的sign签名,将微信返回的sign字段与重新签名的sign进行对比,如果一致,则表明参数准确无误没有被篡改过,否则一定是在某一个环节发生了问题(Tips:要善于利用微信公众平台接口调试工具来进行问题排查,如果签名方法和参数字段都没问题,但是调试工具得到的sign值和原有参数中的sign值不一致,那么,哈哈哈,恭喜你,你被黑客盯上了,否则请检查你的签名方法和参数字段是否一一对应)。
(2)生成APP前端调用支付所需参数并签名,返回给前端
签名验证无误后,再通过统一下单接口返回的数据生成前端调用支付所需字段,同时对这些字段进行签名。例如Android APP支付所需的参数如下:
填入appid、mch_id、prepayid、packageValue,重新生成noncestr和timestamp,然后将这些参数排好序同时加上API密钥拼成参数链字符串,对该字符串进行MD5签名得到sign字段,最后将这些字段全部返回给前端即可(在与微信进行交互时,其命名规则一定要按照微信官方文档进行定义,而在跟APP进行交互时,其参数key命名是否带有下划线、是否符合驼峰命名并不影响,这里的命名约定主要是服务器和APP前端两者的约定。在此处我只能按照之前服务端接口小哥的命名来定义,所以各位不必深究)。
至此,服务器接口的前期工作已经完成。下面看看服务器接口在支付后期的工作内容。
服务器接口的后期工作是:编写支付回调页面,接收支付成功回调并修改订单状态,给微信返回订单处理完成信息
在调用统一下单接口时需要填入一个notify_url回调页面,这个页面就是监听支付成功通知的处理页面,其本质就是给微信服务器在支付成功后的一个调用接口。在该页面中,主要做三件事:
(1) 验证接口传入参数的安全性
微信调用该页面时,会传入以下信息:
在回调页面收到这些参数数据时,首先需要对result_code、return_code进行验证,只有两者都为SUCCESS时才表明支付顺利完成,否则在支付的某个环节中出了问题,请对照微信支付错误码进行错误排除。
验证了支付顺利完成后,需要对sign签名字段进行安全性验证,其验证规则与前期工作中的验证规则一致。如果验证通过,那么可以根据out_trade_no字段修改订单状态为已支付了。否则给微信服务器返回验证错误信息(跳转到下面的第3步)。
(2) 修改订单状态
验证通过后,那么根据out_trade_no字段将订单状态修改为已支付状态,如果修改过程一切顺利,则可以给微信服务器返回处理成功信息,否则如果订单修改过程中出现了问题(例如订单已经不存在了),则给微信服务器返回订单处理失败信息。
(3)给微信服务器回复处理完成信息
在参数验证和订单处理过程中,无论成功与否,都需要给微信服务器返回一个处理完成信息,其返回格式如图:
如果回调页面处理的过程中出现了错误,那么请针对错误进行问题排查。如果没有错误,成功处理回调请求,则该订单支付顺利完成。(Tips:回调页面必须是外网能访问的才能被微信服务器调用到,否则在调试阶段请模拟数据进行调用)
至此,服务器接口的后期工作完成。并且整个微信支付开发过程中,服务器的工作也完成了。
2.2.2 APP代码开发
APP在整个微信支付中主要负责中期的工作,该工作内容包含两个方面:
1、调用服务器接口获得预支付id等信息
当用户点击付款按钮时,APP调用服务器的接口,将用户id和商品id作为参数传递给服务器,获取到包含sign签名字段在内的返回数据(即服务器前期工作中最后一步的返回数据),用以发起微信支付请求。
2、利用微信API调用微信客户端进行微信支付
获取到预支付id等信息后,就可以调用微信支付SDK中的API,发起支付请求,弹出微信支付确认页面,要求用户输入密码进行支付。
至此,APP端就完成了支付的中期工作。
其实,APP端也有一个支付的后期工作。因为在前面微信支付流程图中也有提到了,微信支付的结果通知,除了给服务器端,也会给APP端,所以APP端的这个后期工作是监听微信支付结果通知。APP端监听支付结果的方式是在项目工程的包名下,新建一个包文件夹,其必须命名为“wxapi”,在该包名下新建一个Activity,其命名也规定为“WXPayEntryActivity”,该Activity要实现IWXAPIEventHandler接口,另外别忘了在清单文件中声明这个Activity。微信会在支付结果通知中回调该Activity的onResp方法,告知支付结果,Activity则在该方法中分别对三种结果进行响应处理(三种结果是:“resp.errCode == 0” 表明支付成功 ,“resp.errCode == -1”表明支付发生错误,“resp.errCode == -2” 表明用户取消了支付)。
Tips:如果支付结果发生了错误,resp.errCode == -1,那么可能的原因是:
1、keystore签名错误
在微信支付开放平台创建APP时配置的应用签名信息不对,请用正式发版的签名keystore修正应用签名信息;或者没有打包签名apk进行测试,用正式发版的签名keystore签名apk后再测。
2、sign字段签名错误
服务器的返回数据中,sign签名过程发生了错误。sign签名过程中,参与签名的参数字段命名一定要和微信规定的命名一致,签名方法要按照官方文档里面的说明进行。
3、未注册appid、项目设置appid不正确、注册的appid与后台设置的不匹配、该appid没有申请移动支付功能
先检查代码,看看在发起支付请求前有没有调用API将appid注册到微信。如果代码有注册了appid,那么联系服务器开发和账号申请相关人员,对appid的值进行排查比对,对appid与商户mch_id是否一一对应进行确认,对该应用是否申请微信支付功能进行确认。
4、用户微信客户端的登录被挤下线
可能用户当前的微信客户端登录状态已经被其他设备挤下线了,但是用户并没有在该设备重新登录过微信,这时需要提示用户重新登录微信解决(曾经开发过程中遇到的这个问题,当时用模拟器和手机同时进行测试,一开始百试百灵,每次都能成功调用支付界面,但是模拟器和手机交叉进行测试有时却不行了,后来在问题重现的过程中发现了这个规律,原来是当前设备微信登录状态被挤下线导致的)。
5、没有进行微信客户端是否安装以及版本是否支持微信支付检测
在发起支付请求前,就应该调用微信支付API对是否安装微信以及安装的微信版本是否支持微信支付进行判断,如果没安装则提示安装,如果微信版本不支持支付则提示用户升级微信(Tips:一些会玩的Android手机玩家,常常通过绿色守护等软件将微信客户端绿色化,导致微信实际是安装在手机上的,但是无法通过微信API获得其是否支持支付的结果,所以这里在提示上面可以修改为“请先启动或者升级你的微信客户端来完成微信支付”)。
6、其他异常等。
to be continue
至此,APP端的微信支付完成了所有的工作。
三、结束语
到这里,经过分工合作,你应该已经成功接入了微信支付。在文章中只涉及了微信支付的付款流程,关于其他订单查询、退款等流程可以参照微信支付开放文档进行接入(也许你也发现了,在前面完全没有提及AppSecret,这是因为在付款的过程中并不会用到这个东东)。
好了,这篇文章到此结束,如果有不当之处或遗漏部分,欢迎留言指正,谢谢。