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*]; [self sendWeChatPrePayRequestWithOrderID:object.orderID orderTitle:object.orderTitle price:centPrice completion:^(NSError *error, id requestObject, id responseObject) {
if (error != nil) {
// NSLog(@"Domain:%@\n Description:%@\n request:%@\n", error.domain, error.description, requestObject);
if (completion != nil) {
completion(error, requestObject, nil);
}
} else { if (centPrice.floatValue < ) {
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 == ) {
NSError *error = [NSError errorWithDomain:PAYMENT_ERROR_DOMAIN_WX_PREPAYID code:NYPaymentErrorCodeWeChatGetPrePayIDFailed userInfo:@{NSLocalizedDescriptionKey:@"获取prepayid失败!\n"}];
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() );
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 != ) { 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失败!\n"}];
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 = ;
// if (self.completion != nil) {
// self.completion(nil, nil, nil);
// }
// [debugInfo appendFormat:@"获取预支付交易标示成功!\n"];
} 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: @"服务器返回签名验证错误!!!\n返回信息:%@", return_msg]}];
if (self.completion != nil) {
self.completion(error, send_sign, nil);
}
// last_errcode = 1;
// [debugInfo appendFormat:@"gen_sign=%@\n _sign=%@\n",sign,send_sign];
// [debugInfo appendFormat:@"服务器返回签名验证错误!!!\n"]; }
} else {
NSError *error = [NSError errorWithDomain:PAYMENT_ERROR_DOMAIN_WX_SERVER code:NYPaymentErrorCodeWeChatSignVerifyError userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"请求接口返回错误!!!\n返回信息:%@", return_msg]}];
if (self.completion != nil) {
self.completion(error, packageSign, nil);
} // last_errcode = 2;
// [debugInfo appendFormat:@"接口返回错误!!!\n"];
}
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>\n"];
for (NSString *categoryId in sortedArray) {
[reqPars appendFormat:@"<%@>%@</%@>\n", categoryId, [packageParams objectForKey:categoryId],categoryId];
}
[reqPars appendFormat:@"<sign>%@</sign>\n</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 : { break;
}
case -: {
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 -: {
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