1榄檬、收款協(xié)議以及賬戶(hù)等的創(chuàng)建
內(nèi)購(gòu)收款協(xié)議等的創(chuàng)建卜范,這里一般由運(yùn)營(yíng)負(fù)責(zé),這里不做介紹鹿榜,但是如果想要了解先朦,請(qǐng)參考這位博主的文章,里面的圖文都解釋得很清楚犬缨。http://www.reibang.com/p/86ac7d3b593a
2、開(kāi)發(fā)者中心文件創(chuàng)建
要開(kāi)啟iOS內(nèi)購(gòu)功能棉浸,首先在apple develop 中心怀薛,先創(chuàng)建證書(shū)以及描述文件并包含內(nèi)購(gòu)功能。在項(xiàng)目中打開(kāi) In - App-purchase 功能即可繼續(xù)下面的代碼實(shí)現(xiàn)迷郑。
3枝恋、代碼實(shí)現(xiàn)
我這邊的代碼實(shí)現(xiàn)自己實(shí)現(xiàn)了一個(gè)工具類(lèi)创倔。然后內(nèi)購(gòu)的相關(guān)代碼以及邏輯在這個(gè)類(lèi)實(shí)現(xiàn),這樣做的好處是不需要在控制器中寫(xiě)過(guò)多的代碼焚碌,方便轉(zhuǎn)移使用畦攘,符合代碼高聚合性低耦合性的原則。
首先導(dǎo)入在項(xiàng)目的 Build Phases 下的Link Binary With libraires 中添加StoreKit.framework
在這個(gè)工具類(lèi)里面 十电,我寫(xiě)了一個(gè)單例方法,包括添加內(nèi)購(gòu)監(jiān)聽(tīng)知押,停止內(nèi)購(gòu)監(jiān)聽(tīng)以及發(fā)起內(nèi)購(gòu)購(gòu)買(mǎi)的方法,話(huà)不多說(shuō)直接上代碼 。
下面是IPAPurchase.h的代碼
#import <Foundation/Foundation.h>
/**
block
@param isSuccess 是否支付成功
@param certificate 支付成功得到的憑證(用于在自己服務(wù)器驗(yàn)證)
@param errorMsg 錯(cuò)誤信息
*/
typedef void(^PayResult)(BOOL isSuccess,NSString *certificate,NSString *errorMsg);
@interface IPAPurchase : NSObject
@property (nonatomic, copy)PayResult payResultBlock;
/**
單例方法
*/
+ (instancetype)manager;
/**
開(kāi)啟內(nèi)購(gòu)監(jiān)聽(tīng) 在程序入口didFinishLaunchingWithOptions實(shí)現(xiàn)
*/
-(void)startManager;
/**
停止內(nèi)購(gòu)監(jiān)聽(tīng) 在AppDelegate.m中的applicationWillTerminate方法實(shí)現(xiàn)
*/
-(void)stopManager;
/**
拉起內(nèi)購(gòu)支付
@param productID 內(nèi)購(gòu)商品ID
@param payResult 結(jié)果
*/
-(void)buyProductWithProductID:(NSString *)productID payResult:(PayResult)payResult;
以下是IPAPurchase.m文件的代碼
#import "IPAPurchase.h"
#import <StoreKit/StoreKit.h>
static NSString * const receiptKey = @"receipt_key";
dispatch_queue_t iap_queue() {
static dispatch_queue_t as_iap_queue;
static dispatch_once_t onceToken_iap_queue;
dispatch_once(&onceToken_iap_queue, ^{
as_iap_queue = dispatch_queue_create("com.iap.queue", DISPATCH_QUEUE_CONCURRENT);
});
return as_iap_queue;
}
@interface IPAPurchase()<SKPaymentTransactionObserver,
SKProductsRequestDelegate>
{
SKProductsRequest *request;
}
//購(gòu)買(mǎi)憑證
@property (nonatomic,copy)NSString *receipt;//存儲(chǔ)base64編碼的交易憑證
//產(chǎn)品ID
@property (nonnull,copy)NSString * profductId;
@end
static IPAPurchase * manager = nil;
@implementation IPAPurchase
#pragma mark -- 單例方法
+ (instancetype)manager{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (!manager) {
manager = [[IPAPurchase alloc] init];
}
});
return manager;
}
#pragma mark -- 漏單處理
-(void)startManager{
dispatch_sync(iap_queue(), ^{
[[SKPaymentQueue defaultQueue] addTransactionObserver:manager];
});
}
#pragma mark -- 移除交易事件
-(void)stopManager{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
});
}
#pragma mark -- 發(fā)起購(gòu)買(mǎi)的方法
-(void)buyProductWithProductID:(NSString *)productID payResult:(PayResult)payResult{
self.payResultBlock = payResult;
//移除上次未完成的交易訂單
[self removeAllUncompleteTransactionBeforeStartNewTransaction];
[RRHUD showWithContainerView:RR_keyWindow status:NSLocalizedString(@"購(gòu)買(mǎi)中...", @"")];
self.profductId = productID;
if (!self.profductId.length) {
UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"溫馨提示" message:@"沒(méi)有對(duì)應(yīng)的商品" delegate:nil cancelButtonTitle:@"確定" otherButtonTitles: nil];
[alertView show];
}
if ([SKPaymentQueue canMakePayments]) {
[self requestProductInfo:self.profductId];
}else{
UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"溫馨提示" message:@"請(qǐng)先開(kāi)啟應(yīng)用內(nèi)付費(fèi)購(gòu)買(mǎi)功能鹃骂。" delegate:nil cancelButtonTitle:@"確定" otherButtonTitles: nil];
[alertView show];
}
}
#pragma mark -- 結(jié)束上次未完成的交易 防止串單
-(void)removeAllUncompleteTransactionBeforeStartNewTransaction{
NSArray* transactions = [SKPaymentQueue defaultQueue].transactions;
if (transactions.count > 0) {
//檢測(cè)是否有未完成的交易
SKPaymentTransaction* transaction = [transactions firstObject];
if (transaction.transactionState == SKPaymentTransactionStatePurchased) {
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
return;
}
}
}
#pragma mark -- 發(fā)起購(gòu)買(mǎi)請(qǐng)求
-(void)requestProductInfo:(NSString *)productID{
NSArray * productArray = [[NSArray alloc]initWithObjects:productID,nil];
NSSet * IDSet = [NSSet setWithArray:productArray];
request = [[SKProductsRequest alloc]initWithProductIdentifiers:IDSet];
request.delegate = self;
[request start];
}
#pragma mark -- SKProductsRequestDelegate 查詢(xún)成功后的回調(diào)
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSArray *myProduct = response.products;
if (myProduct.count == 0) {
[RRHUD hide];
[RRHUD showErrorWithContainerView:UL_rootVC.view status:NSLocalizedString(@"沒(méi)有該商品信息", @"")];
if (self.payResultBlock) {
self.payResultBlock(NO, nil, @"無(wú)法獲取產(chǎn)品信息,購(gòu)買(mǎi)失敗");
}
return;
}
SKProduct * product = nil;
for(SKProduct * pro in myProduct){
NSLog(@"SKProduct 描述信息%@", [pro description]);
NSLog(@"產(chǎn)品標(biāo)題 %@" , pro.localizedTitle);
NSLog(@"產(chǎn)品描述信息: %@" , pro.localizedDescription);
NSLog(@"價(jià)格: %@" , pro.price);
NSLog(@"Product id: %@" , pro.productIdentifier);
if ([pro.productIdentifier isEqualToString:self.profductId]) {
product = pro;
break;
}
}
if (product) {
SKMutablePayment * payment = [SKMutablePayment paymentWithProduct:product];
//內(nèi)購(gòu)?fù)競(jìng)鲄?shù)
payment.applicationUsername = self.order;
[[SKPaymentQueue defaultQueue] addPayment:payment];
}else{
NSLog(@"沒(méi)有此商品");
}
}
//查詢(xún)失敗后的回調(diào)
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
if (self.payResultBlock) {
self.payResultBlock(NO, nil, [error localizedDescription]);
}
}
//如果沒(méi)有設(shè)置監(jiān)聽(tīng)購(gòu)買(mǎi)結(jié)果將直接跳至反饋結(jié)束台盯;
-(void)requestDidFinish:(SKRequest *)request{
}
#pragma mark -- 監(jiān)聽(tīng)結(jié)果
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions{
//當(dāng)用戶(hù)購(gòu)買(mǎi)的操作有結(jié)果時(shí),就會(huì)觸發(fā)下面的回調(diào)函數(shù)畏线,
for (SKPaymentTransaction * transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:{
[self completeTransaction:transaction];
}break;
case SKPaymentTransactionStateFailed:{
[self failedTransaction:transaction];
}break;
case SKPaymentTransactionStateRestored:{//已經(jīng)購(gòu)買(mǎi)過(guò)該商品
[RRHUD hide];
[self restoreTransaction:transaction];
}break;
case SKPaymentTransactionStatePurchasing:{
NSLog(@"正在購(gòu)買(mǎi)中...");
}break;
case SKPaymentTransactionStateDeferred:{
NSLog(@"最終狀態(tài)未確定");
}break;
default:
break;
}
}
}
//完成交易
#pragma mark -- 交易完成的回調(diào)
- (void)completeTransaction:(SKPaymentTransaction *)transaction{
NSLog(@"購(gòu)買(mǎi)成功,準(zhǔn)備驗(yàn)證發(fā)貨");
[self getReceipt]; //獲取交易成功后的購(gòu)買(mǎi)憑證
[self saveReceipt:transaction]; //存儲(chǔ)交易憑證
[self checkIAPFiles:transaction];
}
#pragma mark -- 處理交易失敗回調(diào)
- (void)failedTransaction:(SKPaymentTransaction *)transaction
{
[RRHUD hide];
NSString *error = nil;
if(transaction.error.code != SKErrorPaymentCancelled) {
[RRHUD showInfoWithContainerView:UL_rootVC.view status:NSLocalizedString(@"購(gòu)買(mǎi)失敗", @"")];
} else {
[RRHUD showInfoWithContainerView:UL_rootVC.view status:NSLocalizedString(@"取消購(gòu)買(mǎi)", @"")];
}
if (self.payResultBlock) {
self.payResultBlock(NO, nil, error);
}
[[SKPaymentQueue defaultQueue]finishTransaction:transaction];
}
- (void)restoreTransaction:(SKPaymentTransaction *)transaction{
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
#pragma mark -- 獲取購(gòu)買(mǎi)憑證
-(void)getReceipt{
NSURL * receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
NSData * receiptData = [NSData dataWithContentsOfURL:receiptUrl];
NSString * base64String = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
self.receipt = base64String;
}
#pragma mark -- 存儲(chǔ)購(gòu)買(mǎi)憑證
-(void)saveReceipt:(SKPaymentTransaction *)transaction{
NSString * userId;
NSString * order;
if (self.userid) {
userId = self.userid;
[[NSUserDefaults standardUserDefaults]setObject:userId forKey:@"unlock_iap_userId"];
}else{
userId = [[NSUserDefaults standardUserDefaults]objectForKey:@"unlock_iap_userId"];
}
order = transaction.payment.applicationUsername;
NSString *fileName = [NSString UUID];
NSString *savedPath = [NSString stringWithFormat:@"%@/%@.plist", [SandBoxHelper iapReceiptPath], fileName];
NSMutableDictionary * dic = [[NSMutableDictionary alloc]init];
[dic setValue: self.receipt forKey:receiptKey];
[dic setValue: userId forKey:@"user_id"];
[dic setValue: order forKey:@"order"];
BOOL ifWriteSuccess = [dic writeToFile:savedPath atomically:YES];
if (ifWriteSuccess) {
NSLog(@"購(gòu)買(mǎi)憑據(jù)存儲(chǔ)成功!");
}
}
#pragma mark -- 驗(yàn)證本地?cái)?shù)據(jù)
-(void)checkIAPFiles:(SKPaymentTransaction *)transaction{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = nil;
NSArray *cacheFileNameArray = [fileManager contentsOfDirectoryAtPath:[SandBoxHelper iapReceiptPath] error:&error];
if (error == nil) {
for (NSString *name in cacheFileNameArray) {
if ([name hasSuffix:@".plist"]){ //如果有plist后綴的文件静盅,說(shuō)明就是存儲(chǔ)的購(gòu)買(mǎi)憑證
NSString *filePath = [NSString stringWithFormat:@"%@/%@", [SandBoxHelper iapReceiptPath], name];
[self sendAppStoreRequestBuyPlist:filePath trans:transaction];
}
}
} else {
}
}
#pragma mark -- 存儲(chǔ)成功訂單
-(void)SaveIapSuccessReceiptDataWithReceipt:(NSString *)receipt Order:(NSString *)order UserId:(NSString *)userId{
NSMutableDictionary * mdic = [[NSMutableDictionary alloc]init];
[mdic setValue:[self getCurrentZoneTime] forKey:@"time"];
[mdic setValue: order forKey:@"order"];
[mdic setValue: userId forKey:@"userid"];
[mdic setValue: receipt forKey:receiptKey];
NSString *fileName = [NSString UUID];
NSString * successReceiptPath = [NSString stringWithFormat:@"%@/%@.plist", [SandBoxHelper SuccessIapPath], fileName];
//存儲(chǔ)購(gòu)買(mǎi)成功的憑證
[mdic writeToFile:successReceiptPath atomically:YES];
}
#pragma mark -- 獲取系統(tǒng)時(shí)間的方法
-(NSString *)getCurrentZoneTime{
NSDate * date = [NSDate date];
NSDateFormatter*formatter = [[NSDateFormatter alloc]init];
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
NSString*dateTime = [formatter stringFromDate:date];
return dateTime;
}
#pragma mark -- 去服務(wù)器驗(yàn)證購(gòu)買(mǎi)
-(void)sendAppStoreRequestBuyPlist:(NSString *)plistPath trans:(SKPaymentTransaction *)transaction{
NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];
NSString * receipt = [dic objectForKey:receiptKey];
NSString * order = [dic objectForKey:@"order"];
NSString * userId = [dic objectForKey:@"user_id"];
#pragma mark -- 發(fā)送信息去驗(yàn)證是否成功
[[ULSDKAPI shareAPI] sendVertifyWithReceipt:receipt order:order success:^(ULSDKAPI *api, id responseObject) {
if (RequestSuccess) {
NSLog(@"服務(wù)器驗(yàn)證成功!");
[[SKPaymentQueue defaultQueue]finishTransaction:transaction];
[RRHUD hide];
[RRHUD showSuccessWithContainerView:UL_rootVC.view status:NSLocalizedString(@"購(gòu)買(mǎi)成功", @"")];
[[NSUserDefaults standardUserDefaults]removeObjectForKey:@"unlock_iap_userId"];
NSData * data = [NSData dataWithContentsOfFile:[[[NSBundle mainBundle] appStoreReceiptURL] path]];
NSString *result = [data base64EncodedStringWithOptions:0];
if (self.payResultBlock) {
self.payResultBlock(YES, result, nil);
}
//這里將成功但存儲(chǔ)起來(lái)
[self SaveIapSuccessReceiptDataWithReceipt:receipt Order:order UserId:userId];
[self successConsumptionOfGoodsWithOrder:order];
}else{
//在這里向服務(wù)器發(fā)送驗(yàn)證失敗相關(guān)信息
} failure:^(ULSDKAPI *api, NSString *failure) {
}
#pragma mark -- 根據(jù)訂單號(hào)來(lái)移除本地憑證的方法
-(void)successConsumptionOfGoodsWithOrder:(NSString * )cpOrder{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError * error;
if ([fileManager fileExistsAtPath:[SandBoxHelper iapReceiptPath]]) {
NSArray * cacheFileNameArray = [fileManager contentsOfDirectoryAtPath:[SandBoxHelper iapReceiptPath] error:&error];
if (error == nil) {
for (NSString * name in cacheFileNameArray) {
NSString * filePath = [NSString stringWithFormat:@"%@/%@", [SandBoxHelper iapReceiptPath], name];
[self removeReceiptWithPlistPath:filePath ByCpOrder:cpOrder];
}
}
}
}
#pragma mark -- 根據(jù)訂單號(hào)來(lái)刪除 存儲(chǔ)的憑證
-(void)removeReceiptWithPlistPath:(NSString *)plistPath ByCpOrder:(NSString *)cpOrder{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError * error;
NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];
NSString * order = [dic objectForKey:@"order"];
if ([cpOrder isEqualToString:order]) {
//移除與游戲cp訂單號(hào)一樣的plist 文件
BOOL ifRemove = [fileManager removeItemAtPath:plistPath error:&error];
if (ifRemove) {
NSLog(@"成功訂單移除成功");
}else{
NSLog(@"成功訂單移除失敗");
}
}else{
NSLog(@"本地?zé)o與之匹配的訂單");
}
}
接下來(lái)是遇到的坑與解決
坑 1
因?yàn)槲覀冏叩姆?wù)器驗(yàn)證發(fā)貨的流程,因此服務(wù)器驗(yàn)證這一步尤其重要寝殴。如果用戶(hù)付了款 蒿叠,但是沒(méi)有發(fā)貨的話(huà)那問(wèn)題就大了,客戶(hù)是無(wú)法忍受這種情況的(你丫的吞老子的錢(qián))蚣常。剛開(kāi)始的時(shí)候我是把以下結(jié)束交易的代碼 寫(xiě)到了購(gòu)買(mǎi)回調(diào)-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions 方法里市咽,導(dǎo)致一走到購(gòu)買(mǎi)回調(diào)里就告訴蘋(píng)果這個(gè)交易結(jié)束了。但是如果此時(shí)我們服務(wù)器沒(méi)收到購(gòu)買(mǎi)憑證或者中途出了問(wèn)題的話(huà)史隆,玩家是收不到購(gòu)買(mǎi)的東西的魂务。導(dǎo)致我們后臺(tái)沒(méi)有匹配的訂單號(hào),蘋(píng)果又沒(méi)有提供與我們平臺(tái)訂單號(hào)的匹配的參數(shù)泌射,導(dǎo)致無(wú)法確定是用戶(hù)充值了沒(méi)收到貨粘姜,還是用戶(hù)裝可憐來(lái)訛我們,這一度讓我們很痛苦熔酷,無(wú)奈只能告訴玩家去蘋(píng)果申請(qǐng)退款孤紧。經(jīng)過(guò)各種百度和研究實(shí)驗(yàn),所以在這里重點(diǎn)注意的是>苊亍:畔浴!躺酒!如果是后臺(tái)做驗(yàn)證的話(huà)請(qǐng)把以下代碼寫(xiě)到成功提交內(nèi)購(gòu)憑證到服務(wù)器后臺(tái)之后再結(jié)束這次交易押蚤。這樣確保后臺(tái)收到了憑證驗(yàn)證成功,因此每次用戶(hù)來(lái)問(wèn)我們?cè)趺礇](méi)收到貨或者什么的,我們都有據(jù)可循羹应。漏單率也大大降低揽碘。
[[SKPaymentQueue defaultQueue]finishTransaction:transaction];
坑2
因?yàn)槲沂窃谟螒蚬緦?xiě)SDK給研發(fā)用的,因此在給接口研發(fā)對(duì)接的時(shí)候就遇到一個(gè)問(wèn)題,就是研發(fā)接入的時(shí)候沒(méi)有實(shí)現(xiàn)內(nèi)購(gòu)監(jiān)聽(tīng)的代碼雳刺,也就是IPAPurchase.h 中的以下方法劫灶,我們看看這個(gè)方法是干什么的?蘋(píng)果的注釋是 Observers are not retained. The transactions array will only be synchronized with the server while the queue has observers. This may require that the user authenticate.假如我們沒(méi)寫(xiě)這個(gè)方法會(huì)怎樣掖桦?答案是:你購(gòu)買(mǎi)之后他壓根就沒(méi)走購(gòu)買(mǎi)的代理方法本昏,也就是說(shuō),他不會(huì)走-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions回調(diào)方法枪汪,就算你能獲取到商品又怎樣涌穆?反正你不添加監(jiān)聽(tīng),我就是不走料饥。這會(huì)導(dǎo)致什么問(wèn)題呢蒲犬?導(dǎo)致的問(wèn)題是:你購(gòu)買(mǎi)成功后的邏輯都不會(huì)走,你驗(yàn)證不了岸啡,你更發(fā)不了貨原叮。然而更加恐怖的是什么呢?假如不知道自己沒(méi)寫(xiě)巡蘸,不停地點(diǎn)擊購(gòu)買(mǎi)奋隶,買(mǎi)了100+次,這下你就攤上大事兒了悦荒。當(dāng)你知道自己沒(méi)寫(xiě)之后唯欣,將[[SKPaymentQueue defaultQueue] addTransactionObserver:self]代碼添加進(jìn)去之后 ,你會(huì)發(fā)現(xiàn)-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions這個(gè)方法被回調(diào)了100+次搬味。OMG!所以,記住了境氢,這個(gè)代碼最好在程序啟動(dòng)入口就實(shí)現(xiàn),這樣的話(huà)會(huì)在程序一進(jìn)來(lái)就去遍歷過(guò)往未完成的單碰纬。
-(void)startManager
坑3
我們假想一種情況萍聊,比如有兩個(gè)玩家A和B,首先A在應(yīng)用內(nèi)發(fā)起內(nèi)購(gòu)購(gòu)買(mǎi),成功了但是在去服務(wù)器驗(yàn)證的中間發(fā)生異常應(yīng)用退出了。也就是說(shuō)漏單了悦析,他買(mǎi)了東西沒(méi)有到賬寿桨。然后他找到B說(shuō),我曹强戴!我剛才買(mǎi)了東西亭螟,但是沒(méi)收到貨。我懷疑是不是我手機(jī)有問(wèn)題骑歹,我在你手機(jī)上登錄看看预烙,會(huì)不會(huì)到貨了。于是道媚,A在B的手機(jī)上登陸了自己的appid 并且進(jìn)入應(yīng)用內(nèi)發(fā)現(xiàn)們依然沒(méi)有到賬扁掸。于是他倆都把應(yīng)用刪除了欢嘿,然后從新下載安裝發(fā)現(xiàn),還是沒(méi)有到貨也糊。他們倆很氣,找到你們公司客服說(shuō):你丫的羡宙,怎么我買(mǎi)了東西都沒(méi)到賬狸剃。我都收到蘋(píng)果發(fā)送的憑據(jù)了。你們信不信我去工商局告你狗热?然后你們客服問(wèn)后臺(tái)說(shuō)钞馁,后臺(tái),他們說(shuō)他們已經(jīng)付款了匿刮,但是沒(méi)收到貨僧凰,你能查一下后臺(tái)有沒(méi)有對(duì)應(yīng)的訂單么?然后后臺(tái)趕緊去看一下熟丸,竟然沒(méi)有對(duì)應(yīng)的訂單训措。于是猜測(cè)說(shuō)他們是來(lái)訛我們的,不用管光羞。于是就這樣绩鸣,用戶(hù)付了錢(qián)沒(méi)收到東西,服務(wù)器端也找都不到對(duì)應(yīng)的訂單纱兑。大家相互猜疑和指責(zé)呀闻。問(wèn)題不了了之,對(duì)用戶(hù)而言潜慎,他們無(wú)辜的浪費(fèi)了金錢(qián)得不到東西捡多,體驗(yàn)很差。對(duì)公司而言铐炫,無(wú)法確認(rèn)問(wèn)題垒手,導(dǎo)致用戶(hù)的流失。這都是我們不想遇到的驳遵。那這個(gè)問(wèn)題的出現(xiàn)原因在哪里呢淫奔?首先,如果按照漏單流程來(lái)走的話(huà)堤结,獲取A用戶(hù)在下次進(jìn)應(yīng)用的時(shí)候就會(huì)收到東西唆迁,但是他沒(méi)有這樣而是在B的手機(jī)上登陸了APPid,也沒(méi)到賬的情況下,他們把應(yīng)用都刪除了竞穷。重點(diǎn)就是這一步唐责,刪除了。一般的補(bǔ)單流程是這樣的瘾带,如果沒(méi)告訴蘋(píng)果這筆訂單已經(jīng)完成鼠哥,那么下次進(jìn)來(lái)的時(shí)候,他會(huì)走一個(gè)補(bǔ)單的流程。也就是重新走 -(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions朴恳,成功后走服務(wù)器驗(yàn)證->驗(yàn)證成功之后發(fā)貨抄罕。但是如果刪除了應(yīng)用那問(wèn)題就不一樣了。假如于颖,你們后臺(tái)的驗(yàn)證流程是需要購(gòu)買(mǎi)憑證以及平臺(tái)訂單號(hào)的呆贿。那么如果刪除了應(yīng)用,那此時(shí)走補(bǔ)單的流程森渐,這個(gè)訂單號(hào)該怎么獲茸鋈搿?因?yàn)閯h除了應(yīng)用也就是說(shuō)同衣,之前存儲(chǔ)的訂單號(hào)都沒(méi)了竟块,那為什么B的手機(jī)也沒(méi)法補(bǔ)單成功呢?那是因?yàn)锽本來(lái)就沒(méi)存儲(chǔ)平臺(tái)訂單號(hào)耐齐。所以去服務(wù)器驗(yàn)證當(dāng)然也驗(yàn)證不過(guò)浪秘,因?yàn)槿鄙倨脚_(tái)訂單參數(shù),所以請(qǐng)求無(wú)法完成蚪缀。在iOS7 之前秫逝,針對(duì)這種情況,沒(méi)法解決這種情況询枚。但是在iOS7 之后违帆。蘋(píng)果新增了一個(gè)applicationUsername的屬性,那這個(gè)屬性是干嘛的金蜀?這個(gè)屬性是在創(chuàng)建內(nèi)購(gòu)支付的透?jìng)鲄?shù)刷后,在iOS 7 之后蘋(píng)果新增的。他的作用渊抄,在發(fā)起支付前把這個(gè)參數(shù)的值設(shè)置為平臺(tái)訂單號(hào)尝胆,是在購(gòu)買(mǎi)成功之后,這個(gè)參數(shù)原樣一并返回到回調(diào)方法的transcation 中护桦,通過(guò)transcation.payment.applicationUsername可以獲取到含衔,而且是每筆訂單一一對(duì)應(yīng)的。這樣我們?cè)趧?chuàng)建交易的時(shí)候加上這個(gè)參數(shù)二庵,這個(gè)參數(shù)的值為我們的平臺(tái)訂單號(hào)贪染,這樣我們的平臺(tái)訂單就能跟每筆內(nèi)購(gòu)交易對(duì)應(yīng)上了。
SKMutablePayment * payment = [SKMutablePayment paymentWithProduct:product];
//內(nèi)購(gòu)?fù)競(jìng)鲄?shù),與transaction一一對(duì)應(yīng)
payment.applicationUsername = self.order;
[[SKPaymentQueue defaultQueue] addPayment:payment];
因此催享,無(wú)論我們?cè)谀桥_(tái)手機(jī)上登錄杭隙,都可以獲取到交易對(duì)應(yīng)的平臺(tái)訂單,也就可以向服務(wù)器驗(yàn)證成功了因妙。耶~~
坑4
當(dāng)你因?yàn)槟撤N原因購(gòu)買(mǎi)了東西痰憎,但是沒(méi)告訴蘋(píng)果這個(gè)交易已經(jīng)完成的時(shí)候再次發(fā)起購(gòu)買(mǎi)票髓,會(huì)發(fā)生什么事呢?你會(huì)發(fā)現(xiàn)出現(xiàn)一個(gè)提示“您已購(gòu)買(mǎi)此App內(nèi)購(gòu)買(mǎi)項(xiàng)目,此項(xiàng)目將會(huì)免費(fèi)恢復(fù)”铣耘。當(dāng)然出現(xiàn)這種的可能性不高洽沟,但是還是會(huì)有遇到。如果是消耗性的商品蜗细,如果不處理會(huì)導(dǎo)致這個(gè)內(nèi)購(gòu)項(xiàng)目一直無(wú)法購(gòu)買(mǎi)的問(wèn)題玲躯。那怎么處理呢?首先鳄乏,在以下方法中存儲(chǔ)著未完成的單,
NSArray* transactions = [SKPaymentQueue defaultQueue].transactions;
在發(fā)起新的購(gòu)買(mǎi)之前棘利,我們先去檢查一下是否有已經(jīng)購(gòu)買(mǎi)成功但是未結(jié)束交易的單橱野,如果有的話(huà),實(shí)現(xiàn)以下代碼將未結(jié)束交易的單結(jié)束掉再發(fā)起新的購(gòu)買(mǎi)就OK 啦善玫。
NSArray* transactions = [SKPaymentQueue defaultQueue].transactions;
if (transactions.count > 0) {
//檢測(cè)是否有未完成的交易
SKPaymentTransaction* transaction = [transactions firstObject];
if (transaction.transactionState == SKPaymentTransactionStatePurchased) {
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
return;
}
}
github 的直通鏈接 https://github.com/jiajiaailaras/ULIPAPurchase 覺(jué)得有幫助的水援。可以給個(gè)Star 支持一下茅郎。