微信支付最详解教程
最近要用微信支付功能,在此总结一下!
需要下面第三方支持
备注: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: 支付失败");
}
}
这只是一个简单的使用,里面没有用到数据模型,在使用过程中,里面的有些参数要转成数据模型。