iOS內(nèi)購訂閱相關(guān)代碼(可直接使用)

一、內(nèi)購支付的流程
用戶選擇需要訂閱的商品芬位,發(fā)起購買无拗,支付完成,校驗票據(jù)晶衷。
具體步驟:
1.新建內(nèi)購相關(guān)文件
.h代碼

//
//  JPurchaseManager.h
//  CameraProduct
//
//  Created by lier on 2024/9/6.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface JPurchaseManager : NSObject

@property(nonatomic,strong)NSMutableDictionary *__nullable track;

+(instancetype)shareManager;
// 購買
-(void)purchaseWithProductIdentifier:(NSString *)identifier;
//恢復(fù)購買
-(void)resumePurchase;

@end

NS_ASSUME_NONNULL_END

.m代碼

//
//  JPurchaseManager.m
//  CameraProduct
//
//  Created by lier on 2024/9/6.
//

#import "JPurchaseManager.h"
#import <StoreKit/StoreKit.h>

@interface JPurchaseManager ()<SKProductsRequestDelegate,SKPaymentTransactionObserver>

@property(nonatomic,strong)SKProductsRequest *__nullable productsRequest;
@property(nonatomic,copy)NSString *productIdentify;
@property(nonatomic,strong)SKPaymentTransaction *refreshTrans;
@property(nonatomic,assign)BOOL isShowLoading;
@property(nonatomic,assign)BOOL isRestore;

@end

@implementation JPurchaseManager

-(void)dealloc {
    [self releaseRequest];
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}

- (void)releaseRequest {
    if(_productsRequest) {
        [_productsRequest cancel];
        _productsRequest.delegate = nil;
        _productsRequest = nil;
    }
}

+(instancetype)shareManager {
    static JPurchaseManager *manager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager = [[JPurchaseManager alloc] init];
    });
    
    return manager;
}

-(instancetype)init {
    self = [super init];
    if (self) {
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    }
    return self;
}
//購買
-(void)purchaseWithProductIdentifier:(NSString *)identifier {
    self.isShowLoading = YES;
    _productIdentify = identifier;
    NSArray *transactions = [SKPaymentQueue defaultQueue].transactions;
    if (transactions.count > 0) {
        for (SKPaymentTransaction *transaction in transactions) {
            if (transaction.transactionState == SKPaymentTransactionStatePurchased
                || transaction.transactionState == SKPaymentTransactionStateRestored) {
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            }
        }
    }
    
    if (identifier.length > 0) {
        if ([SKPaymentQueue canMakePayments]) {
            [SVProgressHUD showWithStatus:@"正在添加商品"];
            [self releaseRequest];
            NSSet *productIdentifiers = [NSSet setWithObjects:identifier, nil];
            self.productsRequest = [[SKProductsRequest alloc]initWithProductIdentifiers:productIdentifiers];
            self.productsRequest.delegate = self;
            [self.productsRequest start];
        } else {
            _productIdentify = nil;
            self.isShowLoading = NO;
            [SVProgressHUD showErrorWithStatus:@"尚未開啟應(yīng)用內(nèi)付費(fèi)購買"];
        }
    } else {
        _productIdentify = nil;
        self.isShowLoading = NO;
        [SVProgressHUD showErrorWithStatus:@"無效商品"];
    }
}
//恢復(fù)購買
- (void)resumePurchase {
    self.isRestore = YES;
    self.isShowLoading = YES;
    _productIdentify = nil;
    self.track = nil;
    [SVProgressHUD showWithStatus:@"恢復(fù)購買中..."];
    [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}

#pragma mark - SKProductsRequestDelegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
    NSArray * arrProducts = response.products;
    
    if ([arrProducts count] == 0) {
        _productIdentify = nil;
        self.isShowLoading = NO;
        [SVProgressHUD showErrorWithStatus:@"沒有對應(yīng)的可訂閱項目"];
        
        return;
    }
    
    SKProduct *currentPoduct = nil;
    for(SKProduct *product in arrProducts) {
        if ([self.productIdentify isEqualToString:product.productIdentifier]) {
            currentPoduct = product;
        }
    }
    
    if (currentPoduct != nil) {
        SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:currentPoduct];
        payment.quantity = 1;
        payment.applicationUsername = [JScreenUnit manager].device_id;
        [[SKPaymentQueue defaultQueue] addPayment:payment];
    } else {
        _productIdentify = nil;
        self.isShowLoading = NO;
        [SVProgressHUD showErrorWithStatus:@"沒有對應(yīng)的可訂閱項目"];
    }
}

