Como você adiciona uma compra no aplicativo a um aplicativo iOS?

257

Como você adiciona uma compra no aplicativo a um aplicativo iOS? Quais são todos os detalhes e existe algum código de exemplo?

Isso pretende ser uma espécie de captura geral de como adicionar compras no aplicativo a aplicativos iOS

Jojodmo
fonte
11
Que tal ler o "Guia de programação de compras no aplicativo"?
rmaddy

Respostas:

554

Usuários Swift

Os usuários do Swift podem conferir Minha resposta rápida para esta pergunta .
Ou confira a resposta de Yedidya Reiss , que traduz esse código de Objective-C para Swift.

Usuários do Objective-C

O restante desta resposta está escrito em Objective-C

App Store Connect

  1. Acesse appstoreconnect.apple.com e faça login
  2. Clique My Appse clique no aplicativo ao qual você deseja adicionar a compra
  3. Clique no Featurescabeçalho e selecione In-App Purchasesà esquerda
  4. Clique no +ícone no meio
  5. Neste tutorial, adicionaremos uma compra no aplicativo para remover anúncios, então escolha non-consumable. Se você iria enviar um item físico para o usuário ou fornecer algo que ele possa comprar mais de uma vez, você escolheria consumable.
  6. Para o nome de referência, coloque o que quiser (mas saiba o que é)
  7. Para colocar o ID do produto, tld.websitename.appname.referencenameisso funcionará melhor; por exemplo, você pode usarcom.jojodmo.blix.removeads
  8. Escolha cleared for salee escolha o nível de preço como 1 (99 ¢). A camada 2 seria de US $ 1,99 e a camada 3 seria de US $ 2,99. A lista completa está disponível se você clicar em view pricing matrixEu recomendo que você use a camada 1, porque geralmente é o máximo que alguém pagará para remover anúncios.
  9. Clique no add languagebotão azul e insira as informações. Tudo isso será mostrado ao cliente, portanto, não coloque nada que você não queira que ele veja
  10. Para hosting content with Appleescolher não
  11. Você pode deixar as notas de revisão em branco por enquanto .
  12. Pule o screenshot for review FOR NOW , tudo o que pularmos, voltaremos.
  13. Clique em "salvar"

Pode levar algumas horas para que o seu ID do produto seja registrado App Store Connect, portanto seja paciente.

Configurando seu projeto

Agora que você configurou suas informações de compra no aplicativo na App Store Connect, entre no seu projeto Xcode e vá para o gerenciador de aplicativos (ícone azul semelhante a uma página na parte superior de onde estão seus métodos e arquivos de cabeçalho) clique em seu aplicativo em metas (deve ser o primeiro) e vá para geral. Na parte inferior, você deve linked frameworks and librariesclicar no pequeno símbolo de adição e adicionar a estrutura. StoreKit.frameworkSe você não fizer isso, a compra no aplicativo NÃO funcionará!

Se você estiver usando o Objective-C como o idioma do seu aplicativo, pule estas cinco etapas . Caso contrário, se você estiver usando o Swift, poderá seguir Minha resposta rápida para esta pergunta, aqui ou, se preferir usar o Objective-C para o código de compra no aplicativo, mas estiver usando o Swift no seu aplicativo, poderá fazer o seguinte :

  1. Criar um novo .harquivo (cabeçalho), indo para File> New> File...( Command ⌘+ N). Este arquivo será referido como "Seu .harquivo" no restante do tutorial

  2. Quando solicitado, clique em Criar Bridging Header . Este será o nosso arquivo de cabeçalho de ponte. Se você não for solicitado, vá para a etapa 3. Se for solicitado, pule a etapa 3 e vá diretamente para a etapa 4.

  3. Crie outro .harquivo nomeado Bridge.hna pasta principal do projeto. Em seguida, vá para o Application Manager (o ícone azul semelhante a uma página), selecione o aplicativo na Targetsseção e clique em Build Settings. Localize a opção que diz Swift Compiler - Geração de código e defina a opção Objective-C Bridging Header comoBridge.h

  4. No seu arquivo de cabeçalho de ponte, adicione a linha #import "MyObjectiveCHeaderFile.h", onde MyObjectiveCHeaderFileestá o nome do arquivo de cabeçalho que você criou na etapa um. Portanto, por exemplo, se você nomear seu arquivo de cabeçalho InAppPurchase.h , você adicionará a linha #import "InAppPurchase.h"ao arquivo de cabeçalho da ponte.

  5. Criar um novo Objective-C Métodos ( .marquivo), indo para File> New> File...( Command ⌘+ N). Nomeie o mesmo que o arquivo de cabeçalho que você criou na etapa 1. Por exemplo, se você chamou o arquivo na etapa 1 InAppPurchase.h , você chamaria esse novo arquivo de InAppPurchase.m . Este arquivo será referido como "Seu .marquivo" no restante do tutorial.

