最近在研究ios支付功能,首先第一个拿银联开刀,来学习一下。
支付流程图:
1.sdk下载:https://open.unionpay.com/ajweb/help/file
在inc和libs里面分别有UPPaymentControl.h和libPaymentControl.a两个文件,这就是我们需要的,直接添加到项目中,别的不用看了。
2.配置:
a.添加需要的框架和静态库CFNetwork.framework、SystemConfiguration.framework 、libz。
b.在工程info.plist设置中添加一个URL Types回调协议(在UPPayDemo工程中使用“UPPayDemo”作为协议),用于在支付完成后返回商户客户端。(知识扩展:http://blog.csdn.net/qq_29892943/article/details/65444498)
c.http请求设置
在Xcode7.0之后的版本中进行http请求时,需要在工程对应的plist文件中添加NSAppTransportSecurity Dictionary 并同时设置里面NSAllowsArbitraryLoads 属性值为 YES
d.添加协议白名单
在Xcode7.0之后的版本中进行开发,需要在工程对应的plist文件中,添加LSApplicationQueriesSchemes Array并加入uppaysdk、uppaywallet、uppayx1、uppayx2、uppayx3五个item,具体设置可参考以下截图:
e.加-ObjC宏 (不理解的看这遍博客:http://blog.csdn.net/qq_29892943/article/details/64439624)
选择工程targets——》build settings ->Linking->other linker flags
f.调用插件
在需要调用支付控件接口的代码文件内引用头文件UPPaymentControl.h。
(注意:如果工程的compile source as 选项的值不是Objective–C++,则引用此头文件的文件类型都要改为.mm)
商户App从商户服务器获取tn(注:先通过调用后台接口获取订单信息)
* @param tn 订单信息
* @param schemeStr 调用支付的app注册在info.plist中的scheme
* @param mode 支付环境”00”代表接入生产环境(正式版本需要);”01”代表接入开发测试环境
* @param viewController 启动支付控件的viewController
* @return 返回成功失败
if (tn != nil && tn.length > 0)
{
NSLog(@"tn=%@",tn);
[[UPPaymentControl defaultControl] startPay:tn fromScheme:@"UPPayDemo" mode:kMode_Development viewController:self];
}
7.返回结果接口调用
支付控件结果处理函数handlePaymentResult: completeBlock:需要在工程AppDelegate文件的application: openURL: sourceApplication: annotation: 方法中进行调用。
支付控件结果处理函数handlePaymentResult: completeBlock:包含两个参数,参数1url为支付结果串,由handlePaymentResult: completeBlock:方法解析url内容;参数2completionBlock为商户APP定义的结果处理方法,包含两个传入参数code和data,其中code表示支付结果,取值为suceess,fail,cancel分别表示支付成功、支付失败和支付取消,data表示结果签名数据,商户使用银联公钥验证结果真实性。
completeBlock中的NSDictionary *data结构如下:
sign —— 签名后做Base64的数据
data —— 用于签名的原始数据,结构如下:
pay_result —— 支付结果success,fail,cancel
tn —— 订单号
Data转换为String后的示例如下:
“{“sign”:”ZnZY4nqFGu/ugcXNIhniJh6UDVriWANlHtIDRzV9w120E6tUgpL9Z7jIFzWrSV73hmrkk8BZMXMc/9b8u3Ex1ugnZn0OZtWfMZk2I979dxp2MmOB+1N+Zxf8iHr7KNhf9xb+VZdEydn3Wc/xX/B4jncg0AwDJO/0pezhSZqdhSivTEoxq7KQTq2KaHJmNotPzBatWI5Ta7Ka2l/fKUv8zr6DGu3/5UaPqHhnUq1IwgxEWOYxGWQgtyTMo/tDIRx0OlXOm4iOEcnA9DWGT5hXTT3nONkRFuOSyqS5Rzc26gQE6boD+wkdUZTy55ns8cDCdaPajMrnuEByZCs70yvSgA==”,”data”:”pay_result=success&tn=201512151321481233778”}”
对于新增的签名信息需注意以下几点:
1. 前台返回的支付结果中包含银联签名,要在商户后台对签名进行校验后才能展示结果。
2. 前台签名使用的密钥和算法与后台结果中的签名一致。
3. 如果商户APP在客户端内进行签名验证,要自行实现签名密钥更新的机制,否则更换密钥后会导致验签失败。(不推荐)
4. 商户订单是否成功支付应该以商户后台收到全渠道返回的支付结果为准,此处支付控件返回的结果仅作为参考。
调用支付接口后,结果处理方法示例代码(AppDelegate.h):
//当用户通过其它应用启动本应用时,会回调这个方法,url参数是其它应用调用openURL:方法时传过来的。
- (BOOL) application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
[[UPPaymentControl defaultControl] handlePaymentResult:url completeBlock:^(NSString *code, NSDictionary *data) {
//结果code为成功时,先校验签名,校验成功后做后续处理
if([code isEqualToString:@"success"]) {
//如果想对结果数据验签,可使用下面这段代码,但建议不验签,直接去商户后台查询交易结果
if(data != nil){
//数据从NSDictionary转换为NSString
NSData *signData = [NSJSONSerialization dataWithJSONObject:data
options:0
error:nil];
NSString *sign = [[NSString alloc] initWithData:signData encoding:NSUTF8StringEncoding];
//此处的verify建议送去商户后台做验签,如要放在手机端验,则代码必须支持更新证书
if([self verify:sign]) {
//验签成功,展示支付成功
NSLog(@"验签成功,展示支付成功");
}
else {
//验签失败,交易结果数据被篡改,商户app后台查询交易结果
}
}
//结果code为成功时,去商户后台查询一下确保交易是成功的再展示成功
}
else if([code isEqualToString:@"fail"]) {
//交易失败
}
else if([code isEqualToString:@"cancel"]) {
//交易取消
}
}];
return YES;
}
// NOTE: 9.0以后使用新API接口
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString*, id> *)options
{
[[UPPaymentControl defaultControl] handlePaymentResult:url completeBlock:^(NSString *code, NSDictionary *data) {
if([code isEqualToString:@"success"]) {
//如果想对结果数据验签,可使用下面这段代码,但建议不验签,直接去商户后台查询交易结果
if(data != nil){
//数据从NSDictionary转换为NSString
NSData *signData = [NSJSONSerialization dataWithJSONObject:data
options:0
error:nil];
NSString *sign = [[NSString alloc] initWithData:signData encoding:NSUTF8StringEncoding];
//此处的verify建议送去商户后台做验签,如要放在手机端验,则代码必须支持更新证书
if([self verify:sign]) {
//验签成功
}
else {
//验签失败
}
}
//结果code为成功时,去商户后台查询一下确保交易是成功的再展示成功
}
else if([code isEqualToString:@"fail"]) {
//交易失败
}
else if([code isEqualToString:@"cancel"]) {
//交易取消
}
}];
return YES;
}
最后附上代码demo:
//
// ViewController.m
// 支付demo
//
// Created by huasu on 17/3/17.
// Copyright © 2017年 JY. All rights reserved.
// 运行时机制
#import "ViewController.h"
#import "UPPaymentControl.h"
#import "AFNetworking.h"
#define kMode_Development @"01"
#define kURL_TN_Normal @"http://101.231.204.84:8091/sim/getacptn"
#define kURL_TN_Configure @"http://101.231.204.84:8091/sim/app.jsp?user=123456789"
@interface ViewController ()
{
NSMutableData* _responseData;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIButton *btn=[[UIButton alloc]init];
btn.backgroundColor=[UIColor redColor];
[btn setTitle:@"点我支付" forState:UIControlStateNormal];
[btn addTarget:self action:@selector(btnclick) forControlEvents:UIControlEventTouchUpInside];
btn.frame=CGRectMake(10, 100, 100, 50);
[btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[self.view addSubview:btn];
}
-(void)btnclick
{
NSURL *url=[NSURL URLWithString:kURL_TN_Normal];
NSURLRequest * urlRequest=[NSURLRequest requestWithURL:url];
NSURLConnection* urlConn = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
[urlConn start];
// AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
manager.requestSerializer=[AFJSONRequestSerializer serializer];
[manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
//
// NSString *strurl=kURL_TN_Normal;
//
//
// [manager GET:strurl parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {
//
// }
// success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
//
// NSLog(@"返回tn成功");
// NSLog(@"JSON: %@", responseObject);
//
if (tn != nil && tn.length > 0)
{
NSLog(@"tn=%@",tn);
[[UPPaymentControl defaultControl] startPay:tn fromScheme:@"UPPayDemo" mode:self.tnMode viewController:self];
}
//
//
// }
//
// failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
//
// NSLog(@"请求失败");
// NSLog(@"Error: %@", error);
//
// }];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse*)response
{
_responseData = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[_responseData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSString* tn = [[NSMutableString alloc] initWithData:_responseData encoding:NSUTF8StringEncoding];
if (tn != nil && tn.length > 0)
{
NSLog(@"tn=%@",tn);
[[UPPaymentControl defaultControl] startPay:tn fromScheme:@"UPPayDemo" mode:kMode_Development viewController:self];
}
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
@end
测试卡号信息:
借记卡:6226090000000048
手机号:18100000000
密码:111101
短信验证码:123456
(短信验证码记得点下获取验证码之后再输入)
贷记卡:6226388000000095;
手机号:18100000000;
cvn2:248;
有效期:1219;
短信验证码:123456
(短信验证码记得点下获取验证码之后再输入)