- (void)request:(SKRequest*)request didFailWithError:(NSError*)error {
    _productIdentify = nil;
    self.isShowLoading = NO;
    [SVProgressHUD showErrorWithStatus:@"操作失敗"];
}

#pragma mark - SKPaymentTransactionObserver
- (BOOL)paymentQueue:(SKPaymentQueue *)queue shouldAddStorePayment:(SKPayment *)payment forProduct:(SKProduct *)product {
    return YES;
}

/** 監(jiān)聽購買結(jié)果 */
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
//    NSLog(@"=== updatedTransactions ===");
    if (!self.isRestore) {
        for (int a = 0 ; a < transactions.count; a++) {
            SKPaymentTransaction *transaction = [transactions objectAtIndex:a];
//            NSLog(@"updatedTransactions ===%@",transaction);
            switch (transaction.transactionState) {
                case SKPaymentTransactionStatePurchasing: {
                    [SVProgressHUD showWithStatus:@"正在處理"];
                }
                    break;
                case SKPaymentTransactionStateFailed: {
                    //交易失敗
                    if (transaction.error.code == SKErrorPaymentCancelled) {
                        self.isShowLoading = NO;
                        [SVProgressHUD showInfoWithStatus:@"您取消了支付"];
                        self.productIdentify = nil;
                    } else {
                        self.isShowLoading = NO;
                        [SVProgressHUD showErrorWithStatus:@"交易失敗"];
                    }
                    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                }
                    break;
                case SKPaymentTransactionStatePurchased: {
                    NSString *ID = @"";
                    if (![NSString isBlank:transaction.originalTransaction.transactionIdentifier]) {
                        ID = transaction.originalTransaction.transactionIdentifier;
                    } else if (![NSString isBlank:transaction.transactionIdentifier]) {
                        ID = transaction.transactionIdentifier;
                    }
                    [self verifyTicketsWithTransation:transaction transIds:@[ID]];
                }
                    break;
                case SKPaymentTransactionStateDeferred: {
                    //等待蓝纲,不做任何處理
                }
                    break;
                default:
                    break;
            }
        }
    } else {
        for (int a = 0 ; a < transactions.count; a++) {
            SKPaymentTransaction *transaction = [transactions objectAtIndex:a];
//            NSLog(@"+++ updatedTransactions ===%@===transactionState %ld",transaction.transactionIdentifier,transaction.transactionState);
            switch (transaction.transactionState) {
                case SKPaymentTransactionStateRestored: {
                    //已經(jīng)購買過該商品
                    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                }
                    break;
                default:
                    break;
            }
        }
    }
}

- (void)paymentQueue:(SKPaymentQueue*)queue restoreCompletedTransactionsFailedWithError:(NSError*)error {
    //在將交易從用戶的購買歷史記錄添加回隊列時遇到錯誤時
    self.isShowLoading = NO;
    [SVProgressHUD showErrorWithStatus:@"恢復(fù)購買失敗"];
}

- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue*)queue {
    //當(dāng)用戶的購買歷史記錄中的所有交易成功添加回隊列時
    if (queue.transactions.count == 0) {
        self.isShowLoading = NO;
        self.isRestore = NO;
        [SVProgressHUD showErrorWithStatus:@"無訂閱記錄,現(xiàn)在去訂閱"];
    } else {
        //驗證票據(jù)一下
        NSArray *arrIDs = [self getoriginalTransIds:queue.transactions];
        
        [self verifyTicketsWithTransation:nil transIds:arrIDs];
    }
}

