iOS開發(fā) IAP支付總結(jié)
一胸私、IAP介紹
1.1锌仅、簡介
這里先把官方文檔給大家
內(nèi)購:只要在iPhone App上購買的不是實物產(chǎn)品(也就是虛擬產(chǎn)品如qq幣摄凡、魚翅蝎宇、電子書......) 都需要走內(nèi)購流程,蘋果這里面抽走30%
蘋果規(guī)定徽龟,凡是在App內(nèi)提供的服務(wù)需要付費時贤旷,必須使用IAP广料,比如說游戲的金幣,道具等幼驶;而在App外提供的服務(wù)需要付費時艾杏,可以使用其他的支付方式,比如支付寶SDK盅藻、微信SDK等购桑。說的更通俗一點,如果付費購買的商品是虛擬商品氏淑,比如游戲中的道具勃蜘,并不是現(xiàn)實中存在的,那么必須使用IAP假残;如果付費購買的商品是真實產(chǎn)品缭贡,比如在淘寶中買了件衣服炉擅,是實實在在存在的,那么沒有必要使用IAP阳惹。因此谍失,在使用IAP之前,首先要確認(rèn)是否一定要使用IAP莹汤,如果不使用IAP也可以快鱼,那么盡量不要用IAP,因為IAP流程纲岭、使用復(fù)雜度相比支付寶SDK抹竹、微信SDK來說,要復(fù)雜很多止潮。
1.2柒莉、內(nèi)購流程
1.1.1 填寫協(xié)議,稅務(wù)和銀行業(yè)務(wù)
1、登錄https://appstoreconnect.apple.com沽翔,選擇進(jìn)入App Store Connect兢孝。
2、進(jìn)入“協(xié)議仅偎、稅務(wù)和銀行業(yè)務(wù)”
3跨蟹、內(nèi)購用的是付費應(yīng)用程序,先簽署《付費應(yīng)用程序協(xié)議》橘沥,同意后狀態(tài)變更為“用戶信息待處理”窗轩,等待審核。
4座咆、狀態(tài)更改完畢后痢艺,點擊“開始設(shè)置稅務(wù)、銀行業(yè)務(wù)和聯(lián)系信息”介陶。
a.添加銀行賬戶,按照要求填寫相關(guān)內(nèi)容即可堤舒。
b.選擇報稅表,并填寫哺呜。(我是可愛的中國公民舌缤,在美國有沒有商業(yè)活動,所以我填的是否某残。)
然后繼續(xù)填寫報稅表国撵,按照填寫要求填寫就行了(要是英文閱讀有點困難,那就雙擊網(wǎng)頁玻墅,應(yīng)該會有翻譯成中文的功能介牙;沒有的話,那就詞典澳厢。环础。囚似。你懂得,哈哈哈)喳整, 我是個人開發(fā)者賬戶相對公司開發(fā)者賬戶填的會少一點,不過沒關(guān)系裸扶。都是一些基本信息框都。
c.填寫聯(lián)系信息,一共5個呵晨。高級管理魏保、財務(wù)、技術(shù)摸屠、法務(wù)谓罗、營銷。
5季二、上面的稅務(wù)表填完了之后檩咱,點擊“我的APP”,進(jìn)入到項目APP的信息頁胯舷,點擊功能刻蚯,在彈出的頁面點擊App內(nèi)購買項目后面的+。
創(chuàng)建完成之后 填寫內(nèi)購買項目信息
信息填寫完成了點擊右上角的 “存儲”桑嘶,然后點擊左邊 “App 內(nèi)購買項目”炊汹。出現(xiàn)“元數(shù)據(jù)丟失”說明里面信息沒填寫完整,在點進(jìn)去填寫逃顶。直到顯示“準(zhǔn)備提交”讨便。
6、添加沙箱測試人員
7以政、我們需要在工程里配置好證書霸褒,測試證書是必須的因為我們內(nèi)購需要連接到蘋果的App Store的,需要正式的測試證書才能測試盈蛮,同時把下圖工程中的這一配置打開
二傲霸、IAP代碼部分
我這里就直接上代碼記錄了
2.1、大體代碼流程
typedefenum: NSUInteger {
EPaymentTransactionStateNoPaymentPermission,//沒有Payment權(quán)限
EPaymentTransactionStateAddPaymentFailed,//addPayment失敗
EPaymentTransactionStatePurchasing,//正在購買
EPaymentTransactionStatePurchased,//購買完成(銷毀交易)
EPaymentTransactionStateFailed,//購買失敗(銷毀交易)
EPaymentTransactionStateCancel,//用戶取消
EPaymentTransactionStateRestored,//恢復(fù)購買(銷毀交易)
EPaymentTransactionStateDeferred,//最終狀態(tài)未確定
} EPaymentTransactionState;
// 這個大家要熟悉哦~
步驟一:App Store請求內(nèi)購項
注意:此步驟建議在開始創(chuàng)建購買訂單前完成眉反,這樣可以減少購買時查詢訂單的時間
1昙啄、判斷用戶是否具備支付權(quán)限
//是否允許內(nèi)購
if ([SKPaymentQueue canMakePayments]) {
[self getRequestAppleProduct];
}else{
[self removeLoadingHandle];
[self removeIAPObserverHandle];
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUDManager showFailedHUD:TZKeyWindow text:@"請打開Apple支付"];
});
}
2、創(chuàng)建一個商品查詢的請求寸五,productIdentifiers指需要查詢的“產(chǎn)品ID”的數(shù)組
- (void)getRequestAppleProduct
{
NSLog(@"---------請求對應(yīng)的產(chǎn)品信息------------");
[MBProgressHUDManager showHUD:TZKeyWindow text:@"等待響應(yīng)..."];
NSArray *product = [[NSArray alloc] initWithObjects:self.productID, nil];
NSSet *nsset = [NSSet setWithArray:product];
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
request.delegate = self;
[request start];
}
查詢的結(jié)果將通過SKProductsRequestDelegate得到查詢的結(jié)果
獲取商品的查詢結(jié)果
#pragma mark - SKProductsRequestDelegate
//接收到產(chǎn)品的返回信息梳凛,然后用返回的商品信息進(jìn)行發(fā)起購買請求
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response NS_AVAILABLE_IOS(3_0){
NSArray *product = response.products;
//沒有產(chǎn)品
if([product count] == 0){
[self removeLoadingHandle];
[self removeIAPObserverHandle];
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUDManager showFailedHUD:TZKeyWindow text:@"網(wǎng)絡(luò)開小差了,請稍后重試"];
});
return;
}
SKProduct *requestProduct = nil;
for (SKProduct *pro in product) {
CYLOG(@"描述信息-%@", [pro description]);
CYLOG(@"產(chǎn)品標(biāo)題-%@", [pro localizedTitle]);
CYLOG(@"產(chǎn)品描述信息-%@", [pro localizedDescription]);
CYLOG(@"價格-%@", [pro price]);
CYLOG(@"Product id-%@", [pro productIdentifier]);
CYLOG(@"位置-%@", pro.priceLocale.localeIdentifier);
// 確保訂單的正確性
if([pro.productIdentifier isEqualToString:self.productID]){
requestProduct = pro;
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:requestProduct];
payment.applicationUsername = self.orderId;
[[SKPaymentQueue defaultQueue] addPayment:payment];
break;
}
}
}``
步驟二:開始構(gòu)建購買請求
SKPayment * payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
步驟三:添加支付交易的Observer
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
注意在適當(dāng)?shù)臅r候移除Observer
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
可以通過遵循SKPaymentTransactionObserver協(xié)議來監(jiān)聽整個交易的過程
交易狀態(tài)發(fā)生改變時,包括狀態(tài)的改變梳杏,交易的結(jié)束
//監(jiān)聽購買結(jié)果
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
NSLog(@"==監(jiān)聽購買結(jié)果==");
[self addLoadingHandle];
[self addIAPObserverHandle];
for(SKPaymentTransaction *tran in transactions){
switch (tran.transactionState) {
case SKPaymentTransactionStatePurchased:
{
NSLog(@"交易完成");
[self didPurchaseTransaction:tran queue:queue];
}
break;
case SKPaymentTransactionStatePurchasing:{
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUDManager showHUD:TZKeyWindow text:@"正在購買..."];
});
}
break;
case SKPaymentTransactionStateRestored:{
CYLOG(@"已經(jīng)購買過商品");
//消耗型不用寫
// [self removeLoadingHandle];
// [self removeIAPObserverHandle];
// [[SKPaymentQueue defaultQueue] finishTransaction:tran];
}
break;
case SKPaymentTransactionStateFailed:{
NSLog(@"交易失敗");
[self removeLoadingHandle];
[self removeIAPObserverHandle];
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUDManager showFailedHUD:TZKeyWindow text:@"交易失敗"];
});
}
break;
case SKPaymentTransactionStateDeferred:{
NSLog(@"還在隊列里");
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUDManager showHUD:TZKeyWindow text:@"正在購買..."];
});
}
break;
default:
break;
}
}
}
步驟四:校驗憑證
我這里直接把我這邊相關(guān)的思路以及代碼提供大家參考了:
有問題可以隨時聯(lián)系我韧拒、我后面會講一下我所遇到過的坑以及解決方案淹接。
#pragma mark Transaction State
我這里使用后臺校驗憑證、更加安全
- (void)didPurchaseTransaction:(SKPaymentTransaction *)transaction queue:(SKPaymentQueue*)queue
{
CYLOG(@"transaction purchased with product ---%@", transaction.payment.productIdentifier);
CYLOG(@"transaction ID ---%@", transaction.transactionIdentifier);
if(transaction.payment.productIdentifier != nil){
if([self.orderId length] && !self.ischecking){
//如果這個參數(shù)存在叛溢,則肯定是通過主動發(fā)起購買請求引起的
//在支付成功后塑悼,將parameters中的預(yù)訂單號存起來,并與蘋果的訂單號綁定起來楷掉,并存儲到keychain中
if([self.orderId length] && transaction.transactionIdentifier){
[SAMKeychain setPassword:self.orderId forService:TZServiceKey account:transaction.transactionIdentifier];
}
}
}
WS(weakSelf);
// appStoreReceiptURL iOS7.0增加的厢蒜,購買交易完成后,會將憑據(jù)存放在該地址
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
// 從沙盒中獲取到購買憑據(jù)
NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
NSString *payload = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr];
NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding];
NSString *applicationUsername = transaction.payment.applicationUsername;
NSString *productId = transaction.payment.productIdentifier;
NSString *transactionId = transaction.transactionIdentifier;
//發(fā)送POST請求烹植,對購買憑據(jù)進(jìn)行驗證
//測試驗證地址:https://sandbox.itunes.apple.com/verifyReceipt
//正式驗證地址:https://buy.itunes.apple.com/verifyReceipt
if(applicationUsername.length == 0){
NSString *savedOrderNumber = [SAMKeychain passwordForService:TZServiceKey account:transactionId];
if ([savedOrderNumber length]) {
applicationUsername = savedOrderNumber;//獲取到訂單號
}
}
if ([applicationUsername length] && [encodeStr length] && [productId length] && [transactionId length]) {
[[HTTPAPIManager manager] reqeustPayAppleReceiptWithOutTradeNo:applicationUsername receiptData:encodeStr useSandbox:TZPAYSandbox productId:productId transactionId:transactionId success:^(NSURLSessionDataTask * _Nullable task, id _Nullable responseObject, NSDictionary * _Nullable inforDict) {
_errTimes = 0;
weakSelf.timer = [NSTimer scheduledTimerWithTimeInterval:(2.0)
target:weakSelf
selector:@selector(handleTimer:)
userInfo:@{@"transaction":transaction}
repeats:YES];
[[NSRunLoop mainRunLoop]addTimer:weakSelf.timer forMode:NSDefaultRunLoopMode];
[weakSelf.timer setFireDate:[NSDate date]];
} failure:^(NSURLSessionDataTask * _Nullable task, YWHTTPError * _Nullable error, NSDictionary * _Nullable responseDict) {
_errTimes = 0;
weakSelf.timer = [NSTimer scheduledTimerWithTimeInterval:(2.0)
target:weakSelf
selector:@selector(handleTimer:)
userInfo:@{@"transaction":transaction}
repeats:YES];
[[NSRunLoop mainRunLoop]addTimer:weakSelf.timer forMode:NSDefaultRunLoopMode];
[weakSelf.timer setFireDate:[NSDate date]];
}];
if (!_ischecking) {
[MBProgressHUDManager showHUD:TZKeyWindow text:@"正在確認(rèn)支付..."];
}
} else {
[[HTTPAPIManager manager] reqeustAppleFailRecordWithOutTradeNo:applicationUsername receiptData:encodeStr productId:productId transactionId:transactionId success:^(NSURLSessionDataTask * _Nullable task, id _Nullable responseObject, NSDictionary * _Nullable inforDict) {
_ischecking = NO;
[MBProgressHUDManager hiddenHUD:TZKeyWindow];
[weakSelf destroyTimer];
[weakSelf removeLoadingHandle];
[weakSelf removeIAPObserverHandle];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
[[NSNotificationCenter defaultCenter] postNotificationName:TZBuyVipSuccessNotification object:nil userInfo:nil];
} failure:^(NSURLSessionDataTask * _Nullable task, YWHTTPError * _Nullable error, NSDictionary * _Nullable responseDict) {
[MBProgressHUDManager hiddenHUD:TZKeyWindow];
}];
}
}
三斑鸦、重點總結(jié)
1.獲取內(nèi)購列表(從App內(nèi)讀取或從自己服務(wù)器讀取)
2.App Store請求可用的內(nèi)購列表
3.向用戶展示內(nèi)購列表
4.用戶選擇了內(nèi)購列表草雕,再發(fā)個購買請求巷屿,收到購買完成的回調(diào)(購買完成
后會把錢打給申請內(nèi)購的銀行卡內(nèi))
5.購買流程結(jié)束后, 向服務(wù)器發(fā)起驗證憑證以及支付結(jié)果的請求
6.自己的服務(wù)器將支付結(jié)果信息返回給前端并發(fā)放虛擬產(chǎn)品
7.服務(wù)端的工作比較簡單,分4步:
7.1.接收ios端發(fā)過來的購買憑證墩虹。
7.2.判斷憑證是否已經(jīng)存在或驗證過嘱巾,然后存儲該憑證。
7.3.將該憑證發(fā)送到蘋果的服務(wù)器驗證诫钓,并將驗證結(jié)果返回給客戶端浓冒。
7.4.如果需要,修改用戶相應(yīng)的會員權(quán)限尖坤。
7.5.考慮到網(wǎng)絡(luò)異常情況稳懒,服務(wù)器的驗證應(yīng)該是一個可恢復(fù)的隊列,如果網(wǎng)絡(luò)失敗了慢味,應(yīng)該進(jìn)行重試场梆。
簡單來說就是將該購買憑證用Base64編碼,然后POST給蘋果的驗證服務(wù)
器纯路,蘋果將驗證結(jié)果以JSON形式返回或油。
四、總結(jié)坑
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:requestProduct];
payment.applicationUsername = self.orderId;
[[SKPaymentQueue defaultQueue] addPayment:payment];
//我這里剛才只是對訂單號的存儲驰唬、并且把訂單號存在了applicationUsername
上面的處理中顶岸、上線后我這里第一個測出了真實支付中的漏單情況、訂單號返回的nil,由于對payment.applicationUsername的極度信任叫编、造成自己不得不緊急解決下這個問題發(fā)版辖佣、不過我事先作的還是有一些功課的、做了埋點、及時的定位到了問題、并且讓后臺進(jìn)行了手動補單师枣。相信大家看到這里的時候就不會只是簡單的這樣做了。
五世蔗、解決方案
1端逼、針對掉單的問題、網(wǎng)上的資料討論的太多了我這里簡單說下我的方案吧
我這里進(jìn)行了對訂單號污淋、transactionId顶滩、進(jìn)行對應(yīng)的鑰匙串存儲。這樣可以解決大部分的異常場景寸爆、基本沒什么漏單了礁鲁、并且我對掉單每次啟動進(jìn)行了掉單查詢、還有就是再次購買頁面也會有相應(yīng)的提示而昨、讓用戶自己繼續(xù)處理救氯、便可以重新提交訂單了找田。方便太多啦
/// 掉單處理
- (void)checkIAPHandle{
_ischecking = YES;
_isShowErrorM = NO;
[self addIAPObserverHandle];
}
- (void)checkTransactionHandle
{
NSArray *transactions = [SKPaymentQueue defaultQueue].transactions;
if (transactions && [transactions isKindOfClass:[NSArray class]] && [transactions count]) {
for (SKPaymentTransaction *transaction in transactions) {
if (transaction.transactionState == SKPaymentTransactionStatePurchased) {
NSString *title = @"您有一筆會員訂單未完成歌憨,請繼續(xù)處理";
NYAlertView *alertView = [[NYAlertView alloc] initWithTitle:@"發(fā)現(xiàn)未完成訂單"
message:title
cancelButtonTitle:nil
otherButtonTitles:@"繼續(xù)處理", nil];
[alertView setClickButtonBlock:^(NYAlertView * _Nonnull alert, NSInteger index) {
[[ApplePayManager sharedManager] handleCheckPurchaseTransaction:transaction];
}];
alertView.titleTextAlignment = NSTextAlignmentLeft;
alertView.messageTextAlignment = NSTextAlignmentLeft;
[alertView.otherButton setTitleColor:[UIColor wb_colorWithHexString:@"F44A4A"] forState:UIControlStateNormal];
[alertView show];
}
}
}
}
六、附言
大家做內(nèi)購過程中遇到問題可以隨時溝通哈6昭谩N竦铡! QQ:304517331
有好的建議也記得及時分享哦??
祝大家工作順利F岣摹P牧濉!挫剑!~~~~