IOS客户端接入微信支付
实际上,从代码的角度,调起支付app就是把一些关键的参数通过一定方式打包成为一个订单,然后发送到支付平台的服务器。所以,只要搞清楚了参数设置,搞清楚了每个支付平台的sdk里面一些关键api的使用,基本上就可以很简单的支持支付。
今天记录一下客户端里面,如何支持微信支付。首先。我们要仔细阅读一下微信sdk的开发文档,了解一下整个支付的大概流程。
然后根据提示,把相应的sdk下载下来,所谓的sdk,也就是一个链接库和两个头文件,很简单。
下载完毕,需要把sdk导入到工程里面,并且配置一下工程。因为开发者文档已经有详细描述,这里就不再复述。
从文档看到,调起微信支付其实最核心的是一下这么一段
<code class="hljs" vbscript="">payreq *request = [[[payreq alloc] init] autorelease]; request.partnerid = @10000100; request.prepayid= @1101000000140415649af9fc314aa427; request.package = @sign=wxpay; request.noncestr= @a462b76e7436e98e0ed6e13c64b4fd1c; request.timestamp= @1397527777; request.sign= @582282d72dd2b03ad892830965f428cb16e7a256; [wxapi sendreq:request];</code>
这里的范例是一段hardcode,真正使用的时候,参数都需要自行传入。
为了搞清楚如何使用api,我们可以下载sample代码。不过,这个sample代码应该是微信的实习生写的,而且应该是一个对于c++比较熟悉,对于objectc比较陌生的实习生。。。代码风格可以看出很多东西哈。。所以这个sample读起来总觉得有点奇怪。当然,写出这个demo也是需要不错的水平,因为这个sample不仅仅是一些api的调用,还包括了一些算法的实现,md5之类的。
看懂了sample之后,一般可以自己重构一下,成为自己app里面的一个manager类。
我是在2015 5 23下载的微信sampel代码,里面包括有:
apixml.h
apixml.m
wxutil.h
wxutil.m
payrequesthandler.h
payrequesthandler.m
如果比较看重命名规范的oc程序猿,就会觉得这个payrequesthandler类非常别扭,不符合camel命名规则,而且handler这个词更偏向于c++风格。我就以这个类为原型,重构了一下,并改装成一个传参的方法,供自己的app调用。app里面卖商品,一般就是商品名字,价格两个关键参数。所以这个重构的方法也只是提供这两个参数的接口。
apixml.h && apixml.m && wxutil.h && wxutil.m不变
<code class="hljs" objectivec="">// // wechatpaymanager.h // // created by huangcharlie on 5/24/15. // // #import <foundation foundation.h=""> #import wxutil.h #import apixml.h #import wxapi.h // 账号帐户资料 // 更改商户把相关参数后可测试 #define app_id @wx@@@@@@@@@@@@@@@@ //appid #define app_secret @ //appsecret,看起来好像没用 //商户号,填写商户对应参数 #define mch_id @@@@@@@@@@@ //商户api密钥,填写相应参数 #define partner_id @12345678901234567890123456789012 //支付结果回调页面 #define notify_url @http://wxpay.weixin.qq.com/pub_v2/pay/notify.v2.php //获取服务器端支付数据地址(商户自定义)(在小吉这里,签名算法直接放在app端,故不需要自定义) #define sp_url @http://wxpay.weixin.qq.com/pub_v2/app/app_pay.php @interface wechatpaymanager : nsobject { } //预支付网关url地址 @property (nonatomic,strong) nsstring* payurl; //debug信息 @property (nonatomic,strong) nsmutablestring *debuginfo; @property (nonatomic,assign) nsinteger lasterrcode;//返回的错误码 //商户关键信息 @property (nonatomic,strong) nsstring *appid,*mchid,*spkey; //初始化函数 -(id)initwithappid:(nsstring*)appid mchid:(nsstring*)mchid spkey:(nsstring*)key; //获取当前的debug信息 -(nsstring *) getdebuginfo; //获取预支付订单信息(核心是一个prepayid) - (nsmutabledictionary*)getprepaywithordername:(nsstring*)name price:(nsstring*)price device:(nsstring*)device; @end </foundation></code> <code class="hljs" objectivec="">// // wechatpaymanager.m // // created by huangcharlie on 5/24/15. // // #import wechatpaymanager.h @implementation wechatpaymanager //初始化函数 -(id)initwithappid:(nsstring*)appid mchid:(nsstring*)mchid spkey:(nsstring*)key { self = [super init]; if(self) { //初始化私有参数,主要是一些和商户有关的参数 self.payurl = @https://api.mch.weixin.qq.com/pay/unifiedorder; if (self.debuginfo == nil){ self.debuginfo = [nsmutablestring string]; } [self.debuginfo setstring:@]; self.appid = appid;//微信分配给商户的appid self.mchid = mchid;// self.spkey = key;//商户的密钥 } return self; } //获取debug信息 -(nsstring*) getdebuginfo { nsstring *res = [nsstring stringwithstring:self.debuginfo]; [self.debuginfo setstring:@]; return res; } //创建package签名 -(nsstring*) createmd5sign:(nsmutabledictionary*)dict { nsmutablestring *contentstring =[nsmutablestring string]; nsarray *keys = [dict allkeys]; //按字母顺序排序 nsarray *sortedarray = [keys sortedarrayusingcomparator:^nscomparisonresult(id obj1, id obj2) { return [obj1 compare:obj2 options:nsnumericsearch]; }]; //拼接字符串 for (nsstring *categoryid in sortedarray) { if ( ![[dict objectforkey:categoryid] isequaltostring:@] && ![categoryid isequaltostring:@sign] && ![categoryid isequaltostring:@key] ) { [contentstring appendformat:@%@=%@&, categoryid, [dict objectforkey:categoryid]]; } } //添加key字段 [contentstring appendformat:@key=%@, self.spkey]; //得到md5 sign签名 nsstring *md5sign =[wxutil md5:contentstring]; //输出debug info [self.debuginfo appendformat:@md5签名字符串: %@ ,contentstring]; return md5sign; } //获取package带参数的签名包 -(nsstring *)genpackage:(nsmutabledictionary*)packageparams { nsstring *sign; nsmutablestring *reqpars=[nsmutablestring string]; //生成签名 sign = [self createmd5sign:packageparams]; //生成xml的package nsarray *keys = [packageparams allkeys]; [reqpars appendstring:@<xml> ]; for (nsstring *categoryid in keys) { [reqpars appendformat:@<%@>%@<!--%@--> , categoryid, [packageparams objectforkey:categoryid],categoryid]; } [reqpars appendformat:@<sign>%@</sign> </xml>, sign]; return [nsstring stringwithstring:reqpars]; } //提交预支付 -(nsstring *)sendprepay:(nsmutabledictionary *)prepayparams { nsstring *prepayid = nil; //获取提交支付 nsstring *send = [self genpackage:prepayparams]; //输出debug info [self.debuginfo appendformat:@api链接:%@ , self.payurl]; [self.debuginfo appendformat:@发送的xml:%@ , send]; //发送请求post xml数据 nsdata *res = [wxutil httpsend:self.payurl method:@post data:send]; //输出debug info [self.debuginfo appendformat:@服务器返回: %@ ,[[nsstring alloc] initwithdata:res encoding:nsutf8stringencoding]]; xmlhelper *xml = [[xmlhelper alloc] autorelease]; //开始解析 [xml startparse:res]; nsmutabledictionary *resparams = [xml getdict]; //判断返回 nsstring *return_code = [resparams objectforkey:@return_code]; nsstring *result_code = [resparams objectforkey:@result_code]; if ( [return_code isequaltostring:@success] ) { //生成返回数据的签名 nsstring *sign = [self createmd5sign:resparams ]; nsstring *send_sign =[resparams objectforkey:@sign] ; //验证签名正确性 if( [sign isequaltostring:send_sign]){ if( [result_code isequaltostring:@success]) { //验证业务处理状态 prepayid = [resparams objectforkey:@prepay_id]; return_code = 0; [self.debuginfo appendformat:@获取预支付交易标示成功! ]; } }else{ self.lasterrcode = 1; [self.debuginfo appendformat:@gen_sign=%@ _sign=%@ ,sign,send_sign]; [self.debuginfo appendformat:@服务器返回签名验证错误!!! ]; } }else{ self.lasterrcode = 2; [self.debuginfo appendformat:@接口返回错误!!! ]; } return prepayid; } - (nsmutabledictionary*)getprepaywithordername:(nsstring*)name price:(nsstring*)price device:(nsstring*)device { //订单标题,展示给用户 nsstring* ordername = name; //订单金额,单位(分) nsstring* orderprice = price;//以分为单位的整数 //支付设备号或门店号 nsstring* orderdevice = device; //支付类型,固定为app nsstring* ordertype = @app; //发器支付的机器ip,暂时没有发现其作用 nsstring* orderip = @196.168.1.1; //随机数串 srand( (unsigned)time(0) ); nsstring *noncestr = [nsstring stringwithformat:@%d, rand()]; nsstring *orderno = [nsstring stringwithformat:@%ld,time(0)]; //================================ //预付单参数订单设置 //================================ nsmutabledictionary *packageparams = [nsmutabledictionary dictionary]; [packageparams setobject: self.appid forkey:@appid]; //开放平台appid [packageparams setobject: self.mchid forkey:@mch_id]; //商户号 [packageparams setobject: orderdevice forkey:@device_info]; //支付设备号或门店号 [packageparams setobject: noncestr forkey:@nonce_str]; //随机串 [packageparams setobject: ordertype forkey:@trade_type]; //支付类型,固定为app [packageparams setobject: ordername forkey:@body]; //订单描述,展示给用户 [packageparams setobject: notify_url forkey:@notify_url]; //支付结果异步通知 [packageparams setobject: orderno forkey:@out_trade_no];//商户订单号 [packageparams setobject: orderip forkey:@spbill_create_ip];//发器支付的机器ip [packageparams setobject: orderprice forkey:@total_fee]; //订单金额,单位为分 //获取prepayid(预支付交易会话标识) nsstring *prepayid; prepayid = [self sendprepay:packageparams]; if(prepayid == nil) { [self.debuginfo appendformat:@获取prepayid失败! ]; return nil; } //获取到prepayid后进行第二次签名 nsstring *package, *time_stamp, *nonce_str; //设置支付参数 time_t now; time(&now); time_stamp = [nsstring stringwithformat:@%ld, now]; nonce_str = [wxutil md5:time_stamp]; //重新按提交格式组包,微信客户端暂只支持package=sign=wxpay格式,须考虑升级后支持携带package具体参数的情况 //package = [nsstring stringwithformat:@sign=%@,package]; package = @sign=wxpay; //第二次签名参数列表 nsmutabledictionary *signparams = [nsmutabledictionary dictionary]; [signparams setobject: self.appid forkey:@appid]; [signparams setobject: self.mchid forkey:@partnerid]; [signparams setobject: nonce_str forkey:@noncestr]; [signparams setobject: package forkey:@package]; [signparams setobject: time_stamp forkey:@timestamp]; [signparams setobject: prepayid forkey:@prepayid]; //生成签名 nsstring *sign = [self createmd5sign:signparams]; //添加签名 [signparams setobject: sign forkey:@sign]; [self.debuginfo appendformat:@第二步签名成功,sign=%@ ,sign]; //返回参数列表 return signparams; } @end</code>
然后,在需要调用微信支付的controller里面,新建一个方法。在合适的地方调用。这个方法里面利用wechatpaymanager这个类进行了初始化和参数封装,然后把上述的核心代码(payreq那一段)
<code class="hljs" objectivec="">- (void)wxpaywithordername:(nsstring*)name price:(nsstring*)price { //创建支付签名对象 && 初始化支付签名对象 wechatpaymanager* wxpaymanager = [[[wechatpaymanager alloc]initwithappid:app_id mchid:mch_id spkey:partner_id] autorelease]; //获取到实际调起微信支付的参数后,在app端调起支付 //生成预支付订单,实际上就是把关键参数进行第一次加密。 nsstring* device = [[usermanager defaultmanager]userid]; nsmutabledictionary *dict = [wxpaymanager getprepaywithordername:name price:price device:device]; if(dict == nil){ //错误提示 nsstring *debug = [wxpaymanager getdebuginfo]; return; } nsmutablestring *stamp = [dict objectforkey:@timestamp]; //调起微信支付 payreq* req = [[[payreq alloc] init]autorelease]; req.openid = [dict objectforkey:@appid]; req.partnerid = [dict objectforkey:@partnerid]; req.prepayid = [dict objectforkey:@prepayid]; req.noncestr = [dict objectforkey:@noncestr]; req.timestamp = stamp.intvalue; req.package = [dict objectforkey:@package]; req.sign = [dict objectforkey:@sign]; // bool flag = [wxapi sendreq:req]; bool flag = [wxapi safesendreq:req]; }</code>
再者,支付完成了需要调用一个delegate,这个delegate方便个性化显示支付结果。一般直接把这两个delegate放在appdelegate就好了。因为有一些其他内容也是需要在appdelegate里面实现,省的分开找不到。
<code class="hljs" objectivec="">-(void) onresp:(baseresp*)resp { //启动微信支付的response nsstring *strmsg = [nsstring stringwithformat:@errcode:%d, resp.errcode]; if([resp iskindofclass:[payresp class]]){ //支付返回结果,实际支付结果需要去微信服务器端查询 switch (resp.errcode) { case 0: strmsg = @支付结果:成功!; break; case -1: strmsg = @支付结果:失败!; break; case -2: strmsg = @用户已经退出支付!; break; default: strmsg = [nsstring stringwithformat:@支付结果:失败!retcode = %d, retstr = %@, resp.errcode,resp.errstr]; break; } } }</code>
注意事项:
1)如果app里面已经使用了sharesdk,就有一些地方要注意。不要再重复导入微信的sdk,因为sharesdk里面的extend已经包括了微信的sdk。
2)微信本身是鼓励客户app把签名算法放到服务器上面,这样信息就不容易被破解。但是如果客户app本身没有服务器端,或者认为不需要放到服务器端,也可以直接把签名(加密)的部分直接放在app端。sample代码的注释有点乱,多次提到服务器云云,但是其实可以不这么做。
3)微信的price单位是分。注意下即可。
4)暂时想不到,以后想到了再记录。。
以上是本文给大家叙述的ios客户端接入微信支付的全部内容,希望大家喜欢。