iOS內(nèi)購詳細教程&iOS內(nèi)購坑

1、收款協(xié)議以及賬戶等的創(chuàng)建

內(nèi)購收款協(xié)議等的創(chuàng)建,這里一般由運營負責,這里不做介紹,但是如果想要了解,請參考這位博主的文章,里面的圖文都解釋得很清楚。http://www.itdecent.cn/p/86ac7d3b593a

2、開發(fā)者中心文件創(chuàng)建

要開啟iOS內(nèi)購功能,首先在apple develop 中心,先創(chuàng)建證書以及描述文件并包含內(nèi)購功能。在項目中打開 In - App-purchase 功能即可繼續(xù)下面的代碼實現(xiàn)。

3、代碼實現(xiàn)

我這邊的代碼實現(xiàn)自己實現(xiàn)了一個工具類。然后內(nèi)購的相關(guān)代碼以及邏輯在這個類實現(xiàn),這樣做的好處是不需要在控制器中寫過多的代碼,方便轉(zhuǎn)移使用,符合代碼高聚合性低耦合性的原則。

首先導入在項目的 Build Phases 下的Link Binary With libraires 中添加StoreKit.framework

在這個工具類里面 ,我寫了一個單例方法,包括添加內(nèi)購監(jiān)聽,停止內(nèi)購監(jiān)聽以及發(fā)起內(nèi)購購買的方法,話不多說直接上代碼 。

下面是IPAPurchase.h的代碼

#import <Foundation/Foundation.h>

/**
 block

 @param isSuccess 是否支付成功
 @param certificate 支付成功得到的憑證(用于在自己服務(wù)器驗證)
 @param errorMsg 錯誤信息
 */
typedef void(^PayResult)(BOOL isSuccess,NSString *certificate,NSString *errorMsg);

@interface IPAPurchase : NSObject
@property (nonatomic, copy)PayResult payResultBlock;

/**
單例方法
*/
+ (instancetype)manager;

/**
 開啟內(nèi)購監(jiān)聽 在程序入口didFinishLaunchingWithOptions實現(xiàn)
 */
-(void)startManager;

/**
停止內(nèi)購監(jiān)聽 在AppDelegate.m中的applicationWillTerminate方法實現(xiàn)
*/
-(void)stopManager;

