接到新需求接入支付宝SDK后先到官方网站上面读接入文档支付宝官方文档。若手机安装支付宝则调起支付宝,没有安装就跳到网页支付。
详细步骤这里有公司后台妹子画的时序图如下:1.将从支付宝官方下载的SDK拖入到工程中,AlipaySDK.bundle,AlipaySDK.framework,在Build Phases选项卡的Link Binary With Libraries中,增加以下依赖。
image
image
2.从从后台请求接口,得到加密后的字符串,调起支付宝。此时需要将处理回调结果的方式保存起来,存在delegate中,在App被杀死情况下,通过在delegate中的方法获取支付结果。
-(void)processOrderWithPaymentResult:(NSURL*)resultUrl standbyCallback:(CompletionBlock)completionBlock;
- 组装请求信息放在服务端完成,客户端要从服务端拿到请求得到的加密后的字符串信息后,调起支付宝SDK。
- 最新版的SDK是智能判断用户手机上是否安装支付宝钱包APP,如果安装了就会调起支付宝APP进行支付,如果没有安装就跳到网页支付。
- 用户在支付宝钱包之后支付完成后,需要回到调起的APP,需要在info.plist文件里面设置自己的url scheme已让支付宝钱包识别。
- 在openUrl方法里面的解析支付宝结果的函数只是在APP被杀的情况下获取支付结果,正常情况的支付结果,不管是支付宝钱包支付还是网页支付的结果都会在调起支付宝的SDK里面获取。
- 在成功调起支付宝钱包之后的的同步返回结果中含有code码如9000,这些错误码,不会作为客户端判断支付结果的依据,最终的依据还要根据解析结果里面的流水号去调后端接口进行查询,看付款是否已经到账而作为结果依据。
- 沙箱环境只支持Android环境并不支持iOS,要想模拟iOS环境可以将从后台请求到的串放到官方demo里面,看能否调起SDK,可以将支付金额写为0.01元(我们后台妹子测试环境这么写的)。
如下:
NSString *orderString = @"alipay_sdk=alipay-sdk-java-dynamicVersionNo&app_id=2017070707675461&biz_content=%7B%22body%22%3A%22%E5%B0%8F%E8%8A%B1%E5%95%86%E5%9F%8E%22%2C%22subject%22%3A%22%E5%B0%8F%E8%8A%B1%E5%95%86%E5%9F%8E%E8%AE%A2%E5%8D%95%22%2C%22out_trade_no%22%3A%2220170821000000005494%22%2C%22timeout_express%22%3A%2230m%22%2C%22total_amount%22%3A%220.01%22%2C%22product_code%22%3A%22QUICK_MSECURITY_PAY%22%2C%22goods_type%22%3A%221%22%2C%22passback_params%22%3A%22orderBizCode%253D20170821000000005494%22%2C%22promo_params%22%3Anull%2C%22extend_params%22%3Anull%2C%22enable_pay_channels%22%3A%22balance%2CcreditCardExpress%2CdebitCardExpress%22%2C%22disable_pay_channels%22%3Anull%2C%22store_id%22%3Anull%7D&charset=utf-8&format=json&method=alipay.trade.app.pay&sign=WmNgLwX5Kyr19zT7CYMZGVDdmZ73nA3w3dOnD2K1GUDRvQZRR8dKzdCqQoZm5ZrdG4kfAzJs9Lcm3fURRKWzKkpLwhMZpFEyhenYg5K5Ainq3021IOvJbmEuWizQoLv4WC6CIyVxz2LnK04iq%2BK5P%2BbC0Nsl12JioJXHY0aGkbtxmCjrILRmL1cqz7BlCO%2Bp1TOq%2BD66H0inoQKJ25r59TMtH9IPakt6Fdsa%2FTZGT8%2BgEX1NoeaLEdzUe7UrxPTqiYEljuxuplcD4jQ1pO5qnUKxYUTPsFZp85tFTGodJNMV38dW6OesJhEWDVMkyK%2Bu2TSwIJu%2B7R27KLR0QBl7JQ%3D%3D&sign_type=RSA2×tamp=2017-08-21+11%3A53%3A28&version=1.0";
[[AlipaySDK defaultService] payOrder:orderString fromScheme:appScheme callback:^(NSDictionary *resultDic) {
NSLog(@"reslut = %@",resultDic);
}];
调起支付宝代码如下:
AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
//此处的回调用于APP被杀情况下,APPDelegate里面openurl方法获取支付结果之后的回调
delegate.AlipayResultCallback = handler;
NSDictionary *dict = @{};
//商城大订单号
NSString *orderBigId = kCheckNil(jsDict[@"orderBigId"]);
//商城支付流水号
NSString *orderBizCode = kCheckNil(jsDict[@"orderBizCode"]);
//支付渠道
NSString *payWay = kCheckNil(jsDict[@"payWay"]);
//到后台去请求调起支付宝的加密过后的串
[[ShopManager sharedInstance] alipayOrderBigId:orderBigId inSerialNo:orderBizCode payWay:payWay
onComplete:^(BOOL isSuccessful, id result, NSString *error) {
if (isSuccessful) {
NSDictionary *alipayDict = (NSDictionary *)result;
if (alipayDict) {
if (alipayDict[@"wrappedThreePartyReqBody"])
{
[[AlipaySDK defaultService] payOrder:alipayDict[@"wrappedThreePartyReqBody"] fromScheme:@"xxxxxxxx" callback:^(NSDictionary *resultDic){
NSLog(@"resultDic = %@",resultDic);
handler(YES,resultDic);
}];
}
}
}
else
{
handler(NO,dict);
}
}];
在AppDelegate方法里面openUrl里面获取APP被杀情况下的支付结果。
// 其他如支付等SDK的回调
__weak typeof(self) _weakSelf = self;
if ([url.host isEqualToString:@"safepay"]) {
[[AlipaySDK defaultService] processOrderWithPaymentResult:url standbyCallback:^(NSDictionary *resultDic) {
XHLog(@"appdegate的方法里面:result = %@",resultDic);
_weakSelf.AlipayResultCallback(YES, resultDic);
}];
此时有两个方法里面都要去写:
// 支持iOS9以下系统
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation;
// 支持所有iOS系统
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options;
至此客户端的代码就已经写完了。
遇到的问题:支付宝钱包支付成功之后跳到支付宝网页登录再次进行支付。
现象在iOS10的系统上面我的一切正常,在iOS10以下我在成功调起支付宝钱包付款之后,回到我的APP又会进入网页支付。这就尴尬了,相当于提示用户付款两次。
经过百度之后,说是导入了ShareSDK的问题,但是我的APP没有导入ShareSDK,用的是友盟,于是我就把友盟删掉了,但是还是有这个现象。
刚好碰上周末,支付宝技术客服不上班(链接在这儿输入签约的支付宝账号或PartnerID就可以进行在线咨询)。纠结了两天之后,周一支付宝技术客服回答了我。
于是我开始全局搜索我的项目里面的openUrl函数。找到了UIApplication的一个类别(也有叫分类)。
+(void)load
{
/**
- (void)sendAction:(SEL)action to:(nullable id)target forEvent:(nullable UIEvent *)event;
**/
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originalSelector = @selector(openURL:);
SEL swizzledSelector = @selector(swiz_openURL:);
[XHSwizUtil swizzlingInClass:[self class] originalSelector:originalSelector swizzledSelector:swizzledSelector];
SEL originalSelector2 = @selector(openURL:options:completionHandler:);
SEL swizzledSelector2 = @selector(swiz_openURL:options:completionHandler:);
[XHSwizUtil swizzlingInClass:[self class] originalSelector:originalSelector2 swizzledSelector:swizzledSelector2];
SEL originalSelector3 = @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:);
SEL swizzledSelector3 = @selector(swiz_application:didRegisterForRemoteNotificationsWithDeviceToken:);
[XHSwizUtil swizzlingInClass:[self class] originalSelector:originalSelector3 swizzledSelector:swizzledSelector3];
});
}
#pragma mark - openurl
- (void)swiz_openURL:(NSURL *)url
{
[self trackUrl:url];
[self swiz_openURL:url];
}
由上面可以看到openurl函数确实被替换掉了,通过读苹果的官方文档,对我的bug现象进一步了解了。openurl这个函数是有返回值的,如果打开了别的APP就会返回YES,没打开就返回NO,而这儿通过runtime替换方法之后,直接没有返回值了,那就默认是返回NO了,那么支付宝SDK内部就会认为没有打开支付宝钱包,所以又会跳到网页里面去再次进行支付,而iOS10则不会再走这个方法,而是走的另外一个openURL:options:completeHander:方法,这个方法没有返回值。不会造成影响。
所以将上面的方法改为如下就好了。
#pragma mark - openurl
- (BOOL)swiz_openURL:(NSURL *)url
{
[self trackUrl:url];
return [self swiz_openURL:url];
}
此时有一种特殊情况:iOS系统9.0以后,左上角多了一个返回键。在app里调起支付,跳转到支付宝或者微信的时候,左上角有一个返回键,点击这个返回键,支付宝和微信是不给app回调的,因此用户返回app的时候,app无法判断支付结果。解决办法详情请见文档。
上面大致意思是说:iOS9.0以后,系统左上角多了一个自带的返回按钮。此时,支付宝和微信客户端都没有给回调到App,需要另外做处理。
image
image
image
image
image
image
1.点击返回的时候,支付宝和微信都会走- (void)applicationWillEnterForeground:(UIApplication *)application方法,此时告知调起App的方法此次支付失败,但是仅仅这样做事不够的,因为app压后台,再次打开的情况很多,比如分享返回也走这个接口,我怎么会知道是不是支付调起的返回呢,那么我就在发起支付的时候,做了一个标记,这里我用了系统单例NSUserDefaults,这样我在返回app的时候,就知道是不是支付返回的了。//此时做个标记:标记是支付调起的支付宝或微信客户端
[[NSUserDefaults standardUserDefaults] setObject:@"isActived" forKey:@"alipay"];
AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
delegate.AlipayResultCallback = handler;
NSDictionary *dict = @{};
//商城大订单号
NSString *orderBigId = kCheckNil(jsDict[@"orderBigId"]);
//商城支付流水号
NSString *orderBizCode = kCheckNil(jsDict[@"orderBizCode"]);
//支付渠道
NSString *payWay = kCheckNil(jsDict[@"payWay"]);
// orderBigId = 20170927000000005201;
// orderBizCode = 20170927000000000200;
// payWay = alipay;
[[ShopManager sharedInstance] alipayOrderBigId:orderBigId inSerialNo:orderBizCode payWay:payWay
onComplete:^(BOOL isSuccessful, id result, NSString *error) {
if (isSuccessful)
{
NSDictionary *alipayDict = (NSDictionary *)result;
if (alipayDict)
{
//此时做个标记:标记是支付调起的支付宝或微信客户端
[[NSUserDefaults standardUserDefaults] setObject:@"isActived" forKey:@"alipay"];
if (alipayDict[@"wrappedThreePartyReqBody"] && [payWay isEqualToString:@"alipay"])
{
[[AlipaySDK defaultService] payOrder:alipayDict[@"wrappedThreePartyReqBody"] fromScheme:@"xhscmall" callback:^(NSDictionary *resultDic){
XHLog(@"resultDic = %@",resultDic);
handler(YES,resultDic);
}];
}
else if (alipayDict[@"wrappedThreePartyReqBody"] && [payWay isEqualToString:@"wechatpay"])
{
//回调字典给H5,成功回调statusCode为9000,失败回调空字符串。
[[PayServer sharedPayServer] wxPay:alipayDict[@"wrappedThreePartyReqBody"] withcomplete:^(PayType type, NSDictionary * _Nonnull message) {
if (type == PaySuccess) {
handler(YES,message);
}else if(type == PayCancle){
handler(NO,message);
}else if (type == PayFail){
handler(NO,message);
}
}];
}
}
}
else
{
handler(NO,dict);
}
}];
2.此时若用户点击取消或者完成的时候,app返回不仅会走WillEnterForeground方法,还会走- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options方法,openUrl的方法是后进入的。此时会有两次支付结果返回。为了解决以上问题,用了GCD,在进入WillEnterForeground方法里的时候,让里面的方法等0.5秒执行,如果是有回调的返回,就利用bool值,变为ture,这是下面判断这个bool是ture,WillEnterForeground方法里的判断就不进,如果是没有回调的返回,这个bool值是不会改变的,0.5秒后继续执行WillEnterForeground方法里的判断。
- (void)applicationWillEnterForeground:(UIApplication *)application {
//iOS9.0后点击左上角,返回会走此方法,将是点击左上角返回按钮返回APP标记出来
_isHavedAlipayResultBlock = NO;
NSString *isActived = [[NSUserDefaults standardUserDefaults] objectForKey:@"alipay"];
__weak typeof(self) _weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//如果回调标记位为YES,表明有回调,则不会走下面的回调而会走有回调的结果
if (!_weakSelf.isHavedAlipayResultBlock && isActived && [isActived isEqualToString:@"isActived"]) {
_weakSelf.AlipayResultCallback(NO, @{@"resultStatus":@"",@"errorMsg":@"",@"result":@{}});
}
});
}
// 支持所有iOS系统
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
return [self handleApplicationOpenURL:url];
}
//注:以上为建议使用的系统openURL回调,且新浪平台仅支持以上回调。还有以下两种回调方式,如果开发者选取以下回调,也请补充相应的函数调用。
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options
{
return [self handleApplicationOpenURL:url];
}
-(BOOL)application:(UIApplication *)app handleOpenURL:(nonnull NSURL *)url
{
return [self handleApplicationOpenURL:url];
}
//2.支持目前所有iOS系统
- (BOOL)handleApplicationOpenURL:(NSURL *)url{
//针对iOS9.0以上系统,标识此时有支付宝和微信支付的回调,区别于如果是点击系统左上角返回按钮返回APP还是点击取消和完成返回APP。
self.isHavedAlipayResultBlock = YES;
//微信支付
if([url.scheme isEqualToString:kShareWechatAppkey]){
return [[PayServer sharedPayServer]handleUrl:url];
}
BOOL result = [ShareAgent handleOpenURL:url];
if (!result) {
// 支付宝SDK的回调
__weak typeof(self) _weakSelf = self;
if ([url.host isEqualToString:@"safepay"]) {
[[AlipaySDK defaultService] processOrderWithPaymentResult:url standbyCallback:^(NSDictionary *resultDic) {
NSLog(@"appdegate的方法里面:result = %@",resultDic);
_weakSelf.AlipayResultCallback(YES, resultDic);
}];
}
return YES;
}
return result;
}