最近要用微信支付功能,在此總結一下!
須要下面第三方支持
備註:JSONKit框架是基於MRC的,若是工程開發環境是ARC的話,請在編譯時設定 編譯參數 -fno-objc-arc
一、首先到微信開放平臺上,申請app及與T進行簽約、認證
https://open.weixin.qq.com/
獲取到:
/**
* 微信開放平臺申請獲得的 appid, 須要同時添加在info.plist文件中URL schema,用於完成時,回調到app
*/
#define WXAppId @"wxd930ea5d5a258f4f"
#define WXAppSecret @"db426a9829e4b49a0dcac7b4162da6b6"
以上兩個參數用於獲取access_token
access_token是APP的全局惟一票據,APP調用各接口時都需使用access_token。正常狀況下access_token有效期爲7200秒,重複獲取將致使上次獲取的access_token失效。
APP可使用AppID和AppSecret調用本接口來獲取access_token。AppID和AppSecret可在開放平臺後臺得到。注意調用接口時需使用https協議。
接口調用請求說明
https://api.weixin.qq.com/cgi-
bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
返回說明
正常狀況下,微信會返回下述JSON數據包給開發者:
{"access_token":"ACCESS_TOKEN","expires_in":7200}
參數說明:
appkey 、partnerId、partnerKey、paySignKey
appkey:appkey就是Paysignkey,申請支付經過以後由
財付通下發。
partnerId:財付通商戶身份的標識。審覈經過後,在財付通發送的郵件中查看。
partnerKey:財付通商戶權限密鑰Key。審覈經過後,在財付通發送的郵件中查看。
paySignKey:除了支付請求須要用到paySignKey,公衆平臺接口API的權限獲取所需密鑰Key,在使用全部公衆平臺API時,都須要先用它去換取access_token,而後再進行調用。審覈經過後,在微信發送的郵件中查看。
二、代碼實現
@interface SkyAppDelegate : UIResponder <UIApplicationDelegate, WXApiDelegate> //在appDelegate方法中實現WXApiDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// 向微信終端註冊appID
[WXApi registerApp:WXAppId withDescription:@"weixin demo"];
/*! @brief WXApi的成員函數,在微信終端程序中註冊第三方應用。
*
* 須要在每次啓動第三方應用程序時調用。第一次調用後,會在微信的可用應用列表中出現。
* @param appid 微信開發者ID
* @param appdesc 應用附加信息,長度不超過1024字節
* @return 成功返回YES,失敗返回NO。
*/
return YES;
}
//用於完成支付後的程序回調,
- (BOOL) application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
NSLog(@"%@",url);//跳轉到URL schema中配置的地址
return [WXApi handleOpenURL:url delegate:self];
}
//收到一個來自微信的處理結果。調用一次sendReq後會收到onResp。
- (void)onResp:(BaseResp *)resp
{
if ([resp isKindOfClass:[PayResp class]])
{
PayResp *response = (PayResp *)resp;
NSString *strTitle = [NSString stringWithFormat:@"支付結果"];
NSString *strMsg = [NSString stringWithFormat:@"errcode:%d", response.errCode];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:strTitle
message:strMsg
delegate:self
cancelButtonTitle:@"OK"
otherButtonTitles:nil, nil];
[alert show];
switch (response.errCode) {
case WXSuccess: {
NSNotification *notification = [NSNotification notificationWithName:ORDER_PAY_NOTIFICATION object:@"success"];
[[NSNotificationCenter defaultCenter] postNotification:notification];
break;
}
default: {
NSNotification *notification = [NSNotification notificationWithName:ORDER_PAY_NOTIFICATION object:@"fail"];
[[NSNotificationCenter defaultCenter] postNotification:notification];
break;
}
}
}
}
爲了保證發送請求的方便,本身封裝了兩個工具類
一、http
typedef void (^BKHttpCallback)(BOOL isSuccessed, NSDictionary *result);
/**
* GET方法請求數據
*
* @param url 請求的URL
* @param params 請求參數
* @param (BOOL isSuccessed, Result *result))callback 回調方法
*/
+ (void)doGetWithUrl:(NSString *)url path:(NSString *)path params:(NSDictionary *)params callback:(BKHttpCallback) callback;
/**
* 請求WebService數據
*
* @param baseUrl 請求的基礎URL
* @param params 請求參數
* @param (BOOL isSuccessed, Result *result))callback 回調方法
*/
+ (void)doPostWithUrl:(NSString *)url path:(NSString *)path params:(NSDictionary *)params callback:(BKHttpCallback)callback;
/**
* Get方法請求圖片
*
* @param url 圖片URL
* @param (BOOL isSuccessed, Result *result))callback 回調方法
*/
+ (void)getImageWithUrl:(NSString *)url callback:(BKHttpCallback)callback;
.m文件
+ (void)doGetWithUrl:(NSString *)url path:(NSString *)path params:(NSDictionary *)params callback:(BKHttpCallback) callback
{
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:url]];
[httpClient getPath:path
parameters:params
success:^(AFHTTPRequestOperation *operation, id responseObject){
NSString *responseJson = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
if (responseJson)
{
NSDictionary *result = [responseJson objectFromJSONString];
callback(YES, result);
}
else
{
callback(NO, nil);
}
}
failure:^(AFHTTPRequestOperation *operation, NSError *error){
callback(NO, nil);
}];
}
+ (void)doPostWithUrl:(NSString *)url path:(NSString *)path params:(NSDictionary *)params callback:(BKHttpCallback)callback
{
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:url]];
httpClient.parameterEncoding = AFJSONParameterEncoding;
[httpClient postPath:path
parameters:params
success:^(AFHTTPRequestOperation *operation, id responseObject){
NSString *responseJson = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
if (responseJson)
{
NSDictionary *result = [responseJson objectFromJSONString];
callback(YES, result);
}
else
{
callback(NO, nil);
}
}
failure:^(AFHTTPRequestOperation *operation, NSError *error){
callback(NO, nil);
}];
}
+ (void)getImageWithUrl:(NSString *)url callback:(BKHttpCallback)callback
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
AFImageRequestOperation *requestOperation = [[AFImageRequestOperation alloc] initWithRequest:request];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
UIImage *image = responseObject;
NSDictionary *result = @{@"image":image};
callback(YES, result);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
callback(NO, nil);
}];
[requestOperation start];
}
二、MD5加密
+ (NSString *)md5:(NSString *)input;
+ (NSString *)sha1:(NSString *)input;
+ (NSString *)getIPAddress:(BOOL)preferIPv4;
+ (NSDictionary *)getIPAddresses;
.m文件
#define IOS_CELLULAR @"pdp_ip0"
#define IOS_WIFI @"en0"
#define IP_ADDR_IPv4 @"ipv4"
#define IP_ADDR_IPv6 @"ipv6"
@implementation CommonUtil
+ (NSString *)md5:(NSString *)input
{
const char *cStr = [input UTF8String];
unsigned char digest[16];
CC_MD5( cStr, strlen(cStr), digest ); // This is the md5 call
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;
}
+ (NSString *)sha1:(NSString *)input
{
const char *ptr = [input UTF8String];
int i =0;
int len = strlen(ptr);
Byte byteArray[len];
while (i!=len)
{
unsigned eachChar = *(ptr + i);
unsigned low8Bits = eachChar & 0xFF;
byteArray[i] = low8Bits;
i++;
}
unsigned char digest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1(byteArray, len, digest);
NSMutableString *hex = [NSMutableString string];
for (int i=0; i<20; i++)
[hex appendFormat:@"%02x", digest[i]];
NSString *immutableHex = [NSString stringWithString:hex];
return immutableHex;
}
+ (NSString *)getIPAddress:(BOOL)preferIPv4
{
NSArray *searchArray = preferIPv4 ?
@[ IOS_WIFI @"/" IP_ADDR_IPv4, IOS_WIFI @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6 ] :
@[ IOS_WIFI @"/" IP_ADDR_IPv6, IOS_WIFI @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4 ] ;
NSDictionary *addresses = [self getIPAddresses];
//NSLog(@"addresses: %@", addresses);
__block NSString *address;
[searchArray enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop)
{
address = addresses[key];
if(address) *stop = YES;
} ];
return address ? address : @"0.0.0.0";
}
+ (NSDictionary *)getIPAddresses
{
NSMutableDictionary *addresses = [NSMutableDictionary dictionaryWithCapacity:8];
// retrieve the current interfaces - returns 0 on success
struct ifaddrs *interfaces;
if(!getifaddrs(&interfaces)) {
// Loop through linked list of interfaces
struct ifaddrs *interface;
for(interface=interfaces; interface; interface=interface->ifa_next) {
if(!(interface->ifa_flags & IFF_UP) || (interface->ifa_flags & IFF_LOOPBACK)) {
continue; // deeply nested code harder to read
}
const struct sockaddr_in *addr = (const struct sockaddr_in*)interface->ifa_addr;
if(addr && (addr->sin_family==AF_INET || addr->sin_family==AF_INET6)) {
NSString *name = [NSString stringWithUTF8String:interface->ifa_name];
char addrBuf[INET6_ADDRSTRLEN];
if(inet_ntop(addr->sin_family, &addr->sin_addr, addrBuf, sizeof(addrBuf))) {
NSString *key = [NSString stringWithFormat:@"%@/%@", name, addr->sin_family == AF_INET ? IP_ADDR_IPv4 : IP_ADDR_IPv6];
addresses[key] = [NSString stringWithUTF8String:addrBuf];
}
}
}
// Free memory
freeifaddrs(interfaces);
}
// The dictionary keys have the form "interface" "/" "ipv4 or ipv6"
return [addresses count] ? addresses : nil;
}
主體代碼:
#define BASE_URL @"https://api.weixin.qq.com"
@interfaceSkyViewController ()
@property (nonatomic, copy) NSString *timeStamp;
@property (nonatomic, copy) NSString *nonceStr;
@property (nonatomic, copy) NSString *traceId;
@end
@implementation SkyViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(getOrderPayResult:) name:ORDER_PAY_NOTIFICATION object:nil];//監聽一個通知
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter]removeObserver:self];//移除通知
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)pay:(id)sender
{
[self getAccessToken];//獲取access_token
}
#pragma mark - 主體流程
// 獲取token
- (void)getAccessToken
{
NSString *tokenUrl = @"cgi-bin/token";
NSDictionary *param = @{@"grant_type":@"client_credential", @"appid":WXAppId, @"secret":WXAppSecret};
[HttpUtil doGetWithUrl:BASE_URL
path:tokenUrl
params:param
callback:^(BOOL isSuccessed, NSDictionary *result){
NSString *accessToken = result[AccessTokenKey];
[self getPrepayId:accessToken];
}];
}
// 生成預支付訂單
- (void)getPrepayId:(NSString *)accessToken
{
NSString *prepayIdUrl = [NSString stringWithFormat:@"pay/genprepay?access_token=%@", accessToken];
// 拼接詳細的訂單數據
NSDictionary *postDict = [self getProductArgs];
[HttpUtil doPostWithUrl:BASE_URL
path:prepayIdUrl
params:postDict
callback:^(BOOL isSuccessed, NSDictionary *result){
NSString *prePayId = result[PrePayIdKey];
// 獲取預支付訂單id,調用微信支付sdk
if (prePayId)
{
NSLog(@"--- PrePayId: %@", prePayId);
// 調起微信支付
PayReq *request = [[PayReq alloc] init];
request.partnerId = WXPartnerId;
request.prepayId = prePayId;
request.package = @"Sign=WXPay";
request.nonceStr = self.nonceStr;
request.timeStamp = [self.timeStamp intValue];
// 構造參數列表
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:WXAppId forKey:@"appid"];
[params setObject:WXAppKey forKey:@"appkey"];
[params setObject:request.nonceStr forKey:@"noncestr"];
[params setObject:request.package forKey:@"package"];
[params setObject:request.partnerId forKey:@"partnerid"];
[params setObject:request.prepayId forKey:@"prepayid"];
[params setObject:self.timeStamp forKey:@"timestamp"];
request.sign = [self genSign:params];
// 在支付以前,若是應用沒有註冊到微信,應該先調用 [WXApi registerApp:appId] 將應用註冊到微信
[WXApi safeSendReq:request];//發送一個安全請求
}
}];
}
#pragma mark - 生成各類參數
// 獲取時間戳
- (NSString *)genTimeStamp
{
return [NSString stringWithFormat:@"%.0f", [[NSDate date] timeIntervalSince1970]];
}
/**
* 獲取32位內的隨機串, 防重發
*
* 注意:商戶
系統內部的訂單號,32個字符內、可包含字母,確保在商戶系統惟一
*/
- (NSString *)genNonceStr
{
return [CommonUtil md5:[NSString stringWithFormat:@"%d", arc4random() % 10000]];
}
/**
* 獲取商家對用戶的惟一標識
*
* traceId 由開發者自定義,可用於訂單的查詢與跟蹤,建議根據支付用戶信息生成此id
* 建議 traceid 字段包含用戶信息及訂單信息,方便後續對訂單狀態的查詢和跟蹤
*/
- (NSString *)genTraceId
{
return [NSString stringWithFormat:@"crestxu_%@", [self genTimeStamp]];
}
- (NSString *)genOutTradNo
{
return [CommonUtil md5:[NSString stringWithFormat:@"%d", arc4random() % 10000]];
}
// 訂單詳情
- (NSString *)genPackage
{
// 構造訂單參數列表
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:@"WX" forKey:@"bank_type"];
[params setObject:@"千足金箍棒" forKey:@"body"];
[params setObject:@"1" forKey:@"fee_type"];
[params setObject:@"UTF-8" forKey:@"input_charset"];
[params setObject:@"http://weixin.qq.com" forKey:@"notify_url"];
[params setObject:[self genOutTradNo] forKey:@"out_trade_no"];
[params setObject:WXPartnerId forKey:@"partner"];
[params setObject:[CommonUtil getIPAddress:YES] forKey:@"spbill_create_ip"];
[params setObject:@"1" forKey:@"total_fee"]; // 1 == ¥0.01
NSArray *keys = [params allKeys];
NSArray *sortedKeys = [keys sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj1 compare:obj2 options:NSNumericSearch];
}];
// 生成 packageSign
NSMutableString *package = [NSMutableString string];
for (NSString *key in sortedKeys) {
[package appendString:key];
[package appendString:@"="];
[package appendString:[params objectForKey:key]];
[package appendString:@"&"];
}
[package appendString:@"key="];
[package appendString:WXPartnerKey]; // 注意:不能hardcode在客戶端,建議genPackage這個過程都由服務器端完成
// 進行md5摘要前,params內容爲原始內容,未通過url encode處理
NSString *packageSign = [[CommonUtil md5:[package copy]] uppercaseString];
package = nil;
// 生成 packageParamsString
NSString *value = nil;
package = [NSMutableString string];
for (NSString *key in sortedKeys)
{
[package appendString:key];
[package appendString:@"="];
value = [params objectForKey:key];
// 對全部鍵值對中的 value 進行 urlencode 轉碼
value = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)value, nil, (CFStringRef)@"!*'&=();:@+$,/?%#[]", kCFStringEncodingUTF8));
[package appendString:value];
[package appendString:@"&"];
}
NSString *packageParamsString = [package substringWithRange:NSMakeRange(0, package.length - 1)];
NSString *result = [NSString stringWithFormat:@"%@&sign=%@", packageParamsString, packageSign];
NSLog(@"--- Package: %@", result);
return result;
}
// 簽名
- (NSString *)genSign:(NSDictionary *)signParams
{
// 排序
NSArray *keys = [signParams allKeys];
NSArray *sortedKeys = [keys sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj1 compare:obj2 options:NSNumericSearch];
}];
// 生成
NSMutableString *sign = [NSMutableString string];
for (NSString *key in sortedKeys) {
[sign appendString:key];
[sign appendString:@"="];
[sign appendString:[signParams objectForKey:key]];
[sign appendString:@"&"];
}
NSString *signString = [[sign copy] substringWithRange:NSMakeRange(0, sign.length - 1)];
NSString *result = [CommonUtil sha1:signString];
NSLog(@"--- Gen sign: %@", result);
return result;
}
// 構造訂單參數列表
- (NSDictionary *)getProductArgs
{
self.timeStamp = [self genTimeStamp]; // 獲取時間戳
self.nonceStr = [self genNonceStr]; // 獲取32位內的隨機串, 防重發
self.traceId = [self genTraceId]; // 獲取商家對用戶的惟一標識
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:WXAppId forKey:@"appid"];
[params setObject:WXAppKey forKey:@"appkey"];
[params setObject:self.timeStamp forKey:@"noncestr"];
[params setObject:self.timeStamp forKey:@"timestamp"];
[params setObject:self.traceId forKey:@"traceid"];
[params setObject:[self genPackage] forKey:@"package"];
[params setObject:[self genSign:params] forKey:@"app_signature"];
[params setObject:@"sha1" forKey:@"sign_method"];
return params;
}
#pragma mark - 支付結果
- (void)getOrderPayResult:(NSNotification *)notification
{
if ([notification.object isEqualToString:@"success"])
{
NSLog(@"success: 支付成功");
}
else
{
NSLog(@"fail: 支付失敗");
}
}
這只是一個簡單的使用,裏面沒有用到數據模型,在使用過程當中,裏面的有些參數要轉成數據模型。