Как добавить покупку из приложения в приложение для iOS?


257

Как добавить покупку из приложения в приложение для iOS? Каковы все детали и есть ли пример кода?

Предполагается, что это будет всеобщая информация о том, как добавлять покупки из приложений в приложения для iOS.


11
А как насчет чтения "Руководства по программированию покупки в приложении"?
rmaddy

Ответы:


554

Swift Users

Пользователи Swift могут проверить My 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 Compiler - Generation , а затем установите для параметра Objective-C Bridging Header значение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 reviewв App Store Connect. Теперь отмените оплату.

Теперь перейдите в App Store Connect , а затем перейти к My Apps> the app you have the In-app purchase on> In-App Purchases. Затем нажмите на покупку в приложении и нажмите «Изменить» под информацией о покупке в приложении. Как только вы это сделаете, импортируйте фотографию, которую вы только что сделали на свой iPhone, в свой компьютер, и загрузите ее в качестве скриншота для просмотра, а затем, в примечаниях к обзору, укажите свой адрес электронной почты и пароль для TEST USER . Это поможет Apple в процессе обзора.

После того, как вы это сделаете, вернитесь в приложение на вашем устройстве iOS, все еще войдя в систему в качестве тестовой учетной записи пользователя, и нажмите кнопку покупки. На этот раз подтвердите платеж. Не волнуйтесь, это НЕ будет списывать с вашего счета ЛЮБЫЕ деньги, тестовые учетные записи пользователей получают все покупки в приложении бесплатно. После подтверждения оплаты убедитесь, что происходит, когда пользователь фактически покупает ваш продукт. случается. Если это не так, то это будет ошибкой в ​​вашем doRemoveAdsметоде. Опять же, я рекомендую использовать изменение фона на синий для тестирования покупки в приложении, хотя это не должно быть вашей реальной покупкой в ​​приложении. Если все работает и у тебя все хорошо! Просто добавьте покупку в приложении в новый бинарный файл, когда загрузите его в App Store Connect!


Вот несколько распространенных ошибок:

Записано: No Products Available

Это может означать четыре вещи:

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

Если это не сработает в первый раз, не расстраивайтесь! Не сдавайся! Это заняло у меня около 5 часов, прежде чем я смог заставить это работать, и около 10 часов в поисках правильного кода! Если вы используете приведенный выше код точно, он должен работать нормально. Не стесняйтесь комментировать , если у вас есть какие - либо вопросы на всех .

Я надеюсь, что это поможет всем тем, кто хочет добавить покупку в приложение для своего iOS-приложения. Ура!


1
но если я не добавлю эту строку, когда я нажму на кнопку восстановления, ничего не произойдет .. в любом случае большое спасибо за этот урок;)
Иларио

1
"if ( * транзакция * == SKPaymentTransactionStateRestored) {" должна быть if ( * транзакция.transactionState * == SKPaymentTransactionStateRestored) {
Massmaker

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

3
Я получаю счет 0 продуктов, но я уже проверил 3 возможные причины, которые вы перечислили. Единственное, что приходит на ум, если я не настроил контактную информацию, банковские реквизиты и налоговую информацию о «контракте на платное приложение ios» в itunes connect, может ли это быть причиной?
Кристофер Франсиско

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в ваш проект.

кодирование

Теперь мы собираемся начать кодирование!

Во-первых, создайте новый swift-файл, который будет управлять всеми вашими покупками в приложении. Я собираюсь позвонить IAPManager.swift.

В этом файле мы собираемся создать новый класс, IAPManagerкоторый называется a 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(), как где-то кнопка! Имейте в виду, что в соответствии с рекомендациями App Store вам необходимо предоставить кнопку для восстановления покупок где-либо.

Отправка на проверку

Последнее, что нужно сделать, это отправить свой IAP на рассмотрение в App Store Connect! Для получения подробных инструкций о том, как это сделать, вы можете следовать последней части моего ответа в Objective-C под заголовком Отправка для проверки .


4

RMStore - это легкая библиотека iOS для покупок внутри приложения. Он оборачивает StoreKit API и предоставляет вам удобные блоки для асинхронных запросов. Приобрести продукт так же просто, как вызвать один метод.

Для опытных пользователей эта библиотека также обеспечивает проверку квитанций, загрузку контента и сохранение транзакций.


-1

Я знаю, что опоздал, чтобы опубликовать это, но я делюсь подобным опытом, когда я изучил веревки модели IAP.

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

Подвести итоги:

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

Эти SKProducts должны использоваться для заполнения пользовательского интерфейса вашего магазина, который пользователь может использовать для покупки определенного товара.

2 - Оформить запрос на оплату - используйте SKPayment & SKPaymentQueue, чтобы добавить платеж в очередь транзакций.

3 - Отслеживание очереди транзакций для обновления статуса - используйте метод updatedTransactions протокола 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's restoreCompletedTransactions для выполнения этого - шаг 3 позаботится об остальном, наряду со следующими методами SKPaymentTransactionObserver:

paymentQueueRestoreCompletedTransactionsFinished
restoreCompletedTransactionsFailedWithError

Вот пошаговое руководство (написанное мной в результате моих собственных попыток понять его), которое объясняет это. В конце он также предоставляет пример кода, который вы можете использовать напрямую.

Вот еще один, который я создал, чтобы объяснить некоторые вещи, которые только текст мог бы описать лучше.


21
StackOverflow - это сайт для помощи другим, а не для того, чтобы зарабатывать на них деньги. Вам следует либо удалить вторую, либо последнюю ссылку, или просто опубликовать то, что сделано в этом руководстве, бесплатно.
Jojodmo

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

12
Там нет никаких указаний против этого, но если вы здесь, чтобы заработать деньги, вы, вероятно, здесь по неправильным причинам. IMO, ваш ответ, кажется, сосредоточен на том, чтобы заставить людей записаться на ваши видеоуроки, а не на помощь другим
Jojodmo

3
Это всего лишь раздражение.
Дуразно
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.