Як додати покупку через додаток до програми iOS?


257

Як додати покупку через додаток до програми iOS? Які всі деталі та чи є зразок коду?

Це означає, що це всілякі передумови щодо того, як додавати покупки через додаток до додатків iOS


11
А як же прочитати "Посібник з програмування програм придбання"?
rmaddy

Відповіді:


554

Швидкі користувачі

Користувачі Swift можуть переглянути мій відповідь Swift на це питання .
Або перегляньте відповідь Єдідії Рейс , яка переводить цей код Objective-C на Swift.

Користувачі Objective-C

Решта цієї відповіді написана в Objective-C

App Store Connect

  1. Перейдіть на сторінку appstoreconnect.apple.com та увійдіть у систему
  2. Клацніть, My Appsа потім натисніть додаток, до якого потрібно додати покупку
  3. Клацніть Featuresзаголовок, а потім виберіть In-App Purchasesзліва
  4. Клацніть +піктограму посередині
  5. У цьому підручнику ми будемо додавати покупку через додаток для видалення реклами, тому вибирайте non-consumable. Якщо ви збиралися надіслати фізичний предмет користувачеві або подарувати йому щось, що вони можуть купувати не один раз, ви вибрали б consumable.
  6. Для довідкового імені введіть все, що завгодно (але переконайтеся, що ви знаєте, що це таке)
  7. Для ідентифікатора продукту tld.websitename.appname.referencenameце буде працювати найкраще, так, наприклад, ви можете використовуватиcom.jojodmo.blix.removeads
  8. Виберіть, cleared for saleа потім виберіть рівень цін як 1 (99 ¢). 2-й рівень склав би 1,99 долара, а рівень 3 - 2,99 долара. Повний список доступний, якщо натиснути, view pricing matrixя рекомендую вам використовувати рівень 1, оскільки це, як правило, найбільше хто-небудь заплатить за видалення реклами.
  9. Клацніть синю add languageкнопку та введіть інформацію. Це ВСЕ буде показано замовнику, тому не вкладайте нічого, чого ви не хочете, щоб вони бачили
  10. Для hosting content with Appleвибору немає
  11. Записки огляду можна залишити порожніми ЗА СЕГА .
  12. Пропустимо screenshot for review НАЗАД , до всього, що ми пропустимо, ми повернемось.
  13. Натисніть «зберегти»

Реєстрація вашого ідентифікатора продукту може зайняти кілька годин App Store Connect, тому будьте терплячі.

Налаштування вашого проекту

Тепер, коли ви налаштували інформацію про придбання через додаток у App Store Connect, перейдіть до свого проекту Xcode та перейдіть до менеджера програм (синій значок у формі сторінки вгорі, де знаходяться ваші методи та файли заголовків), натисніть на ваш додаток під цілями (має бути першим), а потім перейти до загального. Унизу ви повинні побачити linked frameworks and librariesсимвол "плюс" та додати рамку. StoreKit.frameworkЯкщо цього не зробити, покупка через додаток НЕ буде працювати!

Якщо ви використовуєте Objective-C як мову свого додатка, вам слід пропустити ці п’ять кроків . В іншому випадку, якщо ви використовуєте Swift, ви можете відповісти на мій відповідь Swift для цього питання тут , або, якщо ви віддаєте перевагу використовувати Objective-C для коду покупки через додаток, але ви використовуєте Swift у своєму додатку, ви можете зробити наступне :

  1. Створіть новий .hфайл (заголовок), перейшовши в File> New> File...( Command ⌘+ N). Цей файл буде згадуватися як "Ваш .hфайл" в решті підручника

  2. Коли буде запропоновано, натисніть кнопку Створити мостиковий заголовок . Це буде наш мостовий файл заголовка. Якщо вам не запропоновано, перейдіть до кроку 3. Якщо вам буде запропоновано, пропустіть крок 3 і перейдіть безпосередньо до кроку 4.

  3. Створіть інший .hфайл, названий Bridge.hу головній папці проекту, Потім перейдіть до диспетчера програм (синій значок, схожий на сторінку), потім виберіть додаток у Targetsрозділі та натисніть Build Settings. Знайдіть опцію, яка говорить про компілятор Swift - Генерація коду , а потім встановіть для параметра Об'єктивний заголовок Objective-C значенняBridge.h

  4. У своєму мостовому файлі заголовка додайте рядок #import "MyObjectiveCHeaderFile.h", де MyObjectiveCHeaderFileзнаходиться назва файлу заголовка, який ви створили на першому кроці. Так, наприклад, якщо ви назвали свій файл заголовка InAppPurchase.h , ви додали б рядок #import "InAppPurchase.h"до вашого файлу заголовка мосту.

  5. Створити нову Objective-C методи ( .mфайл), перейшовши в File> New> File...( Command ⌘+ N). Назвіть його так само, як файл заголовка, який ви створили на кроці 1. Наприклад, якщо ви назвали файл на кроці 1 InAppPurchase.h , ви б назвали цей новий файл InAppPurchase.m . Цей файл буде згадуватися як "Ваш .mфайл" в решті підручника.

Кодування