#pragma mark - 驗證購買
- (void)verifyTicketsWithTransation:(SKPaymentTransaction *__nullable)transaction transIds:(NSArray *)transIds {
    // 從沙盒中獲取交易憑證(收據(jù))
    NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
    NSData *receiptData = [NSData dataWithContentsOfURL:receiptUrl];
    // 轉(zhuǎn)化為base64字符串
    NSString *receipt = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
    
    if (receipt.length > 0) {
        if (self.isShowLoading) {
            [SVProgressHUD showWithStatus:@"憑證驗證中..."];
        }
        NSMutableDictionary *para = [NSMutableDictionary dictionary];
        if (transIds.count > 0) {
            [para setValue:transIds forKey:@"transaction_ids"];
        }
        
        [para setValue:@(self.isRestore) forKey:@"restore_of"];
        [para setObject:receipt forKey:@"receipt"];
        
        if (_track && ![NSString isBlank:self.productIdentify]) {
            self.productIdentify = nil;
            [_track setValue:[NSString getCurrentTimeInterval] forKey:@"action_time"];
            [para setValue:_track forKey:@"track"];
        }
        MJWeakSelf
        [JRequestHTTPEngine requestWithURL:@"校驗票據(jù)接口" withMethod:postMethod params:para successBlock:^(BOOL isSuccess, NSDictionary * _Nonnull result) {
            weakSelf.track = nil;
            if (transaction) {
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            }
            if (isSuccess) {
                NSDictionary *dataDic = [result objectForKey:@"data"];
                BOOL vip = [[dataDic objectForKey:@"vip"] boolValue];
                if (vip) {
                    [JScreenUnit manager].mineInfo.identity.vip = YES;
                    kPostNotification(kMembershipStatusChangeNotify, nil);
                    kPostNotification(kPurchaseSuccessCloseNotify, nil);
                    if (weakSelf.isShowLoading) {
                        weakSelf.isShowLoading = NO;
                        if (!weakSelf.isRestore) {
                            [SVProgressHUD showSuccessWithStatus:@"交易成功"];
                        } else {
                            [SVProgressHUD showSuccessWithStatus:@"恢復(fù)成功"];
                        }
                    }
                } else {
                    if (weakSelf.isShowLoading) {
                        weakSelf.isShowLoading = NO;
                        if (!weakSelf.isRestore) {
                            [SVProgressHUD showErrorWithStatus:@"交易服務(wù)中斷了晌纫,請聯(lián)系我們~"];
                        } else {
                            [SVProgressHUD showErrorWithStatus:@"會員已過期税迷,請重新訂閱"];
                        }
                    }
                }
            } else {
                if (weakSelf.isShowLoading) {
                    weakSelf.isShowLoading = NO;
                    [SVProgressHUD showErrorWithStatus:result[@"message"]];
                }
            }
            weakSelf.isRestore = NO;
        } failureBlock:^(NSString * _Nonnull errorString) {
            if (weakSelf.isShowLoading) {
                weakSelf.isShowLoading = NO;
                [SVProgressHUD showErrorWithStatus:errorString];
            }
            weakSelf.isRestore = NO;
        }];
    } else {
        self.isRestore = NO;
        self.productIdentify = nil;
        if (self.isShowLoading) {
            self.isShowLoading = NO;
            [SVProgressHUD dismiss];
        }
    }
}

- (NSArray *)getoriginalTransIds:(NSArray *)transArr {
    //遍歷取得原始訂單號,按時間正序去重加入數(shù)組
    NSMutableDictionary *originalTransDic = @{}.mutableCopy;
    for (SKPaymentTransaction *trans in transArr) {
        NSString *tid = trans.transactionIdentifier;
        NSString *timestamp = [NSString stringWithFormat:@"%ld", (NSInteger)[trans.transactionDate timeIntervalSince1970]];
        if (trans.originalTransaction) {
            tid = trans.originalTransaction.transactionIdentifier;
            timestamp = [NSString stringWithFormat:@"%ld", (NSInteger)[trans.originalTransaction.transactionDate timeIntervalSince1970]];
        }
        if (![NSString isBlank:tid] && ![NSString isBlank:timestamp]) {
            [originalTransDic setValue:tid forKey:timestamp];
        }
    }
    //按時間升序
    NSArray *keysArr = [originalTransDic.allKeys sortedArrayUsingComparator:^NSComparisonResult(NSString *  _Nonnull obj1, NSString *  _Nonnull obj2) {
        //升序
        NSComparisonResult result = [obj1 compare:obj2];
        return result;
    }];
    //NSSet會去重
    NSMutableSet *mutSet = [NSMutableSet set];
    for (NSString *key in keysArr) {
        if ([originalTransDic objectForKey:key]) {
            [mutSet addObject:originalTransDic[key]];
        }
    }
    return [mutSet allObjects];
}