/**
 拉起內(nèi)購支付
 @param productID 內(nèi)購商品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;
}

//購買憑證
@property (nonatomic,copy)NSString *receipt;//存儲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ā)起購買的方法
-(void)buyProductWithProductID:(NSString *)productID  payResult:(PayResult)payResult{

self.payResultBlock = payResult;
//移除上次未完成的交易訂單
[self removeAllUncompleteTransactionBeforeStartNewTransaction];

[RRHUD showWithContainerView:RR_keyWindow status:NSLocalizedString(@"購買中...", @"")];

self.profductId = productID;

if (!self.profductId.length) {

    UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"溫馨提示" message:@"沒有對應(yīng)的商品" delegate:nil cancelButtonTitle:@"確定" otherButtonTitles: nil];

    [alertView show];
}

if ([SKPaymentQueue canMakePayments]) {

    [self requestProductInfo:self.profductId];

}else{

    UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"溫馨提示" message:@"請先開啟應(yīng)用內(nèi)付費購買功能。" delegate:nil cancelButtonTitle:@"確定" otherButtonTitles: nil];

    [alertView show];

      }
}

#pragma mark -- 結(jié)束上次未完成的交易 防止串單
-(void)removeAllUncompleteTransactionBeforeStartNewTransaction{

NSArray* transactions = [SKPaymentQueue defaultQueue].transactions;
if (transactions.count > 0) {
    //檢測是否有未完成的交易
    SKPaymentTransaction* transaction = [transactions firstObject];
    if (transaction.transactionState == SKPaymentTransactionStatePurchased) {
        [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
        return;
    }
}
}

#pragma mark -- 發(fā)起購買請求
-(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 查詢成功后的回調(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(@"沒有該商品信息", @"")];

    if (self.payResultBlock) {
        self.payResultBlock(NO, nil, @"無法獲取產(chǎn)品信息,購買失敗");
    }

    return;
}

SKProduct * product = nil;
for(SKProduct * pro in myProduct){

    NSLog(@"SKProduct 描述信息%@", [pro description]);
    NSLog(@"產(chǎn)品標題 %@" , pro.localizedTitle);
    NSLog(@"產(chǎn)品描述信息: %@" , pro.localizedDescription);
    NSLog(@"價格: %@" , 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)購透傳參數(shù)
payment.applicationUsername = self.order;

[[SKPaymentQueue defaultQueue] addPayment:payment];

}else{

    NSLog(@"沒有此商品");
   }
}

//查詢失敗后的回調(diào)
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {

if (self.payResultBlock) {
    self.payResultBlock(NO, nil, [error localizedDescription]);
      }
}

//如果沒有設(shè)置監(jiān)聽購買結(jié)果將直接跳至反饋結(jié)束;
-(void)requestDidFinish:(SKRequest *)request{

}

#pragma mark -- 監(jiān)聽結(jié)果
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions{

//當用戶購買的操作有結(jié)果時,就會觸發(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)購買過該商品

        [RRHUD hide];
        [self restoreTransaction:transaction];

        }break;

        case SKPaymentTransactionStatePurchasing:{

            NSLog(@"正在購買中...");

        }break;

        case SKPaymentTransactionStateDeferred:{

            NSLog(@"最終狀態(tài)未確定");

        }break;

        default:
            break;
      }
   }
}

//完成交易
#pragma mark -- 交易完成的回調(diào)
- (void)completeTransaction:(SKPaymentTransaction *)transaction{

NSLog(@"購買成功,準備驗證發(fā)貨");
[self getReceipt]; //獲取交易成功后的購買憑證
[self saveReceipt:transaction]; //存儲交易憑證
[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(@"購買失敗", @"")];
} else {

    [RRHUD showInfoWithContainerView:UL_rootVC.view status:NSLocalizedString(@"取消購買", @"")];
}

if (self.payResultBlock) {
    self.payResultBlock(NO, nil, error);
}

[[SKPaymentQueue defaultQueue]finishTransaction:transaction];

}

- (void)restoreTransaction:(SKPaymentTransaction *)transaction{

[[SKPaymentQueue defaultQueue] finishTransaction:transaction];

}

#pragma mark -- 獲取購買憑證
-(void)getReceipt{

NSURL * receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
NSData * receiptData = [NSData dataWithContentsOfURL:receiptUrl];
NSString * base64String = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
 self.receipt = base64String;
}

#pragma mark -- 存儲購買憑證
-(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(@"購買憑據(jù)存儲成功!");
  }
}

#pragma mark -- 驗證本地數(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后綴的文件,說明就是存儲的購買憑證

            NSString *filePath = [NSString stringWithFormat:@"%@/%@", [SandBoxHelper iapReceiptPath], name];

            [self sendAppStoreRequestBuyPlist:filePath trans:transaction];
        }
    }

} else {

  }
}

#pragma mark -- 存儲成功訂單
-(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];
//存儲購買成功的憑證
[mdic writeToFile:successReceiptPath atomically:YES];

}

#pragma mark -- 獲取系統(tǒng)時間的方法
-(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ù)器驗證購買
-(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ā)送信息去驗證是否成功
[[ULSDKAPI shareAPI] sendVertifyWithReceipt:receipt order:order success:^(ULSDKAPI *api, id responseObject) {

    if (RequestSuccess) {

        NSLog(@"服務(wù)器驗證成功!");

        [[SKPaymentQueue defaultQueue]finishTransaction:transaction];

        [RRHUD hide];

        [RRHUD showSuccessWithContainerView:UL_rootVC.view status:NSLocalizedString(@"購買成功", @"")];

        [[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);
        }
        //這里將成功但存儲起來
        [self SaveIapSuccessReceiptDataWithReceipt:receipt Order:order UserId:userId];

        [self successConsumptionOfGoodsWithOrder:order];

    }else{            
      //在這里向服務(wù)器發(fā)送驗證失敗相關(guān)信息
} failure:^(ULSDKAPI *api, NSString *failure) {

}

#pragma mark -- 根據(jù)訂單號來移除本地憑證的方法
-(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ù)訂單號來刪除 存儲的憑證
-(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訂單號一樣的plist 文件
    BOOL ifRemove =  [fileManager removeItemAtPath:plistPath error:&error];
    if (ifRemove) { 
        NSLog(@"成功訂單移除成功");
    }else{
        NSLog(@"成功訂單移除失敗");
    }
}else{
        NSLog(@"本地無與之匹配的訂單");
    }
}

接下來是遇到的坑與解決

坑 1

因為我們走的服務(wù)器驗證發(fā)貨的流程,因此服務(wù)器驗證這一步尤其重要。如果用戶付了款 ,但是沒有發(fā)貨的話那問題就大了,客戶是無法忍受這種情況的(你丫的吞老子的錢)。剛開始的時候我是把以下結(jié)束交易的代碼 寫到了購買回調(diào)-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions 方法里,導致一走到購買回調(diào)里就告訴蘋果這個交易結(jié)束了。但是如果此時我們服務(wù)器沒收到購買憑證或者中途出了問題的話,玩家是收不到購買的東西的。導致我們后臺沒有匹配的訂單號,蘋果又沒有提供與我們平臺訂單號的匹配的參數(shù),導致無法確定是用戶充值了沒收到貨,還是用戶裝可憐來訛我們,這一度讓我們很痛苦,無奈只能告訴玩家去蘋果申請退款。經(jīng)過各種百度和研究實驗,所以在這里重點注意的是?。。?!如果是后臺做驗證的話請把以下代碼寫到成功提交內(nèi)購憑證到服務(wù)器后臺之后再結(jié)束這次交易。這樣確保后臺收到了憑證驗證成功,因此每次用戶來問我們怎么沒收到貨或者什么的,我們都有據(jù)可循。漏單率也大大降低。

[[SKPaymentQueue defaultQueue]finishTransaction:transaction];

坑2

因為我是在游戲公司寫SDK給研發(fā)用的,因此在給接口研發(fā)對接的時候就遇到一個問題,就是研發(fā)接入的時候沒有實現(xiàn)內(nèi)購監(jiān)聽的代碼,也就是IPAPurchase.h 中的以下方法,我們看看這個方法是干什么的?蘋果的注釋是 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.假如我們沒寫這個方法會怎樣?答案是:你購買之后他壓根就沒走購買的代理方法,也就是說,他不會走-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions回調(diào)方法,就算你能獲取到商品又怎樣?反正你不添加監(jiān)聽,我就是不走。這會導致什么問題呢?導致的問題是:你購買成功后的邏輯都不會走,你驗證不了,你更發(fā)不了貨。然而更加恐怖的是什么呢?假如不知道自己沒寫,不停地點擊購買,買了100+次,這下你就攤上大事兒了。當你知道自己沒寫之后,將[[SKPaymentQueue defaultQueue] addTransactionObserver:self]代碼添加進去之后 ,你會發(fā)現(xiàn)-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions這個方法被回調(diào)了100+次。OMG!所以,記住了,這個代碼最好在程序啟動入口就實現(xiàn),這樣的話會在程序一進來就去遍歷過往未完成的單。

-(void)startManager

坑3

我們假想一種情況,比如有兩個玩家A和B,首先A在應(yīng)用內(nèi)發(fā)起內(nèi)購購買,成功了但是在去服務(wù)器驗證的中間發(fā)生異常應(yīng)用退出了。也就是說漏單了,他買了東西沒有到賬。然后他找到B說,我曹!我剛才買了東西,但是沒收到貨。我懷疑是不是我手機有問題,我在你手機上登錄看看,會不會到貨了。于是,A在B的手機上登陸了自己的appid 并且進入應(yīng)用內(nèi)發(fā)現(xiàn)們依然沒有到賬。于是他倆都把應(yīng)用刪除了,然后從新下載安裝發(fā)現(xiàn),還是沒有到貨。他們倆很氣,找到你們公司客服說:你丫的,怎么我買了東西都沒到賬。我都收到蘋果發(fā)送的憑據(jù)了。你們信不信我去工商局告你?然后你們客服問后臺說,后臺,他們說他們已經(jīng)付款了,但是沒收到貨,你能查一下后臺有沒有對應(yīng)的訂單么?然后后臺趕緊去看一下,竟然沒有對應(yīng)的訂單。于是猜測說他們是來訛我們的,不用管。于是就這樣,用戶付了錢沒收到東西,服務(wù)器端也找都不到對應(yīng)的訂單。大家相互猜疑和指責。問題不了了之,對用戶而言,他們無辜的浪費了金錢得不到東西,體驗很差。對公司而言,無法確認問題,導致用戶的流失。這都是我們不想遇到的。那這個問題的出現(xiàn)原因在哪里呢?首先,如果按照漏單流程來走的話,獲取A用戶在下次進應(yīng)用的時候就會收到東西,但是他沒有這樣而是在B的手機上登陸了APPid,也沒到賬的情況下,他們把應(yīng)用都刪除了。重點就是這一步,刪除了。一般的補單流程是這樣的,如果沒告訴蘋果這筆訂單已經(jīng)完成,那么下次進來的時候,他會走一個補單的流程。也就是重新走 -(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions,成功后走服務(wù)器驗證->驗證成功之后發(fā)貨。但是如果刪除了應(yīng)用那問題就不一樣了。假如,你們后臺的驗證流程是需要購買憑證以及平臺訂單號的。那么如果刪除了應(yīng)用,那此時走補單的流程,這個訂單號該怎么獲???因為刪除了應(yīng)用也就是說,之前存儲的訂單號都沒了,那為什么B的手機也沒法補單成功呢?那是因為B本來就沒存儲平臺訂單號。所以去服務(wù)器驗證當然也驗證不過,因為缺少平臺訂單參數(shù),所以請求無法完成。在iOS7 之前,針對這種情況,沒法解決這種情況。但是在iOS7 之后。蘋果新增了一個applicationUsername的屬性,那這個屬性是干嘛的?這個屬性是在創(chuàng)建內(nèi)購支付的透傳參數(shù),在iOS 7 之后蘋果新增的。他的作用,在發(fā)起支付前把這個參數(shù)的值設(shè)置為平臺訂單號,是在購買成功之后,這個參數(shù)原樣一并返回到回調(diào)方法的transcation 中,通過transcation.payment.applicationUsername可以獲取到,而且是每筆訂單一一對應(yīng)的。這樣我們在創(chuàng)建交易的時候加上這個參數(shù),這個參數(shù)的值為我們的平臺訂單號,這樣我們的平臺訂單就能跟每筆內(nèi)購交易對應(yīng)上了。

SKMutablePayment * payment = [SKMutablePayment paymentWithProduct:product];
//內(nèi)購透傳參數(shù),與transaction一一對應(yīng)
 payment.applicationUsername = self.order;
[[SKPaymentQueue defaultQueue] addPayment:payment];

因此,無論我們在那臺手機上登錄,都可以獲取到交易對應(yīng)的平臺訂單,也就可以向服務(wù)器驗證成功了。耶~~

坑4

當你因為某種原因購買了東西,但是沒告訴蘋果這個交易已經(jīng)完成的時候再次發(fā)起購買,會發(fā)生什么事呢?你會發(fā)現(xiàn)出現(xiàn)一個提示“您已購買此App內(nèi)購買項目,此項目將會免費恢復”。當然出現(xiàn)這種的可能性不高,但是還是會有遇到。如果是消耗性的商品,如果不處理會導致這個內(nèi)購項目一直無法購買的問題。那怎么處理呢?首先,在以下方法中存儲著未完成的單,

NSArray* transactions = [SKPaymentQueue defaultQueue].transactions;

在發(fā)起新的購買之前,我們先去檢查一下是否有已經(jīng)購買成功但是未結(jié)束交易的單,如果有的話,實現(xiàn)以下代碼將未結(jié)束交易的單結(jié)束掉再發(fā)起新的購買就OK 啦。

  NSArray* transactions = [SKPaymentQueue defaultQueue].transactions;
if (transactions.count > 0) {
    //檢測是否有未完成的交易
    SKPaymentTransaction* transaction = [transactions firstObject];
    if (transaction.transactionState == SKPaymentTransactionStatePurchased) {
        [[SKPaymentQueue defaultQueue] finishTransaction:transaction];            
        return;
    }
}

github 的直通鏈接 https://github.com/jiajiaailaras/ULIPAPurchase 覺得有幫助的。可以給個Star 支持一下。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容