Тепер ми перейдемо до фактичного кодування. Додайте у свій .hфайл наступний код :

BOOL areAdsRemoved;

- (IBAction)restore;
- (IBAction)tapsRemoveAds;

Далі вам потрібно імпортувати StoreKitрамку у свій .mфайл, а також додати SKProductsRequestDelegateта SKPaymentTransactionObserverпісля @interfaceдекларації:

#import <StoreKit/StoreKit.h>

//put the name of your view controller in place of MyViewController
@interface MyViewController() <SKProductsRequestDelegate, SKPaymentTransactionObserver>

@end

@implementation MyViewController //the name of your view controller (same as above)
  //the code below will be added here
@end

і тепер додайте у свій .mфайл наступне , ця частина ускладнюється, тому пропоную прочитати коментарі в коді:

//If you have more than one in-app purchase, you can define both of
//of them here. So, for example, you could define both kRemoveAdsProductIdentifier
//and kBuyCurrencyProductIdentifier with their respective product ids
//
//for this example, we will only use one product

#define kRemoveAdsProductIdentifier @"put your product id (the one that we just made in App Store Connect) in here"

- (IBAction)tapsRemoveAds{
    NSLog(@"User requests to remove ads");

    if([SKPaymentQueue canMakePayments]){
        NSLog(@"User can make payments");
    
        //If you have more than one in-app purchase, and would like
        //to have the user purchase a different product, simply define 
        //another function and replace kRemoveAdsProductIdentifier with 
        //the identifier for the other product

        SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:kRemoveAdsProductIdentifier]];
        productsRequest.delegate = self;
        [productsRequest start];
    
    }
    else{
        NSLog(@"User cannot make payments due to parental controls");
        //this is called the user cannot make payments, most likely due to parental controls
    }
}

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
    SKProduct *validProduct = nil;
    int count = [response.products count];
    if(count > 0){
        validProduct = [response.products objectAtIndex:0];
        NSLog(@"Products Available!");
        [self purchase:validProduct];
    }
    else if(!validProduct){
        NSLog(@"No products available");
        //this is called if your product id is not valid, this shouldn't be called unless that happens.
    }
}

- (void)purchase:(SKProduct *)product{
    SKPayment *payment = [SKPayment paymentWithProduct:product];

    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    [[SKPaymentQueue defaultQueue] addPayment:payment];
}

- (IBAction) restore{
    //this is called when the user restores purchases, you should hook this up to a button
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}

- (void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
    NSLog(@"received restored transactions: %i", queue.transactions.count);
    for(SKPaymentTransaction *transaction in queue.transactions){
        if(transaction.transactionState == SKPaymentTransactionStateRestored){
            //called when the user successfully restores a purchase
            NSLog(@"Transaction state -> Restored");

            //if you have more than one in-app purchase product,
            //you restore the correct product for the identifier.
            //For example, you could use
            //if(productID == kRemoveAdsProductIdentifier)
            //to get the product identifier for the
            //restored purchases, you can use
            //
            //NSString *productID = transaction.payment.productIdentifier;
            [self doRemoveAds];
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            break;
        }
    }   
}

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
    for(SKPaymentTransaction *transaction in transactions){
        //if you have multiple in app purchases in your app,
        //you can get the product identifier of this transaction
        //by using transaction.payment.productIdentifier
        //
        //then, check the identifier against the product IDs
        //that you have defined to check which product the user
        //just purchased            

        switch(transaction.transactionState){
            case SKPaymentTransactionStatePurchasing: NSLog(@"Transaction state -> Purchasing");
                //called when the user is in the process of purchasing, do not add any of your own code here.
                break;
            case SKPaymentTransactionStatePurchased:
            //this is called when the user has successfully purchased the package (Cha-Ching!)
                [self doRemoveAds]; //you can add your code for what you want to happen when the user buys the purchase here, for this tutorial we use removing ads
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                NSLog(@"Transaction state -> Purchased");
                break;
            case SKPaymentTransactionStateRestored:
                NSLog(@"Transaction state -> Restored");
                //add the same code as you did from SKPaymentTransactionStatePurchased here
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
            case SKPaymentTransactionStateFailed:
                //called when the transaction does not finish
                if(transaction.error.code == SKErrorPaymentCancelled){
                    NSLog(@"Transaction state -> Cancelled");
                    //the user cancelled the payment ;(
                }
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
        }
    }
}

Тепер ви хочете додати свій код для того, що станеться, коли користувач закінчить транзакцію. У цьому підручнику ми використовуємо видалення добавок, вам доведеться додати свій власний код для того, що станеться при завантаженні перегляду банера.

- (void)doRemoveAds{
    ADBannerView *banner;
    [banner setAlpha:0];
    areAdsRemoved = YES;
    removeAdsButton.hidden = YES;
    removeAdsButton.enabled = NO;
    [[NSUserDefaults standardUserDefaults] setBool:areAdsRemoved forKey:@"areAdsRemoved"];
    //use NSUserDefaults so that you can load whether or not they bought it
    //it would be better to use KeyChain access, or something more secure
    //to store the user data, because NSUserDefaults can be changed.
    //You're average downloader won't be able to change it very easily, but
    //it's still best to use something more secure than NSUserDefaults.
    //For the purpose of this tutorial, though, we're going to use NSUserDefaults
    [[NSUserDefaults standardUserDefaults] synchronize];
}

