iOS開發之第三方支付微信支付教程,史上最新最全第三方微信支付方式實現、微信整合教程,微信實現流程
1. 微信支付微信支付前奏大致流程為 :
1. 公司需要到微信進行申請app支付功能 , 獲得appid和微信支付商戶號(mch_id)和API祕鑰(key) 、 Appsecret(secret),開發中用到的,很重要
- appid:appid是微信公眾賬號或開放平臺APP的唯一標識,在公眾平臺申請公眾賬號或者在開放平臺申請APP賬號後,微信會自動分配對應的appid,用於標識該應用。可在微信公眾平臺-->開發者中心檢視,商戶的微信支付稽核通過郵件中也會包含該欄位值。
-
微信支付商戶號:商戶申請微信支付後,由微信支付分配的商戶收款賬號。
-
API金鑰: 交易過程生成簽名的金鑰,僅保留在商戶系統和微信支付後臺,不會在網路中傳播。商戶妥善保管該Key,切勿在網路中傳輸,不能在其他客戶端中儲存,保證key不會被洩漏。商戶可根據郵件提示登入微信商戶平臺進行設定。也可按一下路徑設定:微信商戶平臺(pay.weixin.qq.com)-->賬戶設定-->API安全-->金鑰設定
-
App secret : AppSecret是APPID對應的介面密碼,用於獲取介面呼叫憑證access_token時使用。
還有Demo地址.
3. 根據上面的整合教程地址 ,搭建好專案
4. 設定好URL Types ,(具體怎麼設定也在環境搭建的文章中說到了,請大家注意看)。
5. 記得把網路從https 改變成為 http。
6. 設定微信白名單 。
7. 現在基本就是環境搭配完畢了。
2. 微信支付具體流程如下:
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】
3.開發前需要注意的幾個方面。
特別注意以下重要規則:
◆ 引數名ASCII碼從小到大排序(字典序);
◆ 如果引數的值為空不參與簽名;
◆ 引數名區分大小寫;
◆ 驗證呼叫返回或微信主動通知簽名時,傳送的sign引數不參與簽名,將生成的簽名與該sign值作校驗。
◆ 微信介面可能增加欄位,驗證簽名時必須支援增加的擴充套件欄位
4. 具體的客戶端開發過程:
步驟1:使用者進入商戶APP,選擇商品下單、確認購買,進入支付環節。商戶服務後臺生成支付訂單,簽名後將資料傳輸到APP端,可參照官方支付demo。 https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=11_1
步驟2:使用者點選後發起支付操作,進入到微信介面,調起微信支付,出現確認支付介面,。
步驟3:使用者確認收款方和金額,點選立即支付後出現輸入密碼介面,可選擇零錢或銀行卡支付.
第四步:輸入正確密碼後,支付完成,使用者端微信出現支付詳情頁面。
第五步:回跳到商戶APP中,商戶APP根據支付結果個性化展示訂單處理結果.
5. 正式程式設計 。
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 <Foundation/Foundation.h>
#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,上面講的很詳細。