微信支付流程其实挺简单的,但苦逼的我需要在客户端下单,疯狂百度之后终于是搞定了,所以有必要把代码记录一下。
一,导入SDK
如果使用了分享的SDK中包含微信SDK,就不用再次导入了。
依赖库
二,配置
1.URL Types
2.白名单
三、代码导入
1.AppDelegate.m中
#import "WXApi.h"
<WXApiDelegate>
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options
{
return [WXApi handleOpenURL:url delegate:self];
}
- (void)onResp:(BaseResp *)resp
{
//支付返回结果,实际支付结果需要去微信服务器端查询
NSString *strMsg = [NSString stringWithFormat:@"支付结果"];
switch (resp.errCode) {
case WXSuccess:
strMsg = @"支付结果:成功!";
NSLog(@"支付成功-PaySuccess,retcode = %d", resp.errCode);
break;
default:
strMsg = [NSString stringWithFormat:@"支付结果:失败!retcode = %d, retstr = %@", resp.errCode,resp.errStr];
NSLog(@"错误,retcode = %d, retstr = %@", resp.errCode,resp.errStr);
break;
}
self.alipayNav = nil;
}
官方把代理写在了WXApiManager.h类里
2.支付文件中
//微信支付商户号
#define MCH_ID @"10位数字那个"
//开户邮件中的(公众账号APPID或者应用APPID)
#define WX_AppID @"wx开头的那个"
//安全校验码(MD5)密钥,商户平台登录账户和密码登录http://pay.weixin.qq.com 平台设置的“API密钥”,为了安全,请设置为以数字和字母组成的32字符串。
#define WX_PartnerKey @"纯英文的那个"
//获取用户openid,可使用APPID对应的公众平台登录http://mp.weixin.qq.com 的开发者中心获取AppSecret。
#define WX_AppSecret @"app secret"
下单,个别参数在下面给出方法,我自己分装在其他类里了
//统一下单
- (void)order:(NSString *)tradeNo withMoney:(NSString *)price
{
NSString *appid = WX_AppID;
NSString *mch_id = MCH_ID;
NSString *partner = WX_PartnerKey;
NSString *nonce_str =[self generateTradeNO];
NSString *body = @"商品购买";
NSString *out_trade_no = tradeNo;
NSString *total_fee = [NSString stringWithFormat:@"%d",(int)([price floatValue]*10000/100)];
NSString *notify_url = @"回调地址";
NSString *spbill_create_ip = [getIPhoneIP getIPAddress:NO];
NSString *trade_type = @"APP";
DataMD5 *data = [[DataMD5 alloc] initWithAppid:appid mch_id:mch_id nonce_str:nonce_str partner_id:partner body:body out_trade_no:out_trade_no total_fee:total_fee spbill_create_ip:spbill_create_ip notify_url:notify_url trade_type:trade_type];
NSString *sign = [data getSignForMD5];
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
[dic setValue:appid forKey:@"appid"];//公众账号ID
[dic setValue:mch_id forKey:@"mch_id"];//商户号
[dic setValue:nonce_str forKey:@"nonce_str"];//随机字符串
[dic setValue:sign forKey:@"sign"];//签名
[dic setValue:body forKey:@"body"];//商品描述
[dic setValue:out_trade_no forKey:@"out_trade_no"];//订单号
[dic setValue:total_fee forKey:@"total_fee"];//金额,注意100=1元
[dic setValue:spbill_create_ip forKey:@"spbill_create_ip"];//终端IP
[dic setValue:notify_url forKey:@"notify_url"];//通知地址
[dic setValue:trade_type forKey:@"trade_type"];//交易类型
NSLog(@"+++%@",dic);
NSString *xmlString = [dic XMLString];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
//这里传入的xml字符串只是形似xml,但是不是正确是xml格式,需要使用af方法进行转义
manager.responseSerializer = [[AFHTTPResponseSerializer alloc] init];
[manager.requestSerializer setValue:@"text/xml;charset=utf-8" forHTTPHeaderField:@"Content-Type"];
[manager.requestSerializer setValue:@"https://api.mch.weixin.qq.com/pay/unifiedorder" forHTTPHeaderField:@"SOAPAction"];
[manager.requestSerializer setQueryStringSerializationWithBlock:^NSString *(NSURLRequest *request, NSDictionary *parameters, NSError *__autoreleasing *error) {
return xmlString;
}];
//发起请求
[manager POST:@"https://api.mch.weixin.qq.com/pay/unifiedorder"
parameters:xmlString
progress:nil
success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSString *responseString = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding] ;
// LXLog(@"responseString is %@",responseString);
//将微信返回的xml数据解析转义成字典
NSDictionary *dic = [NSDictionary dictionaryWithXMLString:responseString];
NSLog(@"++++++++++%@,%@",dic,[dic objectForKey:@"return_msg"]);
//此处为调起支付接口
[self WXPayRequest:appid nonceStr:[dic objectForKey:@"nonce_str"] package:@"Sign=WXPay" partnerId:[dic objectForKey:@"mch_id"] prepayId:[dic objectForKey:@"prepay_id"] timeStamp:@"1" sign:[dic objectForKey:@"sign"]];
}
failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
}];
}
支付:
//调起微信接口
- (void)WXPayRequest:(NSString *)appId nonceStr:(NSString *)nonceStr package:(NSString *)package partnerId:(NSString *)partnerId prepayId:(NSString *)prepayId timeStamp:(NSString *)timeStamp sign:(NSString *)sign{
//调起微信支付
PayReq* wxreq = [[PayReq alloc] init];
wxreq.partnerId= partnerId;
wxreq.prepayId= prepayId;
wxreq.nonceStr= nonceStr;
NSDate *date = [NSDate date];
NSString *time1970 = [NSString stringWithFormat:@"%ld", (long)[date timeIntervalSince1970]];
wxreq.timeStamp= [time1970 intValue];
wxreq.package= package;
DataMD5 *md5 = [[DataMD5 alloc] init];
wxreq.sign=[md5 createMD5SingForPay:appId partnerid:wxreq.partnerId prepayid:wxreq.prepayId package:wxreq.package noncestr:wxreq.nonceStr timestamp:wxreq.timeStamp];
[WXApi sendReq:wxreq];
}
3.nonce_str随机字符串的获得
- (NSString *)generateTradeNO {
static int kNumber = 15;
NSString *sourceStr = @"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
NSMutableString *resultStr = [[NSMutableString alloc] init];
srand(time(0)); // 此行代码有警告:
for (int i = 0; i < kNumber; i++) {
unsigned index = rand() % [sourceStr length];
NSString *oneStr = [sourceStr substringWithRange:NSMakeRange(index, 1)];
[resultStr appendString:oneStr];
}
return resultStr;
}
4,IP地址的获得
#pragma mark - 获取设备当前网络IP地址
+ (NSString *)getIPAddress:(BOOL)preferIPv4
{
NSArray *searchArray = preferIPv4 ?
@[ IOS_VPN @"/" IP_ADDR_IPv4, IOS_VPN @"/" IP_ADDR_IPv6, IOS_WIFI @"/" IP_ADDR_IPv4, IOS_WIFI @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6 ] :
@[ IOS_VPN @"/" IP_ADDR_IPv6, IOS_VPN @"/" IP_ADDR_IPv4, 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];
//筛选出IP地址格式
if([self isValidatIP:address]) *stop = YES;
} ];
return address ? address : @"0.0.0.0";
}
+ (BOOL)isValidatIP:(NSString *)ipAddress {
if (ipAddress.length == 0) {
return NO;
}
NSString *urlRegEx = @"^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])$";
NSError *error;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:urlRegEx options:0 error:&error];
if (regex != nil) {
NSTextCheckingResult *firstMatch=[regex firstMatchInString:ipAddress options:0 range:NSMakeRange(0, [ipAddress length])];
if (firstMatch) {
NSRange resultRange = [firstMatch rangeAtIndex:0];
NSString *result=[ipAddress substringWithRange:resultRange];
//输出结果
NSLog(@"%@",result);
return YES;
}
}
return NO;
}
+ (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;
char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ];
if(addr && (addr->sin_family==AF_INET || addr->sin_family==AF_INET6)) {
NSString *name = [NSString stringWithUTF8String:interface->ifa_name];
NSString *type;
if(addr->sin_family == AF_INET) {
if(inet_ntop(AF_INET, &addr->sin_addr, addrBuf, INET_ADDRSTRLEN)) {
type = IP_ADDR_IPv4;
}
} else {
const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)interface->ifa_addr;
if(inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf, INET6_ADDRSTRLEN)) {
type = IP_ADDR_IPv6;
}
}
if(type) {
NSString *key = [NSString stringWithFormat:@"%@/%@", name, type];
addresses[key] = [NSString stringWithUTF8String:addrBuf];
}
}
}
// Free memory
freeifaddrs(interfaces);
}
return [addresses count] ? addresses : nil;
}
5.获取sign签名
///获取sign签名
- (NSString *)getSignForMD5{
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
[dic setValue:_appid forKey:@"appid"];
[dic setValue:_mch_id forKey:@"mch_id"];
[dic setValue:_nonce_str forKey:@"nonce_str"];
[dic setValue:_body forKey:@"body"];
[dic setValue:_out_trade_no forKey:@"out_trade_no"];
[dic setValue:_total_fee forKey:@"total_fee"];
[dic setValue:_spbill_create_ip forKey:@"spbill_create_ip"];
[dic setValue:_notify_url forKey:@"notify_url"];
[dic setValue:_trade_type forKey:@"trade_type"];
return [self createMd5Sign:dic];
}
//创建签名
-(NSString*) createMd5Sign:(NSMutableDictionary*)dict
{
NSMutableString *contentString =[NSMutableString string];
NSArray *keys = [dict allKeys];
//按字母顺序排序
NSArray *sortedArray = [keys sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj1 compare:obj2 options:NSNumericSearch];
}];
//拼接字符串
for (NSString *categoryId in sortedArray) {
if ( ![[dict objectForKey:categoryId] isEqualToString:@""]
&& ![[dict objectForKey:categoryId] isEqualToString:@"sign"]
&& ![[dict objectForKey:categoryId] isEqualToString:@"key"]
)
{
[contentString appendFormat:@"%@=%@&", categoryId, [dict objectForKey:categoryId]];
}
}
//添加商户密钥key字段
[contentString appendFormat:@"key=%@", _partnerkey];
//得到MD5 sign签名
NSString *md5Sign =[self md5:contentString];
return md5Sign;
}
6.创建发起支付时的sige签名
//创建发起支付时的sige签名
-(NSString *)createMD5SingForPay:(NSString *)appid_key partnerid:(NSString *)partnerid_key prepayid:(NSString *)prepayid_key package:(NSString *)package_key noncestr:(NSString *)noncestr_key timestamp:(UInt32)timestamp_key{
NSMutableDictionary *signParams = [NSMutableDictionary dictionary];
[signParams setObject:appid_key forKey:@"appid"];
[signParams setObject:noncestr_key forKey:@"noncestr"];
[signParams setObject:package_key forKey:@"package"];
[signParams setObject:partnerid_key forKey:@"partnerid"];
[signParams setObject:prepayid_key forKey:@"prepayid"];
[signParams setObject:[NSString stringWithFormat:@"%u",timestamp_key] forKey:@"timestamp"];
NSMutableString *contentString =[NSMutableString string];
NSArray *keys = [signParams allKeys];
//按字母顺序排序
NSArray *sortedArray = [keys sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj1 compare:obj2 options:NSNumericSearch];
}];
//拼接字符串
for (NSString *categoryId in sortedArray) {
if ( ![[signParams objectForKey:categoryId] isEqualToString:@""]
&& ![[signParams objectForKey:categoryId] isEqualToString:@"sign"]
&& ![[signParams objectForKey:categoryId] isEqualToString:@"key"]
)
{
[contentString appendFormat:@"%@=%@&", categoryId, [signParams objectForKey:categoryId]];
}
}
//添加商户密钥key字段
#warning 注意此处一定要添加上商户密钥 别忘了改
[contentString appendFormat:@"key=%@", @"partnerkey"];
NSString *result = [self md5:contentString];
return result;
}
-(NSString *) md5:(NSString *)str
{
const char *cStr = [str UTF8String];
//加密规则,因为逗比微信没有出微信支付demo,这里加密规则是参照安卓demo来得
unsigned char result[16]= "0123456789abcdef";
CC_MD5(cStr, (CC_LONG)strlen(cStr), result);
//这里的x是小写则产生的md5也是小写,x是大写则md5是大写,这里只能用大写,逗比微信的大小写验证很逗
return [NSString stringWithFormat:
@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
result[0], result[1], result[2], result[3],
result[4], result[5], result[6], result[7],
result[8], result[9], result[10], result[11],
result[12], result[13], result[14], result[15]
];
}