Якщо у вашій програмі немає оголошень, ви можете використовувати будь-яку іншу річ, яку хочете. Наприклад, ми могли б зробити колір тла синім. Для цього ми хотіли б використовувати:

- (void)doRemoveAds{
    [self.view setBackgroundColor:[UIColor blueColor]];
    areAdsRemoved = YES
    //set the bool for whether or not they purchased it to YES, you could use your own boolean here, but you would have to declare it in your .h file

    [[NSUserDefaults standardUserDefaults] setBool:areAdsRemoved forKey:@"areAdsRemoved"];
    //use NSUserDefaults so that you can load wether or not they bought it
    [[NSUserDefaults standardUserDefaults] synchronize];
}

Тепер десь у вашому viewDidLoadметоді вам потрібно буде додати такий код:

areAdsRemoved = [[NSUserDefaults standardUserDefaults] boolForKey:@"areAdsRemoved"];
[[NSUserDefaults standardUserDefaults] synchronize];
//this will load wether or not they bought the in-app purchase

if(areAdsRemoved){
    [self.view setBackgroundColor:[UIColor blueColor]];
    //if they did buy it, set the background to blue, if your using the code above to set the background to blue, if your removing ads, your going to have to make your own code here
}

Тепер, коли ви додали весь код, увійдіть у свій файл .xibчи storyboardфайл та додайте дві кнопки, одну приказка, а інша відновлення. Підключіть tapsRemoveAds IBActionщойно зроблену вами кнопку покупки та кнопку restore IBActionвідновлення. restoreДія буде перевіряти , якщо користувач раніше придбав покупку в додатку, і дати їм покупку в додаток безкоштовно, якщо вони вже не мають його.

Подання на розгляд

Далі, перейдіть у App Store Connect і натисніть, а Users and Accessпотім клацніть Sandbox Testersзаголовок, а потім натисніть +символ ліворуч, де написано Testers. Ви можете просто помістити випадкові речі на ім’я та прізвище, а електронна пошта не повинна бути справжньою - ви просто повинні мати можливість запам'ятати її. Введіть пароль (який вам доведеться запам’ятати) та заповніть решту інформації. Я рекомендую вам Date of Birthвказати дату, яка зробить користувача 18 років і старше. App Store Territory МАЄ бути у правильній країні. Далі вийдіть із свого наявного облікового запису iTunes (ви можете увійти в систему після цього підручника).

Тепер запустіть додаток на своєму пристрої iOS, якщо ви спробуєте запустити його на тренажері, покупка завжди буде помилкою, ви повинні запустити його на пристрої iOS. Після запуску програми натисніть кнопку придбання. Коли буде запропоновано увійти у свій акаунт iTunes, увійдіть як тестовий користувач, який ми щойно створили. Далі, коли він попросить підтвердити покупку в 99 ¢ або будь-який інший, який ви встановите ціновий рівень, ВІДКЛЮЧИТЬ ЕКРАН ЗНАЧЕННЯ З цього, це те, що ви збираєтеся використовувати для свого screenshot for reviewApp Store Connect. Тепер скасуйте платіж.

Тепер перейдіть в App Store Connect , а потім перейти до My Apps> the app you have the In-app purchase on> In-App Purchases. Потім натисніть покупку через додаток та натисніть кнопку "Редагувати" під деталями покупки через додаток. Щойно ви зробите це, імпортуйте фотографію, яку ви тільки що взяли на iPhone, у свій комп’ютер, і завантажте її як скріншот для огляду, а потім у примітках з огляду введіть електронну пошту та пароль свого TEST USER . Це допоможе яблуку в процесі огляду.

Після цього поверніться до програми на пристрої iOS, яка все ще увійшла як тестовий обліковий запис користувача, і натисніть кнопку придбання. Цього разу підтвердьте платіж. Не хвилюйтесь, це НЕ буде стягувати рахунок з будь-яких грошей, тестуйте облікові записи користувачів, отримуйте всі покупки через додаток безкоштовно Після підтвердження платежу переконайтеся, що станеться, коли користувач фактично купує ваш продукт буває. Якщо це не так, то помилка з вашим doRemoveAdsметодом стане помилкою . Знову ж таки, я рекомендую змінити фон на синій для тестування покупки через додаток, однак це не має бути фактичною покупкою через додаток. Якщо все працює, і ви добре підете! Просто переконайтеся, що включите покупку через додаток у свій новий бінарний файл, коли ви завантажуєте його в App Store Connect!


Ось деякі поширені помилки:

Зареєстровано: No Products Available

