iOS集成微信支付

刚集成完微信支付,总结总结

1. 准备

微信平台分为微信公众平台微信开放平台,公众平台是运营微信公众号的管理系统,开放平台主要针对app、网站开发,提供登录、分享、支付等功能。

注册开放平台之后,新建应用,填写应用信息(Android、iOS等信息),创建之后需要等待审核(这个审核很快的,几个小时就通过了)。

然后是为该应用申请支付功能,要注意个人是无法申请的,具体可参考微信支付申请条件和资格。这里牵涉到一系列公司资质的审核和费用支付,需要几个工作日的时间

完成之后可以获取到appid(微信开放平台为应用生成的唯一识别码)、商户id、商户secretKey。对于app端来说只用到appid,商户id最好通过接口从server获取,商户secretKey是用来签名的,一般只有server能用到。

2. 支付流程

先上一个开放平台给出的流程图:

支付流程.png

这个图很实用很详细很清晰,但一开始看可能会觉得复杂。其实对开发者来说,比较关心的流程是:

  1. app向server发起支付请求
  2. server收到请求后向微信后台调用统一下单API,获得预付单信息
  3. server生成带签名的客户端支付信息并返回给app
  4. 用户确认支付,app调起微信客户端进行支付
  5. app获得支付结果后向server查询最终结果并显示

流程了解之后,了解下需要定义的接口和前后端的具体工作:

新接口:
  • app向server发起请求,获得签名后的app支付信息
  • app支付之后向server查询支付结果(微信回调的结果不可信,必须以server的结果为准)
app需要做的:
  • 项目接入微信支付sdk
  • 向server请求支付信息
  • 用支付信息调起微信客户端,然后支付
  • 收到微信回调之后向server查询支付结果
  • 根据支付结果展示页面
server需要做的:
  • 收到app端支付请求后调用统一下单API向微信后台获取预支付信息
  • 将app端需要的支付信息签名之后返回给app
  • 接收微信后台回调信息(支付结果),以供app查询

3. iOS开发

开发文档:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_1

  1. 下载并运行demo
    其实把demo看明白了,直接运用到自己的app里,也不是不可以的
  2. 设置项目
    在Xcode中,projectName-->Info-->URL Types 添加appid。设置之后才能实现应用间跳转


    urltypes.png

导入sdk

sdk.png

如果使用了pod,直接在Podfile中添加pod 'WechatOpenSDK' 然后执行 pod update即可。如果没有使用pod,在添加了sdk文件包之后,需要在pojectName-->General-->Linked Frameworks and Libraries 中添加相应内容

libs.png
  1. 代码
  • AppDelegate.m
    didFinishLaunchingWithOptions方法中
    [WXApi registerApp:@"wx000999888777"];//注册appid
    openUrl、handleOpenURL方法
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
    return  [WXApi handleOpenURL:url delegate:[PaymentManager sharedManager]];
}

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
    return [WXApi handleOpenURL:url delegate:[PaymentManager sharedManager]];
}
// NOTE: 9.0以后使用新API接口
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString*, id> *)options
{
    return [WXApi handleOpenURL:url delegate:[PaymentManager sharedManager]];
}

如果之前项目中使用了友盟等第三方框架,直接并排写就可以:

- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url{
    [WXApi handleOpenURL:url delegate:[PaymentAction sharedPayment]];
    [UMSocialSnsService handleOpenURL:url];
    return YES;
}

另外,这个地方的delegate也可以直接设置成nil,表示用当前AppDelegate作为微信支付的代理,这样一来支付回调写在AppDelegate.m中即可。
但不推荐这种做法,我是设置了PaymentManager作为代理,这是项目中专门管理支付的一个类,包括之前的支付宝支付。这也是微信demo中的做法。(支付宝是用block获取回调的,个人感觉代码更紧凑,不容易乱;微信是用的代理,刚开始看晕乎乎的)

  • PaymentManager
    首先是遵守协议WXApiDelegate
    然后在PayVC中通过接口获取支付信息
    然后调起微信客户端:
- (void)weiXinPayWithDic:(NSDictionary *)wechatPayDic {
    PayReq *req = [[PayReq alloc] init];
    req.openID = [wechatPayDic objectForKey:@"appId"];
    req.partnerId = [wechatPayDic objectForKey:@"partnerId"];
    req.prepayId = [wechatPayDic objectForKey:@"prepayId"];
    req.package = [wechatPayDic objectForKey:@"packages"];
    req.nonceStr = [wechatPayDic objectForKey:@"nonceStr"];
    req.timeStamp = [[wechatPayDic objectForKey:@"timesTamp"] intValue];
    req.sign = [wechatPayDic objectForKey:@"sign"];
    [WXApi sendReq:req];
}

这里的数据wechatPayDic一定是server经过二次签名的

回调

// 微信支付返回结果回调
- (void)onResp:(BaseResp *)resp {
    if ([resp isKindOfClass:[PayResp class]]) {
        
        PayResp *response = (PayResp *)resp;
        if (_delegate && [_delegate respondsToSelector:@selector(managerDidRecvPaymentResponse:)]) {
            [_delegate managerDidRecvPaymentResponse:response];
        }
    }
}

当然前提是在PaymentManager.h中已经定义了代理:

@protocol WXApiManagerDelegate <NSObject>

@required
- (void)managerDidRecvPaymentResponse:(PayResp *)response;
@end

@interface PaymentManager : NSObject <WXApiDelegate>

@property (nonatomic, assign) id<WXApiManagerDelegate> delegate;

这个代理是用来处理回调结果,展示页面的,所以设置成PayVC控制器

