首先sdk
银联开放平台: https://open.unionpay.com/ajweb/product/detail?id=3
添加依赖库 3 个
libz
CFNetwork
SystemConfiguration
拖入需要文件
UPPaymentControl.h
libPayMentControl.a
RSA.h
RSA.m
修改配置
1 URL Types - URL Schemes
2 info plist 添加
LSApplicationQueriesSchemes <NSArray>
value - uppaysdk uppaywallet uppayx1 uppayx2 uppayx3
3 other link 添加路径
-force_load $(PROJECT_DIR)/upppay/libPaymentControl.a
4 将使用UPPay 的 VC,修改为 .mm
基本上就 差不多
Appdelegate 复制代码
#import "UPPaymentControl.h"
#import "iToast.h"
#import "RSA.h"
#import <CommonCrypto/CommonDigest.h>
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
[[NSNotificationCenter defaultCenter] postNotificationName:@"updateTitleForUPPay" object:nil];
}
- (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"]) {
//数据从NSDictionary转换为NSString
NSDictionary *data;
NSData *signData = [NSJSONSerialization dataWithJSONObject:data options:0 error:nil];
NSString *sign = [[NSString alloc]initWithData:signData encoding:NSUTF8StringEncoding];
//判断签名数据是否存在
if(data ==nil){
//如果没有签名数据,建议商户app后台查询交易结果
return;
}
//验签证书同后台验签证书
//此处的verify,商户需送去商户后台做验签
if([self verify:sign]) {
//支付成功且验签成功,展示支付成功提示
}
else {
//验签失败,交易结果数据被篡改,商户app后台查询交易结果
}
}
else if([code isEqualToString:@"fail"]) {
//交易失败
}
else if([code isEqualToString:@"cancel"]) {
//交易取消
}
}];
return YES;
}
- (NSString *) readPublicKey:(NSString *) keyName
{
if (keyName ==nil || [keyName isEqualToString:@""])return nil;
NSMutableArray *filenameChunks = [[keyName componentsSeparatedByString:@"."]mutableCopy];
NSString *extension = filenameChunks[[filenameChunks count] -1];
[filenameChunks removeLastObject];// remove the extension
NSString *filename = [filenameChunks componentsJoinedByString:@"."];// reconstruct the filename with no extension
NSString *keyPath = [[NSBundle mainBundle]pathForResource:filename ofType:extension];
NSString *keyStr = [NSString stringWithContentsOfFile:keyPath encoding:NSUTF8StringEncoding error:nil];
return keyStr;
}
-(BOOL) verify:(NSString *) resultStr {
//验签证书同后台验签证书
//此处的verify,商户需送去商户后台做验签
return NO;
}
- (NSString*)sha1:(NSString *)string
{
unsigned char digest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1_CTX context;
NSString *description;
CC_SHA1_Init(&context);
memset(digest,0,sizeof(digest));
description = @"";
if (string == nil) {
return nil;
}
// Convert the given 'NSString *' to 'const char *'.
const char *str = [string cStringUsingEncoding:NSUTF8StringEncoding];
// Check if the conversion has succeeded.
if (str == NULL) {
return nil;
}
// Get the length of the C-string.
int len = (int)strlen(str);
if (len == 0) {
return nil;
}
if (str == NULL) {
return nil;
}
CC_SHA1_Update(&context, str, len);
CC_SHA1_Final(digest, &context);
description = [NSString stringWithFormat:
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
digest[ 0], digest[ 1], digest[ 2], digest[ 3],
digest[ 4], digest[ 5], digest[ 6], digest[ 7],
digest[ 8], digest[ 9], digest[10], digest[11],
digest[12], digest[13], digest[14], digest[15],
digest[16], digest[17], digest[18], digest[19]];
return description;
}
/*
- (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"]) {
NSLog(@"开始交易");
//判断签名数据是否存在
if(data == nil){
//如果没有签名数据,建议商户app后台查询交易结果
NSLog(@"缺少签名");
return;
}
//数据从NSDictionary转换为NSString
NSData *signData = [NSJSONSerialization dataWithJSONObject:data
options:0
error:nil];
NSString *sign = [[NSString alloc] initWithData:signData encoding:NSUTF8StringEncoding];
//验签证书同后台验签证书
//此处的verify,商户需送去商户后台做验签
if([self verifyLocal:sign]) {
//支付成功且验签成功,展示支付成功提示
NSLog(@"支付成功");
}
else {
//验签失败,交易结果数据被篡改,商户app后台查询交易结果
NSLog(@"支付失败");
}
}
else if([code isEqualToString:@"fail"]) {
//交易失败
NSLog(@"交易失败");
}
else if([code isEqualToString:@"cancel"]) {
//交易取消
NSLog(@"取消交易");
}
[[iToast makeText:code] show];
}];
return YES;
}
- (NSString *) readPublicKey:(NSString *) keyName{
if (keyName == nil || [keyName isEqualToString:@""]) return nil;
NSMutableArray *filenameChunks = [[keyName componentsSeparatedByString:@"."] mutableCopy];
NSString *extension = filenameChunks[[filenameChunks count] - 1];
[filenameChunks removeLastObject]; // remove the extension
NSString *filename = [filenameChunks componentsJoinedByString:@"."]; // reconstruct the filename with no extension
NSString *keyPath = [[NSBundle mainBundle] pathForResource:filename ofType:extension];
NSString *keyStr = [NSString stringWithContentsOfFile:keyPath encoding:NSUTF8StringEncoding error:nil];
return keyStr;
}
// 验证 签名
//-(BOOL) verify:(NSString *) resultStr {
//
// //验签证书同后台验签证书
// //此处的verify,商户需送去商户后台做验签
// return NO;
//}
// 验证 本地签名
-(BOOL) verifyLocal:(NSString *) resultStr {
//从NSString转化为NSDictionary
NSData *resultData = [resultStr dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *data = [NSJSONSerialization JSONObjectWithData:resultData options:0 error:nil];
//获取生成签名的数据
NSString *sign = data[@"sign"];
NSString *signElements = data[@"data"];
//NSString *pay_result = signElements[@"pay_result"];
//NSString *tn = signElements[@"tn"];
//转换服务器签名数据
NSData *nsdataFromBase64String = [[NSData alloc]
initWithBase64EncodedString:sign options:0];
//生成本地签名数据,并生成摘要
// NSString *mySignBlock = [NSString stringWithFormat:@"pay_result=%@tn=%@",pay_result,tn];
NSData *dataOriginal = [[self sha1:signElements] dataUsingEncoding:NSUTF8StringEncoding];
//验证签名
#warning TODO:此处如果是正式环境需要换成public_product.key
NSString *pubkey =[self readPublicKey:@"public_test.key"];
OSStatus result= [RSA encryptData:dataOriginal publicKey:pubkey]; //[RSA verifyData:dataOriginal sig:nsdataFromBase64String publicKey:pubkey];
//签名验证成功,商户app做后续处理
if(result == 0) {
//支付成功且验签成功,展示支付成功提示
return YES;
}
else {
//验签失败,交易结果数据被篡改,商户app后台查询交易结果
return NO;
}
return NO;
}
// 本地签名
- (NSString*)sha1:(NSString *)string {
unsigned char digest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1_CTX context;
NSString *description;
CC_SHA1_Init(&context);
memset(digest, 0, sizeof(digest));
description = @"";
if (string == nil) {
return nil;
}
// Convert the given 'NSString *' to 'const char *'.
const char *str = [string cStringUsingEncoding:NSUTF8StringEncoding];
// Check if the conversion has succeeded.
if (str == NULL) {
return nil;
}
// Get the length of the C-string.
int len = (int)strlen(str);
if (len == 0) {
return nil;
}
if (str == NULL) {
return nil;
}
CC_SHA1_Update(&context, str, len);
CC_SHA1_Final(digest, &context);
description = [NSString stringWithFormat:
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
digest[ 0], digest[ 1], digest[ 2], digest[ 3],
digest[ 4], digest[ 5], digest[ 6], digest[ 7],
digest[ 8], digest[ 9], digest[10], digest[11],
digest[12], digest[13], digest[14], digest[15],
digest[16], digest[17], digest[18], digest[19]];
return description;
}
*/
VC demoPay
#import "ViewController.h"
#import "UPPaymentControl.h"
#include <sys/socket.h> // Per msqr
#include <sys/sysctl.h>
#include <net/if.h>
#include <net/if_dl.h>
#define KBtn_width 200
#define KBtn_height 80
#define KXOffSet (self.view.frame.size.width - KBtn_width) / 2
#define KYOffSet 80
#define kCellHeight_Normal 50
#define kCellHeight_Manual 145
#define kVCTitle @"商户测试"
#define kBtnFirstTitle @"获取订单,开始测试"
#define kWaiting @"正在获取TN,请稍后..."
#define kNote @"提示"
#define kConfirm @"确定"
#define kErrorNet @"网络错误"
#define kResult @"支付结果:%@"
#define kMode_Development @"01"
//在后台开发实现消费(获取tn)请求前,demo里默认由银联的一个商户仿真获取tn(http://202.101.25.178:8080/sim/gettn或http://101.231.204.84:8091/sim/getacptn)之后需要改从商户自己的后台那里获取tn的。
//#define kURL_TN_Normal @"http://101.231.204.84:8091/sim/getacptn"
//#define kURL_TN_Normal @"http://202.101.25.178:8080/sim/gettn"
#define kURL_TN_Normal @"http://www.baidu.com/"
#define kURL_TN_Configure @"http://101.231.114.217:8080/sim/app.jsp?user=admin"
@interface ViewController ()<UITableViewDataSource, UITableViewDelegate,NSURLConnectionDataDelegate> {
NSMutableData* _responseData;
UIAlertView* _alertView;
CGFloat _maxWidth;
CGFloat _maxHeight;
}
@property (nonatomic, strong) NSArray* labels;
@property(nonatomic, copy)NSString *tnMode;
@property (nonnull,strong)UITextField *tnProductText;
@property (nonnull,strong)UITextField *tnPMText;
@property (nonnull,strong)UITextField *tnTestText;
- (void)startNetWithURL:(NSURL *)url;
@end
//@implementation NSURLRequest (NSURLRequestWithIgnoreSSL)
//+ (BOOL)allowsAnyHTTPSCertificateForHost:(NSString *)host
//{
// return YES;
//}
//@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self extendedLayout];
self.labels = [NSArray arrayWithObjects:@"DEVELOPER",@"TEST",@"PM",@"RELEASE",@"WALLET",nil];
UITableView* tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, _maxWidth, _maxHeight)];
[self.view addSubview:tableView];
tableView.delegate = self;
tableView.dataSource = self;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateTitle) name:@"updateTitleForUPPay" object:nil];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:@"updateTitleForUPPay"];
}
- (void)viewDidAppear:(BOOL)animated
{
[self updateTitle];
}
- (void)updateTitle
{
self.title=@"银行联调用商户Demo";
}
- (void)extendedLayout
{
BOOL iOS7 = [UIDevice currentDevice].systemVersion.floatValue >= 7.0;
if (iOS7) {
self.edgesForExtendedLayout = UIRectEdgeNone;
self.automaticallyAdjustsScrollViewInsets = NO;
}
CGFloat offset = iOS7 ? 64 : 44;
_maxWidth = CGRectGetWidth([UIScreen mainScreen].bounds);
_maxHeight = CGRectGetHeight([UIScreen mainScreen].bounds)-offset;
self.navigationController.navigationBar.translucent = NO;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
switch (indexPath.row) {
case 0:
[tableView deselectRowAtIndexPath:indexPath animated:YES];
return;
break;
case 1:
[tableView deselectRowAtIndexPath:indexPath animated:YES];
break;
default:
break;
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 4;
}
- (void) productVerify {
self.tnMode = @"00";
[[UPPaymentControl defaultControl] startPay:self.tnProductText.text fromScheme:@"paydemo" mode:self.tnMode viewController:self];
}
- (void) pmVerify {
self.tnMode = @"01";
[[UPPaymentControl defaultControl] startPay:self.tnPMText.text fromScheme:@"paydemo" mode:self.tnMode viewController:self];
}
- (void) testVerify {
self.tnMode = @"02";
[[UPPaymentControl defaultControl] startPay:self.tnTestText.text fromScheme:@"paydemo" mode:self.tnMode viewController:self];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.detailTextLabel.font = [UIFont systemFontOfSize:10];
switch (indexPath.row) {
case 0:
{
UIButton* btnStartPay = [UIButton buttonWithType:UIButtonTypeRoundedRect];
btnStartPay = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btnStartPay setTitle:@"生产验证" forState:UIControlStateNormal];
[btnStartPay addTarget:self action:@selector(productVerify) forControlEvents:UIControlEventTouchUpInside];
[btnStartPay setFrame:CGRectMake(240, 10, 80, 40)];
[cell.contentView addSubview:btnStartPay];
self.tnProductText = [[UITextField alloc] initWithFrame:CGRectMake(20,10,210,40)];
self.tnProductText.placeholder = @"请在此处输入正式环境tn";
self.tnProductText.keyboardType = UIKeyboardTypeNumbersAndPunctuation;
[cell.contentView addSubview:self.tnProductText];
cell.accessoryType = UITableViewCellAccessoryNone;
}
break;
case 1:
{
UIButton* btnStartPay = [UIButton buttonWithType:UIButtonTypeRoundedRect];
btnStartPay = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btnStartPay setTitle:@"PM验证" forState:UIControlStateNormal];
[btnStartPay addTarget:self action:@selector(pmVerify) forControlEvents:UIControlEventTouchUpInside];
[btnStartPay setFrame:CGRectMake(240, 10, 80, 40)];
[cell.contentView addSubview:btnStartPay];
self.tnPMText = [[UITextField alloc] initWithFrame:CGRectMake(20,10,210,40)];
self.tnPMText.placeholder = @"请在此处输入PM环境tn";
self.tnPMText.keyboardType = UIKeyboardTypeNumbersAndPunctuation;
[cell.contentView addSubview:self.tnPMText];
cell.accessoryType = UITableViewCellAccessoryNone;
}
break;
case 2:
{
UIButton* btnStartPay = [UIButton buttonWithType:UIButtonTypeRoundedRect];
btnStartPay = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btnStartPay setTitle:@"测试验证" forState:UIControlStateNormal];
[btnStartPay addTarget:self action:@selector(testVerify) forControlEvents:UIControlEventTouchUpInside];
[btnStartPay setFrame:CGRectMake(240, 10, 80, 40)];
[cell.contentView addSubview:btnStartPay];
self.tnTestText = [[UITextField alloc] initWithFrame:CGRectMake(20,10,210,40)];
self.tnTestText.placeholder = @"请在此处输入test环境tn";
self.tnTestText.keyboardType = UIKeyboardTypeNumbersAndPunctuation;
[cell.contentView addSubview:self.tnTestText];
cell.accessoryType = UITableViewCellAccessoryNone;
}
break;
case 3:
{
UIButton* btnStartPay = [UIButton buttonWithType:UIButtonTypeRoundedRect];
btnStartPay = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btnStartPay setTitle:@"支付" forState:UIControlStateNormal];
[btnStartPay addTarget:self action:@selector(testPay) forControlEvents:UIControlEventTouchUpInside];
[btnStartPay setFrame:CGRectMake(240, 10, 80, 40)];
[cell.contentView addSubview:btnStartPay];
self.tnTestText = [[UITextField alloc] initWithFrame:CGRectMake(20,10,210,40)];
self.tnTestText.placeholder = @"直接点击右侧按钮进行支付";
self.tnTestText.keyboardType = UIKeyboardTypeNumbersAndPunctuation;
[cell.contentView addSubview:self.tnTestText];
cell.accessoryType = UITableViewCellAccessoryNone;
}
break;
default:
break;
}
return cell;
}
#pragma mark - Alert
- (void)showAlertWait
{
[self hideAlert];
_alertView = [[UIAlertView alloc] initWithTitle:kWaiting message:nil delegate:self cancelButtonTitle:nil otherButtonTitles: nil];
[_alertView show];
UIActivityIndicatorView* aiv = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
aiv.center = CGPointMake(_alertView.frame.size.width / 2.0f - 15, _alertView.frame.size.height / 2.0f + 10 );
[aiv startAnimating];
[_alertView addSubview:aiv];
}
- (void)showAlertMessage:(NSString*)msg
{
[self hideAlert];
_alertView = [[UIAlertView alloc] initWithTitle:kNote message:msg delegate:self cancelButtonTitle:kConfirm otherButtonTitles:nil, nil];
[_alertView show];
}
- (void)hideAlert
{
if (_alertView != nil)
{
[_alertView dismissWithClickedButtonIndex:0 animated:NO];
_alertView = nil;
}
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
_alertView = nil;
}
// 开始 支付
- (void)testPay{
NSLog(@"支付");
self.tnMode = kMode_Development;
[self startNetWithURL:[NSURL URLWithString:kURL_TN_Normal]];
}
#pragma mark UPPayPluginResult - 需要自己 写个 代理 回调 appdelegate 中的 处理。不写 是没有的。
- (void)UPPayPluginResult:(NSString *)result{
NSString* msg = [NSString stringWithFormat:@"支付结果:%@", result];
NSLog(@"%@",msg);
}
// 开始 网络请求
- (void)startNetWithURL:(NSURL *)url
{
NSLog(@"等待请求");
// [_curField resignFirstResponder];
// _curField = nil;
[self showAlertWait];
NSURLRequest * urlRequest=[NSURLRequest requestWithURL:url];
NSURLConnection* urlConn = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
[urlConn start];
}
#pragma mark - connection
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse*)response
{
NSHTTPURLResponse* rsp = (NSHTTPURLResponse*)response;
NSInteger code = [rsp statusCode];
if (code != 200)
{
NSLog(@"网络错误");
[self showAlertMessage:kErrorNet];
[connection cancel];
}
else
{
_responseData = [[NSMutableData alloc] init];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[_responseData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[self hideAlert];
NSString* tn = [[NSMutableString alloc] initWithData:_responseData encoding:NSUTF8StringEncoding];
if (tn != nil && tn.length > 0)
{// NSLog(@"tn=%@",tn); ///UPPayDemo
[[UPPaymentControl defaultControl] startPay:tn fromScheme:@"paydemo" mode:self.tnMode viewController:self];
}
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
NSLog(@"网络错误");
}
@end
RSA.h
@interface RSA : NSObject
// return base64 encoded string
+ (NSString *)encryptString:(NSString *)str publicKey:(NSString *)pubKey;
// return raw data
+ (NSData *)encryptData:(NSData *)data publicKey:(NSString *)pubKey;
// return base64 encoded string
+ (NSString *)encryptString:(NSString *)str privateKey:(NSString *)privKey;
// return raw data
+ (NSData *)encryptData:(NSData *)data privateKey:(NSString *)privKey;
// decrypt base64 encoded string, convert result to string(not base64 encoded)
+ (NSString *)decryptString:(NSString *)str publicKey:(NSString *)pubKey;
+ (NSData *)decryptData:(NSData *)data publicKey:(NSString *)pubKey;
+ (NSString *)decryptString:(NSString *)str privateKey:(NSString *)privKey;
+ (NSData *)decryptData:(NSData *)data privateKey:(NSString *)privKey;
@end
RSA.m
#import "RSA.h"
#import <Security/Security.h>
@implementation RSA
/*
static NSString *base64_encode(NSString *str){
NSData* data = [str dataUsingEncoding:NSUTF8StringEncoding];
if(!data){
return nil;
}
return base64_encode_data(data);
}
*/
static NSString *base64_encode_data(NSData *data){
data = [data base64EncodedDataWithOptions:0];
NSString *ret = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
return ret;
}
static NSData *base64_decode(NSString *str){
NSData *data = [[NSData alloc] initWithBase64EncodedString:str options:NSDataBase64DecodingIgnoreUnknownCharacters];
return data;
}
+ (NSData *)stripPublicKeyHeader:(NSData *)d_key{
// Skip ASN.1 public key header
if (d_key == nil) return(nil);
unsigned long len = [d_key length];
if (!len) return(nil);
unsigned char *c_key = (unsigned char *)[d_key bytes];
unsigned int idx = 0;
if (c_key[idx++] != 0x30) return(nil);
if (c_key[idx] > 0x80) idx += c_key[idx] - 0x80 + 1;
else idx++;
// PKCS #1 rsaEncryption szOID_RSA_RSA
static unsigned char seqiod[] =
{ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
0x01, 0x05, 0x00 };
if (memcmp(&c_key[idx], seqiod, 15)) return(nil);
idx += 15;
if (c_key[idx++] != 0x03) return(nil);
if (c_key[idx] > 0x80) idx += c_key[idx] - 0x80 + 1;
else idx++;
if (c_key[idx++] != '\0') return(nil);
// Now make a new NSData from this buffer
return([NSData dataWithBytes:&c_key[idx] length:len - idx]);
}
//credit: http://hg.mozilla.org/services/fx-home/file/tip/Sources/NetworkAndStorage/CryptoUtils.m#l1036
+ (NSData *)stripPrivateKeyHeader:(NSData *)d_key{
// Skip ASN.1 private key header
if (d_key == nil) return(nil);
unsigned long len = [d_key length];
if (!len) return(nil);
unsigned char *c_key = (unsigned char *)[d_key bytes];
unsigned int idx = 22; //magic byte at offset 22
if (0x04 != c_key[idx++]) return nil;
//calculate length of the key
unsigned int c_len = c_key[idx++];
int det = c_len & 0x80;
if (!det) {
c_len = c_len & 0x7f;
} else {
int byteCount = c_len & 0x7f;
if (byteCount + idx > len) {
//rsa length field longer than buffer
return nil;
}
unsigned int accum = 0;
unsigned char *ptr = &c_key[idx];
idx += byteCount;
while (byteCount) {
accum = (accum << 8) + *ptr;
ptr++;
byteCount--;
}
c_len = accum;
}
// Now make a new NSData from this buffer
return [d_key subdataWithRange:NSMakeRange(idx, c_len)];
}
+ (SecKeyRef)addPublicKey:(NSString *)key{
NSRange spos = [key rangeOfString:@"-----BEGIN PUBLIC KEY-----"];
NSRange epos = [key rangeOfString:@"-----END PUBLIC KEY-----"];
if(spos.location != NSNotFound && epos.location != NSNotFound){
NSUInteger s = spos.location + spos.length;
NSUInteger e = epos.location;
NSRange range = NSMakeRange(s, e-s);
key = [key substringWithRange:range];
}
key = [key stringByReplacingOccurrencesOfString:@"\r" withString:@""];
key = [key stringByReplacingOccurrencesOfString:@"\n" withString:@""];
key = [key stringByReplacingOccurrencesOfString:@"\t" withString:@""];
key = [key stringByReplacingOccurrencesOfString:@" " withString:@""];
// This will be base64 encoded, decode it.
NSData *data = base64_decode(key);
data = [RSA stripPublicKeyHeader:data];
if(!data){
return nil;
}
//a tag to read/write keychain storage
NSString *tag = @"RSAUtil_PubKey";
NSData *d_tag = [NSData dataWithBytes:[tag UTF8String] length:[tag length]];
// Delete any old lingering key with the same tag
NSMutableDictionary *publicKey = [[NSMutableDictionary alloc] init];
[publicKey setObject:(__bridge id) kSecClassKey forKey:(__bridge id)kSecClass];
[publicKey setObject:(__bridge id) kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
[publicKey setObject:d_tag forKey:(__bridge id)kSecAttrApplicationTag];
SecItemDelete((__bridge CFDictionaryRef)publicKey);
// Add persistent version of the key to system keychain
[publicKey setObject:data forKey:(__bridge id)kSecValueData];
[publicKey setObject:(__bridge id) kSecAttrKeyClassPublic forKey:(__bridge id)
kSecAttrKeyClass];
[publicKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)
kSecReturnPersistentRef];
CFTypeRef persistKey = nil;
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)publicKey, &persistKey);
if (persistKey != nil){
CFRelease(persistKey);
}
if ((status != noErr) && (status != errSecDuplicateItem)) {
return nil;
}
[publicKey removeObjectForKey:(__bridge id)kSecValueData];
[publicKey removeObjectForKey:(__bridge id)kSecReturnPersistentRef];
[publicKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnRef];
[publicKey setObject:(__bridge id) kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
// Now fetch the SecKeyRef version of the key
SecKeyRef keyRef = nil;
status = SecItemCopyMatching((__bridge CFDictionaryRef)publicKey, (CFTypeRef *)&keyRef);
if(status != noErr){
return nil;
}
return keyRef;
}
+ (SecKeyRef)addPrivateKey:(NSString *)key{
NSRange spos;
NSRange epos;
spos = [key rangeOfString:@"-----BEGIN RSA PRIVATE KEY-----"];
if(spos.length > 0){
epos = [key rangeOfString:@"-----END RSA PRIVATE KEY-----"];
}else{
spos = [key rangeOfString:@"-----BEGIN PRIVATE KEY-----"];
epos = [key rangeOfString:@"-----END PRIVATE KEY-----"];
}
if(spos.location != NSNotFound && epos.location != NSNotFound){
NSUInteger s = spos.location + spos.length;
NSUInteger e = epos.location;
NSRange range = NSMakeRange(s, e-s);
key = [key substringWithRange:range];
}
key = [key stringByReplacingOccurrencesOfString:@"\r" withString:@""];
key = [key stringByReplacingOccurrencesOfString:@"\n" withString:@""];
key = [key stringByReplacingOccurrencesOfString:@"\t" withString:@""];
key = [key stringByReplacingOccurrencesOfString:@" " withString:@""];
// This will be base64 encoded, decode it.
NSData *data = base64_decode(key);
data = [RSA stripPrivateKeyHeader:data];
if(!data){
return nil;
}
//a tag to read/write keychain storage
NSString *tag = @"RSAUtil_PrivKey";
NSData *d_tag = [NSData dataWithBytes:[tag UTF8String] length:[tag length]];
// Delete any old lingering key with the same tag
NSMutableDictionary *privateKey = [[NSMutableDictionary alloc] init];
[privateKey setObject:(__bridge id) kSecClassKey forKey:(__bridge id)kSecClass];
[privateKey setObject:(__bridge id) kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
[privateKey setObject:d_tag forKey:(__bridge id)kSecAttrApplicationTag];
SecItemDelete((__bridge CFDictionaryRef)privateKey);
// Add persistent version of the key to system keychain
[privateKey setObject:data forKey:(__bridge id)kSecValueData];
[privateKey setObject:(__bridge id) kSecAttrKeyClassPrivate forKey:(__bridge id)
kSecAttrKeyClass];
[privateKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)
kSecReturnPersistentRef];
CFTypeRef persistKey = nil;
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)privateKey, &persistKey);
if (persistKey != nil){
CFRelease(persistKey);
}
if ((status != noErr) && (status != errSecDuplicateItem)) {
return nil;
}
[privateKey removeObjectForKey:(__bridge id)kSecValueData];
[privateKey removeObjectForKey:(__bridge id)kSecReturnPersistentRef];
[privateKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnRef];
[privateKey setObject:(__bridge id) kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
// Now fetch the SecKeyRef version of the key
SecKeyRef keyRef = nil;
status = SecItemCopyMatching((__bridge CFDictionaryRef)privateKey, (CFTypeRef *)&keyRef);
if(status != noErr){
return nil;
}
return keyRef;
}
/* START: Encryption & Decryption with RSA private key */
+ (NSData *)encryptData:(NSData *)data withKeyRef:(SecKeyRef) keyRef isSign:(BOOL)isSign {
const uint8_t *srcbuf = (const uint8_t *)[data bytes];
size_t srclen = (size_t)data.length;
size_t block_size = SecKeyGetBlockSize(keyRef) * sizeof(uint8_t);
void *outbuf = malloc(block_size);
size_t src_block_size = block_size - 11;
NSMutableData *ret = [[NSMutableData alloc] init];
for(int idx=0; idx<srclen; idx+=src_block_size){
//NSLog(@"%d/%d block_size: %d", idx, (int)srclen, (int)block_size);
size_t data_len = srclen - idx;
if(data_len > src_block_size){
data_len = src_block_size;
}
size_t outlen = block_size;
OSStatus status = noErr;
if (isSign) {
status = SecKeyRawSign(keyRef,
kSecPaddingPKCS1,
srcbuf + idx,
data_len,
outbuf,
&outlen
);
} else {
status = SecKeyEncrypt(keyRef,
kSecPaddingPKCS1,
srcbuf + idx,
data_len,
outbuf,
&outlen
);
}
if (status != 0) {
NSLog(@"SecKeyEncrypt fail. Error Code: %d", status);
ret = nil;
break;
}else{
[ret appendBytes:outbuf length:outlen];
}
}
free(outbuf);
CFRelease(keyRef);
return ret;
}
+ (NSString *)encryptString:(NSString *)str privateKey:(NSString *)privKey{
NSData *data = [RSA encryptData:[str dataUsingEncoding:NSUTF8StringEncoding] privateKey:privKey];
NSString *ret = base64_encode_data(data);
return ret;
}
+ (NSData *)encryptData:(NSData *)data privateKey:(NSString *)privKey{
if(!data || !privKey){
return nil;
}
SecKeyRef keyRef = [RSA addPrivateKey:privKey];
if(!keyRef){
return nil;
}
return [RSA encryptData:data withKeyRef:keyRef isSign:YES];
}
+ (NSData *)decryptData:(NSData *)data withKeyRef:(SecKeyRef) keyRef{
const uint8_t *srcbuf = (const uint8_t *)[data bytes];
size_t srclen = (size_t)data.length;
size_t block_size = SecKeyGetBlockSize(keyRef) * sizeof(uint8_t);
UInt8 *outbuf = malloc(block_size);
size_t src_block_size = block_size;
NSMutableData *ret = [[NSMutableData alloc] init];
for(int idx=0; idx<srclen; idx+=src_block_size){
//NSLog(@"%d/%d block_size: %d", idx, (int)srclen, (int)block_size);
size_t data_len = srclen - idx;
if(data_len > src_block_size){
data_len = src_block_size;
}
size_t outlen = block_size;
OSStatus status = noErr;
status = SecKeyDecrypt(keyRef,
kSecPaddingNone,
srcbuf + idx,
data_len,
outbuf,
&outlen
);
if (status != 0) {
NSLog(@"SecKeyEncrypt fail. Error Code: %d", status);
ret = nil;
break;
}else{
//the actual decrypted data is in the middle, locate it!
int idxFirstZero = -1;
int idxNextZero = (int)outlen;
for ( int i = 0; i < outlen; i++ ) {
if ( outbuf[i] == 0 ) {
if ( idxFirstZero < 0 ) {
idxFirstZero = i;
break;
} else {
// idxNextZero = i;
// break;
}
}
}
[ret appendBytes:&outbuf[idxFirstZero+1] length:idxNextZero-idxFirstZero-1];
}
}
free(outbuf);
CFRelease(keyRef);
return ret;
}
+ (NSString *)decryptString:(NSString *)str privateKey:(NSString *)privKey{
NSData *data = [[NSData alloc] initWithBase64EncodedString:str options:NSDataBase64DecodingIgnoreUnknownCharacters];
data = [RSA decryptData:data privateKey:privKey];
NSString *ret = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
return ret;
}
+ (NSData *)decryptData:(NSData *)data privateKey:(NSString *)privKey{
if(!data || !privKey){
return nil;
}
SecKeyRef keyRef = [RSA addPrivateKey:privKey];
if(!keyRef){
return nil;
}
return [RSA decryptData:data withKeyRef:keyRef];
}
/* END: Encryption & Decryption with RSA private key */
/* START: Encryption & Decryption with RSA public key */
+ (NSString *)encryptString:(NSString *)str publicKey:(NSString *)pubKey{
NSData *data = [RSA encryptData:[str dataUsingEncoding:NSUTF8StringEncoding] publicKey:pubKey];
NSString *ret = base64_encode_data(data);
return ret;
}
+ (NSData *)encryptData:(NSData *)data publicKey:(NSString *)pubKey{
if(!data || !pubKey){
return nil;
}
SecKeyRef keyRef = [RSA addPublicKey:pubKey];
if(!keyRef){
return nil;
}
return [RSA encryptData:data withKeyRef:keyRef isSign:NO];
}
+ (NSString *)decryptString:(NSString *)str publicKey:(NSString *)pubKey{
NSData *data = [[NSData alloc] initWithBase64EncodedString:str options:NSDataBase64DecodingIgnoreUnknownCharacters];
data = [RSA decryptData:data publicKey:pubKey];
NSString *ret = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
return ret;
}
+ (NSData *)decryptData:(NSData *)data publicKey:(NSString *)pubKey{
if(!data || !pubKey){
return nil;
}
SecKeyRef keyRef = [RSA addPublicKey:pubKey];
if(!keyRef){
return nil;
}
return [RSA decryptData:data withKeyRef:keyRef];
}
/* END: Encryption & Decryption with RSA public key */
@end
实际 wk web中接入h5
如果用按自己的项目改
https://juejin.cn/post/6908989251690430472
WKWebView
#define ScriptMessageNameCallNative @"CallNativePay"
- (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration {
self = [super initWithFrame:frame configuration:configuration];
if (self) {
JDBMWeakScriptMessageHandler *weakScriptMessageHandler = [[JDBMWeakScriptMessageHandler alloc] initWithScriptMessageHandler:self];
[configuration.userContentController addScriptMessageHandler:weakScriptMessageHandler name:ScriptMessageNameCallNative];
// 设置进度条
_progressBar = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 3)];
_progressBar.backgroundColor = JDUtils.getColorByHex(@"FB4E38");
[self addSubview:_progressBar];
// 设置相关代理
self.navigationDelegate = self;
self.UIDelegate = self;
[self addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew| NSKeyValueObservingOptionOld context:nil];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
JDBMWeakScriptMessageHandler *weakScriptMessageHandler = [[JDBMWeakScriptMessageHandler alloc] initWithScriptMessageHandler:self];
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
// 设置偏好设置
config.preferences = [[WKPreferences alloc] init];
// 默认为0
config.preferences.minimumFontSize = 10;
// 默认认为YES
config.preferences.javaScriptEnabled = YES;
// 在iOS上默认为NO,表示不能自动通过窗口打开
config.preferences.javaScriptCanOpenWindowsAutomatically = NO;
// web内容处理池
config.processPool = [JDWJWebView sharedProcessPool];
// 通过JS与webview内容交互
config.userContentController = [[WKUserContentController alloc] init];
NSString *configUA = [WJRouter openURL:@"xxxxx" arg:nil error:nil completion:nil];
config.applicationNameForUserAgent = [config.applicationNameForUserAgent stringByAppendingString:[NSString stringWithFormat:@";%@", configUA]];
config.allowsInlineMediaPlayback = YES;
// 注入JS对象名称AppModel,当JS通过AppModel来调用时,
// 我们可以在WKScriptMessageHandler代理中接收到
[config.userContentController addScriptMessageHandler:weakScriptMessageHandler name:@"AppShareModel"];
[config.userContentController addScriptMessageHandler:weakScriptMessageHandler name:ScriptMessageNameCallNative];
self = [super initWithFrame:frame configuration:config];
if (self) {
// 设置进度条
_progressBar = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 3)];
_progressBar.backgroundColor = JDUtils.getColorByHex(@"FB4E38");
[self addSubview:_progressBar];
// 设置相关代理
self.navigationDelegate = self;
self.UIDelegate = self;
[self addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew| NSKeyValueObservingOptionOld context:nil];
}
return self;
}
WebViewController (xxx)
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//要特别注意你替换的方法到底是哪个性质的方法
//When swizzling a Instance method, use the following:
//Class class = [self class];
//When swizzling a class method, use the following:
//Class class = object_getClass((id)self);
Class class = [self class];
// /* 交换方法 */
// SEL originalSelector = @selector(xxx_webView:decidePolicyForNavigationAction:decisionHandler:);
// SEL swizzledSelector = @selector(swizz_xxx_webView:decidePolicyForNavigationAction:decisionHandler:);
//
// Method originalMethod = class_getInstanceMethod(class, originalSelector);
// Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
//
// //给一个类增加方法的实现,如果这个类有这个方法的实现,那么添加失败,如果没有这个方法的实现,那么添加成功
// //如果这个类的父类有这个类的方法实现,那么也会添加成功,只有当这个类本身有这个方法的实现的时候,才不会添加成功
// BOOL didAddMethod = class_addMethod(class,
// originalSelector,
// method_getImplementation(swizzledMethod),
// method_getTypeEncoding(swizzledMethod));
//
// //didAddMethod = YES
// if (didAddMethod) {
//
// //有class_addMethod和method_setImplementation两个作用
// class_replaceMethod(class,
// swizzledSelector,
// method_getImplementation(originalMethod),
// method_getTypeEncoding(originalMethod));
// } else {
//
// method_exchangeImplementations(originalMethod, swizzledMethod);
// }
//
// /* 交换方法 */
// originalSelector = @selector(xxx_UserContentController:didReceiveScriptMessage:);
// swizzledSelector = @selector(swizz_xxx_UserContentController:didReceiveScriptMessage:);
//
// originalMethod = class_getInstanceMethod(class, originalSelector);
// swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
//
// didAddMethod = class_addMethod(class,
// originalSelector,
// method_getImplementation(swizzledMethod),
// method_getTypeEncoding(swizzledMethod));
//
// //didAddMethod = YES
// if (didAddMethod) {
//
// //有class_addMethod和method_setImplementation两个作用
// class_replaceMethod(class,
// swizzledSelector,
// method_getImplementation(originalMethod),
// method_getTypeEncoding(originalMethod));
// } else {
//
// method_exchangeImplementations(originalMethod, swizzledMethod);
// }
/* 交换方法 xxx_webView:didFinishNavigation: */
SEL webFinishNavigationSelector = @selector(xxx_webView:didFinishNavigation:);
SEL swizzledFinishNavigationSelector = @selector(swizz_xxx_webView:didFinishNavigation:);
Method originalFinishNavMethod = class_getInstanceMethod(class, webFinishNavigationSelector);
Method swizzledFinishNavMethod = class_getInstanceMethod(class, swizzledFinishNavigationSelector);
//给一个类增加方法的实现,如果这个类有这个方法的实现,那么添加失败,如果没有这个方法的实现,那么添加成功
//如果这个类的父类有这个类的方法实现,那么也会添加成功,只有当这个类本身有这个方法的实现的时候,才不会添加成功
BOOL didAddFinishNavMethod = class_addMethod(class,
webFinishNavigationSelector,
method_getImplementation(swizzledFinishNavMethod),
method_getTypeEncoding(swizzledFinishNavMethod));
//didAddFinishNavMethod = YES
if (didAddFinishNavMethod) {
//有class_addMethod和method_setImplementation两个作用
class_replaceMethod(class,
swizzledFinishNavigationSelector,
method_getImplementation(originalFinishNavMethod),
method_getTypeEncoding(originalFinishNavMethod));
} else {
method_exchangeImplementations(originalFinishNavMethod, swizzledFinishNavMethod);
}/* 交换方法 xxx_webView:didFinishNavigation: end */
});
}
/**
页面加载完成之后调用
@param webView webView
@param navigation navigation
*/
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
- (void)swizz_xxx_webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation{
[self swizz_xxx_webView:webView didFinishNavigation:navigation];
NSString *jsonStr = @{@"payWays":@"JdPay;UnionPay;WechatPay", @"APPVersion":@"1.0.1", @"OS":@"ios"}.yy_modelToJSONString ;
NSString *onReady = [NSString stringWithFormat:@"ListenerNative.onReady('%@')",jsonStr];
[self.webView evaluateJavaScript:onReady completionHandler:^(id _Nullable value, NSError * _Nullable error) {
}];
[self.webView evaluateJavaScript:@"window.CallNative = {Cashier : function (type,data) {window.webkit.messageHandlers.xxxCallNativePay.postMessage({\"type\":type,\"data\":data})}}" completionHandler:^(id _Nullable value, NSError * _Nullable error) {
}];
}
- (void)swizz_xxx_UserContentController:(nonnull WKUserContentController *)userContentController didReceiveScriptMessage:(nonnull WKScriptMessage *)message {
[self swizz_xxx_UserContentController:userContentController didReceiveScriptMessage:message];
NSDictionary *params = message.body;
NSDictionary *dic = [self handleScriptMessage:params];
if ([message.name isEqualToString:ScriptMessageNameCallNative]) {
NSString *schemeStr = @"openapp.xxx";
#if DEBUG
schemeStr = @"openapp.xxx-test";
#endif
NSDictionary *dataTnDic = [self dictionaryWithJsonString:params[@"data"]];
if (JDUtils.validateDictionary(dataTnDic)) {
[[UPPaymentControl defaultControl] startPay:[dataTnDic objectForKey:@"tn"] fromScheme:schemeStr mode:@"00" viewController:self];
}
}
}
application Appdelegate
+ (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
[QQApiInterface handleOpenURL:url delegate:[self shared]];
[WXApi handleOpenURL:url delegate:[self shared]];
if ([url.scheme isEqualToString:@"openapp.jdbmall"] || [url.scheme isEqualToString:@"openapp.jdbmall-test"] ) {
if([url.host isEqualToString:@"mobileChecker"]){
//埋点SDK进入调试模式
[JDMTA setDebuggerWithData:url.query];
}else if ([url.scheme containsString:WX_APP_ID]){
[QQApiInterface handleOpenURL:url delegate:[self shared]];
[WXApi handleOpenURL:url delegate:[self shared]];
} else {
[OpenAppHandle openURL:url.absoluteString arg:nil error:nil completion:nil];
[self handlerOpenAppSchemeURL:url];
}
NSArray *urlCom = [url pathComponents];
if([url.host isEqualToString:@"uppayresult"] || [urlCom.lastObject isEqualToString:@"uppayresult"]){
[[UPPaymentControl defaultControl] handlePaymentResult:url completeBlock:^(NSString *code,NSDictionary *data)
{
NSInteger status = 2;//服务端标识 支付失败
NSString *payMsg = @"";
//结果code为成功时,先校验签名,校验成功后做后续处理
if ([code isEqualToString:@"success"]) {
status = 1;
payMsg = @"支付成功";
}
else if([code isEqualToString:@"fail"]) {//交易失败
status = 2;
payMsg = @"支付失败";
}
else if([code isEqualToString:@"cancel"]) {//交易取消
status = 3;
payMsg = @"支付取消";
}
JDWJWebViewController *vc;
JDBMTabbarController *tabVC = [UIApplication sharedApplication].keyWindow.rootViewController;
JDBMNavigationController *nav = (JDBMNavigationController *)tabVC.selectedViewController;
for (UIViewController *childVC in nav.viewControllers) {
if ([childVC isKindOfClass:[JDWJWebViewController class]]) {
vc = (JDWJWebViewController *)childVC;
}
}
if(vc.webView){
NSString *payStatusJsonStr = @{@"status":[NSNumber numberWithInteger:status], @"msg":payMsg, @"object":(data)?:@""}.yy_modelToJSONString ;//object返回对象
NSString *PaymentStatus = [NSString stringWithFormat:@"ListenerNative.PaymentStatus('%@')",payStatusJsonStr];
[ vc.webView evaluateJavaScript:PaymentStatus completionHandler:^(id _Nullable value, NSError * _Nullable error) {
}];
}//if vc.webView end
}];//UPPaymentControl end
}//url.host uppayresult end
}
return YES;
}