1. 公司需要到微信进行申请app支付功能 , 获得appid和微信支付商户号(mch_id)和API秘钥(key) 、 Appsecret(secret),开发中用到的,很重要
2. 下载微信SDK : https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=11_1
还有Demo地址.
3. 根据上面的集成教程地址 ,搭建好项目
4. 设置好URL Types ,(具体怎么设置也在环境搭建的文章中说到了,请大家注意看)。
5. 记得把网络从https 改变成为 http。
6. 设置微信白名单 。
7. 现在基本就是环境搭配完毕了。
1. 流程图一览
2. 具体流程
步骤1:用户在商户APP中选择商品,提交订单,选择微信支付。
步骤2:商户后台收到用户支付单,调用微信支付统一下单接口。参见【统一下单API】。
步骤3:统一下单接口返回正常的prepay_id,再按签名规范重新生成签名后,将数据传输给APP。参与签名的字段名为appId,partnerId,prepayId,nonceStr,timeStamp,package。注意:package的值格式为Sign=WXPay
步骤4:商户APP调起微信支付。api参见本章节【app端开发步骤说明】
步骤5:商户后台接收支付通知。api参见【支付结果通知API】
步骤6:商户后台查询支付结果。,api参见【查询订单API】
1. 微信规定的规则,必须遵循,不然可能导致不能让微信支付成功。参照网址: https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_1
2. 微信订单支付 ,参数规定 : 参照网址 : https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_2
3. 微信订单 ,参数的拼接, 签名的算法、 以及相关证书的详细讲解 : 参照网址: https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_3
特别注意以下重要规则:
◆ 参数名ASCII码从小到大排序(字典序);
◆ 如果参数的值为空不参与签名;
◆ 参数名区分大小写;
◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。
◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段
开发参照微信官方api 地址 : https://open.weixin.qq.com/zh_CN/htmledition/res/dev/document/sdk/ios/interface_w_x_app_extend_object.html
微信开发 支付 签名参数校验: https://pay.weixin.qq.com/wiki/tools/signverify/
步骤1:用户进入商户APP,选择商品下单、确认购买,进入支付环节。商户服务后台生成支付订单,签名后将数据传输到APP端,可参照官方支付demo。 https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=11_1
步骤2:用户点击后发起支付操作,进入到微信界面,调起微信支付,出现确认支付界面,。
步骤3:用户确认收款方和金额,点击立即支付后出现输入密码界面,可选择零钱或银行卡支付.
第四步:输入正确密码后,支付完成,用户端微信出现支付详情页面。
第五步:回跳到商户APP中,商户APP根据支付结果个性化展示订单处理结果.
1. 再次确认url schemes中设置 APPID , 商户在微信开放平台申请开发APP应用后,微信开放平台会生成APP的唯一标识APPID。在Xcode中打开项目,设置项目属性中的URL Schemes为您的APPID。
2. 商户APP工程中引入微信lib库和头文件,调用API前,需要先向微信注册您的APPID,代码如下:[WXApi registerApp:@"wxd930ea5d5a258f4f" withDescription:@"123"];
3. 调起微信支付 (参数拼接, 签名)
4 支付结果回调
核心代码展示 :
回调方法展示:
appdelegate.m
/*! 微信回调,不管是登录还是分享成功与否,都是走这个方法 @brief 发送一个sendReq后,收到微信的回应
*
* 收到一个来自微信的处理结果。调用一次sendReq后会收到onResp。
* 可能收到的处理结果有SendMessageToWXResp、SendAuthResp等。
* @param resp具体的回应内容,是自动释放的
*/
-(void) onResp:(BaseResp*)resp{
NSLog(@"resp %d",resp.errCode);
/*
enum WXErrCode {
WXSuccess = 0, 成功
WXErrCodeCommon = -1, 普通错误类型
WXErrCodeUserCancel = -2, 用户点击取消并返回
WXErrCodeSentFail = -3, 发送失败
WXErrCodeAuthDeny = -4, 授权失败
WXErrCodeUnsupport = -5, 微信不支持
};
*/
if ([resp isKindOfClass:[SendAuthResp class]]) { //授权登录的类。
if (resp.errCode == 0) { //成功。
//这里处理回调的方法 。 通过代理吧对应的登录消息传送过去。
if ([_wxDelegate respondsToSelector:@selector(loginSuccessByCode:)]) {
SendAuthResp *resp2 = (SendAuthResp *)resp;
[_wxDelegate loginSuccessByCode:resp2.code];
}
}else{ //失败
NSLog(@"error %@",resp.errStr);
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"登录失败" message:[NSString stringWithFormat:@"reason : %@",resp.errStr] delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];
[alert show];
}
}
if ([resp isKindOfClass:[SendMessageToWXResp class]]) { //微信分享 微信回应给第三方应用程序的类
SendMessageToWXResp *response = (SendMessageToWXResp *)resp;
NSLog(@"error code %d error msg %@ lang %@ country %@",response.errCode,response.errStr,response.lang,response.country);
if (resp.errCode == 0) { //成功。
//这里处理回调的方法 。 通过代理吧对应的登录消息传送过去。
if (_wxDelegate) {
if([_wxDelegate respondsToSelector:@selector(shareSuccessByCode:)]){
[_wxDelegate shareSuccessByCode:response.errCode];
}
}
}else{ //失败
NSLog(@"error %@",resp.errStr);
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"分享失败" message:[NSString stringWithFormat:@"reason : %@",resp.errStr] delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];
[alert show];
}
}
/*
0 展示成功页面
-1 可能的原因:签名错误、未注册APPID、项目设置APPID不正确、注册的APPID与设置的不匹配、其他异常等。
-2 用户取消 无需处理。发生场景:用户不支付了,点击取消,返回APP。
*/
if ([resp isKindOfClass:[PayResp class]]) { // 微信支付
PayResp*response=(PayResp*)resp;
switch(response.errCode){
case 0:
//服务器端查询支付通知或查询API返回的结果再提示成功
NSLog(@"支付成功");
break;
default:
NSLog(@"支付失败,retcode=%d errormsg %@",resp.errCode ,resp.errStr);
break;
}
}
}
viewController.m方法
#pragma mark 微信支付
- (IBAction)weixinPayAction:(id)sender {
/**
* 外界调用的微信支付方法
*
* @param ordeNumber 系统下发订单号%100000000 得出的数字,确保唯一。
* @param myNumber 订单号 确保唯一
* @param price 价格
付款流程:
1. 获取 AccessToken
2. 获取 genPackage
3. 调起微信付款
4. 在appdelegate 中的 onResp 监听 回调方法。 看是付款成功。
回调代码参数说明:
0 展示成功页面
-1 可能的原因:签名错误、未注册APPID、项目设置APPID不正确、注册的APPID与设置的不匹配、其他异常等。
-2 用户取消 无需处理。发生场景:用户不支付了,点击取消,返回APP。
*/
helper = [[WeixinPayHelper alloc] init];
[helper payProductWith:@"Test Product" andName:@"product number 1" andPrice:[NSString stringWithFormat:@"%d",1500]];
}
WeixinPayHelper.h
//
// WeixinPayHelper.h
// weixinLoginDemo
//
// Created by 张国荣 on 16/7/1.
// Copyright © 2016年 BateOrganization. All rights reserved.
//
#import
#import "WXApi.h"
#import "AFNetworking.h"
/**
* 微信支付工具类
*/
@interface WeixinPayHelper : NSObject
@property (nonatomic, strong) AFHTTPSessionManager *request;
@property (nonatomic, copy) NSString *timeStamp;
@property (nonatomic, copy) NSString *nonceStr;
@property (nonatomic, copy) NSString *traceId;
@property (nonatomic, copy) NSString *orderId;
@property (nonatomic, copy) NSString *orderPrice;
@property (nonatomic, copy) NSString *orderAllId;
+ (instancetype)shareInstance;
/**
* 外界调用的微信支付方法
*
* @param ordeNumber 系统下发订单号%100000000 得出的数字,确保唯一。
* @param myNumber 订单号 确保唯一
* @param price 价格
付款流程:
1. 获取 AccessToken
2. 获取 PrepayId 、 包含参数拼接,签名、genPackage等
3. 调起微信付款
4. 在appdelegate 中的 onResp 监听 回调方法。 看是付款成功。
回调代码参数说明:
0 展示成功页面
-1 可能的原因:签名错误、未注册APPID、项目设置APPID不正确、注册的APPID与设置的不匹配、其他异常等。
-2 用户取消 无需处理。发生场景:用户不支付了,点击取消,返回APP。
*/
- (void)payProductWith:(NSString*)ordeNumber andName:(NSString*)myNumber andPrice:(NSString*)price;
@end
WeixinPayHelper.m
//
// WeixinPayHelper.m
// weixinLoginDemo
//
// Created by 张国荣 on 16/7/1.
// Copyright © 2016年 BateOrganization. All rights reserved.
//
#import "WeixinPayHelper.h"
#import "CommonUtil.h"
#import "Constant.h"
#define ISOFTEN_WECHARPAY_URL @"https://www.sizzee.com/tenpay/payment/mobilesuccess"
@interface WeixinPayHelper()
@end
@implementation WeixinPayHelper
NSString *AccessTokenKey = @"access_token";
NSString *PrePayIdKey = @"prepayid";
NSString *errcodeKey = @"errcode";
NSString *errmsgKey = @"errmsg";
NSString *expiresInKey = @"expires_in";
/**
* 微信开放平台申请得到的 appid, 需要同时添加在 URL schema
*/
NSString * const WXAppId = @"app id";
/**
* 微信开放平台和商户约定的支付密钥
*
* 注意:不能hardcode在客户端,建议genSign这个过程由服务器端完成
*/
NSString * const WXAppKey = @"appkey";
/**
* 微信开放平台和商户约定的密钥
*
* 注意:不能hardcode在客户端,建议genSign这个过程由服务器端完成
*/
NSString * const WXAppSecret = @"app Secret";
/**
* 微信开放平台和商户约定的支付密钥
*
* 注意:不能hardcode在客户端,建议genSign这个过程由服务器端完成
*/
NSString * const WXPartnerKey = @"秘钥、 放服务器的,这儿方便演示";
/**
* 微信公众平台商户模块生成的ID
*/
NSString * const WXPartnerId = @"商户id";
+ (instancetype)shareInstance
{
static WeixinPayHelper *sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedClient = [[WeixinPayHelper alloc] init];
});
return sharedClient;
}
/**
* 外界调起微信下单的方法
*
* @param ordeNumber 订单号 ,由服务器下发、经过处理的 生成genPackage 需要用到
* @param myNumber 订单号 ,由服务器下发 生成genPackage 需要用到
* @param price 商品价格 生成genPackage 需要用到
*/
- (void)payProductWith:(NSString *)ordeNumber andName:(NSString *)myNumber andPrice:(NSString *)price{
self.orderId = ordeNumber;
self.orderPrice = price;
self.orderAllId = myNumber;
[self getAccessToken];
}
#pragma mark - 生成各种参数
#pragma mark 时间戳 生成
- (NSString *)genTimeStamp
{
return [NSString stringWithFormat:@"%.0f", [[NSDate date] timeIntervalSince1970]];
}
/**
* 注意:商户系统内部的订单号,32个字符内、可包含字母,确保在商户系统唯一
*/
- (NSString *)genNonceStr
{
return [CommonUtil md5:[NSString stringWithFormat:@"%d", arc4random() % 10000]];
}
/**
* 建议 traceid 字段包含用户信息及订单信息,方便后续对订单状态的查询和跟踪
*/
- (NSString *)genTraceId
{
return [NSString stringWithFormat:@"crestxu_%@", [self genTimeStamp]];
}
- (NSString *)genOutTradNo
{
return [CommonUtil md5:[NSString stringWithFormat:@"%d", arc4random() % 10000]];
}
#pragma mark genPackage 这个应该是服务器完成。 在这儿方便演示。
- (NSString *)genPackage
{
// 构造参数列表
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:@"WX" forKey:@"bank_type"];
[params setObject:[NSString stringWithFormat:@"拾裳%@订单", self.orderAllId] forKey:@"body"];
[params setObject:@"1" forKey:@"fee_type"];
[params setObject:@"UTF-8" forKey:@"input_charset"];
[params setObject:ISOFTEN_WECHARPAY_URL 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:self.orderPrice forKey:@"total_fee"]; // 1 == ¥0.01
NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:[[NSURL URLWithString:@"https://www.sizzee.com"] absoluteURL]];
NSHTTPCookie *cookie;
NSMutableArray* arr=[NSMutableArray array];
for (cookie in cookies) {
if (![cookie.name isEqualToString:@"app_code"]) {
NSString* str=[NSString stringWithFormat:@"%@||%@",cookie.name,cookie.value];
[arr addObject:str];
}
}
NSString* aa=[arr componentsJoinedByString:@"||"];
[params setObject:[NSString stringWithFormat:@"%@||%@||%@",self.orderId,[CommonUtil md5:[NSString stringWithFormat:@"1219929601fef54y3d5wsxf5yu55t5g5d5e35yujki%@",self.orderId]],aa] forKey:@"attach"];
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];
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];
return result;
}
- (NSMutableDictionary *)getProductArgs
{
self.timeStamp = [self genTimeStamp];
self.nonceStr = [self genNonceStr]; // traceId 由开发者自定义,可用于订单的查询与跟踪,建议根据支付用户信息生成此id
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"];
NSError *error = nil;
// NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params options:NSJSONWritingPrettyPrinted error: &error];
return params;
}
#pragma mark - 主体流程 - 获取 accessToken , 然后调用再拿着accessToken调用微信的支付接口。
- (void)getAccessToken
{
//字段
NSString *getAccessTokenUrl = [NSString stringWithFormat:@"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%@&secret=%@", WXAppId, WXAppSecret];
self.request = [AFHTTPSessionManager manager];
__weak WeixinPayHelper *weakSelf = self;
self.request.requestSerializer = [AFJSONRequestSerializer serializer];//请求
self.request.responseSerializer = [AFHTTPResponseSerializer serializer];//响应
self.request.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html",@"application/json", @"text/json",@"text/plain", nil];
[self.request POST:getAccessTokenUrl parameters:nil progress:^(NSProgress * _Nonnull uploadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSError *error = nil;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:responseObject options:kNilOptions error:&error];
if (error) {
[weakSelf showAlertWithTitle:@"错误" msg:@"获取 AccessToken 失败"];
return;
}
NSString *accessToken = dict[AccessTokenKey];
if (accessToken) {
__strong WeixinPayHelper *strongSelf = weakSelf;
[strongSelf getPrepayId:accessToken];
} else {
NSString *strMsg = [NSString stringWithFormat:@"errcode: %@, errmsg:%@", dict[errcodeKey], dict[errmsgKey]];
[weakSelf showAlertWithTitle:@"错误" msg:strMsg];
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[weakSelf showAlertWithTitle:@"错误" msg:@"获取 AccessToken 失败"];
}];
}
#pragma mark 根据accessToken 获取 PrePayID . 然后进行下单。
- (void)getPrepayId:(NSString *)accessToken
{
NSString *getPrepayIdUrl = [NSString stringWithFormat:@"https://api.weixin.qq.com/pay/genprepay?access_token=%@", accessToken];
NSMutableDictionary *postData = [self getProductArgs];
__weak WeixinPayHelper *weakSelf = self;
self.request = [AFHTTPSessionManager manager];
self.request.requestSerializer = [AFJSONRequestSerializer serializer];//请求
self.request.responseSerializer = [AFHTTPResponseSerializer serializer];//响应
self.request.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html",@"application/json", @"text/json",@"text/plain", nil];
[self.request POST:getPrepayIdUrl parameters:postData progress:^(NSProgress * _Nonnull uploadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSError *error = nil;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:responseObject options:kNilOptions error:&error];
if (error) {
[weakSelf showAlertWithTitle:@"错误" msg:@"获取 PrePayId 失败"];
return;
}
NSString *prePayId = dict[PrePayIdKey];
if (prePayId) {
// 调起微信支付
PayReq *request = [[PayReq alloc] init];
request.partnerId = WXPartnerId;
request.prepayId = prePayId;
request.package = @"Sign=WXPay"; // 文档为 `Request.package = _package;` , 但如果填写上面生成的 `package` 将不能支付成功
request.nonceStr = weakSelf.nonceStr;
request.timeStamp = [weakSelf.timeStamp longLongValue];
// 构造参数列表
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:weakSelf.timeStamp forKey:@"timestamp"];
request.sign = [weakSelf genSign:params];
// 在支付之前,如果应用没有注册到微信,应该先调用 [WXApi registerApp:appId] 将应用注册到微信
[WXApi sendReq:request];
} else {
NSString *strMsg = [NSString stringWithFormat:@"errcode: %@, errmsg:%@", dict[errcodeKey], dict[errmsgKey]];
[weakSelf showAlertWithTitle:@"错误" msg:strMsg];
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[weakSelf showAlertWithTitle:@"错误" msg:@"获取 PrePayId 失败"];
}];
}
#pragma mark - Alert
- (void)showAlertWithTitle:(NSString *)title msg:(NSString *)msg
{
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:title message: msg delegate: self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
[alert show];
}
@end
总结:
我这里付款做的比官方的demo做的复杂了很多, 做了很多服务器要做的事情,比如说 签名算法 、 以及参数按照key=value的格式 、 拼接秘钥 等。
我介意没有特殊要求的朋友,就稍微看看,不必自己再操作一遍。 大家多去看看官方的api,上面讲的很详细。