第一部分:在Apple后臺添加一個內購產品
1、登錄appStoreConnect,如下圖所示,添加一個商品
增加內購.png
IAP類型類型主要有4種:
1、Consumable products 適用于可多次購買的消耗型項目,如游戲道具、虛擬幣等。
2、Non-consumable products 適用于一次購買永久有效的項目,如電子書、游戲關卡等。該類型項目支持跨設備同步和本地restore,比如說,用戶在某個App中購買了一本書,可在所有相同Apple ID設備的App中免費獲取這本書。
3、Auto-renewable subscriptions 適用于自動續費的訂閱項目,如Apple Music的按月訂閱,用戶購買后會每月自動續費,直到用戶手動取消或者開發者下架IAP項目。
4、Non-renewable subscriptions 適用于固定有效期的非自動續費項目,如云音樂的會員和一些視頻App的會員。
由于我們是充值虛擬幣學點,所以選擇了Consumable類型。
2、填寫商品名稱、Product ID(Product ID一旦創建不可修改),選擇價格,然后拉到最下面添加商品購買時的截圖,最后保存
創建一個學點.jpeg
點擊④,會進入價格列表,對照下圖中的價格,選擇商品想要賣的價格在③中進行選擇
價格參考.jpg
3、記住要添加截圖,不然狀態會變成Miss Metadata,添加截圖數據沒有問題后會變成Ready to Submit狀態
MissMetaData.jpeg
4、在App提交審核時,把App當前版本用到的內購商品添加到App中,不添加的話,蘋果審核會被拒絕,報你有一個或多個內購商品沒有提交審核的Issue
添加內購商品.png
第二部分:蘋果支付流程
支付流程簡單來講就是在App中點擊購買商品按鈕的時候,把在Apple后臺設置的Product ID通過SKProductsRequest傳給Apple后臺,Apple后臺會把商品對象SKProduct回調給App,然后再把SKProduct加到支付隊列中,這個時候就會有彈框顯示支付金額讓你輸密碼了,支付成功后蘋果會把交易憑證返回,拿著憑證去公司服務器驗證,驗證為有效憑證發學點,交易完成
1.點擊購買商品按鈕時傳入在Apple后臺的In-App Purchases中設置的相應的Product ID
2、傳入Product ID參數,通過SKProductsRequest發起請求,監聽回調結果
-(void)starBuyToAppStoreWithGoodsId:(NSString *)goodsID cannotPayment:(void(^)(void))cannotPayment{
//判斷app是否允許apple支付
if (![SKPaymentQueue canMakePayments]) {
if (cannotPayment) {
cannotPayment();
}
return;
}
//1.點擊購買商品時傳入在Apple后臺的In-App Purchases中設置的相應的Product ID
//goodsID 就是在蘋果后臺設置的商品ID
self.goodsId = goodsID; //比如Product ID可以是 com.example.example_LevelA
NSArray *product = [[NSArray alloc] initWithObjects:goodsID,nil];
NSSet *nsset = [NSSet setWithArray:product];
//2、傳入Product ID參數,通過SKProductsRequest發起請求,監聽回調結果
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
request.delegate = self;
[request start];
}
3、在需要監聽商品請求回調的地方,實現SKProductsRequestDelegate,確保Apple后臺返回的Product ID與步驟2中請求的一樣
4、把SKProduct加到支付隊列之前,創建一個訂單持久化到本地,用戶可以查看訂單狀態,在支付流程中會遇到各種情況導致支付失敗,至少可以列舉5種可能的狀態:0=待充值,1=充值完成,2=充值中,3=充值取消,4=充值失敗,可以根據各種狀態去更新訂單狀態
5、把SKProduct加入到支付隊列中,并給SKMutablePayment對象添加唯一標識用于交易結束后獲取相應的訂單改變訂單狀態,當被成功添加到支付隊列后這個時候就會有彈框了
#pragma mark -SKProductsRequestDelegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
NSArray *products = response.products;
if([products count] == 0){
if (self.delegate && [self.delegate respondsToSelector:@selector(appStorePayFailed)]) {
[self.delegate appStorePayFailed];
}
return;
}
//3、在需要監聽商品請求回調的地方,實現SKProductsRequestDelegate,確保Apple后臺返回的Product ID與步驟2中請求的一樣
SKProduct *requestProduct = nil;
for (SKProduct *product in products) {
if([product.productIdentifier isEqualToString:self.goodsId]){
requestProduct = product;
break;
}
}
if (!requestProduct) {
return;
}
//4、把SKProduct加到支付隊列之前,創建一個訂單持久化到本地,用戶可以查看訂單狀態,在支付流程中會遇到各種情況導致支付失敗,至少可以列舉5種可能的狀態:0=待充值,1=充值完成,2=充值中,3=充值取消,4=充值失敗,可以根據各種狀態去更新訂單狀態
NSString *startTime = [NSString stringWithFormat:@"%.0f", [NSDate date].timeIntervalSince1970];
NSString *applicationUsername = [NSString stringWithFormat:@"%@/%@/%@",[ZGAppInfoUtil appID], startTime, requestProduct.price];
NSDictionary *dict = @{
@"price" : requestProduct.price,
@"startTime" : startTime,
@"status" : @(InAppPurchaseStatusPrepare),
@"receiptData" : @"",
@"transactionID" : @"",
@"applicationUserName" : applicationUsername
};
[self.chargeManager makeLearnPointOrderWithInfo:dict];
//5、把SKProduct加入到支付隊列中,并給SKMutablePayment對象添加唯一標識用于交易結束后獲取相應的訂單改變訂單狀態,當被成功添加到支付隊列后這個時候就會有彈框了
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:requestProduct];
payment.applicationUsername = applicationUsername;
[[SKPaymentQueue defaultQueue] addPayment:payment];//將票據加入到交易隊列
}
6、監聽購買結果 - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions,根據不同的交易狀態去處理相應的邏輯
#pragma mark -SKPaymentTransactionObserver
//監聽購買結果
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
//6、支付結果的回調,根據不同的交易狀態去處理相應的邏輯
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
NSLog(@"交易已經添加到服務隊列中");
break;
case SKPaymentTransactionStatePurchased:
NSLog(@"已經付費了,交易完成");
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];
NSLog(@"交易被取消或者添加到交易隊列失敗");
break;
case SKPaymentTransactionStateRestored:
NSLog(@"交易從購買歷史列表中被恢復,客戶端應該完成交易");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateDeferred:
NSLog(@"在交易隊列中,還沒有最終的結果");
break;
default:
break;
}
}
}
7、當交易狀態為SKPaymentTransactionStatePurchased,意味著支付完成了,從沙盒中獲取交易憑證
8、拿到交易憑證后,在去后臺服務器后臺驗證之前,需要把訂單狀態改為充值中,在持久化的訂單列表中對狀態為充值中的訂單可以發起補充值請求
9、用戶蘋果支付完成了,需要拿著蘋果返回的憑證去服務器校驗,校驗成功發放學點,并且該條訂單設置為完成狀態,可能因為網絡原因校驗訂單失敗,該條訂單狀態保持充值中狀態
//支付完成
- (void)completeTransaction:(SKPaymentTransaction *)transaction{
if (transaction.payment.productIdentifier && transaction.transactionIdentifier) {
// 7、從沙盒中拿到交易憑證
NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receiptData = [NSData dataWithContentsOfURL:receiptUrl];
if (!receiptData) {
return;
}
NSString *receiptString = [receiptData base64EncodedStringWithOptions:0];
if (!receiptData) {
return;
}
//8、拿到交易憑證后,在去后臺服務器后臺驗證之前,需要把訂單狀態改為充值中,在持久化的訂單列表中對狀態為充值中的訂單可以發起補充值請求
NSMutableDictionary *orderDic = [self.chargeManager getLearnPointOrderWithApplicationUsername:transaction.payment.applicationUsername].mutableCopy;
[orderDic setObject:@(InAppPurchaseStatusOngoing) forKey:@"status"];
[orderDic setObject:receiptString forKey:@"receiptData"];
[orderDic setObject:transaction.transactionIdentifier forKey:@"transactionID"];
//9、用戶蘋果支付完成了,需要拿著蘋果返回的憑證去服務器校驗,校驗成功發放學點,并且該條訂單設置為完成狀態,可能因為網絡原因校驗訂單失敗,該條訂單狀態保持充值中狀態
NSMutableDictionary *params = [NSMutableDictionary dictionary];
params[@"apple_receipt"] = receiptString ?: @"";
__weak typeof(self) weakSelf = self;
[OrderChargeManager verifyToServerWithReceipt:params success:^(NSDictionary * _Nonnull result) {
if ([result[@"flag"] integerValue] == 1) { //校驗成功
if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(appStoreDidPaySuccess)]) {
[weakSelf.delegate appStoreDidPaySuccess];
}
[orderDic setObject:@(InAppPurchaseStatusCompleted) forKey:@"status"];
} else {
if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(appStoreWillPaySuccess)]) {
[weakSelf.delegate appStoreWillPaySuccess];
}
}
[self.chargeManager makeLearnPointOrderWithInfo:orderDic];
} fail:^(NSError * _Nonnull error) {
if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(appStoreWillPaySuccessNetError)]) {
[weakSelf.delegate appStoreWillPaySuccessNetError];
}
[weakSelf.chargeManager makeLearnPointOrderWithInfo:orderDic];
}];
}
//不管憑證驗證結果,最后完成交易
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
10、如果交易失敗,把訂單狀態更新為相應的狀態
//交易失敗
- (void)failedTransaction:(SKPaymentTransaction *)transaction {
// 10、如果交易失敗,把訂單狀態更新為相應的狀態
NSMutableDictionary *orderDic = [self.chargeManager getLearnPointOrderWithApplicationUsername:transaction.payment.applicationUsername].mutableCopy;
if (transaction.error.code == SKErrorPaymentCancelled) {
if (self.delegate && [self.delegate respondsToSelector:@selector(appStorePayCancel)]) {
[self.delegate appStorePayCancel];
}
[orderDic setObject:@(InAppPurchaseStatusCanceled) forKey:@"status"];
} else {//其他錯誤
if (self.delegate && [self.delegate respondsToSelector:@selector(appStorePayFailed)]) {
[self.delegate appStorePayFailed];
}
[orderDic setObject:@(InAppPurchaseStatusFailed) forKey:@"status"];
}
[self.chargeManager makeLearnPointOrderWithInfo:orderDic];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
第三部分:訂單狀態記錄
在發起支付的過程中會遇到各種各樣的問題,比如用戶中途取消、用戶支付完成后拿著交易憑證去公司服務器驗證的時候網絡不好或者公司服務器掛了,為了防止掉單也讓用戶看到自己訂單的支付記錄,有必要在本地使用Plist持久化一個訂單列表
1、在Document目錄下創建一個Plist文件
- (NSString *)filePath {
if (!_filePath) {
NSString *document = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *path = [document stringByAppendingPathComponent:@"PointCoinOrder.plist"];
_filePath = path;
}
return _filePath;
}
2、創建訂單,更新訂單 (因為一條訂單信息在支付流程的不同階段,最終的狀態會發生改變(創建、取消、失敗、完成等),需要要把老的訂單刪除,更新訂單狀態信息之后的訂單追加進去)
- (dispatch_queue_t)queue {
if (!_queue) {
_queue = dispatch_queue_create("com.example.xxxxx", DISPATCH_QUEUE_SERIAL);
}
return _queue;
}
- (void)makeLearnPointOrderWithInfo:(NSDictionary *)dict {
dispatch_async(self.queue, ^{
NSMutableArray *resultArray = [NSMutableArray arrayWithArray:[NSArray arrayWithContentsOfFile:self.filePath]];
//因為一條訂單信息在支付流程的不同階段,最終的狀態會發生改變(創建、取消、失敗、完成等),需要要把老的訂單刪除,更新狀態信息之后的訂單追加進去
for (NSDictionary *subDict in resultArray) {
if ([[subDict objectForKey:@"applicationUserName"] isEqualToString:[dict objectForKey:@"applicationUserName"]]) {
[resultArray removeObject:subDict];
break;
}
}
if (dict) {
[resultArray addObject:dict];
}
[resultArray writeToFile:self.filePath atomically:YES];
});
}
//根據訂單的ID獲取訂單
- (NSDictionary *)getLearnPointOrderWithApplicationUsername:(NSString *)applicationUsername {
NSMutableArray *resultArray = [NSMutableArray arrayWithArray:[NSArray arrayWithContentsOfFile:self.filePath]];
NSMutableDictionary *resultDict;
for (NSDictionary *subDict in resultArray) {
if ([[subDict objectForKey:@"applicationUserName"] isEqualToString:applicationUsername]) {
resultDict = [NSMutableDictionary dictionaryWithDictionary:subDict];
//移除舊的字典
[resultArray removeObject:subDict];
break;
}
}
return resultDict;
}
3、去公司服務器驗證訂單
+ (void)verifyToServerWithReceipt:(NSDictionary *)receiptInfo success:(void(^)(NSDictionary *result))success fail:(void(^)(NSError *error))fail {
}