iOS應(yīng)用內(nèi)購(gòu)買(mǎi)IAP的支付憑證驗(yàn)證失敗后的重試機(jī)制
當(dāng)用戶在使用應(yīng)用內(nèi)購(gòu)買(mǎi)功能的時(shí)候达吞,如果用戶支付成功了鸵隧,由于網(wǎng)絡(luò)或者其它不可預(yù)計(jì)的因素绸罗,導(dǎo)致APP應(yīng)用沒(méi)有將相應(yīng)的商品或服務(wù)提供給用戶。無(wú)論APP還是用戶豆瘫,都不想看到珊蟀,因此,這是不允許發(fā)生的情況外驱,要由程序去控制穩(wěn)定性育灸。
由此,便引出了此次討論的話題昵宇,iOS應(yīng)用內(nèi)購(gòu)買(mǎi)IAP的支付憑證驗(yàn)證磅崭,失敗后的重試機(jī)制。
整個(gè)流程主要分為以下兩步瓦哎。
1 首次驗(yàn)證失敗后的立即重試
首次驗(yàn)證失敗后砸喻,根據(jù)設(shè)定的重試次數(shù),立即重試指定次數(shù)蒋譬。
如果立即重試指定次數(shù)還是失敗割岛,則進(jìn)入第二步。
2 立即重試后還是失敗的情況處理
2.1 本地文件保存訂單等相關(guān)支付信息
立即重試后還是失敗犯助,則使用本地文件的方式癣漆,保存訂單等相關(guān)支付信息。
具體方式是剂买,將必要的信息存入NSDictionary惠爽,然后保存為plist文件。plist文件的命名瞬哼,最好包含用戶ID婚肆、訂單ID,和其它具有唯一性的字符倒槐,以區(qū)分不同用戶,不同的訂單等附井。
2.1步的保存文件的代碼
/**
存儲(chǔ)用戶購(gòu)買(mǎi)憑證
@param receipt 購(gòu)買(mǎi)憑證
@param sID 唯一標(biāo)識(shí)(比如UserId)
@param orderNum 訂單號(hào)
*/
+ (void)saveReceiptValidation:(NSString *_Nonnull)receipt
withID:(NSString *_Nonnull)sID
orderNum:(NSString *_Nonnull)orderNum
{
NSDate *dateSaved = [NSDate date];
NSString *fileName = [NSString stringWithFormat:@"IAPInfo-%@-%@", sID, orderNum];
NSString *fileDir = [[self class] getIAPInfoLocalFilePath:sID];
NSString *savedPath = [NSString stringWithFormat:@"%@%@.plist", fileDir, fileName];
NSDictionary *dic =[NSDictionary dictionaryWithObjectsAndKeys:
receipt, kReceipStringKey,
dateSaved, kReceipDateKey,
sID, kReceipIdKey,
orderNum, kOrderNumKey,
nil];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL isDir = FALSE;
BOOL isDirExist = [fileManager fileExistsAtPath:fileDir isDirectory:&isDir];
if(!(isDirExist && isDir)) {//目錄不存在
BOOL bCreateDir = [fileManager createDirectoryAtPath:fileDir withIntermediateDirectories:YES attributes:nil error:nil];
if(!bCreateDir){
NSLog(@"Create Directory Failed.");
} else {
[[self class] saveFile:savedPath withDictionary:dic];
}
} else {//目錄存在讨越,直接保存
[[self class] saveFile:savedPath withDictionary:dic];
}
}
+ (BOOL)saveFile:(NSString *)savedPath withDictionary:(NSDictionary *)dic
{
BOOL isWrited = [dic writeToFile:savedPath atomically:YES];
NSLog(@"saveReceiptValidation is success ? %@, at savedPath:%@", @(isWrited), savedPath);
return isWrited;
}
+ (NSString *)getIAPInfoLocalFilePath:(NSString *)sID
{
return [NSString stringWithFormat:@"%@/IAPReceipt-%@/", [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject], sID];
}
2.2 間隔指定時(shí)間不斷重試
保存信息完成后,則間隔指定時(shí)間不斷重試永毅,直到驗(yàn)證成功把跨,或者APP進(jìn)程結(jié)束。
間隔時(shí)間沼死,可自定義着逐,個(gè)人覺(jué)得,5分鐘以上的間隔,會(huì)比較合適耸别。
1步和2步的重試邏輯的部分代碼
if (retried < kRetryMax) {
//重試
[[self class] validateReceipt:receipt orderNum:orderNum retriedTimes:retried+1 success:success failure:failure];
} else {
//重試了kRetryMax次后健芭,還失敗,則創(chuàng)建延時(shí)任務(wù)秀姐,5分鐘后重試
int afterTime = 5 * 60;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(afterTime * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[self class] validateReceipt:receipt orderNum:orderNum retriedTimes:0 success:nil failure:nil];
});
//保存憑證
NSString *userId = [TTVUserInfo sharedTTVUserInfo].currentUser.userId;
if (userId && userId.length > 0) {
[[self class] saveReceiptValidation:receipt withID:userId orderNum:orderNum];
}
//錯(cuò)誤回調(diào)
if (failure) {
failure(errCode, errMessage);
}
}
3 在APP進(jìn)程結(jié)束前慈迈,都未驗(yàn)證成功的情況處理
3.1 在啟動(dòng)或登錄成功時(shí)重新發(fā)起驗(yàn)證
在啟動(dòng)APP時(shí),如果用戶已經(jīng)登錄省有,則將所有驗(yàn)證失敗的支付憑證重新進(jìn)行驗(yàn)證痒留。如果用戶未登錄,則訂閱通知蠢沿,在用戶登錄首次登錄成功后伸头,重新發(fā)起驗(yàn)證流程。
3步的重新發(fā)起驗(yàn)證流程的部分代碼
/**
驗(yàn)證receipt失敗舷蟀,再次驗(yàn)證
@param sID 唯一標(biāo)識(shí)(比如UserId)
*/
+ (void)resendFailedReceiptValidation:(NSString *)sID
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = nil;
NSString *filePath = [[self class] getIAPInfoLocalFilePath:sID];
//搜索該目錄下的所有文件和目錄
NSArray *cacheFileNameArray = [fileManager contentsOfDirectoryAtPath:filePath error:&error];
NSLog(@"resendFailedReceiptValidation has files : %@", cacheFileNameArray);
if (error == nil)
{
for (NSString *name in cacheFileNameArray)
{
if ([name hasSuffix:@".plist"])//如果有plist后綴的文件恤磷,說(shuō)明就是存儲(chǔ)的購(gòu)買(mǎi)憑證
{
NSString *plistPath = [NSString stringWithFormat:@"%@/%@", filePath, name];
[[self class] resendValidationRequest:plistPath];
}
}
}
else
{
NSLog(@"getIAPInfoLocalFilePath error:%@", [error domain]);
}
}
注意事項(xiàng)
在每次重試時(shí),驗(yàn)證成功后雪侥,需要將本地存儲(chǔ)的文件移除碗殷,防止重復(fù)驗(yàn)證。