Codificação

Agora vamos entrar na codificação real. Adicione o seguinte código ao seu .harquivo:

BOOL areAdsRemoved;

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

Em seguida, você precisa importar a StoreKitestrutura para o seu .marquivo, além de adicionar SKProductsRequestDelegatee SKPaymentTransactionObserverapós a sua @interfacedeclaração:

#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

e agora adicione o seguinte ao seu .marquivo, esta parte fica complicada, por isso sugiro que você leia os comentários no código:

//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;
        }
    }
}

Agora você deseja adicionar seu código para o que acontecerá quando o usuário concluir a transação. Para este tutorial, usamos a remoção de adições. Você precisará adicionar seu próprio código para o que acontece quando a exibição do banner for carregada.

- (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];
}

Se você não tiver anúncios em seu aplicativo, poderá usar qualquer outra coisa que desejar. Por exemplo, podemos tornar a cor do fundo azul. Para fazer isso, gostaríamos de usar:

- (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];
}

Agora, em algum lugar do seu viewDidLoadmétodo, você desejará adicionar o seguinte código:

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
}

Agora que você adicionou todo o código, entre no seu arquivo .xibou storyboarde adicione dois botões, um dizendo compra e outro dizendo restauração. Conecte o tapsRemoveAds IBActionbotão de compra que você acabou de fazer e o restore IBActionbotão de restauração. A restoreação verificará se o usuário comprou anteriormente a compra no aplicativo e oferecerá a compra no aplicativo gratuitamente, caso ainda não a tenha.

Enviando para revisão

Em seguida, entre no App Store Connect , Users and Accessclique no Sandbox Testerscabeçalho e clique no +símbolo à esquerda onde está escrito Testers. Você pode simplesmente colocar coisas aleatórias para o nome e o sobrenome, e o e-mail não precisa ser real - você só precisa se lembrar dele. Coloque uma senha (da qual você precisará se lembrar) e preencha o restante das informações. Eu recomendo que você marque a Date of Birthdata que tornaria o usuário 18 anos ou mais. App Store Territory TEM que estar no país correto. Em seguida, efetue logout da sua conta existente do iTunes (você poderá efetuar login novamente após este tutorial).

Agora, execute o aplicativo no seu dispositivo iOS, se você tentar executá-lo no simulador, a compra será sempre erro, você TEM QUE executá-lo em seu dispositivo iOS. Quando o aplicativo estiver em execução, toque no botão de compra. Quando você for solicitado a fazer login na sua conta do iTunes, faça o login como o usuário de teste que acabamos de criar. Em seguida, quando solicitar que você confirme a compra de 99 ¢ ou o que quer que você defina também o preço, FAÇA UM INSTANTÂNEO DE TELA, isto é o que você usará para o seu screenshot for reviewna App Store Connect. Agora cancele o pagamento.

Agora, vá para loja App Ligue , em seguida, ir para a My Apps> the app you have the In-app purchase on> In-App Purchases. Em seguida, clique na sua compra no aplicativo e clique em editar nos detalhes da compra no aplicativo. Depois de fazer isso, importe a foto que você acabou de tirar no seu iPhone para o seu computador e carregue-a como captura de tela para revisão e, em notas de revisão, coloque seu e-mail e senha de USUÁRIO DE TESTE . Isso ajudará a apple no processo de revisão.

Depois de fazer isso, volte para o aplicativo no seu dispositivo iOS, ainda conectado como a conta de usuário de teste e clique no botão comprar. Desta vez, confirme o pagamento Não se preocupe, isso NÃO cobrará QUALQUER dinheiro em sua conta, teste as contas de usuário e receba todas as compras no aplicativo gratuitamente Depois de confirmar o pagamento, verifique se o que acontece quando o usuário compra seu produto realmente acontece. Caso contrário, será um erro no seu doRemoveAdsmétodo. Novamente, eu recomendo usar a alteração do plano de fundo para azul para testar a compra no aplicativo. Porém, essa não deve ser sua compra real no aplicativo. Se tudo funcionar e você estiver pronto! Apenas inclua a compra no aplicativo em seu novo binário ao enviá-lo para o App Store Connect!


Aqui estão alguns erros comuns:

