在线APP支付细节:银联、微信、支付宝
仅在技术方面讨论,至于什么签约、开户什么的不涉及。总的来说,支付大部分工作在后台,前端很简单。
支付宝##
槽点1:配置密钥,很长一段时间文档看不懂,什么RSA公钥、私钥、支付宝公钥,好像还有个商户公钥,到底什么关系什么用。后来清楚了,如下:
RSA公钥私钥是自己配置的,可按照RSA(SHA1)密钥配置来配置,然后保存好,将公钥上传到支付宝如下图3所指,由此生成支付宝公钥。商户公钥应该是签约开户时生成的,作用于后台回调支付成功状态。
槽点2:支付成功提醒,文档要求是必须确定后台与支付宝回调成功后才能提示支付成功,我这边是当前端提示返回9000成功后通知到界面发起支付状态请求,后台返回支付成功后才提示成功。总感觉有些不舒服,觉得没必要在确定后台成功与否,以为前端成功后💰钱已经扣掉了。
相关代码:APP支付文档
导入SDK,引入依赖库,详见开发文档。
坑点1、导入SDK后一般会报错
支付宝 openssl/asn1.h' file not found
解决方案:###
找到Xcode的Build Settings,搜索Header Search Paths,双击点开,添加 “$(SRCROOT)/工程名/Classes/SDK/支付宝” ,我把支付宝的SDK文件放在了支付宝这个文件夹下。
坑点2、和 'ZBarSDK'第三方SDK冲突###
ld: 1 duplicate symbol for architecture x86_64 clang: error: linker
command failed with exit code 1 (use -v to see invocation)
解决方案###
我这边是直接把支付宝中的base64.h 和 base64.m两个文件在工程中删除,没有移到垃圾桶。
支付相关代码如下:###
#import <AlipaySDK/AlipaySDK.h>
- (void)zhifubaoPayForGoodsPrice:(NSString *)price orderNO:(NSString *)orderNO notifyURL:(NSString *)notifyURL subject:(NSString *)subject body:(NSString *)body
{
/*
*商户的唯一的parnter和seller。
*签约后,支付宝会为每个商户分配一个唯一的 parnter 和 seller。
*/
/*============================================================================*/
/*=======================需要填写商户app申请的===================================*/
/*============================================================================*/
NSString *partner = KPartner;
NSString *seller = KSeller;
NSString *privateKey = KPrivateKey2;
/*============================================================================*/
/*============================================================================*/
/*============================================================================*/
//partner和seller获取失败,提示
if ([partner length] == 0 ||
[seller length] == 0 ||
[privateKey length] == 0)
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示"
message:@"缺少partner或者seller或者私钥。"
delegate:self
cancelButtonTitle:@"确定"
otherButtonTitles:nil];
[alert show];
return;
}
/*
*生成订单信息及签名
*/
//将商品信息赋予AlixPayOrder的成员变量
Order *order = [[Order alloc] init];
order.partner = partner;
order.sellerID = seller;
order.outTradeNO = orderNO; //订单ID(由商家自行制定)
order.subject = subject; //商品标题
order.body = body; //商品描述
order.totalFee = price; //商品价格
order.notifyURL = notifyURL; //回调URL
order.service = @"mobile.securitypay.pay";
order.paymentType = @"1";
order.inputCharset = @"utf-8";
order.itBPay = @"30m";
order.showURL = @"m.alipay.com";
//应用注册scheme,在AlixPayDemo-Info.plist定义URL types
NSString *appScheme = KappSchemes;
//将商品信息拼接成字符串
NSString *orderSpec = [order description];
NSLog(@"orderSpec = %@",orderSpec);
//获取私钥并将商户信息签名,外部商户可以根据情况存放私钥和签名,只需要遵循RSA签名规范,并将签名字符串base64编码和UrlEncode
id<DataSigner> signer = CreateRSADataSigner(privateKey);
NSString *signedString = [signer signString:orderSpec];
//将签名成功字符串格式化为订单字符串,请严格按照该格式
NSString *orderString = nil;
if (signedString != nil) {
orderString = [NSString stringWithFormat:@"%@&sign=\"%@\"&sign_type=\"%@\"",
orderSpec, signedString, @"RSA"];
DLog(@"orderString:%@",orderString);
// 这一步才是吊起支付,上面那么多事生成签名文件来吊起支付的,由于牵涉到了RSA私钥,所以上面的生成签名字符串应该由后台来做才更安全。
[[AlipaySDK defaultService] payOrder:orderString fromScheme:appScheme callback:^(NSDictionary *resultDic) {
// 如果用户手机没有安装支付宝,会调起支付宝的web支付界面,回调界面走这一步通知用户支付成功与否。
NSLog(@"reslut = %@",resultDic);
NSString *resultStatusStr = [resultDic objectForKey:@"resultStatus"];
NSString* msg = @"";
if ([resultStatusStr isEqualToString:@"6001"]) {
msg=@"您中途取消支付";
}
if ([resultStatusStr isEqualToString:@"9000"]) {
msg=@"支付宝支付成功";
}
if([resultStatusStr isEqualToString:@"4000"]){
msg = @"支付宝异常,请尝试重新购买";
}
[self alertWithTitle:@"提示" msg:msg];
}];
}
}
实际开发中把生成支付签名字符串放在了后台,因为把RSA私钥签名放在APP内不安全。
所以:前端需要做的就是传参,请求接口后获取到orderString,再根据orderString调起支付,appScheme是在APP内如下图,我用的Bundle Identifier
Screen Shot 2016-11-27 at 4.36.51 PM.png
[[AlipaySDK defaultService] payOrder:orderString fromScheme:appScheme callback:^(NSDictionary *resultDic) {
// 如果用户手机没有安装支付宝,会调起支付宝的web支付界面,回调界面走这一步通知用户支付成功与否,如果安装了支付宝,回调代码则在AppDelegate中写。
DLog(@"reslut = %@",resultDic);
NSString *resultStatusStr = [resultDic objectForKey:@"resultStatus"];
NSString* msg = @"";
if ([resultStatusStr isEqualToString:@"6001"]) {
msg=@"您中途取消支付";
}
if ([resultStatusStr isEqualToString:@"9000"]) {
// msg=@"支付宝支付成功";
[[NSNotificationCenter defaultCenter] postNotificationName:@"zhifubaoNotification" object:resultDic];
}
if([resultStatusStr isEqualToString:@"4000"]){
msg = @"支付宝异常,请尝试重新购买";
}
if (![msg isEqualToString:@""]) {
[self alertWithTitle:@"提示" msg:msg];
}
}];
回调支付状态相关代码######
// NOTE: 9.0以前使用新API接口
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
NSURL *url1 = url;
if ([url.host isEqualToString:@"safepay"]) {
//跳转支付宝钱包进行支付,处理支付结果
[[AlipaySDK defaultService] processOrderWithPaymentResult:url standbyCallback:^(NSDictionary *resultDic) {
NSLog(@"result = %@",resultDic);
NSString *resultStatusStr = [resultDic objectForKey:@"resultStatus"];
NSString* msg = @"";
if ([resultStatusStr isEqualToString:@"6001"]) {
msg=@"您中途取消支付";
}
if ([resultStatusStr isEqualToString:@"9000"]) {
// msg=@"支付宝支付成功";
[[NSNotificationCenter defaultCenter] postNotificationName:@"zhifubaoNotification" object:resultDic];
}
if([resultStatusStr isEqualToString:@"4000"]){
msg = @"支付宝异常,请尝试重新购买";
}
if (![msg isEqualToString:@""]) {
[self alertWithTitle:@"提示" msg:msg];
}
}];
return YES;
}else if ([url.host isEqualToString:@"uppayresult"]){
[[UPPaymentControl defaultControl] handlePaymentResult:url completeBlock:^(NSString *code, NSDictionary *data) {
//结果code为成功时,先校验签名,校验成功后做后续处理
if([code isEqualToString:@"success"]) {
//判断签名数据是否存在
if(data == nil){
//如果没有签名数据,建议商户app后台查询交易结果
[self alertWithTitle:@"提示" msg:@"未获取签名数据,请联系候鸟旅居查询交易结果"];
return;
}
//数据从NSDictionary转换为NSString
NSData *signData = [NSJSONSerialization dataWithJSONObject:data
options:0
error:nil];
NSString *sign = [[NSString alloc] initWithData:signData encoding:NSUTF8StringEncoding];
[[NSNotificationCenter defaultCenter]postNotificationName:@"yinlianNotification" object:sign];
// //验签证书同后台验签证书
// //此处的verify,商户需送去商户后台做验签
// if([self verify:sign]) {
// //支付成功且验签成功,展示支付成功提示
//
// NSLog(@"银联验证支付成功");
// [self alertWithTitle:@"提示" msg:@"银联支付成功!"];
//
// }
// else {
// //验签失败,交易结果数据被篡改,商户app后台查询交易结果
// [self alertWithTitle:@"提示" msg:@"验签失败,请联系候鸟旅居查询交易结果"];
//
// }
}
else if([code isEqualToString:@"fail"]) {
//交易失败
[self alertWithTitle:@"提示" msg:@"交易失败!"];
}
else if([code isEqualToString:@"cancel"]) {
//交易取消
[self alertWithTitle:@"提示" msg:@"交易取消!"];
}
}];
return YES;
}else{
if ([WXApi handleOpenURL:url delegate:[WXApiManager sharedManager]]) {
return YES;
}
return NO;//这里是微信支付
}
}
//
// NOTE: 9.0以后使用新API接口
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString*, id> *)options
{
NSURL *url1 = url;
if ([url.host isEqualToString:@"safepay"]) {
//跳转支付宝钱包进行支付,处理支付结果
[[AlipaySDK defaultService] processOrderWithPaymentResult:url standbyCallback:^(NSDictionary *resultDic) {
NSLog(@"result = %@",resultDic);
NSLog(@"result = %@",resultDic);
NSString *resultStatusStr = [resultDic objectForKey:@"resultStatus"];
NSString* msg = @"";
if ([resultStatusStr isEqualToString:@"6001"]) {
msg=@"您中途取消支付";
}
if ([resultStatusStr isEqualToString:@"9000"]) {
// msg=@"支付宝支付成功";
[[NSNotificationCenter defaultCenter] postNotificationName:@"zhifubaoNotification" object:resultDic];
}
if([resultStatusStr isEqualToString:@"4000"]){
msg = @"支付宝异常,请尝试重新购买";
}
if (![msg isEqualToString:@""]) {
[self alertWithTitle:@"提示" msg:msg];
}
}];
return YES;
}else if ([url.host isEqualToString:@"uppayresult"]){
[[UPPaymentControl defaultControl] handlePaymentResult:url completeBlock:^(NSString *code, NSDictionary *data) {
//结果code为成功时,先校验签名,校验成功后做后续处理
if([code isEqualToString:@"success"]) {
//判断签名数据是否存在
if(data == nil){
//如果没有签名数据,建议商户app后台查询交易结果
[self alertWithTitle:@"提示" msg:@"未获取签名数据,请联系候鸟旅居查询交易结果"];
return;
}
//数据从NSDictionary转换为NSString
NSData *signData = [NSJSONSerialization dataWithJSONObject:data
options:0
error:nil];
NSString *sign = [[NSString alloc] initWithData:signData encoding:NSUTF8StringEncoding];
DLog(@"sign:%@",sign);
[[NSNotificationCenter defaultCenter]postNotificationName:@"yinlianNotification" object:sign];
// //验签证书同后台验签证书
// //此处的verify,商户需送去商户后台做验签
// if([self verify:sign]) {
// //支付成功且验签成功,展示支付成功提示
//
// NSLog(@"银联验证支付成功");
// [self alertWithTitle:@"提示" msg:@"银联支付成功!"];
//
// }
// else {
// //验签失败,交易结果数据被篡改,商户app后台查询交易结果
// [self alertWithTitle:@"提示" msg:@"验签失败,请联系候鸟旅居查询交易结果"];
//
// }
}
else if([code isEqualToString:@"fail"]) {
//交易失败
[self alertWithTitle:@"提示" msg:@"交易失败!"];
}
else if([code isEqualToString:@"cancel"]) {
//交易取消
[self alertWithTitle:@"提示" msg:@"交易取消!"];
}
}];
return YES;
}else{
if ([WXApi handleOpenURL:url delegate:[WXApiManager sharedManager]]) {
return YES;//这里是微信支付
}
return NO;
}
}
微信##
导入SDK,顺便把官方demo中的几个类也添加到工程。
Screen Shot 2016-11-27 at 5.13.34 PM.png微信的支付SDK也只有这三个文件,如果项目中导入了友盟的话,微信支付SDK就不需要重复导入了,(友盟瘦身版除外,友盟现在对功能做了分离,所有主要看有没有这个libWeChatSDK.a文件)
Screen Shot 2016-11-27 at 4.57.12 PM.png上代码:调起微信支付需要下面这些参数,安全起见交给后台来做,看看前端多省事。
PayReq *request = [[PayReq alloc] init];
request.partnerId = @"10000100";
request.prepayId= @"1101000000140415649af9fc314aa427";
request.package = @"Sign=WXPay";
request.nonceStr= @"a462b76e7436e98e0ed6e13c64b4fd1c";
request.timeStamp= @"1397527777";
request.sign= @"582282D72DD2B03AD892830965F428CB16E7A256";
[WXApi sendReq:request];
传参请求数据,根据请求的数据调起支付。
- (void)weixinPayForGoodsOrderno:(NSString *)orderno{
//
//
// BOOL a = [WXApi isWXAppInstalled];
// BOOL b = [WXApi isWXAppSupportApi];
if ([WXApi isWXAppInstalled]) {
[KVNProgress showWithStatus:@"正在调起微信..."];
NSString *userid = [[NSUserDefaults standardUserDefaults] objectForKey:@"userid"];
// NSDictionary *dic = @{@"userid":userid,@"orderno":orderno};
// NSDictionary *dic = @{@"orderno":orderno};
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:orderno,@"orderno",userid,@"userid", nil];
[netoworkTool postWithURLString:接口地址 parameters:dic progress:^(double progress) {
} success:^(id _Nullable responseObject) {
[KVNProgress dismiss];
DLog(@"responseObject:%@",responseObject);
if ([[NSString stringWithFormat:@"%@",responseObject[@"status"]] isEqualToString:@"0"]) {
NSDictionary *dict = responseObject;
NSMutableString *stamp = [dict objectForKey:@"timestamp"];
//调起微信支付
PayReq* req = [[PayReq alloc] init];
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"];
[WXApi sendReq:req];
//日志输出
}else{
[self alertWithTitle:@"提示" msg:responseObject[@"msg"]];
}
} failure:^(NSError * _Nonnull error) {
[KVNProgress dismiss];
[self alertWithTitle:@"提示" msg:@"服务器请求出错"];
} fromClassName:NSStringFromClass([self class])];
}else{
[self alertWithTitle:@"提示" msg:@"你还未安装微信!"];
}
}
微信支付的回调有点特殊:用到了引入的那几个类,微信给回调做了封装,所以只需要设置下代理就好,剩下的在WXApiManager中写相关代码
if ([WXApi handleOpenURL:url delegate:[WXApiManager sharedManager]]) {
return YES;//这里是微信支付
}
return NO;
#pragma mark - WXApiDelegate
- (void)onResp:(BaseResp *)resp {
if ([resp isKindOfClass:[SendMessageToWXResp class]]) {
if (_delegate
&& [_delegate respondsToSelector:@selector(managerDidRecvMessageResponse:)]) {
SendMessageToWXResp *messageResp = (SendMessageToWXResp *)resp;
[_delegate managerDidRecvMessageResponse:messageResp];
}
} else if ([resp isKindOfClass:[SendAuthResp class]]) {
if (_delegate
&& [_delegate respondsToSelector:@selector(managerDidRecvAuthResponse:)]) {
SendAuthResp *authResp = (SendAuthResp *)resp;
[_delegate managerDidRecvAuthResponse:authResp];
}
} else if ([resp isKindOfClass:[AddCardToWXCardPackageResp class]]) {
if (_delegate
&& [_delegate respondsToSelector:@selector(managerDidRecvAddCardResponse:)]) {
AddCardToWXCardPackageResp *addCardResp = (AddCardToWXCardPackageResp *)resp;
[_delegate managerDidRecvAddCardResponse:addCardResp];
}
}else if([resp isKindOfClass:[PayResp class]]){
//支付返回结果,实际支付结果需要去微信服务器端查询
NSString *strMsg,*strTitle = [NSString stringWithFormat:@"支付结果"];
switch (resp.errCode) {
case WXSuccess:
strMsg = @"";
// 支付成功的通知。
[[NSNotificationCenter defaultCenter] postNotificationName:@"weixinverifyNotification" object:nil];
NSLog(@"支付成功-PaySuccess,retcode = %d", resp.errCode);
break;
case WXErrCodeUserCancel:
strMsg = @"取消了支付!";
break;
case WXErrCodeSentFail:
strMsg = @"发送失败!";
break;
case WXErrCodeUnsupport:
strMsg = @"微信不支持!";
break;
default:
strMsg = [NSString stringWithFormat:@"支付结果:失败!retcode = %d, retstr = %@", resp.errCode,resp.errStr];
NSLog(@"错误,retcode = %d, retstr = %@", resp.errCode,resp.errStr);
break;
}
if (![strMsg isEqualToString:@""]) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:strTitle message:strMsg delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
[alert show];
}
}
}
银联##
导入SDK
Screen Shot 2016-11-27 at 5.12.47 PM.png银联前端开发特别简单,需要后台生成一个流水号,根据流水号调起银联支付!
[KVNProgress showWithStatus:@"正在调起银联..."];
NSString *appScheme = KappSchemes;
NSString *userid = [[NSUserDefaults standardUserDefaults] objectForKey:@"userid"];
NSDictionary *dict;
dict = [NSDictionary dictionaryWithObjectsAndKeys:self.money,@"money",@"card",@"type",userid,@"userid", nil];
[netoworkTool postWithURLString:请求地址 parameters:dict progress:^(double progress) {
} success:^(id _Nullable responseObject) {
[KVNProgress dismiss];
if ([[NSString stringWithFormat:@"%@",responseObject[@"status"]] isEqualToString:@"0"]) {
NSString *tn = responseObject[@"tn"];
self.orderno = [NSString stringWithFormat:@"%@",responseObject[@"orderno"]];
if (tn != nil && tn.length > 0)
{
[[UPPaymentControl defaultControl] startPay:tn fromScheme:appScheme mode:@"00" viewController:self];
}
}else{
[KVNProgress showErrorWithStatus:responseObject[@"msg"]];
}
} failure:^(NSError * _Nonnull error) {
[KVNProgress dismiss];
[KVNProgress showErrorWithStatus:@"获取流水号失败!"];
} fromClassName:NSStringFromClass([self class])];
}];
回调部分参考支付宝回调。
总结:后台真苦逼!