最近在做內購項目SDK,現(xiàn)將集成過程和集成內購過程中遇到的問題記載下來:
項目中使用到了中間貨幣(金幣)的形式來進行功能使用,模式是使用RMB換成-金幣比如:(1RMB = 10金幣),所以會集成第三方的支付平臺,使用了微信和支付寶的第三方平臺過后,發(fā)現(xiàn)審核失敗,被蘋果拒絕,查了一查原因,才是因為蘋果對app內的中間幣的購買必須走蘋果內購(比如沖點券,比如買鉆石....)。所以無奈只有使用蘋果內購,由于蘋果內購的步驟很多,設置的東西太多,所以將這步驟記錄下來。
首先設置協(xié)議
1.打開itunes Connect,選擇協(xié)議,稅務和銀行業(yè)務
2.點擊Request Contracts(申請合同)下面的,request谆棺,點了幾個確定和下一步后回到主界面。
Contact info:聯(lián)系人信息
Bank info:銀行信息
Tax info:稅務信息
3.首先設置聯(lián)系人信息,點擊Contact info下面的 Set up(設置),點擊Add New Contract(增加先的聯(lián)系方式)
4.填寫詳情
填寫完成后點擊save(保存)
5.在下面的所有項目中都選擇剛剛填寫的信息,選擇后點擊右下角的done(完成),你可以創(chuàng)建很多聯(lián)系人,在不同的職務選擇不同的聯(lián)系人悉罕。因為我是獨立開發(fā),所以我全部填寫的我自己。
Senior Management:高管
Financial:財務
Technical:技術支持
Legal:法務
Marketing:市場推廣
6.設置銀行信息,點擊Back info下面的Set up,彈出頁面
點擊Add Bank Account(添加銀行賬號)
選擇china,后點擊next。
填寫了CNAPS Code后點擊Next
會彈出你的銀行卡開戶地的信息,確認一下點擊next
填寫銀行卡信息壁袄,注意:戶主名只能寫拼音,比如:李三(Li San)类早。填完后點擊Next
彈出確定信息頁面,在下面打鉤后點擊Save
點擊了save后就可以在彈出的頁面中選擇剛剛填寫的卡了然想。選擇后點擊Save
7.設置稅務信息莺奔,點擊Tax info下面的Set up,此時聯(lián)系人信息已經(jīng)變成可以編輯狀態(tài),銀行信息為瀏覽狀態(tài)。
彈出的界面中,稅務分為三種
U.S Tax Forms: 美國稅務
Australia Tax Forms:澳大利亞稅務
Canada Tax Forms: 加拿大稅務
這里我選擇的美國稅務,就是第一個
彈出第一個選擇,點擊submit(提交)后,彈出第二個選擇
彈出第二個選擇,選擇后點擊submit
彈出第三個頁面令哟,填寫的資料后點擊提交,記得勾選頁面上的幾個復選框
在提交成功后,狀態(tài)就變成processing成功
到這里設置的協(xié)議就已經(jīng)設置完了。
創(chuàng)建項目的內購
1.進入到項目的APP信息頁面,點擊功能屏富,在彈出的頁面點擊App內購買項目后面的?神年。
2.在彈出的新對話框中選擇你需要哪一種服務护奈,由于我的項目需要兌換成消耗的金幣,所以我選擇第一個。選擇后點擊創(chuàng)建岛马。
3.開始填寫內購項目信息湿诊。填完后點擊右上角的存儲(所有信息必須填寫完整)簿晓。
4.點擊存儲后,內購列表就會有剛剛創(chuàng)建的內購條目。
你app有幾個內購級別就需要依次創(chuàng)建幾個條目臀脏。
添加測試賬號,用來測試支付功能
1.點擊圖中用戶和職能
2.點擊沙盒測試員,然后點擊左邊的?按鈕延塑。
3.設置好信息點擊右上角存儲就可以,記住里面的郵箱和密碼用于支付的時候登陸Apple id
代碼集成
打開自己的項目,創(chuàng)建一個測試類关带。代碼都有注釋和步驟,直接上代碼。
注意:
1.必須用真機測試沼撕。
2.測試的時候必須退出自己的apple ID宋雏。彈出頁面后登陸沙盒的測試apple id。
使用的時候首先要導入 #import <StoreKit/StoreKit.h>
先上代碼再細分析
實現(xiàn)觀察者監(jiān)聽付錢的代理方法,只要交易發(fā)生變化就會走下面的方法
// 監(jiān)聽交易操作與結果
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction{
for(SKPaymentTransaction *tran in transaction){
switch (tran.transactionState) {
case SKPaymentTransactionStatePurchased:
{
NSLog(@"交易完成");
[self completeTransaction:tran];
//// 去驗證是否真正的支付成功了
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
}break;
case SKPaymentTransactionStatePurchasing:
{
NSLog(@"商品添加進列表");
}break;
case SKPaymentTransactionStateRestored:
{
NSLog(@"已經(jīng)購買過商品");
[self restoreTransaction:tran];
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
}break;
case SKPaymentTransactionStateFailed:
{
NSLog(@"交易失敗%@",tran.error);
[self failedTransaction:tran];
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
}break;
default:
{
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
}break;
}
}
}
注意:在購買成功后需要釋放
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
請求驗證
獲取到票據(jù)以后我們通過App Store來驗證票據(jù)是否真實
沙盒狀態(tài)下使用:https://sandbox.itunes.apple.com/verifyReceipt來驗證
生產環(huán)境下使用:https://buy.itunes.apple.com/verifyReceipt
常見的驗證狀態(tài)代碼:
InAppPurchaseValidate.h
#import <Foundation/Foundation.h>
typedef void (^SuccessBlock)(id response);
typedef void (^FailBlock)(NSError *error);
#define KK_RECEIPT_VALIDATAURL @"http://10.0.0.110:8001/api/pay/callback_iap"
@interface InAppPurchaseValidate : NSObject
/**
獲取收據(jù)信息
@param successBlock 成功回調
@param failBlock 失嵊回調
*/
+(void)loadReceiptWithSuccessBlock:(SuccessBlock)successBlock failBlock:(FailBlock)failBlock;
/**
驗證收據(jù)信息
配置KK_RECEIPT_VALIDATAURL 為提交receipt到服務端地址
@param recepiptString AppStore返回的收據(jù)信息
@param successBlock 成功回調
@param failBlock 失嵊回調
*/
+(void)validateWithReceipt:(NSString *)recepiptString successBlock:(SuccessBlock)successBlock failBlock:(FailBlock)failBlock;
/**
合并loadReceiptWithSuccessBlock:與validateWithReceipt:獲取recpipt信息并向服務器提交驗證
配置KK_RECEIPT_VALIDATAURL 為提交receipt到服務端地址
@param successBlock 成功回調
@param failBlock 失嵊回調
*/
+(void)ValidatReceipteWithSuccessBlock:(SuccessBlock)successBlock failBlock:(FailBlock)failBlock;
@end
InAppPurchaseValidate.m 文件
#import "InAppPurchaseManager.h"
static NSMutableArray* productIdentifiers = nil;
static InAppPurchaseManager* m_pInstance = nil;
@interface InAppPurchaseManager()
{
SKProductsRequest *productsRequest;
SKProduct *startedPaymentProduct;
}
@property (nonatomic, copy, readwrite) LoadStoreDidBlock loadStoreDidBlock;
@property (nonatomic, copy, readwrite) PurchaseStatusBlock purchaseStatusBlock;
@end
@implementation InAppPurchaseManager
#pragma mark- init
+ (InAppPurchaseManager*) getInstance
{
if (m_pInstance == nil){
m_pInstance = [[InAppPurchaseManager alloc] init];
}
return m_pInstance;
}
+ (void) releaseInstance
{
if (m_pInstance){
m_pInstance = nil;
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}
}
#pragma mark- ProductId
- (void)addProductIdentifiers:(NSArray*)identifiers
{
if (productIdentifiers == nil)
{
productIdentifiers = [[NSMutableArray alloc] init];
}
[productIdentifiers addObjectsFromArray:identifiers];
}
- (void) clearProductIdentifiers
{
if (productIdentifiers)
{
[productIdentifiers removeAllObjects];
}
}
#pragma mark- Public methods
- (void)loadStore:(LoadStoreDidBlock)loadStoreDidBlock
{
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
self.loadStoreDidBlock = loadStoreDidBlock;
[self requestProductData];
}
- (void)requestProductData
{
if(productIdentifiers.count==0) {
NSLog(@"error: no productId");
return;
}
NSSet *productIdentifiersSet = [NSSet setWithArray:productIdentifiers];
productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiersSet];
productsRequest.delegate = self;
[productsRequest start];
}
- (BOOL)canMakePurchases
{
return [SKPaymentQueue canMakePayments];
}
-(void)purchaseWithProductId:(NSString *)identifier purchaseStatusBlock:(PurchaseStatusBlock)purchaseStatusBlock
{
self.purchaseStatusBlock = purchaseStatusBlock;
startedPaymentProduct = nil;
[self addProductIdentifiers:@[identifier]];
if(self.productList == nil) {
__weak typeof(self) weakSelf = self;
[self loadStore:^{
if(weakSelf.productList == nil)
weakSelf.productList = [[NSArray alloc]init];
[weakSelf purchaseWithProductId:identifier purchaseStatusBlock:purchaseStatusBlock];
}];
return;
}
for (int i = 0; i < self.productList.count; ++i) {
SKProduct* p = [self.productList objectAtIndex:i];
if ([[p productIdentifier] isEqualToString:identifier]) {
startedPaymentProduct = p;
break;
}
}
if(startedPaymentProduct == nil) {
NSLog(@"沒有找到該商品");
if(purchaseStatusBlock) purchaseStatusBlock(nil,InAppPurchaseFailure);
return;
}
[self paymentWithProduct:startedPaymentProduct];
}
-(void)paymentWithProduct:(SKProduct *)product
{
if (product == nil) {
NSLog(@"err: startedPaymentProduct is nil");
return;
}
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
#pragma mark- SKProductsRequestDelegate
/// 接收商品信息
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
if (self.productList) {
self.productList = nil;
}
self.productList = response.products;
NSMutableArray* productListArray = [[NSMutableArray alloc] init];
for (int i = 0; i < self.productList.count; ++i) {
NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
SKProduct* p = [self.productList objectAtIndex:i];
[dict setObject:(p.localizedTitle != nil ? p.localizedTitle : @"") forKey:@"localizedTitle"];
[dict setObject:(p.localizedDescription != nil ? p.localizedDescription : @"") forKey:@"localizedDescription"];
[dict setObject:p.price forKey:@"price"];
[dict setObject:p.productIdentifier forKey:@"productIdentifier"];
[productListArray addObject:dict];
}
NSMutableArray* invalidProductArray = [[NSMutableArray alloc] init];
for (NSString *invalidProductId in response.invalidProductIdentifiers)
{
[invalidProductArray addObject:invalidProductId];
}
if(self.loadStoreDidBlock) self.loadStoreDidBlock();
}
#pragma mark - SKPaymentTransactionObserver methods
/// 監(jiān)聽交易操作與結果
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction{
for(SKPaymentTransaction *tran in transaction){
switch (tran.transactionState) {
case SKPaymentTransactionStatePurchased:
{
NSLog(@"交易完成");
[self completeTransaction:tran];
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
}break;
case SKPaymentTransactionStatePurchasing:
{
NSLog(@"商品添加進列表");
}break;
case SKPaymentTransactionStateRestored:
{
NSLog(@"已經(jīng)購買過商品");
[self restoreTransaction:tran];
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
}break;
case SKPaymentTransactionStateFailed:
{
NSLog(@"交易失敗%@",tran.error);
[self failedTransaction:tran];
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
}break;
default:
{
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
}break;
}
}
}
//交易結束
- (void)completeTransaction:(SKPaymentTransaction *)transaction
{
[self recordTransaction:transaction];
[self provideContent:transaction.payment.productIdentifier];
[self finishTransaction:transaction status:0];
}
//交易失敗
- (void)restoreTransaction:(SKPaymentTransaction *)transaction
{
[self recordTransaction:transaction.originalTransaction];
[self provideContent:transaction.originalTransaction.payment.productIdentifier];
[self finishTransaction:transaction status:1];
}
- (void)failedTransaction:(SKPaymentTransaction *)transaction
{
[self finishTransaction:transaction status:-1];
}
- (void)finishTransaction:(SKPaymentTransaction *)transaction status:(int)status
{
InAppPurchaseStatus inAppPurchasestatus = InAppPurchaseSuccess;
if(status == 1) inAppPurchasestatus = InAppPurchaseRestore;
if(status == -1) inAppPurchasestatus = InAppPurchaseFailure;
if(self.purchaseStatusBlock) self.purchaseStatusBlock(transaction, inAppPurchasestatus);
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
在你使用到的 地方直接調用
// 購買產品
[[InAppPurchaseManager getInstance] purchaseWithProductId:@"com.test1.020.App009" purchaseStatusBlock:^(SKPaymentTransaction *paymentTransaction, InAppPurchaseStatus status) {
if(status == InAppPurchaseFailure) {
NSLog(@"未完成支付");
return;
}
NSString *productIdentifier = paymentTransaction.payment.productIdentifier;
// 方法一 獲取票據(jù)并向服務端提交票據(jù)信息
// 需要KK_RECEIPT_VALIDATAURL 配置服務端地址
{
[InAppPurchaseValidate ValidatReceipteWithSuccessBlock:^(id responesData) {
// 提交成功
NSLog(@"服務端已返回驗證結果responesData");
} failBlock:^(NSError *error) {
NSLog(@"error:%@",error);
}];
}
內購的注意事項
1.一般發(fā)生于首次提交app或添加新商品务豺,當你的app通過審核以后磨总,你發(fā)現(xiàn)在生產環(huán)境下獲取不到商品,這是因為app雖然過審核了笼沥,但是內購商品還沒有正式添加到蘋果的服務器里蚪燕,耐心等待一段時間就可以啦~
代碼中的_currentProId所填寫的是你的購買項目的的ID,這個和第二步創(chuàng)建的內購的productID要一致奔浅;本例中是 123馆纳。
在監(jiān)聽購買結果后,一定要調用[[SKPaymentQueue defaultQueue] finishTransaction:tran];來允許你從支付隊列中移除交易汹桦。
沙盒環(huán)境測試appStore內購流程的時候鲁驶,請使用沒越獄的設備。
請務必使用真機來測試舞骆,一切以真機為準钥弯。
項目Bundle identifier需要與您申請AppID時填寫的bundleID一致壹罚,不然會無法請求到商品信息。
真機測試的時候寿羞,一定要退出原來的賬號猖凛,才能用沙盒測試賬號
二次驗證,請注意區(qū)分宏绪穆, 測試用沙盒驗證辨泳,App Store審核的時候也使用的是沙盒購買,所以驗證購買憑證的時候需要判斷返回Status
Code決定是否去沙盒進行二次驗證玖院,為了線上用戶的使用菠红,驗證的順序肯定是先驗證正式環(huán)境,此時若返回值為21007难菌,就需要去沙盒二次驗證试溯,因為此購買的是在沙盒進行的。
9.您的應用是否處于等待開發(fā)者發(fā)布(Pending Developer Release)狀態(tài)郊酒?等待發(fā)布狀態(tài)的IAP是無法測試的遇绞。
10.您的內購項目是否是最近才新建的,或者進行了更改燎窘?內購項目需要一段時間才能反應到所有服務器上摹闽,這個過程一般是一兩小時,也可能再長一些達到若干小時褐健。
11.您在iTC中Contracts, Tax, and Banking Information項目中是否有還沒有設置或者過期了的項目付鹿?不完整的財務信息無法進行內購測試。
12.您是在越獄設備上進行內購測試么蚜迅?越獄設備不能用于正常內購舵匾,您需要重裝或者尋找一臺沒有越獄的設備。
13.您的應用是否是被拒狀態(tài)(Rejected)或自己拒絕(Developer Rejected)了谁不?被拒絕狀態(tài)的應用的話對應還未通過的內購項目也會一起被拒坐梯,因此您需要重新將IAP項目設為Cleared for Sale。
14.您使用的測試賬號是否是美國區(qū)賬號拍谐?雖然不是一定需要烛缔,但是鑒于其他地區(qū)的測試賬號經(jīng)常抽風馏段,加上美國區(qū)賬號一直很穩(wěn)定轩拨,因此強烈建議使用美國區(qū)賬號。正常情況下IAP不需要進行信用卡綁定和其他信息填寫院喜,如果你遇到了這種情況亡蓉,可以試試刪除這個測試賬號再新建一個其他地區(qū)的。
15.您是否將設備上原來的app刪除了喷舀,并重新進行了安裝砍濒?記得在安裝前做一下Clean和Clean Build Folder淋肾。
16.您的plist中的Bundle identifier的內容是否和您的AppID一致?
文章有點長~~~
最后附上小demo:
內購集成Demo