Це може означати чотири речі:

  • Ви не вказали правильний ідентифікатор покупки через додаток у своєму коді (для ідентифікатора kRemoveAdsProductIdentifierв наведеному вище коді
  • Ви не очистили покупку через додаток для продажу в App Store Connect
  • Ви не дочекалися реєстрації ідентифікатора покупки через додаток у App Store Connect . Зачекайте пару годин від створення посвідчення особи, і вашу проблему слід вирішити.
  • Ви не заповнили інформацію про свої угоди, податки та банківську інформацію.

Якщо це не спрацює з першого разу, не засмучуйтесь! Не здавайся! Мені знадобилося близько 5 годин прямо до того, як я змогла це працювати, і близько 10 годин на пошук потрібного коду! Якщо ви точно використовуєте код вище, він повинен працювати добре. Чи не соромтеся коментувати , якщо у вас є якісь - які питання на всіх .

Я сподіваюся, що це допомагає всім, хто сподівається додати покупку через додаток до своєї програми iOS. Ура!


1
але якщо я не додаю цей рядок, коли я натискаю кнопку відновлення, нічого не станеться ... все одно дякую за цей підручник;)
Ilario

1
"if ( * транзакція * == SKPaymentTransactionStateRestored) {" має бути if ( * action.transactionState * == SKPaymentTransactionStateRestored) {
Massmaker

13
Кращі практики Apple рекомендують додавати спостерігач транзакцій до AppDelegate, а не дії контролера перегляду. developer.apple.com/library/ios/technotes/tn2387/_index.html
Craig Pickering

3
Я отримую кількість продуктів, але я вже перевірив 3 можливі причини, які ви перерахували. Єдине, що спадає на думку, якщо я не встановив контактну інформацію, інформацію про банк та податкову інформацію на "підписаний на ios контракт на додаток" всередині itunes, це може бути причиною?
Крістофер Франсіско

4
Ви повинні пояснити, що на кроці 9 відображуване ім'я - це те, що отримується користувачеві. І це відображається таким чином: "Ви хочете придбати одне ІМЕ ДИСПЛЕЙ за $ 0,99?". Це важливо, тому що я зробив своє ім'я "Видалити рекламу", а потім мій додаток було відхилено, оскільки я використовував неправильну граматику у спливаючому вікні! Мені довелося змінити своє відображуване ім’я на "Пакет видалення реклами".
Алан Скарпа

13

Просто перекладіть код Jojodmo на Swift:

class InAppPurchaseManager: NSObject , SKProductsRequestDelegate, SKPaymentTransactionObserver{





//If you have more than one in-app purchase, you can define both of
//of them here. So, for example, you could define both kRemoveAdsProductIdentifier
//and kBuyCurrencyProductIdentifier with their respective product ids
//
//for this example, we will only use one product

let kRemoveAdsProductIdentifier = "put your product id (the one that we just made in iTunesConnect) in here"

@IBAction func tapsRemoveAds() {

    NSLog("User requests to remove ads")

    if SKPaymentQueue.canMakePayments() {
        NSLog("User can make payments")

        //If you have more than one in-app purchase, and would like
        //to have the user purchase a different product, simply define
        //another function and replace kRemoveAdsProductIdentifier with
        //the identifier for the other product
        let set : Set<String> = [kRemoveAdsProductIdentifier]
        let productsRequest = SKProductsRequest(productIdentifiers: set)
        productsRequest.delegate = self
        productsRequest.start()

    }
    else {
        NSLog("User cannot make payments due to parental controls")
        //this is called the user cannot make payments, most likely due to parental controls
    }
}


func purchase(product : SKProduct) {

    let payment = SKPayment(product: product)
    SKPaymentQueue.defaultQueue().addTransactionObserver(self)
    SKPaymentQueue.defaultQueue().addPayment(payment)
}

func restore() {
    //this is called when the user restores purchases, you should hook this up to a button
    SKPaymentQueue.defaultQueue().addTransactionObserver(self)
    SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
}


func doRemoveAds() {
    //TODO: implement
}

/////////////////////////////////////////////////
//////////////// store delegate /////////////////
/////////////////////////////////////////////////
// MARK: - store delegate -


func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {

    if let validProduct = response.products.first {
        NSLog("Products Available!")
        self.purchase(validProduct)
    }
    else {
        NSLog("No products available")
        //this is called if your product id is not valid, this shouldn't be called unless that happens.
    }
}

func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue) {


    NSLog("received restored transactions: \(queue.transactions.count)")
    for transaction in queue.transactions {
        if transaction.transactionState == .Restored {
            //called when the user successfully restores a purchase
            NSLog("Transaction state -> Restored")

            //if you have more than one in-app purchase product,
            //you restore the correct product for the identifier.
            //For example, you could use
            //if(productID == kRemoveAdsProductIdentifier)
            //to get the product identifier for the
            //restored purchases, you can use
            //
            //NSString *productID = transaction.payment.productIdentifier;
            self.doRemoveAds()
            SKPaymentQueue.defaultQueue().finishTransaction(transaction)
            break;
        }
    }
}


func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {

    for transaction in transactions {
        switch transaction.transactionState {
        case .Purchasing: NSLog("Transaction state -> Purchasing")
            //called when the user is in the process of purchasing, do not add any of your own code here.
        case .Purchased:
            //this is called when the user has successfully purchased the package (Cha-Ching!)
            self.doRemoveAds() //you can add your code for what you want to happen when the user buys the purchase here, for this tutorial we use removing ads
            SKPaymentQueue.defaultQueue().finishTransaction(transaction)
            NSLog("Transaction state -> Purchased")
        case .Restored:
            NSLog("Transaction state -> Restored")
            //add the same code as you did from SKPaymentTransactionStatePurchased here
            SKPaymentQueue.defaultQueue().finishTransaction(transaction)
        case .Failed:
            //called when the transaction does not finish
            if transaction.error?.code == SKErrorPaymentCancelled {
                NSLog("Transaction state -> Cancelled")
                //the user cancelled the payment ;(
            }
            SKPaymentQueue.defaultQueue().finishTransaction(transaction)
        case .Deferred:
            // The transaction is in the queue, but its final status is pending external action.
            NSLog("Transaction state -> Deferred")

        }


    }
}
} 