@end
注意:

1.之所以吧SKPaymentTransactionStateRestored這個狀態(tài)放在else進(jìn)行操作锹漱,是為了避免校驗票據(jù)的時候會走多次的問題箭养。可以把

case SKPaymentTransactionStateRestored: {
                    //已經(jīng)購買過該商品
                    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                }
                    break;

這句話放到SKPaymentTransactionStateDeferred之后進(jìn)行打印操作試試哥牍。避免服務(wù)器內(nèi)存瞬間暴漲導(dǎo)致崩潰毕泌。
2.校驗票據(jù)接口里面用到的通知kPostNotification是為了操作一些業(yè)務(wù)。比如刷新用戶VIP狀態(tài)嗅辣、購買成功后當(dāng)前訂閱界面關(guān)閉掉等業(yè)務(wù)撼泛,其他自行操作。
3.isShowLoading是為了是不是顯示加載框澡谭。避免用戶亂操作愿题。
4.device_id使用下面方法獲取:

- (NSString *)device_id {
    if (!_device_id) {
        NSString *locallyID = [kUserDefaults valueForKey:kDeviceKey];
        BOOL locally = [NSString isBlank:locallyID];
        
        NSString *keyChainID = [JScreenUnit readData:deviceIDChain];
        BOOL keyChain = [NSString isBlank:keyChainID];
        
        if (locally && keyChain) {
            CFUUIDRef puuid = CFUUIDCreate(nil);
            CFStringRef uuidString = CFUUIDCreateString(nil, puuid);
            NSString *result = (NSString *)CFBridgingRelease(CFStringCreateCopy(NULL, uuidString));
            NSMutableString *tmpResult = result.mutableCopy;
            _device_id = tmpResult;
        } else if (!locally) {
            _device_id = locallyID;
        } else if (!keyChain) {
            _device_id = keyChainID;
        }
        if (locally) {
            [kUserDefaults setValue:_device_id forKey:kDeviceKey];
            [kUserDefaults synchronize];
        }
        if (keyChain) {
            [JScreenUnit saveData:_device_id withIdentifier:deviceIDChain];
        }
    }
    return _device_id;
}

/*!
 保存數(shù)據(jù)
 */
+ (BOOL)saveData:(id)data withIdentifier:(NSString*)identifier {
    // 獲取存儲的數(shù)據(jù)的條件
    NSMutableDictionary * saveQueryMutableDictionary = [self keyChainIdentifier:identifier];
    // 刪除舊的數(shù)據(jù)
    SecItemDelete((CFDictionaryRef)saveQueryMutableDictionary);
    // 設(shè)置新的數(shù)據(jù)
    [saveQueryMutableDictionary setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];
    // 添加數(shù)據(jù)
    OSStatus saveState = SecItemAdd((CFDictionaryRef)saveQueryMutableDictionary, nil);
    // 釋放對象
    saveQueryMutableDictionary = nil ;
    // 判斷是否存儲成功
    if (saveState == errSecSuccess) {
        return YES;
    }
    return NO;
}
/*!
 讀取數(shù)據(jù)
 */
+ (id)readData:(NSString*)identifier {
    id idObject = nil ;
    // 通過標(biāo)記獲取數(shù)據(jù)查詢條件
    NSMutableDictionary * keyChainReadQueryMutableDictionary = [self keyChainIdentifier:identifier];
    // 這是獲取數(shù)據(jù)的時,必須提供的兩個屬性
    // TODO: 查詢結(jié)果返回到 kSecValueData
    [keyChainReadQueryMutableDictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
    // TODO: 只返回搜索到的第一條數(shù)據(jù)
    [keyChainReadQueryMutableDictionary setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
    // 創(chuàng)建一個數(shù)據(jù)對象
    CFDataRef keyChainData = nil ;
    //NSError  *error;
    // 通過條件查詢數(shù)據(jù)
    if (SecItemCopyMatching((CFDictionaryRef)keyChainReadQueryMutableDictionary , (CFTypeRef *)&keyChainData) == noErr){
        @try {
            idObject = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)(keyChainData)];
        } @catch (NSException * exception){
            NSLog(@"Unarchive of search data where %@ failed of %@ ",identifier,exception);
        }
    }
    if (keyChainData) {
        CFRelease(keyChainData);
    }
    // 釋放對象
    keyChainReadQueryMutableDictionary = nil;
    // 返回數(shù)據(jù)
    return idObject ;
}

