1.前言
现在很多应用都有支付功能,支付也是开发中比较麻烦的一个部分。其实,最麻烦的部分是商户帐号的审核,如果没有商户帐号,就没有你要给钱的那个对公账户。
2.关于交易
在这个金融类项目的开发中,接触了一些金融常识。比如,要是需要进行融资的话,必须要有对公账户,若没有对公账户,那么进行的融资交易即视为非法集资。而申请微信或支付宝的商家账户其实就是开通一个对公账户。但是,最烦人的就是申请账户,等待审核。
3.微信支付
I)业务流程
以上的图是微信官方的图,其实挺坑人的,时序图弄的那么麻烦。
其实简言之就两步:
1)下预支付订单,调用https://api.mch.weixin.qq.com/pay/unifiedorder 统一支付API
2)调起微信支付接口
II)接入微信SDK
微信的支付和微信分享是相同的SDK,再上一篇中已经把SDK引入到项目中不需要再引入了。新的SDK版本为1.6.2,去掉了之前版本预支付调用统一下单API的方法,当然,统一下单接口本来就应该后台调用,以便后台存储商户订单和微信后台的预支付订单。后台调用统一下单接口后,将后台存储的订单ID和预支付ID(prepayid)返回给iOS/Android移动端即可,以便移动端调起微信并让用户支付订单金额。
虽然,新版SDK去掉了预支付流程,但是为了
4.支付单例的设计
支付单例类似于分享单例。为什么要拆开到单独的类中?因为,单独拿出来以便今后再集成支付宝的支付。
I)头文件
// // NYPaymentManager.h // NYShare // // Created by 倪瑶 on 15/12/10. // Copyright © 2015年 nycode. All rights reserved. // #import <Foundation/Foundation.h> #import <UIKit/UIKit.h> #import "NYXMLParser.h" #import "NYWXPayUtility.h" #import "WXApi.h" //暂时用 创友 #define WX_PAY_APP_ID @"" /**< 微信开放应用APP ID !重要:必须是和商户关联的APP ID */ #define WX_PAY_APP_SECRET @"" /**< 微信开放应用APP SECRET */ #define WX_PAY_API_KEY @"" /**< API密钥*/ #define WX_PAY_PARTNER_ID @"" /**< 微信支付商户号 */ #define WX_PAY_DEVICE_INFO @"" /**< 支付设备号或门店号 */ #define WX_PAY_BILL_CREATE_IP @"192.168.0.1" /**< 发器支付的机器ip */ #define WX_PAY_NOTIFY_URL @"" /**< 回调URL,接收异步通知 */ #define WX_PAY_UNIFIEDORDER_API @"https://api.mch.weixin.qq.com/pay/unifiedorder" /**< 统一订单接口,详见https://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=9_1 */ #define WX_PAY_PACKAGE @"Sign=WXPay" #define PAYMENT_ERROR_DOMAIN_WX_PREPAYID @"PAYMENT_ERROR_DOMAIN_WX_PREPAYID" //获取prepayid失败 #define PAYMENT_ERROR_DOMAIN_WX_SIGN @"PAYMENT_ERROR_DOMAIN_WX_SIGN" //服务器返回签名验证错误 #define PAYMENT_ERROR_DOMAIN_WX_SERVER @"PAYMENT_ERROR_DOMAIN_WX_SERVER" //请求接口返回错误 #define PAYMENT_ERROR_DOMAIN_WX_PRICE @"PAYMENT_ERROR_DOMAIN_WX_PRICE" //价格为负数 #define PAYMENT_ERROR_DOMAIN_WX_RESPONSE @"PAYMENT_ERROR_DOMAIN_WX_RESPONSE" //服务器返回对象为空 #define PAYMENT_ERROR_DOMAIN_WX_REQUEST @"PAYMENT_ERROR_DOMAIN_WX_REQUEST" //支付请求失败 #define PAYMENT_ERROR_DOMAIN_WX_RESULT_FAIL @"PAYMENT_ERROR_DOMAIN_WX_RESULT_FAIL"//返回失败 #define PAYMENT_ERROR_DOMAIN_WX_PAYFAILED @"PAYMENT_ERROR_DOMAIN_WX_PAYFAILED"//可能的原因:签名错误、未注册APPID、项目设置APPID不正确、注册的APPID与设置的不匹配、其他异常等 #define PAYMENT_ERROR_DOMAIN_WX_PAYCANCELED @"PAYMENT_ERROR_DOMAIN_WX_PAYCANCELED" /** * 支付错误码类型 **/ typedef NS_ENUM(NSInteger, NYPaymentErrorCode) { NYPaymentErrorCodeWeChatSignVerifyError, NYPaymentErrorCodeWeChatApiResponseError, NYPaymentErrorCodeWeChatGetPrePayIDFailed, NYPaymentErrorCodeWeChatPriceLow, NYPaymentErrorCodeWeChatResponseError, NYPaymentErrorCodeWeChatRequestError, NYPaymentErrorCodeWeChatResultCodeFail, NYPaymentErrorCodeWeChatPayFailed, NYPaymentErrorCodeWeChatPayCanceled, NYPaymentErrorCodeWeChatPaySuccess,//暂不用,最好后台请求获取,以服务器饭或为准 NYPaymentErrorCodeALiPay, }; /** * 支付类型 **/ typedef NS_ENUM(NSInteger, NYPaymentType) { NYPaymentTypeWeChat, NYPaymentTypeALiPay, }; /** * 回调 * @param error 支付请求返回的错误 * @param requestObject 支付请求发送的对象 * @param responseObject 支付相应返回的对象 **/ typedef void(^NYPaymentComletion)(NSError *error, id requestObject, id responseObject); /** * 支付订单对象 **/ @interface NYPaymentObject : NSObject @property (strong, nonatomic) NSString *orderID; /**< 商户订单ID,商户后台提供 */ @property (strong, nonatomic) NSString *orderTitle; /**< 商户订单标题,商户后台提供 */ @property (strong, nonatomic) NSString *orderPrice; /**< 订单价格,单位为元 */ @property (strong, nonatomic) NSString *wxPrePayID; /**< 微信预支付订单号,微信支付时必填! */ @end /** * 支付通用类 **/ @interface NYPaymentManager : NSObject <WXApiDelegate> @property (strong, nonatomic) NYPaymentComletion completion; /**< 回调 */ /** * 单例 **/ + (instancetype)defaultManager; - (void)registerPaymentApp; /** * 微信移动端独立发送统一订单请求方法 * @param orderID 商户订单ID,商户后台提供 * @param orderTitle 商户订单标题,商户后台提供 * @param price 订单价格,单位为分 * @param completion 业务回调 * @brief 该方法用于没有后台请求统一下单接口的情况 * @description 仅供测试用 **/ //- (void)sendWeChatPrePayRequestWithOrderID:(NSString *)orderID orderTitle:(NSString *)orderTitle price:(NSString *)price completion:(NYPaymentComletion)completion; /** * 微信支付方法 * @param object 支付的订单对象 * @param completion 支付的回调方法 **/ - (void)payForWeChatWithOrderObject:(NYPaymentObject *)object completion:(NYPaymentComletion)completion; /** * 支付统一方法 * @param payType 支付类型,阿里支付、微信支付 * @param object 支付订单对象 * @param completion 回调函数 **/ - (void)payForType:(NYPaymentType)payType paymentObject:(NYPaymentObject *)object completion:(NYPaymentComletion)completion; /** * 分享打开三方应用代理回调方法 **/ - (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url; /** * 分享打开三方应用代理回调方法 **/ - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation; @end
II)实现文件
// // NYPaymentManager.m // NYShare // // Created by 倪瑶 on 15/12/10. // Copyright © 2015年 nycode. All rights reserved. // #import "NYPaymentManager.h" @implementation NYPaymentObject @end @implementation NYPaymentManager + (instancetype)defaultManager { static NYPaymentManager *manager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ manager = [[self alloc] init]; }); return manager; } - (instancetype)init { self = [super init]; if (self) { } return self; } - (void)registerPaymentApp { [WXApi registerApp:WX_PAY_APP_ID]; } #pragma mark - payment - (void)payForType:(NYPaymentType)payType paymentObject:(NYPaymentObject *)object completion:(NYPaymentComletion)completion { switch (payType) { case NYPaymentTypeALiPay: { break; } case NYPaymentTypeWeChat: { [[NYPaymentManager defaultManager] payForWeChatWithOrderObject:object completion:completion]; break; } default: break; } } - (void)payTestForWeChatWithOrderObject:(NYPaymentObject *)object completion:(NYPaymentComletion)completion { NSString *centPrice = [NSString stringWithFormat:@"%.f",object.orderPrice.floatValue*100]; [self sendWeChatPrePayRequestWithOrderID:object.orderID orderTitle:object.orderTitle price:centPrice completion:^(NSError *error, id requestObject, id responseObject) { if (error != nil) { // NSLog(@"Domain:%@ Description:%@ request:%@ ", error.domain, error.description, requestObject); if (completion != nil) { completion(error, requestObject, nil); } } else { if (centPrice.floatValue < 0) { NSError *error = [NSError errorWithDomain:PAYMENT_ERROR_DOMAIN_WX_PRICE code:NYPaymentErrorCodeWeChatPriceLow userInfo:@{NSLocalizedDescriptionKey:[NSString stringWithFormat:@"价格为负数!"]}]; if (completion != nil) { completion(error, nil, nil); } } else { if (responseObject != nil) { NSString *time_stamp, *nonce_str; //设置支付参数 time_t now; time(&now); time_stamp = [NSString stringWithFormat:@"%ld", now]; nonce_str = [NYWXPayUtility md5:time_stamp]; //支付请求的参数一定要核对清楚 PayReq *payRequest = [[PayReq alloc] init]; payRequest.openID = WX_PAY_APP_ID; payRequest.partnerId = WX_PAY_PARTNER_ID; payRequest.prepayId = [responseObject objectForKey:@"prepayid"];//!!!! payRequest.nonceStr = nonce_str; payRequest.timeStamp = time_stamp.intValue; payRequest.package = WX_PAY_PACKAGE;//???? //第二次签名参数列表 NSMutableDictionary *signParams = [NSMutableDictionary dictionary]; [signParams setObject: WX_PAY_APP_ID forKey:@"appid"]; [signParams setObject: nonce_str forKey:@"noncestr"]; [signParams setObject: WX_PAY_PACKAGE forKey:@"package"]; [signParams setObject: WX_PAY_PARTNER_ID forKey:@"partnerid"]; [signParams setObject: time_stamp forKey:@"timestamp"]; [signParams setObject: [responseObject objectForKey:@"prepayid"] forKey:@"prepayid"]; //[signParams setObject: @"MD5" forKey:@"signType"]; //生成签名 NSString *sign = [self createMd5Sign:signParams]; payRequest.sign = sign;//???? BOOL status = [WXApi sendReq:payRequest]; if (!status) { NSError *error = [NSError errorWithDomain:PAYMENT_ERROR_DOMAIN_WX_REQUEST code:NYPaymentErrorCodeWeChatRequestError userInfo:@{NSLocalizedDescriptionKey:@"支付请求失败!"}]; if (completion != nil) { completion(error, nil, nil); } } else { if (completion != nil) { completion(nil, nil, nil); } } } else { NSError *error = [NSError errorWithDomain:PAYMENT_ERROR_DOMAIN_WX_RESPONSE code:NYPaymentErrorCodeWeChatResponseError userInfo:@{NSLocalizedDescriptionKey:@"服务器返回对象为空!"}]; if (completion != nil) { completion(error, nil, nil); } } } } }]; } - (void)payForWeChatWithOrderObject:(NYPaymentObject *)object completion:(NYPaymentComletion)completion { if (object.wxPrePayID.length == 0) { NSError *error = [NSError errorWithDomain:PAYMENT_ERROR_DOMAIN_WX_PREPAYID code:NYPaymentErrorCodeWeChatGetPrePayIDFailed userInfo:@{NSLocalizedDescriptionKey:@"获取prepayid失败! "}]; if (completion != nil) { completion(error, nil, nil); } } else { NSString *time_stamp, *nonce_str; //设置支付参数 time_t now; time(&now); time_stamp = [NSString stringWithFormat:@"%ld", now]; nonce_str = [NYWXPayUtility md5:time_stamp]; //支付请求的参数一定要核对清楚 PayReq *payRequest = [[PayReq alloc] init]; payRequest.openID = WX_PAY_APP_ID; payRequest.partnerId = WX_PAY_PARTNER_ID; payRequest.prepayId = object.wxPrePayID;//!!!! payRequest.nonceStr = nonce_str; payRequest.timeStamp = time_stamp.intValue; payRequest.package = WX_PAY_PACKAGE;//???? //第二次签名参数列表 NSMutableDictionary *signParams = [NSMutableDictionary dictionary]; [signParams setObject: WX_PAY_APP_ID forKey:@"appid"]; [signParams setObject: nonce_str forKey:@"noncestr"]; [signParams setObject: WX_PAY_PACKAGE forKey:@"package"]; [signParams setObject: WX_PAY_PARTNER_ID forKey:@"partnerid"]; [signParams setObject: time_stamp forKey:@"timestamp"]; [signParams setObject: object.wxPrePayID forKey:@"prepayid"]; //[signParams setObject: @"MD5" forKey:@"signType"]; //生成签名 NSString *sign = [self createMd5Sign:signParams]; payRequest.sign = sign;//???? BOOL status = [WXApi sendReq:payRequest]; if (!status) { NSError *error = [NSError errorWithDomain:PAYMENT_ERROR_DOMAIN_WX_REQUEST code:NYPaymentErrorCodeWeChatRequestError userInfo:@{NSLocalizedDescriptionKey:@"支付请求失败!"}]; if (completion != nil) { completion(error, nil, nil); } } else { if (completion != nil) { completion(nil, nil, nil); } } } } - (void)sendWeChatPrePayRequestWithOrderID:(NSString *)orderID orderTitle:(NSString *)orderTitle price:(NSString *)price completion:(NYPaymentComletion)completion { self.completion = completion; NSMutableDictionary *preOrder = [NSMutableDictionary dictionary]; srand( (unsigned)time(0) ); NSString *noncestr = [NSString stringWithFormat:@"%d", rand()]; [preOrder setObject: WX_PAY_APP_ID forKey:@"appid"]; //开放平台appid [preOrder setObject: WX_PAY_PARTNER_ID forKey:@"mch_id"]; //商户号 [preOrder setObject: WX_PAY_DEVICE_INFO forKey:@"device_info"]; //支付设备号或门店号 [preOrder setObject: noncestr forKey:@"nonce_str"]; //随机串 [preOrder setObject: @"APP" forKey:@"trade_type"]; //支付类型,固定为APP [preOrder setObject: orderTitle forKey:@"body"]; //订单描述,展示给用户 [preOrder setObject: WX_PAY_NOTIFY_URL forKey:@"notify_url"]; //支付结果异步通知 [preOrder setObject: orderID forKey:@"out_trade_no"];//商户订单号 [preOrder setObject: WX_PAY_BILL_CREATE_IP forKey:@"spbill_create_ip"];//发器支付的机器ip [preOrder setObject: price forKey:@"total_fee"]; //订单金额,单位为分 NSString *prePayID = [self getPrePayIDWithPrePayOrder:preOrder]; if (prePayID.length != 0) { NSString *package, *time_stamp, *nonce_str; //设置支付参数 time_t now; time(&now); time_stamp = [NSString stringWithFormat:@"%ld", now]; nonce_str = [NYWXPayUtility md5:time_stamp]; //重新按提交格式组包,微信客户端暂只支持package=Sign=WXPay格式,须考虑升级后支持携带package具体参数的情况 //package = [NSString stringWithFormat:@"Sign=%@",package]; package = WX_PAY_PACKAGE; //第二次签名参数列表 NSMutableDictionary *signParams = [NSMutableDictionary dictionary]; [signParams setObject: WX_PAY_APP_ID forKey:@"appid"]; [signParams setObject: nonce_str forKey:@"noncestr"]; [signParams setObject: package forKey:@"package"]; [signParams setObject: WX_PAY_PARTNER_ID forKey:@"partnerid"]; [signParams setObject: time_stamp forKey:@"timestamp"]; [signParams setObject: prePayID forKey:@"prepayid"]; //[signParams setObject: @"MD5" forKey:@"signType"]; //生成签名 NSString *sign = [self createMd5Sign:signParams]; //添加签名 [signParams setObject: sign forKey:@"sign"]; if (self.completion != nil) { self.completion(nil, nil, signParams); } } else { NSError *error = [NSError errorWithDomain:PAYMENT_ERROR_DOMAIN_WX_PREPAYID code:NYPaymentErrorCodeWeChatGetPrePayIDFailed userInfo:@{NSLocalizedDescriptionKey:@"获取prepayid失败! "}]; if (self.completion != nil) { self.completion(error, preOrder, nil); } } } - (NSString *)getPrePayIDWithPrePayOrder:(NSMutableDictionary *)preOrder { NSString *prePayID = nil; NSString *packageSign = [self packageSign:preOrder]; NSData *response = [NYWXPayUtility postSynchronousRequestWithURL:WX_PAY_UNIFIEDORDER_API httpBody:packageSign]; NYXMLParser *xml = [[NYXMLParser alloc] init]; [xml parseData:response]; NSMutableDictionary *dictionary = [xml dictionary]; //判断返回 NSString *return_code = [dictionary objectForKey:@"return_code"]; NSString *return_msg = [dictionary objectForKey:@"return_msg"]; NSString *result_code = [dictionary objectForKey:@"result_code"]; NSString *err_code = [dictionary objectForKey:@"err_code"]; NSString *err_code_des = [dictionary objectForKey:@"err_code_des"]; if ( [return_code isEqualToString:@"SUCCESS"] ) { //生成返回数据的签名 NSString *sign = [self createMd5Sign:dictionary ]; NSString *send_sign =[dictionary objectForKey:@"sign"] ; //验证签名正确性 if( [sign isEqualToString:send_sign]){ if( [result_code isEqualToString:@"SUCCESS"]) { //验证业务处理状态 prePayID = [dictionary objectForKey:@"prepay_id"]; return_code = 0; // if (self.completion != nil) { // self.completion(nil, nil, nil); // } // [debugInfo appendFormat:@"获取预支付交易标示成功! "]; } else { NSError *error = [NSError errorWithDomain:PAYMENT_ERROR_DOMAIN_WX_RESULT_FAIL code:NYPaymentErrorCodeWeChatResultCodeFail userInfo:@{NSLocalizedDescriptionKey:[NSString stringWithFormat:@"获得prepayid失败, %@, %@", err_code, err_code_des]}]; if (self.completion != nil) { self.completion(error, nil, nil); } } } else { NSError *error = [NSError errorWithDomain:PAYMENT_ERROR_DOMAIN_WX_SIGN code:NYPaymentErrorCodeWeChatSignVerifyError userInfo:@{NSLocalizedDescriptionKey:[NSString stringWithFormat: @"服务器返回签名验证错误!!! 返回信息:%@", return_msg]}]; if (self.completion != nil) { self.completion(error, send_sign, nil); } // last_errcode = 1; // [debugInfo appendFormat:@"gen_sign=%@ _sign=%@ ",sign,send_sign]; // [debugInfo appendFormat:@"服务器返回签名验证错误!!! "]; } } else { NSError *error = [NSError errorWithDomain:PAYMENT_ERROR_DOMAIN_WX_SERVER code:NYPaymentErrorCodeWeChatSignVerifyError userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"请求接口返回错误!!! 返回信息:%@", return_msg]}]; if (self.completion != nil) { self.completion(error, packageSign, nil); } // last_errcode = 2; // [debugInfo appendFormat:@"接口返回错误!!! "]; } return prePayID; } //获取package带参数的签名包 - (NSString *)packageSign:(NSMutableDictionary *)packageParams { NSString *sign; NSMutableString *reqPars = [NSMutableString string]; //生成签名 sign = [self createMd5Sign:packageParams]; //生成xml的package NSArray *keys = [packageParams allKeys]; NSArray *sortedArray = [keys sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { return [obj1 compare:obj2 options:NSNumericSearch]; }]; [reqPars appendString:@"<xml> "]; for (NSString *categoryId in sortedArray) { [reqPars appendFormat:@"<%@>%@</%@> ", categoryId, [packageParams objectForKey:categoryId],categoryId]; } [reqPars appendFormat:@"<sign>%@</sign> </xml>", sign]; return [NSString stringWithString:reqPars]; } /** * 具体签名加密方法见 https://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=4_3 **/ - (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:@""] && ![categoryId isEqualToString:@"sign"] && ![categoryId isEqualToString:@"key"] ) { [contentString appendFormat:@"%@=%@&", categoryId, [dict objectForKey:categoryId]]; } } //添加key字段 [contentString appendFormat:@"key=%@", WX_PAY_API_KEY]; //得到MD5 sign签名 NSString *md5Sign =[NYWXPayUtility md5:contentString]; return md5Sign; } - (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url { BOOL result = NO; if ([url.scheme isEqualToString:[NSString stringWithFormat:@"%@", WX_PAY_APP_ID]]) { result = [WXApi handleOpenURL:url delegate:self]; } return result; } - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation { BOOL result = NO; if ([url.scheme isEqualToString:[NSString stringWithFormat:@"%@", WX_PAY_APP_ID]]) { result = [WXApi handleOpenURL:url delegate:self]; } return result; } #pragma mark - wechat delegate - (void)onReq:(BaseReq *)req { } - (void)onResp:(BaseResp *)resp { if ([resp isKindOfClass:[PayResp class]]) { PayResp *paymentResponse = (PayResp *)resp; switch (paymentResponse.errCode) { case 0: { break; } case -1: { NSError *error = [NSError errorWithDomain:PAYMENT_ERROR_DOMAIN_WX_PAYFAILED code:NYPaymentErrorCodeWeChatPayFailed userInfo:@{NSLocalizedDescriptionKey:@"支付失败,可能的原因:签名错误、未注册APPID、项目设置APPID不正确、注册的APPID与设置的不匹配、其他异常等"}]; if (self.completion != nil) { self.completion(error, nil, nil); } break; } case -2: { NSError *error = [NSError errorWithDomain:PAYMENT_ERROR_DOMAIN_WX_PAYCANCELED code:NYPaymentErrorCodeWeChatPayCanceled userInfo:@{NSLocalizedDescriptionKey:@"用户取消支付"}]; if (self.completion != nil) { self.completion(error, nil, nil); } break; } default: break; } } } @end