6

Швидкий відповідь

Це покликане доповнити мою відповідь Objective-C для користувачів Swift, щоб уникнути занадто великої відповіді від Objective-C.

Налаштування

Спочатку встановіть покупку через додаток на appstoreconnect.apple.com . Дотримуйтесь початкової частини моєї відповіді Objective-C (кроки 1-13, під заголовком App Store Connect ), щоб отримати інструкції щодо цього.

Реєстрація вашого ідентифікатора продукту в App Store Connect може зайняти кілька годин, тому будьте терплячі.

Тепер, коли ви налаштували інформацію про покупку через додаток у App Store Connect, нам потрібно додати в програму Apple для покупки StoreKitчерез додаток.

Зайдіть у свій проект Xcode та перейдіть до менеджера програм (синій значок у вигляді сторінки у верхній частині лівої панелі, де знаходяться файли вашої програми). Клацніть на додаток під цілями зліва (це повинен бути перший варіант), а потім перейдіть до "Можливості" вгорі. У списку ви побачите опцію "Покупка в додатку". Увімкніть цю можливість, і Xcode додасть StoreKitваш проект.

Кодування

Тепер ми почнемо кодування!

Спочатку створіть новий швидкий файл, який керуватиме всіма вашими покупками через додаток. Я буду називати цеIAPManager.swift .

У цьому файлі ми збираємося створити новий клас, який називається IAPManagera SKProductsRequestDelegateі SKPaymentTransactionObserver. Угорі переконайтесь, що ви імпортуєте FoundationтаStoreKit

import Foundation
import StoreKit

public class IAPManager: NSObject, SKProductsRequestDelegate,
                         SKPaymentTransactionObserver {
}

Далі ми додамо змінну для визначення ідентифікатора для нашої покупки через додаток (ви також можете скористатися послугою enum, яку було б легше підтримувати, якщо у вас є кілька IAP).

// This should the ID of the in-app-purchase you made on AppStore Connect.
// if you have multiple IAPs, you'll need to store their identifiers in
// other variables, too (or, preferably in an enum).
let removeAdsID = "com.skiplit.removeAds"

Давайте додамо наступний ініціалізатор для нашого класу:

// This is the initializer for your IAPManager class
//
// A better, and more scaleable way of doing this
// is to also accept a callback in the initializer, and call
// that callback in places like the paymentQueue function, and
// in all functions in this class, in place of calls to functions
// in RemoveAdsManager (you'll see those calls in the code below).

let productID: String
init(productID: String){
    self.productID = productID
}

Тепер ми збираємося додати необхідні функції для SKProductsRequestDelegateі SKPaymentTransactionObserverдля роботи:

RemoveAdsManagerКлас ми додамо пізніше

// This is called when a SKProductsRequest receives a response
public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse){
    // Let's try to get the first product from the response
    // to the request
    if let product = response.products.first{
        // We were able to get the product! Make a new payment
        // using this product
        let payment = SKPayment(product: product)

        // add the new payment to the queue
        SKPaymentQueue.default().add(self)
        SKPaymentQueue.default().add(payment)
    }
    else{
        // Something went wrong! It is likely that either
        // the user doesn't have internet connection, or
        // your product ID is wrong!
        //
        // Tell the user in requestFailed() by sending an alert,
        // or something of the sort

        RemoveAdsManager.removeAdsFailure()
    }
}

// This is called when the user restores their IAP sucessfully
private func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue){
    // For every transaction in the transaction queue...
    for transaction in queue.transactions{
        // If that transaction was restored
        if transaction.transactionState == .restored{
            // get the producted ID from the transaction
            let productID = transaction.payment.productIdentifier

            // In this case, we have only one IAP, so we don't need to check
            // what IAP it is. However, this is useful if you have multiple IAPs!
            // You'll need to figure out which one was restored
            if(productID.lowercased() == IAPManager.removeAdsID.lowercased()){
                // Restore the user's purchases
                RemoveAdsManager.restoreRemoveAdsSuccess()
            }

            // finish the payment
            SKPaymentQueue.default().finishTransaction(transaction)
        }
    }
}