Registrado: No Products Available

Isso pode significar quatro coisas:

  • Você não colocou o código de compra no aplicativo correto no seu código (para o identificador kRemoveAdsProductIdentifierno código acima
  • Você não cancelou sua compra no aplicativo para venda na App Store Connect
  • Você não esperou que o ID de compra no aplicativo fosse registrado na App Store Connect . Aguarde algumas horas para criar o ID e seu problema deve ser resolvido.
  • Você não concluiu o preenchimento das informações de contratos, impostos e serviços bancários.

Se não funcionar pela primeira vez, não fique frustrado! Não desista! Demorei cerca de 5 horas seguidas até que eu conseguisse fazer isso funcionar, e cerca de 10 horas procurando o código certo! Se você usar o código acima exatamente, ele deverá funcionar bem. Sinta-se livre para comentar, se você tiver quaisquer perguntas em tudo .

Espero que isso ajude todos aqueles que desejam adicionar uma compra no aplicativo ao aplicativo iOS. Felicidades!

Jojodmo
fonte
1
Mas se eu não adicionar essa linha, quando eu clico no botão restaurar nada acontecer .. obrigado de qualquer maneira muito para este tutorial;)
Ilario
1
"if ( * transaction * == SKPaymentTransactionStateRestored) {" deve ser if ( * transaction.transactionState * == SKPaymentTransactionStateRestored) {
Massmaker 30/03/15
13
As práticas recomendadas da Apple recomendam que você adicione o observador de transações ao AppDelegate, não às ações do controlador de exibição. developer.apple.com/library/ios/technotes/tn2387/_index.html
Craig Pickering
3
Estou recebendo 0 produtos, mas já verifiquei os três motivos possíveis que você listou. A única coisa que vem à mente se eu não tiver configurado as informações de contato, informações bancárias e impostos sobre o "contrato de aplicativo pago do iOS" dentro do iTunes Connect, poderia ser esse o motivo?
Christopher Francisco
4
Você deve explicar que, na Etapa 9, o nome de exibição é o que é apresentado ao usuário. E é apresentado desta maneira: "Você deseja comprar um NOME DE EXIBIÇÃO por US $ 0,99?". Isso é importante porque criei meu nome de exibição "Remover anúncios" e, em seguida, meu aplicativo foi rejeitado porque estava usando uma gramática inadequada na pop-up! Eu tive que mudar meu nome para exibição para "Pacote de remoção de anúncios".
Alan Scarpa
13

Basta traduzir o código Jojodmo para 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")

        }


    }
}
} 
Yedidya Reiss
fonte
6

Resposta rápida

Isso visa complementar minha resposta Objective-C para usuários Swift, para impedir que a resposta Objective-C fique muito grande.

Configuração

Primeiro, configure a compra no aplicativo em appstoreconnect.apple.com . Siga a parte inicial da minha resposta Objective-C (etapas 1 a 13, abaixo do cabeçalho da App Store Connect ) para obter instruções sobre como fazer isso.

Pode demorar algumas horas para o seu ID do produto se registrar no App Store Connect, por isso seja paciente.

Agora que você configurou suas informações de compra no aplicativo no App Store Connect, precisamos adicionar a estrutura da Apple para compras StoreKitno aplicativo.

Entre no seu projeto do Xcode e vá para o gerenciador de aplicativos (ícone azul semelhante a uma página na parte superior da barra esquerda, onde estão os arquivos do seu aplicativo). Clique no seu aplicativo em destinos à esquerda (deve ser a primeira opção) e vá para "Recursos" na parte superior. Na lista, você verá a opção "Compra no aplicativo". Ative esse recurso e o Xcode será adicionado StoreKitao seu projeto.

Codificação

Agora, vamos começar a codificar!

Primeiro, crie um novo arquivo rápido que gerencie todas as suas compras no aplicativo. Eu vou ligar IAPManager.swift.

Neste arquivo, vamos criar uma nova classe, chamada IAPManageris SKProductsRequestDelegatee SKPaymentTransactionObserver. No topo, certifique-se de importar FoundationeStoreKit

import Foundation
import StoreKit

public class IAPManager: NSObject, SKProductsRequestDelegate,
                         SKPaymentTransactionObserver {
}

Em seguida, adicionaremos uma variável para definir o identificador de nossa compra no aplicativo (você também pode usar um enum, que seria mais fácil de manter se você tiver vários IAPs).

// 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"

Vamos adicionar um inicializador para a nossa classe a seguir:

// 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
}