处理回调结果
PayVC.m
遵守协议WXApiManagerDelegate
在viewDidLoad中设置[PaymentManager sharedManager].delegate = self;

- (void)managerDidRecvPaymentResponse:(PayResp *)response {
        switch (response.errCode) {
            case WXSuccess:
                [self checkWechatPayResult];
                break;
            case WXErrCodeUserCancel:
                [[HintManager shareManager] showHint:@"中途取消"];
                break;
            default:{
                [[HintManager shareManager]showHint:@"支付失败"];
            }
                
                break;
        }
}

然后在checkWechatPayResult向server查询支付结果,刷新页面
哦了~~ 泼佛客特

4. 出现的问题

当然了,并不泼佛客特

  • 系统版本大于等于iOS9的,调起微信客户端之后,可以直接点击状态栏左侧按钮返回,这时是不走回调方法的。
    这样在支付成功之后,不走回调方法,就无法知道支付状态,当前页面无法给出提示。
    解决方案是,在AppDelegate.m的applicationWillEnterForeground方法中,调用查询支付结果接口然后刷新当然页面。需要设置bool变量作为标志,否则每次应用进入前台都去查询,就不符合业务要求了。
  • 进入微信支付页面之后,不做操作,切换到自己应用中,退出当前支付页面,然后再进入微信客户端点击支付或者取消,此时自己的应用会崩溃闪退
    原因是退出页面后页面已经出栈被销毁,但wx回调时还是去调用其中的代理方法,就会出现野指针。
    解决方法是,在页面的viewWillDisappear方法中加入[PaymentManager sharedManager].delegate = nil;

5. 需要注意的点

  • 一定设置好scheme,否则应用无法跳转
  • 在调起微信支付使用的PayReq类,一定要用WXApi中自带的类,不要自己创建新类。我之前自己创建的类,定义了同样的变量,同样继承了BaseReq,然并卵,sendReq方法一直返回false
  • 调起微信支付的数据,一定是server经过二次签名的,不要把调用统一下单API获取到的数据直接返给app
  • 虽然类中的变量命名是驼峰式,但签名时的key值全部是小写的,签名时不要忘了后面加上商户key。

6. 签名

可能某些server端由于这样那样的原因不愿意做二次签名,app可以自己做。(不过因为签名需要用到secretKey,让app端做签名是有风险的)
下面是签名代码:

#import <CommonCrypto/CommonDigest.h>

// wechatDic中的数据是调用统一下单API之后返回的数据
- (void)wxpay:(NSDictionary *)wechatDic {
    time_t now;
    time(&now);
    NSString *timestamp = [NSString stringWithFormat:@"%ld",now];
    NSString *noncestr = [self md5:timestamp];
    NSDictionary *dict = @{
                           @"appid":[wechatDic objectForKey:@"appId"],
                           @"noncestr":noncestr,
                           @"package":@"Sign=WXPay",
                           @"partnerid":[wechatDic objectForKey:@"partnerId"],
                           @"prepayid":[wechatDic objectForKey:@"prepayId"],
                           @"timestamp":timestamp
                           };
    NSMutableString *contentString = [NSMutableString string];
    NSArray *keys = [dict allKeys];
    // 按照ASCII 码排序
    NSArray *sortedArray = [keys sortedArrayUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
        return [obj1 compare:obj2 options:NSNumericSearch];
    }];
    // 拼接字符串
    for (NSString *categoryId in sortedArray) {
        if (![[dict objectForKey:categoryId] isEqualToString:@""]&&![[dict objectForKey:categoryId] isEqualToString:@"key"]&&![[dict objectForKey:categoryId] isEqualToString:@"sign"]) {
            [contentString appendFormat:@"%@=%@&",categoryId,dict[categoryId]];
        }
    }
    // 添加商户key字段
    NSString *secretkey = @"1K2222ILTKCH33CQ4444SI5ZNMTM66VS";
    [contentString appendFormat:@"key=%@",secretkey];
    // 加密
    NSString *md5Sign = [self md5:contentString];
    // 支付数据
    PayReq *req = [[PayReq alloc] init];
    req.openID = [wechatDic objectForKey:@"appId"];
    req.partnerId = [wechatDic objectForKey:@"partnerId"];
    req.prepayId = [wechatDic objectForKey:@"prepayId"];
    req.package = @"Sign=WXPay";
    req.nonceStr = noncestr;
    req.timeStamp = [timestamp intValue];
    req.sign = md5Sign;
    [WXApi sendReq:req];
}

- (NSString *)md5:(NSString *)str {
    const char *cStr = [str UTF8String];
    unsigned char digest[CC_MD5_DIGEST_LENGTH];
    CC_MD5(cStr,(unsigned int)strlen(cStr),digest);
    NSMutableString *output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
    for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i ++) {
        [output appendFormat:@"%02X",digest[i]];
    }
    return output;
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • iOS集成微信支付-Swift版 微信支付在微信红包的推动发展势头越来越猛,甚至有超过支付宝的趋势,那么在App集...
    iyakexi阅读 8,003评论 3 12
  • 使用场景:商户APP调用微信提供的SDK调用微信支付模块,商户APP会跳转到微信中完成支付,支付完后跳回到商户AP...
    艳晓阅读 779评论 0 1
  • 前言 最近做了一个项目, 又需要集成微信支付, 以前一直没有记录下来, 这次在集成的过程中, 碰到了很多坑, 所以...
    wenjieli阅读 413评论 0 0
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,568评论 25 707
  • 动物的感情远比人类来得炽烈和长久,它们的感情纯粹细腻且不图回报。哦,也许也不那么纯粹不图回报,因为大骨头棒儿和小鱼...
    穗心的ivy阅读 247评论 0 0