第一部分:在Apple后臺(tái)添加一個(gè)內(nèi)購(gòu)產(chǎn)品
1蜡饵、登錄appStoreConnect,如下圖所示,添加一個(gè)商品
IAP類(lèi)型類(lèi)型主要有4種:
1、Consumable products 適用于可多次購(gòu)買(mǎi)的消耗型項(xiàng)目,如游戲道具胸蛛、虛擬幣等。
2樱报、Non-consumable products 適用于一次購(gòu)買(mǎi)永久有效的項(xiàng)目葬项,如電子書(shū)、游戲關(guān)卡等迹蛤。該類(lèi)型項(xiàng)目支持跨設(shè)備同步和本地restore民珍,比如說(shuō),用戶(hù)在某個(gè)App中購(gòu)買(mǎi)了一本書(shū)盗飒,可在所有相同Apple ID設(shè)備的App中免費(fèi)獲取這本書(shū)嚷量。
3、Auto-renewable subscriptions 適用于自動(dòng)續(xù)費(fèi)的訂閱項(xiàng)目逆趣,如Apple Music的按月訂閱蝶溶,用戶(hù)購(gòu)買(mǎi)后會(huì)每月自動(dòng)續(xù)費(fèi),直到用戶(hù)手動(dòng)取消或者開(kāi)發(fā)者下架IAP項(xiàng)目宣渗。
4抖所、Non-renewable subscriptions 適用于固定有效期的非自動(dòng)續(xù)費(fèi)項(xiàng)目,如云音樂(lè)的會(huì)員和一些視頻App的會(huì)員痕囱。
由于我們是充值虛擬幣學(xué)點(diǎn)田轧,所以選擇了Consumable類(lèi)型。
2咐蝇、填寫(xiě)商品名稱(chēng)涯鲁、Product ID(Product ID一旦創(chuàng)建不可修改),選擇價(jià)格有序,然后拉到最下面添加商品購(gòu)買(mǎi)時(shí)的截圖抹腿,最后保存
點(diǎn)擊④,會(huì)進(jìn)入價(jià)格列表旭寿,對(duì)照下圖中的價(jià)格警绩,選擇商品想要賣(mài)的價(jià)格在③中進(jìn)行選擇
3、記住要添加截圖盅称,不然狀態(tài)會(huì)變成Miss Metadata肩祥,添加截圖數(shù)據(jù)沒(méi)有問(wèn)題后會(huì)變成Ready to Submit狀態(tài)
4、在App提交審核時(shí)缩膝,把App當(dāng)前版本用到的內(nèi)購(gòu)商品添加到App中混狠,不添加的話,蘋(píng)果審核會(huì)被拒絕疾层,報(bào)你有一個(gè)或多個(gè)內(nèi)購(gòu)商品沒(méi)有提交審核的Issue
第二部分:蘋(píng)果支付流程
支付流程簡(jiǎn)單來(lái)講就是在App中點(diǎn)擊購(gòu)買(mǎi)商品按鈕的時(shí)候,把在Apple后臺(tái)設(shè)置的Product ID通過(guò)SKProductsRequest傳給Apple后臺(tái)将饺,Apple后臺(tái)會(huì)把商品對(duì)象SKProduct回調(diào)給App,然后再把SKProduct加到支付隊(duì)列中,這個(gè)時(shí)候就會(huì)有彈框顯示支付金額讓你輸密碼了予弧,支付成功后蘋(píng)果會(huì)把交易憑證返回刮吧,拿著憑證去公司服務(wù)器驗(yàn)證,驗(yàn)證為有效憑證發(fā)學(xué)點(diǎn)掖蛤,交易完成
1.點(diǎn)擊購(gòu)買(mǎi)商品按鈕時(shí)傳入在Apple后臺(tái)的In-App Purchases中設(shè)置的相應(yīng)的Product ID
2杀捻、傳入Product ID參數(shù),通過(guò)SKProductsRequest發(fā)起請(qǐng)求蚓庭,監(jiān)聽(tīng)回調(diào)結(jié)果
-(void)starBuyToAppStoreWithGoodsId:(NSString *)goodsID cannotPayment:(void(^)(void))cannotPayment{
//判斷app是否允許apple支付
if (![SKPaymentQueue canMakePayments]) {
if (cannotPayment) {
cannotPayment();
}
return;
}
//1.點(diǎn)擊購(gòu)買(mǎi)商品時(shí)傳入在Apple后臺(tái)的In-App Purchases中設(shè)置的相應(yīng)的Product ID
//goodsID 就是在蘋(píng)果后臺(tái)設(shè)置的商品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參數(shù),通過(guò)SKProductsRequest發(fā)起請(qǐng)求器赞,監(jiān)聽(tīng)回調(diào)結(jié)果
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
request.delegate = self;
[request start];
}
3拄踪、在需要監(jiān)聽(tīng)商品請(qǐng)求回調(diào)的地方,實(shí)現(xiàn)SKProductsRequestDelegate,確保Apple后臺(tái)返回的Product ID與步驟2中請(qǐng)求的一樣
4、把SKProduct加到支付隊(duì)列之前拳魁,創(chuàng)建一個(gè)訂單持久化到本地惶桐,用戶(hù)可以查看訂單狀態(tài),在支付流程中會(huì)遇到各種情況導(dǎo)致支付失敗潘懊,至少可以列舉5種可能的狀態(tài):0=待充值姚糊,1=充值完成,2=充值中授舟,3=充值取消救恨,4=充值失敗,可以根據(jù)各種狀態(tài)去更新訂單狀態(tài)
5释树、把SKProduct加入到支付隊(duì)列中肠槽,并給SKMutablePayment對(duì)象添加唯一標(biāo)識(shí)用于交易結(jié)束后獲取相應(yīng)的訂單改變訂單狀態(tài),當(dāng)被成功添加到支付隊(duì)列后這個(gè)時(shí)候就會(huì)有彈框了
#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奢啥、在需要監(jiān)聽(tīng)商品請(qǐng)求回調(diào)的地方,實(shí)現(xiàn)SKProductsRequestDelegate,確保Apple后臺(tái)返回的Product ID與步驟2中請(qǐng)求的一樣
SKProduct *requestProduct = nil;
for (SKProduct *product in products) {
if([product.productIdentifier isEqualToString:self.goodsId]){
requestProduct = product;
break;
}
}
if (!requestProduct) {
return;
}
//4秸仙、把SKProduct加到支付隊(duì)列之前,創(chuàng)建一個(gè)訂單持久化到本地桩盲,用戶(hù)可以查看訂單狀態(tài)寂纪,在支付流程中會(huì)遇到各種情況導(dǎo)致支付失敗,至少可以列舉5種可能的狀態(tài):0=待充值赌结,1=充值完成捞蛋,2=充值中,3=充值取消柬姚,4=充值失敗拟杉,可以根據(jù)各種狀態(tài)去更新訂單狀態(tài)
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加入到支付隊(duì)列中量承,并給SKMutablePayment對(duì)象添加唯一標(biāo)識(shí)用于交易結(jié)束后獲取相應(yīng)的訂單改變訂單狀態(tài)搬设,當(dāng)被成功添加到支付隊(duì)列后這個(gè)時(shí)候就會(huì)有彈框了
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:requestProduct];
payment.applicationUsername = applicationUsername;
[[SKPaymentQueue defaultQueue] addPayment:payment];//將票據(jù)加入到交易隊(duì)列
}
6啼染、監(jiān)聽(tīng)購(gòu)買(mǎi)結(jié)果 - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions,根據(jù)不同的交易狀態(tài)去處理相應(yīng)的邏輯
#pragma mark -SKPaymentTransactionObserver
//監(jiān)聽(tīng)購(gòu)買(mǎi)結(jié)果
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
//6焕梅、支付結(jié)果的回調(diào),根據(jù)不同的交易狀態(tài)去處理相應(yīng)的邏輯
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
NSLog(@"交易已經(jīng)添加到服務(wù)隊(duì)列中");
break;
case SKPaymentTransactionStatePurchased:
NSLog(@"已經(jīng)付費(fèi)了卦洽,交易完成");
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];
NSLog(@"交易被取消或者添加到交易隊(duì)列失敗");
break;
case SKPaymentTransactionStateRestored:
NSLog(@"交易從購(gòu)買(mǎi)歷史列表中被恢復(fù)贞言,客戶(hù)端應(yīng)該完成交易");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateDeferred:
NSLog(@"在交易隊(duì)列中,還沒(méi)有最終的結(jié)果");
break;
default:
break;
}
}
}
7阀蒂、當(dāng)交易狀態(tài)為SKPaymentTransactionStatePurchased该窗,意味著支付完成了,從沙盒中獲取交易憑證
8蚤霞、拿到交易憑證后酗失,在去后臺(tái)服務(wù)器后臺(tái)驗(yàn)證之前,需要把訂單狀態(tài)改為充值中昧绣,在持久化的訂單列表中對(duì)狀態(tài)為充值中的訂單可以發(fā)起補(bǔ)充值請(qǐng)求
9规肴、用戶(hù)蘋(píng)果支付完成了,需要拿著蘋(píng)果返回的憑證去服務(wù)器校驗(yàn)夜畴,校驗(yàn)成功發(fā)放學(xué)點(diǎn),并且該條訂單設(shè)置為完成狀態(tài),可能因?yàn)榫W(wǎng)絡(luò)原因校驗(yàn)訂單失敗拖刃,該條訂單狀態(tài)保持充值中狀態(tài)
//支付完成
- (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贪绘、拿到交易憑證后兑牡,在去后臺(tái)服務(wù)器后臺(tái)驗(yàn)證之前,需要把訂單狀態(tài)改為充值中税灌,在持久化的訂單列表中對(duì)狀態(tài)為充值中的訂單可以發(fā)起補(bǔ)充值請(qǐng)求
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均函、用戶(hù)蘋(píng)果支付完成了,需要拿著蘋(píng)果返回的憑證去服務(wù)器校驗(yàn)菱涤,校驗(yàn)成功發(fā)放學(xué)點(diǎn),并且該條訂單設(shè)置為完成狀態(tài),可能因?yàn)榫W(wǎng)絡(luò)原因校驗(yàn)訂單失敗苞也,該條訂單狀態(tài)保持充值中狀態(tài)
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) { //校驗(yàn)成功
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];
}];
}
//不管憑證驗(yàn)證結(jié)果,最后完成交易
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
10粘秆、如果交易失敗墩朦,把訂單狀態(tài)更新為相應(yīng)的狀態(tài)
//交易失敗
- (void)failedTransaction:(SKPaymentTransaction *)transaction {
// 10、如果交易失敗翻擒,把訂單狀態(tài)更新為相應(yīng)的狀態(tài)
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 {//其他錯(cuò)誤
if (self.delegate && [self.delegate respondsToSelector:@selector(appStorePayFailed)]) {
[self.delegate appStorePayFailed];
}
[orderDic setObject:@(InAppPurchaseStatusFailed) forKey:@"status"];
}
[self.chargeManager makeLearnPointOrderWithInfo:orderDic];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
第三部分:訂單狀態(tài)記錄
在發(fā)起支付的過(guò)程中會(huì)遇到各種各樣的問(wèn)題氓涣,比如用戶(hù)中途取消、用戶(hù)支付完成后拿著交易憑證去公司服務(wù)器驗(yàn)證的時(shí)候網(wǎng)絡(luò)不好或者公司服務(wù)器掛了陋气,為了防止掉單也讓用戶(hù)看到自己訂單的支付記錄劳吠,有必要在本地使用Plist持久化一個(gè)訂單列表
1、在Document目錄下創(chuàng)建一個(gè)Plist文件
- (NSString *)filePath {
if (!_filePath) {
NSString *document = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *path = [document stringByAppendingPathComponent:@"PointCoinOrder.plist"];
_filePath = path;
}
return _filePath;
}
2巩趁、創(chuàng)建訂單痒玩,更新訂單 (因?yàn)橐粭l訂單信息在支付流程的不同階段淳附,最終的狀態(tài)會(huì)發(fā)生改變(創(chuàng)建、取消蠢古、失敗奴曙、完成等),需要要把老的訂單刪除草讶,更新訂單狀態(tài)信息之后的訂單追加進(jìn)去)
- (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]];
//因?yàn)橐粭l訂單信息在支付流程的不同階段洽糟,最終的狀態(tài)會(huì)發(fā)生改變(創(chuàng)建、取消堕战、失敗坤溃、完成等),需要要把老的訂單刪除嘱丢,更新?tīng)顟B(tài)信息之后的訂單追加進(jìn)去
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];
});
}
//根據(jù)訂單的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薪介、去公司服務(wù)器驗(yàn)證訂單
+ (void)verifyToServerWithReceipt:(NSDictionary *)receiptInfo success:(void(^)(NSDictionary *result))success fail:(void(^)(NSError *error))fail {
}