// This is called when the state of the IAP changes -- from purchasing to purchased, for example.
// This is where the magic happens :)
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]){
    for transaction in transactions{
        // get the producted ID from the transaction
        let productID = transaction.payment.productIdentifier

        // In this case, we have only one IAP, so we don't need to check
        // what IAP it is.
        // However, if you have multiple IAPs, you'll need to use productID
        // to check what functions you should run here!

        switch transaction.transactionState{
        case .purchasing:
            // if the user is currently purchasing the IAP,
            // we don't need to do anything.
            //
            // You could use this to show the user
            // an activity indicator, or something like that
            break
        case .purchased:
            // the user successfully purchased the IAP!
            RemoveAdsManager.removeAdsSuccess()
            SKPaymentQueue.default().finishTransaction(transaction)
        case .restored:
                // the user restored their IAP!
                IAPTestingHandler.restoreRemoveAdsSuccess()
                SKPaymentQueue.default().finishTransaction(transaction)
        case .failed:
                // The transaction failed!
                RemoveAdsManager.removeAdsFailure()
                // finish the transaction
                SKPaymentQueue.default().finishTransaction(transaction)
        case .deferred:
                // This happens when the IAP needs an external action
                // in order to proceeded, like Ask to Buy
                RemoveAdsManager.removeAdsDeferred()
                break
        }
    }
}

Тепер додамо кілька функцій, які можна використовувати для початку або відновлення покупок:

// Call this when you want to begin a purchase
// for the productID you gave to the initializer
public func beginPurchase(){
    // If the user can make payments
    if SKPaymentQueue.canMakePayments(){
        // Create a new request
        let request = SKProductsRequest(productIdentifiers: [productID])
        // Set the request delegate to self, so we receive a response
        request.delegate = self
        // start the request
        request.start()
    }
    else{
        // Otherwise, tell the user that
        // they are not authorized to make payments,
        // due to parental controls, etc
    }
}

// Call this when you want to restore all purchases
// regardless of the productID you gave to the initializer
public func beginRestorePurchases(){
    // restore purchases, and give responses to self
    SKPaymentQueue.default().add(self)
    SKPaymentQueue.default().restoreCompletedTransactions()
}

Далі додамо новий клас утиліт для управління нашими IAP. Весь цей код може бути в одному класі, але наявність його декількох робить його трохи чистішим. Я збираюся зробити новий клас, який називається RemoveAdsManager, і в ньому покласти кілька функцій

public class RemoveAdsManager{

    class func removeAds()
    class func restoreRemoveAds()

    class func areAdsRemoved() -> Bool

    class func removeAdsSuccess()
    class func restoreRemoveAdsSuccess()
    class func removeAdsDeferred()
    class func removeAdsFailure()
}

Перші три функції, removeAds, restoreRemoveAdsі areAdsRemoved, є функціями , які ви будете називати робити певні дії. Останні чотири - це той, який буде викликанийIAPManager .

Додамо трохи коду до перших двох функцій removeAdsі restoreRemoveAds:

// Call this when the user wants
// to remove ads, like when they
// press a "remove ads" button
class func removeAds(){
    // Before starting the purchase, you could tell the
    // user that their purchase is happening, maybe with
    // an activity indicator

    let iap = IAPManager(productID: IAPManager.removeAdsID)
    iap.beginPurchase()
}

// Call this when the user wants
// to restore their IAP purchases,
// like when they press a "restore
// purchases" button.
class func restoreRemoveAds(){
    // Before starting the purchase, you could tell the
    // user that the restore action is happening, maybe with
    // an activity indicator

    let iap = IAPManager(productID: IAPManager.removeAdsID)
    iap.beginRestorePurchases()
}

І нарешті, давайте додамо якийсь код до останніх п’яти функцій.

// Call this to check whether or not
// ads are removed. You can use the
// result of this to hide or show
// ads
class func areAdsRemoved() -> Bool{
    // This is the code that is run to check
    // if the user has the IAP.

    return UserDefaults.standard.bool(forKey: "RemoveAdsPurchased")
}

// This will be called by IAPManager
// when the user sucessfully purchases
// the IAP
class func removeAdsSuccess(){
    // This is the code that is run to actually
    // give the IAP to the user!
    //
    // I'm using UserDefaults in this example,
    // but you may want to use Keychain,
    // or some other method, as UserDefaults
    // can be modified by users using their
    // computer, if they know how to, more
    // easily than Keychain

    UserDefaults.standard.set(true, forKey: "RemoveAdsPurchased")
    UserDefaults.standard.synchronize()
}

// This will be called by IAPManager
// when the user sucessfully restores
//  their purchases
class func restoreRemoveAdsSuccess(){
    // Give the user their IAP back! Likely all you'll need to
    // do is call the same function you call when a user
    // sucessfully completes their purchase. In this case, removeAdsSuccess()

    removeAdsSuccess()
}

// This will be called by IAPManager
// when the IAP failed
class func removeAdsFailure(){
    // Send the user a message explaining that the IAP
    // failed for some reason, and to try again later
}

// This will be called by IAPManager
// when the IAP gets deferred.
class func removeAdsDeferred(){
    // Send the user a message explaining that the IAP
    // was deferred, and pending an external action, like
    // Ask to Buy.
}