Agora, vamos adicionar as funções necessárias para SKProductsRequestDelegatee SKPaymentTransactionObserverde trabalho:

Adicionaremos a RemoveAdsManagerturma mais tarde

// 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
        }
    }
}

Agora, vamos adicionar algumas funções que podem ser usadas para iniciar uma compra ou restaurar compras:

// 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()
}

Em seguida, vamos adicionar uma nova classe de utilitários para gerenciar nossos IAPs. Todo esse código pode estar em uma classe, mas ter vários torna um pouco mais limpo. Vou criar uma nova classe chamada RemoveAdsManager, e nela, colocar algumas funções

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()
}

As três primeiras funções, removeAds, restoreRemoveAdse areAdsRemoved, são funções que você vai chamar para fazer certas ações. Os quatro últimos são aqueles que serão chamados por IAPManager.

Vamos adicionar algum código às duas primeiras funções removeAdse 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()
}

E, finalmente, vamos adicionar um código às cinco últimas funções.

// 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.
}

Juntando tudo, temos algo parecido com isto:

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
            }
        }
    }

}

Por fim, você precisa adicionar uma maneira de o usuário iniciar a compra e ligar RemoveAdsManager.removeAds()e iniciar uma restauração e ligar RemoveAdsManager.restoreRemoveAds(), como um botão em algum lugar! Lembre-se de que, de acordo com as diretrizes da App Store, você precisa fornecer um botão para restaurar as compras em algum lugar.

Enviando para revisão

A última coisa a fazer é enviar seu IAP para análise na App Store Connect! Para obter instruções detalhadas sobre como fazer isso, você pode seguir a última parte da minha resposta Objective-C , no cabeçalho Submetendo para revisão .

Jojodmo
fonte
4

O RMStore é uma biblioteca iOS leve para compras no aplicativo. Ele envolve a API StoreKit e fornece blocos úteis para solicitações assíncronas. Comprar um produto é tão fácil quanto chamar um único método.

Para usuários avançados, essa biblioteca também fornece verificação de recebimento, downloads de conteúdo e persistência de transação.

Vladimir Grigorov
fonte
-1

Sei que estou atrasado para postar isso, mas compartilho experiências semelhantes quando aprendi as regras do modelo IAP.

A compra no aplicativo é um dos fluxos de trabalho mais abrangentes no iOS implementados pela estrutura do Storekit. A documentação toda está muito claro se você paciência para lê-lo, mas é um pouco avançado na natureza de tecnicidade.

Para resumir:

1 - Solicite os produtos - use as classes SKProductRequest e SKProductRequestDelegate para emitir a solicitação de IDs do produto e recebê-las de volta na sua própria loja itunesconnect.

Esses SKProducts devem ser usados ​​para preencher a interface da loja, que o usuário pode usar para comprar um produto específico.

2 - Emitir solicitação de pagamento - use SKPayment & SKPaymentQueue para adicionar pagamento à fila de transações.

3 - Monitorar a fila de transações para atualização de status - use o método updatedTransactions do protocolo SKPaymentTransactionObserver para monitorar o status:

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

4 - Restaurar o fluxo do botão - use restoreCompletedTransactions do SKPaymentQueue para fazer isso - a etapa 3 cuidará do resto, juntamente com os seguintes métodos do SKPaymentTransactionObserver:

paymentQueueRestoreCompletedTransactionsFinished
restoreCompletedTransactionsFailedWithError

Aqui está um tutorial passo a passo (criado por mim como resultado de minhas próprias tentativas de entendê-lo) que explica isso. No final, ele também fornece um exemplo de código que você pode usar diretamente.

Aqui está outro que eu criei para explicar certas coisas que somente o texto poderia descrever de uma maneira melhor.

Nirav Bhatt
fonte
21
StackOverflow é um site para ajudar outras pessoas e não para tentar ganhar dinheiro com elas. Você deve remover o penúltimo link ou apenas postar o que é feito nesse tutorial aqui, gratuitamente.
Jojodmo
@Jojodmo, você pode comprovar sua reivindicação com alguma orientação da SO? Vejo muitas pessoas que comercializam seu próprio SDK (até pagaram um) com um aviso, o que acho que também está presente aqui.
Nirav Bhatt
12
Não há diretrizes contra isso, mas se você está aqui para ganhar dinheiro, provavelmente está aqui pelas razões erradas. IMO, a sua resposta parece estar focada em obter as pessoas para se inscrever em seus tutoriais de vídeo, e não em ajudar os outros
Jojodmo
3
Isso não passa de aborrecimento.
Durazno