此篇針對(duì) iOS 支付進(jìn)行一次小結(jié),很久沒(méi)碰這塊了,有些方法 Apple 官方也進(jìn)行了優(yōu)化,故也將隨之進(jìn)行更新.
首先,code 部分將分為兩部分,一部分在 appdelegate 中,另一部分單獨(dú)封裝在了一個(gè)類(lèi)中執(zhí)行,需要使用的地方調(diào)用的接口方法.
其次,大體支付流程為獲取到充值價(jià)格訂單列表后,選擇對(duì)應(yīng)的價(jià)格后向 Apple 發(fā)起支付請(qǐng)求,接收到 Apple 支付回調(diào)后,根據(jù)結(jié)果處理相關(guān)邏輯,最后將處理完成的結(jié)果反饋至用戶(hù).
其過(guò)程中會(huì)分為幾個(gè)環(huán)節(jié)來(lái)處理:
若支付失敗則執(zhí)行異常處理并將最后處理結(jié)果信息反饋至用戶(hù);
若支付成功則對(duì)支付憑證校驗(yàn),此篇文章中的校驗(yàn)過(guò)程分為兩部分,先是由客戶(hù)端自行校驗(yàn),若校驗(yàn)成功則將相關(guān)用戶(hù)信息和支付憑證發(fā)送至服務(wù)端進(jìn)行二次校驗(yàn);
其中,客戶(hù)端優(yōu)先進(jìn)行交易憑證校驗(yàn),校驗(yàn)失敗則將校驗(yàn)的異常處理信息反饋至用戶(hù);反之,校驗(yàn)成功則再次將相關(guān)用戶(hù)信息和本次支付憑證數(shù)據(jù)一并發(fā)送至服務(wù)端進(jìn)行二次校驗(yàn),最終將雙重驗(yàn)證后的結(jié)果信息反饋至用戶(hù),從而為了避免刷單的情況.
最后,文章中具體處理邏輯中可能會(huì)因?yàn)樾枨蟮牟煌c實(shí)際有些小的出入,但大體流程應(yīng)該是一致的,也會(huì)對(duì)應(yīng)添加相應(yīng)的注釋,若存在不清楚的地方可以帖子下方留言溝通交流.
大致支付流程:
1.蘋(píng)果APP(商家)
2.告訴蘋(píng)果Store服務(wù)器要賣(mài)的商品
3.蘋(píng)果審核完(告訴你是否可以賣(mài))
4.用戶(hù)(買(mǎi)商品)
5.蘋(píng)果APP(商家)
6.開(kāi)發(fā)票給(用戶(hù))
7.用戶(hù)(拿著發(fā)票去蘋(píng)果Store服務(wù)器付款)
8.付款成功(用戶(hù)在APP里獲得服務(wù)商品)
注:如果要模擬測(cè)試內(nèi)購(gòu),需要用真機(jī)才可以測(cè)試
憑證校驗(yàn)地址:
開(kāi)發(fā)環(huán)境: https://sandbox.itunes.apple.com/verifyReceipt
生產(chǎn)環(huán)境: https://buy.itunes.apple.com/verifyReceipt
憑證校驗(yàn)異常 code 參照碼:
內(nèi)購(gòu)驗(yàn)證憑據(jù)返回結(jié)果狀態(tài)碼說(shuō)明(status 狀態(tài))
21000 App Store無(wú)法讀取你提供的JSON數(shù)據(jù)
21002 收據(jù)數(shù)據(jù)不符合格式
21003 收據(jù)無(wú)法被驗(yàn)證
21004 你提供的共享密鑰和賬戶(hù)的共享密鑰不一致
21005 收據(jù)服務(wù)器當(dāng)前不可用
21006 收據(jù)是有效的伴奥,但訂閱服務(wù)已經(jīng)過(guò)期藐唠。當(dāng)收到這個(gè)信息時(shí)笛坦,解碼后的收據(jù)信息也包含在返回內(nèi)容中
21007 收據(jù)信息是測(cè)試用(sandbox),但卻被發(fā)送到產(chǎn)品環(huán)境中驗(yàn)證
21008 收據(jù)信息是產(chǎn)品環(huán)境中使用廓推,但卻被發(fā)送到測(cè)試環(huán)境中驗(yàn)證
Code 如下:
/*
支付_管理類(lèi)(Apple Pay)
*/
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
//充值金額類(lèi)型
typedef NS_ENUM(NSInteger, buyCoinsTag) {
IAP0p20=20,
IAP1p100,
IAP4p600,
IAP9p1000,
IAP24p6000,
};
@import WebKit;
@interface PaymentManager : NSObject <SKPaymentTransactionObserver, SKProductsRequestDelegate> {
/** 購(gòu)買(mǎi)類(lèi)型*/
int buyType;
}
/** 產(chǎn)品 ID*/
@property (nonatomic, strong) NSString *productID;
/** Init*/
+ (PaymentManager *)manager;
#pragma mark - 方法相關(guān)
/** 判斷當(dāng)前環(huán)境是否支持支付購(gòu)買(mǎi)*/
- (void)buyInfo:(NSDictionary *)buyDic AndViewController:(UIViewController *)vc AndSn:(NSString *)sn;
/**
校驗(yàn)交易憑證 - App Store (Plan A)
該方法以客戶(hù)端為基準(zhǔn):
若客戶(hù)端校驗(yàn)結(jié)果失敗,則服務(wù)器不再進(jìn)行二次校驗(yàn);
若客戶(hù)端校驗(yàn)結(jié)果成功,則服務(wù)器再次進(jìn)行二次校驗(yàn).
@param transaction 交易事物
*/
- (void)verifyTransaction:(SKPaymentTransaction *)transaction;
/** 交易失敗*/
- (void)failedTransaction:(SKPaymentTransaction *)transaction;
@end
/*
1.蘋(píng)果APP(商家)
2.告訴蘋(píng)果Store服務(wù)器要賣(mài)的商品
3.蘋(píng)果審核完(告訴你是否可以賣(mài))
4.用戶(hù)(買(mǎi)商品)
5.蘋(píng)果APP(商家)
6.開(kāi)發(fā)票給(用戶(hù))
7.用戶(hù)(拿著發(fā)票去蘋(píng)果Store服務(wù)器付款)
8.付款成功(用戶(hù)在APP里獲得服務(wù)商品)
注:如果要模擬測(cè)試內(nèi)購(gòu),需要用真機(jī)才可以測(cè)試
*/
#import "PaymentManager.h"
#import <objc/runtime.h>
//在內(nèi)購(gòu)項(xiàng)目中創(chuàng)的商品單號(hào)
#define ProductID_IAP0p20 @"***此處與實(shí)際內(nèi)購(gòu)價(jià)格配置表為準(zhǔn)***" //20
#define ProductID_IAP1p100 @"***此處與實(shí)際內(nèi)購(gòu)價(jià)格配置表為準(zhǔn)***" //100
#define ProductID_IAP4p600 @"***此處與實(shí)際內(nèi)購(gòu)價(jià)格配置表為準(zhǔn)***" //600
#define ProductID_IAP9p1000 @"***此處與實(shí)際內(nèi)購(gòu)價(jià)格配置表為準(zhǔn)***" //1000
#define ProductID_IAP24p6000 @"***此處與實(shí)際內(nèi)購(gòu)價(jià)格配置表為準(zhǔn)***" //6000
#define PaySucceed @"充值成功"
#define PayFailed @"充值失敗"
#define PayException @"訂單發(fā)生異常,請(qǐng)聯(lián)系客服"
#define RequestError @"支付成功,等待驗(yàn)證"
/*
AppStore增加了驗(yàn)證內(nèi)購(gòu)(In App Purchasement)的方法, 就是蘋(píng)果提供一個(gè)url地址:
當(dāng)購(gòu)買(mǎi)成功時(shí), 會(huì)得到蘋(píng)果返回的一個(gè)收據(jù)(receipt), 蘋(píng)果推薦的方法是將收據(jù)發(fā)給開(kāi)發(fā)者的 server 服務(wù)端, 由 server 服務(wù)端向上述地址發(fā)起請(qǐng)求(post http)消息, 進(jìn)行驗(yàn)證, 蘋(píng)果將校驗(yàn)結(jié)果返回.此次交易憑證是真購(gòu)買(mǎi)憑證還是偽購(gòu)買(mǎi)憑證.
開(kāi)發(fā)環(huán)境地址: https://sandbox.itunes.apple.com/verifyReceipt
生成環(huán)境地址: https://buy.itunes.apple.com/verifyReceipt
*/
#define SANDBOX_VERIFY_RECEIPT_URL [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"]
#define APP_STORE_VERIFY_RECEIPT_URL [NSURL URLWithString:@"https://buy.itunes.apple.com/verifyReceipt"]
#ifdef DEBUG
#define VERIFY_RECEIPT_URL SANDBOX_VERIFY_RECEIPT_URL
#else
#define VERIFY_RECEIPT_URL APP_STORE_VERIFY_RECEIPT_URL
#endif
@interface PaymentManager () <SKPaymentTransactionObserver,SKProductsRequestDelegate, SKRequestDelegate>
/** 訂單編號(hào)*/
@property (nonatomic, strong) NSString *tradeNo;
@end
@implementation PaymentManager
#pragma mark - Init
+ (PaymentManager *)manager {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
#pragma mark - ************************************ 支付初始化
/**
Pay Method 支付初始化
具體方法介紹詳見(jiàn)上面注釋中連接地址
注:切記綁定 <SKPaymentTransactionObserver> 事件,設(shè)置購(gòu)買(mǎi)隊(duì)列的監(jiān)聽(tīng)器,實(shí)時(shí)監(jiān)聽(tīng)跟蹤訂單狀態(tài),避免發(fā)送丟單的意外,即 [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
@param buyDic 支付所對(duì)應(yīng)的產(chǎn)品信息
@param vc 當(dāng)前 VC 控件
@param sn 用戶(hù)信息
*/
- (void)buyInfo:(NSDictionary *)buyDic AndViewController:(UIViewController *)vc AndSn:(NSString *)sn {
//判斷網(wǎng)絡(luò)是否可用
self.productID = [NSString stringWithFormat:@"%@", [buyDic objectForKey:@"productId"]];
// 訂單編號(hào)
NSString *tradeNo = [NSString stringWithFormat:@"%@", [result objectForKey:@"tradeNo"]];
//設(shè)置購(gòu)買(mǎi)隊(duì)列的監(jiān)聽(tīng)器
[[SKPaymentQueue defaultQueue] addTransactionObserver:weakself];
//判斷當(dāng)前是否可支付
if ([SKPaymentQueue canMakePayments]) {
NSLog(@"允許程序內(nèi)付費(fèi)購(gòu)買(mǎi)");
//請(qǐng)求產(chǎn)品數(shù)據(jù)
[self fetchProductInformationForIds:self.productID AndTradeNo:tradeNo];
} else {
NSLog(@"不允許程序內(nèi)付費(fèi)購(gòu)買(mǎi)");
// Callback
//[MBProgressHUD showError:@"請(qǐng)開(kāi)啟手機(jī)內(nèi)付費(fèi)購(gòu)買(mǎi)功能" toView:vc.view];
[self hudAlertMessage:@"請(qǐng)開(kāi)啟手機(jī)內(nèi)付費(fèi)購(gòu)買(mǎi)功能"];
}
}
/**
獲取對(duì)應(yīng)的產(chǎn)品數(shù)據(jù)信息
@param productIds 產(chǎn)品 id
@param tradeNo 訂單編號(hào)
*/
- (void)fetchProductInformationForIds:(NSString *)productIds AndTradeNo:(NSString *)tradeNo {
NSLog(@"------------請(qǐng)求對(duì)應(yīng)的產(chǎn)品信息------------");
self.tradeNo = tradeNo;
NSArray *product = [[NSArray alloc] initWithObjects:productIds, nil];
//為該產(chǎn)品標(biāo)識(shí)符創(chuàng)建一個(gè)集合
NSSet *nsSet = [NSSet setWithArray:product];
//創(chuàng)建該產(chǎn)品請(qǐng)求對(duì)象,并將上面的集合進(jìn)行初始化它
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsSet];
request.delegate = self;
//向 App Store 發(fā)起請(qǐng)求
[request start];
}
#pragma mark - ************************************ 交易處理中
/**
獲取 App Store 產(chǎn)品反饋信息
注:設(shè)置請(qǐng)求協(xié)議代理: <SKProductsRequestDelegate>
@param request 請(qǐng)求
@param response 應(yīng)用結(jié)果
*/
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
NSLog(@"------------收到產(chǎn)品反饋消息------------");
/** 當(dāng)前產(chǎn)品信息*/
NSArray *myProduct = response.products;
if (0 == myProduct.count) {
NSLog(@"------------暫無(wú)商品------------");
// Callback
//[MBProgressHUD showError:@"App Store支付異常,請(qǐng)重新嘗試" toView:vc.view];
[self hudAlertMessage:@"App Store支付異常,請(qǐng)重新嘗試"];
return;
}
else {
NSLog(@"------------預(yù)購(gòu)商品------------");
SKProduct *p = nil;
for (SKProduct *product in myProduct) {
NSLog(@"*** 產(chǎn)品信息相關(guān)[product info] ***\n1.描述信息(SKProduct): %@\n2.產(chǎn)品標(biāo)題(Title): %@\n3.產(chǎn)品描述信息(Description): %@\n4.產(chǎn)品價(jià)格(Price): %@\n5.產(chǎn)品 id(Product id): %@", product, product.localizedTitle, product.localizedDescription, product.price, product.productIdentifier);
if([product.productIdentifier isEqualToString:self.productID]){
p = product;
}
}
if (p != nil) {
// 將要購(gòu)買(mǎi)的產(chǎn)品
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:p];// The product is available, let's submit a payment request to the queue
NSLog(@"------------發(fā)送購(gòu)買(mǎi)請(qǐng)求------------");
// 發(fā)起準(zhǔn)備購(gòu)買(mǎi)流程(異步)
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
}
}
#pragma mark - ************************************ 交易校驗(yàn)中
/**
校驗(yàn)交易憑證 - App Store
該方法以客戶(hù)端為基準(zhǔn):
若客戶(hù)端校驗(yàn)結(jié)果失敗,則服務(wù)器不再進(jìn)行二次校驗(yàn);
若客戶(hù)端校驗(yàn)結(jié)果成功,則服務(wù)器再次進(jìn)行二次校驗(yàn).
@param transaction 交易事物
*/
- (void)verifyTransaction:(SKPaymentTransaction *)transaction {
/*
使用如下方法獲取購(gòu)買(mǎi)憑證也 ok,則需要對(duì) data 進(jìn)行判空操作,
NSData *transactionReceipt = [PaymentManager receiptDataFromTransaction:transaction];
// 若 data 為空則執(zhí)行如下方法
SKReceiptRefreshRequest *receiptRefreshRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:nil];
receiptRefreshRequest.delegate = self;
[receiptRefreshRequest start];
return;
*/
// 從沙盒中獲取到購(gòu)買(mǎi)憑據(jù)
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
if ([[NSFileManager defaultManager] fileExistsAtPath:[receiptURL path]]) {
NSData *transactionReceipt = [NSData dataWithContentsOfURL:receiptURL];
NSString *encodeStr = [transactionReceipt base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];//轉(zhuǎn)化為base64字符串
/*
驗(yàn)證自動(dòng)訂閱的有效 receipt 示例
{
"receipt-data" : "...",
"password" : "..."
}
*/
// 拼接請(qǐng)求數(shù)據(jù)
NSString *bodyStr = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr];
NSData *bodyData = [bodyStr dataUsingEncoding:NSUTF8StringEncoding];
// 創(chuàng)建請(qǐng)求,驗(yàn)證憑證,蘋(píng)果服務(wù)器比較坑,建議超時(shí)時(shí)長(zhǎng)設(shè)置稍稍長(zhǎng)一些
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:VERIFY_RECEIPT_URL
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:10.0f];
request.HTTPBody = bodyData;
request.HTTPMethod = @"POST";
NSError *error = nil;
// 創(chuàng)建連接并發(fā)送同步請(qǐng)求,獲得官方的驗(yàn)證JSON結(jié)果
NSData *responseData = [NSURLConnection sendSynchronousRequest:request
returningResponse:nil
error:&error];
if (error) {
NSLog(@"App Store 驗(yàn)證購(gòu)買(mǎi)過(guò)程中發(fā)生錯(cuò)誤,錯(cuò)誤信息: %@",error.localizedDescription);
// Callback
//[MBProgressHUD showError:@"網(wǎng)絡(luò)請(qǐng)求超時(shí),請(qǐng)重試" toView:vc.view];
[self hudAlertMessage:@"網(wǎng)絡(luò)請(qǐng)求超時(shí),請(qǐng)重試"];
}
else {
NSDictionary *result = [NSJSONSerialization JSONObjectWithData:responseData
options:NSJSONReadingAllowFragments
error:&error];
/*
內(nèi)購(gòu)驗(yàn)證憑據(jù)返回結(jié)果狀態(tài)碼說(shuō)明(status 狀態(tài))
21000 App Store無(wú)法讀取你提供的JSON數(shù)據(jù)
21002 收據(jù)數(shù)據(jù)不符合格式
21003 收據(jù)無(wú)法被驗(yàn)證
21004 你提供的共享密鑰和賬戶(hù)的共享密鑰不一致
21005 收據(jù)服務(wù)器當(dāng)前不可用
21006 收據(jù)是有效的,但訂閱服務(wù)已經(jīng)過(guò)期。當(dāng)收到這個(gè)信息時(shí)艇炎,解碼后的收據(jù)信息也包含在返回內(nèi)容中
21007 收據(jù)信息是測(cè)試用(sandbox),但卻被發(fā)送到產(chǎn)品環(huán)境中驗(yàn)證
21008 收據(jù)信息是產(chǎn)品環(huán)境中使用腾窝,但卻被發(fā)送到測(cè)試環(huán)境中驗(yàn)證
*/
NSString *status = [NSString stringWithFormat:@"%@", [result objectForKey:@"status"]];
if (0 == [status intValue]) {
NSLog(@"App Store 驗(yàn)證購(gòu)買(mǎi) --- 成功");
NSDictionary *dicReceipt = [NSDictionary dictionaryWithDictionary:[result objectForKey:@"receipt"]];
NSArray *arrInApp = [dicReceipt objectForKey:@"in_app"];
// 注:此處 in_app 字段中數(shù)據(jù)可能為多個(gè),需進(jìn)行循環(huán)
NSMutableDictionary *dicInAppReult = [NSMutableDictionary dictionary];
for (NSDictionary *dict in arrInApp) {
/** 產(chǎn)品標(biāo)識(shí)*/
NSString *product_id = [NSString stringWithFormat:@"%@", [dict objectForKey:@"product_id"]];
/** 事物標(biāo)識(shí)*/
NSString *transaction_id = [NSString stringWithFormat:@"%@", [dict objectForKey:@"transaction_id"]];
[dicInAppReult setValue:product_id forKey:transaction_id];
}
NSString *bundle_id = [NSString stringWithFormat:@"%@", [dicReceipt objectForKey:@"bundle_id"]];
NSString *application_version = [NSString stringWithFormat:@"%@", [dicReceipt objectForKey:@"application_version"]];
if ([bundle_id isEqualToString:kGetBundleId] &&
[application_version isEqualToString:kAppBundle] &&
[dicInAppReult.allKeys containsObject:transaction.transactionIdentifier] &&
[[dicInAppReult objectForKey:transaction.transactionIdentifier] isEqualToString:transaction.payment.productIdentifier]
) {
NSLog(@"App Store 憑證驗(yàn)證 --- 成功");
// 交易成功且憑證驗(yàn)證成功向服務(wù)端提交憑證進(jìn)行處理
[self commitSeversSucceeWithTransaction:transaction];
}
else {
NSLog(@"App Store 憑證驗(yàn)證 --- 失敗");
// Remove the transaction from the payment queue.
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
// Callback
//[MBProgressHUD showError:PayFailed toView:vc.view];
[self hudAlertMessage:PayFailed];
}
}
else {
NSLog(@"App Store 憑證驗(yàn)證 --- 失敗: %@", error.localizedDescription);
// Remove the transaction from the payment queue.
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
// Callback
//[MBProgressHUD showError:PayFailed toView:vc.view];
[self hudAlertMessage:PayFailed];
}
}
}
else {
SKReceiptRefreshRequest *receiptRefreshRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:nil];
receiptRefreshRequest.delegate = self;
[receiptRefreshRequest start];
return;
}
}
/**
獲取交易憑證
@param transaction 交易事物
@return 結(jié)果集
*/
+ (NSData *)receiptDataFromTransaction:(SKPaymentTransaction *)transaction {
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
if ([[NSFileManager defaultManager] fileExistsAtPath:[receiptURL path]]) {
// 從沙盒中獲取到購(gòu)買(mǎi)憑據(jù)
NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
if (!receiptData) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
if ([transaction respondsToSelector:@selector(transactionReceipt)]) {
//Works in iOS3 - iOS8, deprected since iOS7, actual deprecated (returns nil) since
receiptData = transaction.transactionReceipt;
}
#pragma clang diagnostic pop
}
return receiptData;
}
else {
return nil;
}
}
#pragma mark - ************************************ 交易成功 - 向公司服務(wù)器驗(yàn)證購(gòu)買(mǎi)憑證
/**
交易成功 - 向公司服務(wù)器驗(yàn)證購(gòu)買(mǎi)憑證
status:0:訂單開(kāi)始,1:充值成功,2:充值失敗
@param transaction 交易事務(wù)
*/
- (void)commitSeversSucceeWithTransaction:(SKPaymentTransaction *)transaction {
NSLog(@"------------交易成功向公司服務(wù)器驗(yàn)證購(gòu)買(mǎi)憑證------------");
#pragma mark - 交易憑證相關(guān)
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
if (![[NSFileManager defaultManager] fileExistsAtPath:[receiptURL path]]) {
// 取 receipt 的時(shí)候要判空,如果文件不存在,就要從蘋(píng)果服務(wù)器重新刷新下載 receipt 了
// SKReceiptRefreshRequest 刷新的時(shí)候,需要用戶(hù)輸入 Apple ID,同時(shí)需要網(wǎng)絡(luò)狀態(tài)良好
SKReceiptRefreshRequest *receiptRefreshRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:nil];
receiptRefreshRequest.delegate = self;
[receiptRefreshRequest start];
return;
}
NSData *data = [NSData dataWithContentsOfURL:receiptURL];
/** 交易憑證*/
NSString *receipt_data = [data base64EncodedStringWithOptions:0];
/** 事務(wù)標(biāo)識(shí)符(交易編號(hào)) 交易編號(hào)(必傳:防止越獄下內(nèi)購(gòu)被破解,校驗(yàn) in_app 參數(shù))*/
NSString *transaction_id = transaction.transactionIdentifier;
// 此處忽略,純好奇心所驅(qū),一個(gè)神奇的 data,拆不出來(lái) ... 你贏了
// NSString * test1 = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
// NSString * test2 = [data base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
// NSError *error;
// NSDictionary * test3 = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
// 判空相關(guān)
if (receipt_data == nil) {
receipt_data = @"";
}
if (transaction_id == nil) {
transaction_id = @"";
}
NSLog(@"交易憑證:\n%@", receipt_data);
NSLog(@"事務(wù)標(biāo)識(shí)符(交易編號(hào)):\n%@", transaction_id);
NSLog(@"產(chǎn)品標(biāo)識(shí)符(內(nèi)購(gòu)產(chǎn)品編號(hào)) --- productIdentifier:\n%@", transaction.payment.productIdentifier);
// NSLog(@"交易日期\nDate: %@,Date(String): %@", transaction_date, strTransaction_date);
// NSLog(@"事物狀態(tài):\n%@", transaction_state);
NSMutableDictionary *dicParameter = [NSMutableDictionary dictionary];
[dicParameter setValue:kAppBundle forKey:@"appBundle"];
[dicParameter setValue:transaction_id forKey:@"transactionId"];// 查明交易標(biāo)識(shí)符(防止越獄下內(nèi)購(gòu)被破解,校驗(yàn) in_app 參數(shù))
[dicParameter setValue:receipt_data forKey:@"receiptData"];// 收到的收據(jù),即收據(jù)證明 transactionReceipt
kWeakSelf(self);
#pragma mark - 向服務(wù)端發(fā)起請(qǐng)求傳遞所需憑證參數(shù)
// Request
[[HttpRequestManager shareInstance] PayPOST:URL_ApplePay parameters:dicParameter isEncipherment:NO success:^(id responseObject) {
NSDictionary *result = [NSDictionary dictionaryWithDictionary:responseObject];
NSString *status = [NSString stringWithFormat:@"%@", [result objectForKey:@"payStatus"]];
NSLog(@"Pay Callback Result: %@", result);
// Callback
if ([status isEqualToString:@"success"]) {// 成功
// Callback
//[MBProgressHUD showError:PaySucceed toView:vc.view];
[self hudAlertMessage:PaySucceed];
}
else {
// Callback
//[MBProgressHUD showError:PayException toView:vc.view];
[self hudAlertMessage:PayException];
}
// Remove the transaction from the payment queue.
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
} failure:^(NSError *error) {
NSLog(@"commitSeversSucceeWithTransaction: %@", error.localizedDescription);
// Callback
//[MBProgressHUD showError:RequestError toView:vc.view];
[self hudAlertMessage:RequestError];
}];
}
#pragma mark - ************************************ 交易失敗相關(guān)
/*
內(nèi)購(gòu)驗(yàn)證憑據(jù)返回結(jié)果狀態(tài)碼說(shuō)明
21000 App Store無(wú)法讀取你提供的JSON數(shù)據(jù)
21002 收據(jù)數(shù)據(jù)不符合格式
21003 收據(jù)無(wú)法被驗(yàn)證
21004 你提供的共享密鑰和賬戶(hù)的共享密鑰不一致
21005 收據(jù)服務(wù)器當(dāng)前不可用
21006 收據(jù)是有效的缀踪,但訂閱服務(wù)已經(jīng)過(guò)期。當(dāng)收到這個(gè)信息時(shí)虹脯,解碼后的收據(jù)信息也包含在返回內(nèi)容中
21007 收據(jù)信息是測(cè)試用(sandbox)驴娃,但卻被發(fā)送到產(chǎn)品環(huán)境中驗(yàn)證
21008 收據(jù)信息是產(chǎn)品環(huán)境中使用,但卻被發(fā)送到測(cè)試環(huán)境中驗(yàn)證
*/
#pragma mark - 交易失敗 -> 彈出錯(cuò)誤信息 Error
- (void)failedTransaction:(SKPaymentTransaction *)transaction {
NSLog(@"*** 交易失敗 Error code: (%d)",transaction.error.code);
if (transaction.error != nil) {
switch (transaction.error.code) {
case SKErrorUnknown:
NSLog(@"SKErrorUnknown --- 未知的錯(cuò)誤循集,您可能正在使用越獄手機(jī)");
break;
case SKErrorClientInvalid:
NSLog(@"SKErrorClientInvalid --- 當(dāng)前蘋(píng)果賬戶(hù)無(wú)法購(gòu)買(mǎi)商品(如有疑問(wèn)唇敞,可以詢(xún)問(wèn)蘋(píng)果客服)");
break;
case SKErrorPaymentCancelled:
NSLog(@"SKErrorPaymentCancelled --- 訂單已取消");
break;
case SKErrorPaymentInvalid:
NSLog(@"SKErrorPaymentInvalid --- 訂單無(wú)效(如有疑問(wèn),可以詢(xún)問(wèn)蘋(píng)果客服)");
break;
case SKErrorPaymentNotAllowed:
NSLog(@"SKErrorPaymentNotAllowed --- 當(dāng)前蘋(píng)果設(shè)備無(wú)法購(gòu)買(mǎi)商品(如有疑問(wèn)咒彤,可以詢(xún)問(wèn)蘋(píng)果客服)");
break;
case SKErrorStoreProductNotAvailable:
NSLog(@"SKErrorStoreProductNotAvailable --- 當(dāng)前商品不可用");
break;
default:
NSLog(@"No Match Found for error -- 未知錯(cuò)誤");
break;
}
}
// Callback
//[MBProgressHUD showError:PayFailed toView:vc.view];
[self hudAlertMessage:PayFailed];
// Remove the transaction from the payment queue.
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
-(void)requestDidFinish:(SKRequest *)request {
NSLog(@"------------反饋信息結(jié)束------------");
}
#pragma mark - 交易失敗 - 請(qǐng)求失敗 -> 彈出錯(cuò)誤信息 Error
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
NSLog(@"------------彈出錯(cuò)誤信息------------");
// Callback
//[MBProgressHUD showError:error.localizedDescription toView:vc.view];
[self hudAlertMessage:error.localizedDescription];
}
#pragma mark - 交易恢復(fù)處理
- (void)restoreTransaction:(SKPaymentTransaction *)transaction {
// Remove the transaction from the payment queue.
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
#pragma mark - 完成付款隊(duì)列恢復(fù)完成交易
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentTransaction *)transaction {
NSLog(@"****** 完成付款隊(duì)列恢復(fù)完成交易: %@", transaction.transactionIdentifier);
/*
NSMutableArray *productIDsToRestore = From the user ;
SKPaymentTransaction *transaction = Current transaction ;
if ([productIDsToRestore containsObject:transaction.transactionIdentifier]) {
// Re-download the Apple-hosted content, then finish the transaction
// and remove the product identifier from the array of product IDs.
} else {
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
*/
}
#pragma mark - 付款隊(duì)列
- (void)paymentQueue:(SKPaymentQueue *)paymentQueue restoreCompletedTransactionsFailedWithError:(NSError *)error {
NSLog(@"------------付款隊(duì)列(當(dāng)從用戶(hù)的購(gòu)買(mǎi)歷史記錄向隊(duì)列添加事務(wù)時(shí)遇到錯(cuò)誤時(shí)發(fā)送)------------\n%@", error.localizedDescription);
}
#pragma mark - 購(gòu)買(mǎi)交易
- (void)purchasedTransaction:(SKPaymentTransaction *)transaction {
NSLog(@"------------購(gòu)買(mǎi)交易------------");
NSArray *transactions =[[NSArray alloc] initWithObjects:transaction, nil];
[self paymentQueue:[SKPaymentQueue defaultQueue] updatedTransactions:transactions];
}
/**
提示框
@param msg 提示語(yǔ)
*/
- (void)hudAlertMessage:(NSString *)msg {
UIAlertView *alerView = [[UIAlertView alloc] initWithTitle:@""
message:msg
delegate:nil
cancelButtonTitle:NSLocalizedString(@"確定",nil)
otherButtonTitles:nil];
[alerView show];
}
#pragma mark - ************************************ Connection delegate
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(@"connection delegate --- %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
switch([(NSHTTPURLResponse *)response statusCode]) {
case 200:
case 206:
break;
case 304:
break;
case 400:
break;
case 404:
break;
case 416:
break;
case 403:
break;
case 401:
case 500:
break;
default:
break;
}
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(@"connection:(NSURLConnection *)connection didFailWithError:(NSError *)error: %@", error.localizedDescription);
}
- (void)dealloc {
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];// 解除監(jiān)聽(tīng)
}
AppDelegate 中 Code:
#import "AppDelegate.h"
// Apple pay
#import <StoreKit/StoreKit.h>
#import "PaymentManager.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
// 將觀察者添加到支付隊(duì)列中
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
return YES;
}
#pragma mark - ****************************** Apple Pay
/**
監(jiān)聽(tīng)購(gòu)買(mǎi)交易結(jié)果 transactions
@param queue 交易隊(duì)列
@param transactions 交易事物
*/
- (void)paymentQueue:(nonnull SKPaymentQueue *)queue updatedTransactions:(nonnull NSArray<SKPaymentTransaction *> *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:// 商品添加進(jìn)列表
// Transaction is being added to the server queue.
NSLog(@"------------商品添加進(jìn)列表------------");
/* 不用finish疆柔,繼續(xù)觀察支付隊(duì)列,等待transaction狀態(tài)改變 */
break;
case SKPaymentTransactionStateDeferred:
// The transaction is in the queue, but its final status is pending external action.
NSLog(@"------------事務(wù)在隊(duì)列中,但其最終狀態(tài)是等待外部操作");
/* 不用finish镶柱,繼續(xù)觀察支付隊(duì)列 */
break;
case SKPaymentTransactionStateFailed:// 交易失敗
// Transaction was cancelled or failed before being added to the server queue.
NSLog(@"------------交易失敗: %@", transaction.error.localizedDescription);
/* 檢查錯(cuò)誤并根據(jù)需要處理旷档,然后調(diào)用 finishTransaction */
[[PaymentManager manager] failedTransaction:transaction];
break;
case SKPaymentTransactionStatePurchased:// 交易完成
// Transaction is in queue, user has been charged. Client should complete the transaction.
NSLog(@"------------交易完成: %@", transaction.payment.productIdentifier);
/* 分發(fā)內(nèi)容給用戶(hù),然后調(diào)用 finishTransaction */
[[PaymentManager manager] verifyTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:// 已經(jīng)購(gòu)買(mǎi)過(guò)該商品
// Transaction was restored from user's purchase history. Client should complete the transaction.
NSLog(@"------------已經(jīng)購(gòu)買(mǎi)過(guò)該商品");
/* 分發(fā)內(nèi)容給用戶(hù)歇拆,然后調(diào)用 finishTransaction */
[[PaymentManager manager] verifyTransaction:transaction];
break;
default:
break;
}
}
}
以上便是此次內(nèi)購(gòu)支付相關(guān)小結(jié),還望多多指點(diǎn)交流!