5.使用:

#pragma mark - 確認(rèn)
-(void)comeButtonClick{
    if (!self.chooseBtn.selected) {
        [SVProgressHUD showInfoWithStatus:@"請閱讀并同意《隱私協(xié)議》和《服務(wù)條款》"];
        return;
    }
    //購買
    [[JPurchaseManager shareManager] purchaseWithProductIdentifier:[self.model.products firstObject].pID];
}

7.恢復(fù)購買

//恢復(fù)購買
- (void)reBuyClick {
    [[JPurchaseManager shareManager] resumePurchase];
}

6.記得釋放。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末日裙,一起剝皮案震驚了整個濱河市阅签,隨后出現(xiàn)的幾起案子蝎抽,更是在濱河造成了極大的恐慌,老刑警劉巖养交,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件碎连,死亡現(xiàn)場離奇詭異鱼辙,居然都是意外死亡玫镐,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門杜跷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來葛闷,“玉大人淑趾,你說我怎么就攤上這事忧陪。” “怎么了旷赖?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵等孵,是天一觀的道長俯萌。 經(jīng)常有香客問我咐熙,道長辨萍,這世上最難降的妖魔是什么锈玉? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任拉背,我火速辦了婚禮椅棺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘床估。我一直安慰自己鬼雀,他們只是感情好源哩,可當(dāng)我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布励烦。 她就那樣靜靜地躺著坛掠,像睡著了一般治筒。 火紅的嫁衣襯著肌膚如雪耸袜。 梳的紋絲不亂的頭發(fā)上堤框,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天蜈抓,我揣著相機(jī)與錄音昂儒,去河邊找鬼渊跋。 笑死,一個胖子當(dāng)著我的面吹牛叽唱,可吹牛的內(nèi)容都是我干的棺亭。 我是一名探鬼主播蟋软,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼凄敢,長吁一口氣:“原來是場噩夢啊……” “哼湿痢!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拒逮,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤滩援,失蹤者是張志新(化名)和其女友劉穎玩徊,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泣棋,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡外傅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了棚辽。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冰肴。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡联逻,死狀恐怖检痰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情铅歼,我是刑警寧澤椎椰,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布慨飘,位于F島的核電站,受9級特大地震影響瓤的,放射性物質(zhì)發(fā)生泄漏堤瘤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一桥帆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧叶骨,春花似錦祈匙、人聲如沸夺欲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽市埋。三九已至缤谎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間坷澡,已是汗流浹背频敛。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工姻政, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人汁展。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓侈咕,卻偏偏與公主長得像器紧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子熊尉,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 一狰住、介紹 iOS 的 App 內(nèi)購類型有四種:消耗型商品:只可使用一次的產(chǎn)品催植,使用之后即失效,必須再次購買伦忠。示例:...
    凡幾多閱讀 107,503評論 120 181
  • 成熟的小朋友要學(xué)會自己看文檔置于為什么示例代碼是swift稿辙,因為官方文檔沒有給OC版本的邓深,我也懶得改笔刹。更新-后續(xù)的...
    FireStroy閱讀 6,537評論 1 12
  • 一般來說舌菜,開發(fā)人員剛接觸內(nèi)購,都會遇到流程不清楚袱瓮、千頭萬緒爱咬。如何一次性搞定內(nèi)購問題? 一精拟、掌握內(nèi)購流程: 1蜂绎、完成...
    little_ma閱讀 53,633評論 80 146
  • 一、有關(guān)Apple內(nèi)購 1. SKStorefront:包含App Store店面位置和唯一標(biāo)識符的對象怪瓶。 您可以...
    雨澤Sunshine閱讀 2,494評論 0 4
  • 最近有個項目客戶總是反應(yīng)掉單洗贰,于是乎就看了看內(nèi)購相關(guān)的東西,發(fā)現(xiàn)坑還真是不少哆姻,這里做個總結(jié)。 IAP矛缨,即in-Ap...
    糖炒0栗子閱讀 18,070評論 0 22