Збираючи все це разом, ми отримуємо щось подібне:

import Foundation
import StoreKit

public class RemoveAdsManager{

    // Call this when the user wants
    // to remove ads, like when they
    // press a "remove ads" button
    class func removeAds(){
        // Before starting the purchase, you could tell the
        // user that their purchase is happening, maybe with
        // an activity indicator

        let iap = IAPManager(productID: IAPManager.removeAdsID)
        iap.beginPurchase()
    }

    // Call this when the user wants
    // to restore their IAP purchases,
    // like when they press a "restore
    // purchases" button.
    class func restoreRemoveAds(){
        // Before starting the purchase, you could tell the
        // user that the restore action is happening, maybe with
        // an activity indicator

        let iap = IAPManager(productID: IAPManager.removeAdsID)
        iap.beginRestorePurchases()
    }

    // Call this to check whether or not
    // ads are removed. You can use the
    // result of this to hide or show
    // ads
    class func areAdsRemoved() -> Bool{
        // This is the code that is run to check
        // if the user has the IAP.

        return UserDefaults.standard.bool(forKey: "RemoveAdsPurchased")
    }

    // This will be called by IAPManager
    // when the user sucessfully purchases
    // the IAP
    class func removeAdsSuccess(){
        // This is the code that is run to actually
        // give the IAP to the user!
        //
        // I'm using UserDefaults in this example,
        // but you may want to use Keychain,
        // or some other method, as UserDefaults
        // can be modified by users using their
        // computer, if they know how to, more
        // easily than Keychain

        UserDefaults.standard.set(true, forKey: "RemoveAdsPurchased")
        UserDefaults.standard.synchronize()
    }

    // This will be called by IAPManager
    // when the user sucessfully restores
    //  their purchases
    class func restoreRemoveAdsSuccess(){
        // Give the user their IAP back! Likely all you'll need to
        // do is call the same function you call when a user
        // sucessfully completes their purchase. In this case, removeAdsSuccess()
        removeAdsSuccess()
    }

    // This will be called by IAPManager
    // when the IAP failed
    class func removeAdsFailure(){
        // Send the user a message explaining that the IAP
        // failed for some reason, and to try again later
    }

    // This will be called by IAPManager
    // when the IAP gets deferred.
    class func removeAdsDeferred(){
        // Send the user a message explaining that the IAP
        // was deferred, and pending an external action, like
        // Ask to Buy.
    }

}

public class IAPManager: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver{

    // This should the ID of the in-app-purchase you made on AppStore Connect.
    // if you have multiple IAPs, you'll need to store their identifiers in
    // other variables, too (or, preferably in an enum).
    static let removeAdsID = "com.skiplit.removeAds"

    // This is the initializer for your IAPManager class
    //
    // An alternative, and more scaleable way of doing this
    // is to also accept a callback in the initializer, and call
    // that callback in places like the paymentQueue function, and
    // in all functions in this class, in place of calls to functions
    // in RemoveAdsManager.
    let productID: String
    init(productID: String){
        self.productID = productID
    }

    // Call this when you want to begin a purchase
    // for the productID you gave to the initializer
    public func beginPurchase(){
        // If the user can make payments
        if SKPaymentQueue.canMakePayments(){
            // Create a new request
            let request = SKProductsRequest(productIdentifiers: [productID])
            request.delegate = self
            request.start()
        }
        else{
            // Otherwise, tell the user that
            // they are not authorized to make payments,
            // due to parental controls, etc
        }
    }

    // Call this when you want to restore all purchases
    // regardless of the productID you gave to the initializer
    public func beginRestorePurchases(){
        SKPaymentQueue.default().add(self)
        SKPaymentQueue.default().restoreCompletedTransactions()
    }

    // This is called when a SKProductsRequest receives a response
    public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse){
        // Let's try to get the first product from the response
        // to the request
        if let product = response.products.first{
            // We were able to get the product! Make a new payment
            // using this product
            let payment = SKPayment(product: product)

            // add the new payment to the queue
            SKPaymentQueue.default().add(self)
            SKPaymentQueue.default().add(payment)
        }
        else{
            // Something went wrong! It is likely that either
            // the user doesn't have internet connection, or
            // your product ID is wrong!
            //
            // Tell the user in requestFailed() by sending an alert,
            // or something of the sort

            RemoveAdsManager.removeAdsFailure()
        }
    }

    // This is called when the user restores their IAP sucessfully
    private func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue){
        // For every transaction in the transaction queue...
        for transaction in queue.transactions{
            // If that transaction was restored
            if transaction.transactionState == .restored{
                // get the producted ID from the transaction
                let productID = transaction.payment.productIdentifier

                // In this case, we have only one IAP, so we don't need to check
                // what IAP it is. However, this is useful if you have multiple IAPs!
                // You'll need to figure out which one was restored
                if(productID.lowercased() == IAPManager.removeAdsID.lowercased()){
                    // Restore the user's purchases
                    RemoveAdsManager.restoreRemoveAdsSuccess()
                }

                // finish the payment
                SKPaymentQueue.default().finishTransaction(transaction)
            }
        }
    }

    // This is called when the state of the IAP changes -- from purchasing to purchased, for example.
    // This is where the magic happens :)
    public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]){
        for transaction in transactions{
            // get the producted ID from the transaction
            let productID = transaction.payment.productIdentifier

            // In this case, we have only one IAP, so we don't need to check
            // what IAP it is.
            // However, if you have multiple IAPs, you'll need to use productID
            // to check what functions you should run here!

            switch transaction.transactionState{
            case .purchasing:
                // if the user is currently purchasing the IAP,
                // we don't need to do anything.
                //
                // You could use this to show the user
                // an activity indicator, or something like that
                break
            case .purchased:
                // the user sucessfully purchased the IAP!
                RemoveAdsManager.removeAdsSuccess()
                SKPaymentQueue.default().finishTransaction(transaction)
            case .restored:
                // the user restored their IAP!
                RemoveAdsManager.restoreRemoveAdsSuccess()
                SKPaymentQueue.default().finishTransaction(transaction)
            case .failed:
                // The transaction failed!
                RemoveAdsManager.removeAdsFailure()
                // finish the transaction
                SKPaymentQueue.default().finishTransaction(transaction)
            case .deferred:
                // This happens when the IAP needs an external action
                // in order to proceeded, like Ask to Buy
                RemoveAdsManager.removeAdsDeferred()
                break
            }
        }
    }

}

