微信支付最詳解教程

最近要用微信支付功能,在此總結一下!
 
須要下面第三方支持
 
備註: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: 支付失敗");
    }
}
 
這只是一個簡單的使用,裏面沒有用到數據模型,在使用過程當中,裏面的有些參數要轉成數據模型。
相關文章
相關標籤/搜索