微信支付最详解教程

最近要用微信支付功能,在此总结一下!
 
需要下面第三方支持
 
备注:JSONKit框架是基于MRC的,如果工程开发环境是ARC的话,请在编译时设定 编译参数 -fno-objc-arc
 
 
 

 
 
1、首先到微信开放平台上,申请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,然后再进行调用。审核通过后,在微信发送的邮件中查看。
 
2、代码实现
@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;
            }
        }
    }
}
 
为了保证发送请求的方便,自己封装了两个工具类
1、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];
}
2、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: 支付失败");
    }
}
 
这只是一个简单的使用,里面没有用到数据模型,在使用过程中,里面的有些参数要转成数据模型。
posted @ 2015-05-14 12:20  anjing123  阅读(639)  评论(0编辑  收藏  举报