Нарешті, вам потрібно додати певний спосіб, щоб користувач розпочав покупку та дзвінок RemoveAdsManager.removeAds()і почав відновлення та дзвінок RemoveAdsManager.restoreRemoveAds(), як кнопка десь! Майте на увазі, що згідно з правилами магазину додатків вам потрібно надати кнопку, щоб десь відновити покупки.

Подання на розгляд

Останнє, що потрібно зробити, - надіслати свій IAP на розгляд в App Store Connect! Щоб отримати докладні вказівки щодо цього, ви можете дотримуватися останньої частини моєї відповіді Objective-C під заголовком Надіслати на рецензію .


4

RMStore - це легка бібліотека iOS для покупок через додаток. Він завершує API StoreKit і надає вам зручні блоки для асинхронних запитів. Придбати товар так само просто, як викликати єдиний метод.

Для досвідчених користувачів ця бібліотека також забезпечує підтвердження отримання, завантаження вмісту та постійність транзакцій.


-1

Я знаю, що вже запізнююся на це, але поділююсь подібним досвідом, коли дізнався мотузки моделі IAP.

Покупка через додаток - це один із найбільш всебічних робочих процесів в iOS, реалізований рамкою Storekit. Вся документація цілком зрозуміло , якщо ви терпіння прочитати його, але дещо просунулася в природі формальності.

Узагальнити:

1 - Запросіть продукти - використовуйте класи SKProductRequest & SKProductRequestDelegate, щоб надіслати запит на ідентифікатори продукту та отримати їх назад у власному магазині itunesconnect.

Ці SKProducts слід використовувати для заповнення інтерфейсу вашого магазину, який користувач може використовувати для придбання конкретного продукту.

2 - Запит на оплату - використовуйте SKPayment & SKPaymentQueue, щоб додати плату до черги транзакцій.

3 - Монітор черги транзакцій для оновлення статусу - використовуйте оновлений метод транзакцій протоколу SKPaymentTransactionObserver для оновлення стану:

SKPaymentTransactionStatePurchasing - don't do anything
SKPaymentTransactionStatePurchased - unlock product, finish the transaction
SKPaymentTransactionStateFailed - show error, finish the transaction
SKPaymentTransactionStateRestored - unlock product, finish the transaction

4 - Відновлення потоку кнопок - використовуйте відновлення SKPaymentQueue відновленняCompletedTransaction для цього - крок 3 подбає про інше, а також наступні методи SKPaymentTransactionObserver:

paymentQueueRestoreCompletedTransactionsFinished
restoreCompletedTransactionsFailedWithError

Ось покроковий підручник (написаний мною в результаті моїх власних спроб зрозуміти це), який пояснює це. Зрештою, він також надає зразок коду, який ви можете безпосередньо використовувати.

Ось ще один, який я створив, щоб пояснити певні речі, які тільки текст міг би описати кращим чином.


21
StackOverflow - це веб-сайт для допомоги іншим, а не для того, щоб намагатися заробляти гроші. Ви повинні або видалити друге останнє посилання, або просто опублікувати те, що робиться в цьому підручнику тут, безкоштовно.
Джодмомо

@ Джойодмо, чи можете ви обґрунтувати свою заяву якимись вказівками SO? Я бачу, що багато людей продають власну SDK (навіть платну) із відмовою від відповідальності, яка, на мою думку, дуже сильно присутня і тут.
Нірав Бхатт

12
Ніяких вказівок проти цього немає, але якщо ти тут, щоб заробити гроші, ти, мабуть, тут з неправильних причин. ІМО, ваша відповідь, здається, орієнтована на те, щоб залучити людей до участі у ваших відеоуроках, а не на допомогу іншим
Jojodmo

3
Це просто нічого, крім роздратування.
міцно
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.