Come si aggiunge un acquisto in-app a un'applicazione iOS?


Risposte:


554

Utenti veloci

Gli utenti di Swift possono consultare My Swift Answer per questa domanda .
Oppure, dai un'occhiata alla risposta di Yedidya Reiss , che traduce questo codice Objective-C in Swift.

Utenti di Objective-C

Il resto di questa risposta è scritto in Objective-C

App Store Connect

  1. Vai su appstoreconnect.apple.com e accedi
  2. Fare My Appsclic su quindi fare clic sull'app a cui si desidera aggiungere l'acquisto
  3. Fai Featuresclic In-App Purchasessull'intestazione , quindi seleziona a sinistra
  4. Fai clic +sull'icona al centro
  5. Per questo tutorial, aggiungeremo un acquisto in-app per rimuovere gli annunci, quindi scegli non-consumable. Se avessi intenzione di inviare un oggetto fisico all'utente o di dare loro qualcosa che possono acquistare più di una volta, sceglieresti consumable.
  6. Per il nome di riferimento, inserisci quello che vuoi (ma assicurati di sapere di cosa si tratta)
  7. Per quanto riguarda l'id prodotto, tld.websitename.appname.referencenamequesto funzionerà al meglio, quindi ad esempio, potresti usarlocom.jojodmo.blix.removeads
  8. Scegliere cleared for salee quindi scegliere il livello di prezzo come 1 (99 ¢). Il livello 2 sarebbe $ 1,99 e il livello 3 sarebbe $ 2,99. L'elenco completo è disponibile se fai clic su view pricing matrixConsiglio di utilizzare il livello 1, poiché di solito è il massimo che chiunque pagherà per rimuovere gli annunci.
  9. Fai clic sul add languagepulsante blu e inserisci le informazioni. Questo sarà TUTTO mostrato al cliente, quindi non mettere nulla che tu non voglia vedere
  10. Per hosting content with Applescegliere no
  11. Puoi lasciare vuote le note della recensione PER ADESSO .
  12. Salta il screenshot for review FOR NOW , tutto ciò che salta torneremo.
  13. Fai clic su "Salva"

Potrebbero essere necessarie alcune ore per la registrazione dell'ID prodotto App Store Connect, quindi sii paziente.

Impostare il tuo progetto

Ora che hai impostato le informazioni di acquisto in-app su App Store Connect, vai al tuo progetto Xcode e vai al gestore dell'applicazione (icona blu simile a una pagina nella parte superiore di dove sono i tuoi metodi e file di intestazione) fai clic su la tua app sotto target (dovrebbe essere la prima) quindi vai a generale. In fondo, dovresti vedere linked frameworks and librariesfare clic sul piccolo simbolo più e aggiungere il framework StoreKit.frameworkSe non lo fai, l'acquisto in-app NON funzionerà!

Se stai usando Objective-C come lingua per la tua app, dovresti saltare questi cinque passaggi . Altrimenti, se stai usando Swift, puoi seguire My Swift Answer per questa domanda, qui , oppure, se preferisci usare Objective-C per il codice di acquisto in-app ma stai usando Swift nella tua app, puoi fare quanto segue :

  1. Creare un nuovo .hfile di (intestazione) andando a File> New> File...( Command ⌘+ N). Questo file verrà chiamato "Il tuo .hfile" nel resto del tutorial

  2. Quando richiesto, fai clic su Crea intestazione ponte . Questo sarà il nostro file di intestazione ponte. Se non viene richiesto, passare al punto 3. Se si viene richiesto, saltare il passaggio 3 e passare direttamente al punto 4.

  3. Crea un altro .hfile denominato Bridge.hnella cartella principale del progetto, quindi vai a Gestione applicazioni (icona blu simile a una pagina), quindi seleziona l'app nella Targetssezione e fai clic Build Settings. Trova l'opzione che dice Swift Compiler - Generazione di codice , quindi imposta l' opzione Intestazione ponte di Objective-C suBridge.h

  4. Nel file di intestazione ponte, aggiungi la riga #import "MyObjectiveCHeaderFile.h", dove si MyObjectiveCHeaderFiletrova il nome del file di intestazione creato nel passaggio uno. Quindi, ad esempio, se hai chiamato il tuo file di intestazione InAppPurchase.h , aggiungi la linea #import "InAppPurchase.h"al file di intestazione del bridge.

  5. Creare una nuova Objective-C Metodi ( .mfile) andando a File> New> File...( Command ⌘+ N). Denominalo come il file di intestazione creato nel passaggio 1. Ad esempio, se hai chiamato il file nel passaggio 1 InAppPurchase.h , chiameresti questo nuovo file InAppPurchase.m . Questo file verrà chiamato "Il tuo .mfile" nel resto del tutorial.

Coding

Ora entreremo nel codice reale. Aggiungi il seguente codice nel tuo .hfile:

BOOL areAdsRemoved;

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

Successivamente, è necessario importare il StoreKitframework nel .mfile, nonché aggiungere SKProductsRequestDelegatee SKPaymentTransactionObserverdopo la @interfacedichiarazione:

#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 ora aggiungi quanto segue nel tuo .mfile, questa parte diventa complicata, quindi ti suggerisco di leggere i commenti nel codice:

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

Ora vuoi aggiungere il tuo codice per ciò che accadrà quando l'utente termina la transazione, per questo tutorial, usiamo la rimozione di add, dovrai aggiungere il tuo codice per ciò che accade quando viene caricata la vista banner.

- (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 non hai annunci pubblicitari nella tua applicazione, puoi usare qualsiasi altra cosa tu voglia. Ad esempio, potremmo rendere blu il colore dello sfondo. Per fare ciò vorremmo usare:

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

Ora, da qualche parte nel tuo viewDidLoadmetodo, vorrai aggiungere il seguente codice:

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
}

Ora che hai aggiunto tutto il codice, vai nel tuo .xibo nel storyboardfile e aggiungi due pulsanti, uno che dice acquisto e l'altro che dice ripristina. Collega tapsRemoveAds IBActional pulsante di acquisto che hai appena effettuato e restore IBActional pulsante di ripristino. L' restoreazione verificherà se l'utente ha già acquistato l'acquisto in-app e gli darà l'acquisto in-app gratuitamente se non lo possiede già.

Invio per revisione

Successivamente, vai su App Store Connect e fai Users and Accessclic su quindi fai Sandbox Testersclic +sull'intestazione , quindi fai clic sul simbolo a sinistra dove è indicato Testers. Puoi semplicemente inserire cose casuali per il nome e il cognome e l'e-mail non deve essere reale, devi solo essere in grado di ricordarlo. Inserisci una password (che dovrai ricordare) e compila il resto delle informazioni. Consiglierei di fissare Date of Birthuna data che renderebbe l'utente di 18 anni o più. App Store Territory Deve essere nel paese corretto. Quindi, esci dal tuo account iTunes esistente (puoi riconnetterti dopo questo tutorial).

Ora, esegui l'applicazione sul tuo dispositivo iOS, se provi a eseguirla sul simulatore, l'acquisto sarà sempre errore, DEVI eseguirlo sul tuo dispositivo iOS. Una volta eseguita l'app, tocca il pulsante di acquisto. Quando ti viene richiesto di accedere al tuo account iTunes, accedi come utente di prova che abbiamo appena creato. Successivamente, quando ti viene chiesto di confermare l'acquisto di 99 ¢ o qualunque cosa tu abbia impostato anche il livello di prezzo, SCATTA UNO SCHERMO DI ISTANTANEA, questo è ciò che utilizzerai per il tuo screenshot for reviewApp Store Connect. Ora cancella il pagamento.

Ora, andare al App Store Connect , poi vai a My Apps> the app you have the In-app purchase on> In-App Purchases. Quindi fai clic sul tuo acquisto in-app e fai clic su modifica sotto i dettagli dell'acquisto in-app. Una volta fatto, importa la foto che hai appena scattato sul tuo iPhone sul tuo computer e caricala come screenshot per la revisione, quindi, nelle note di revisione, inserisci la tua e-mail e la password dell'UTENTE DI PROVA . Questo aiuterà Apple nel processo di revisione.

Dopo aver effettuato l'operazione, torna all'applicazione sul tuo dispositivo iOS, accedi ancora come account utente di prova e fai clic sul pulsante Acquista. Questa volta, conferma il pagamento Non preoccuparti, questo NON addebiterà alcun importo sul tuo account, testerà gli account degli utenti per ottenere gratuitamente tutti gli acquisti in-app Dopo aver confermato il pagamento, assicurati che cosa succede quando l'utente acquista effettivamente il tuo prodotto accade. In caso contrario, sarà un errore con il tuo doRemoveAdsmetodo. Ancora una volta, ti consiglio di usare il cambio dello sfondo in blu per testare l'acquisto in-app, ma questo non dovrebbe essere il tuo vero acquisto in-app. Se tutto funziona e sei a posto! Assicurati di includere l'acquisto in-app nel tuo nuovo file binario quando lo carichi su App Store Connect!


Ecco alcuni errori comuni:

Logged: No Products Available

Questo potrebbe significare quattro cose:

  • Non hai inserito l'ID acquisto in-app corretto nel codice (per l'identificatore kRemoveAdsProductIdentifiernel codice precedente
  • Non hai cancellato il tuo acquisto in-app per la vendita su App Store Connect
  • Non hai aspettato che l'ID acquisto in-app fosse registrato in App Store Connect . Attendere un paio d'ore dalla creazione dell'ID e il problema dovrebbe essere risolto.
  • Non hai completato la compilazione delle informazioni su Accordi, Fiscali e Bancari.

Se non funziona la prima volta, non sentirti frustrato! Non mollare! Mi ci sono voluti circa 5 ore di fila prima che potessi farlo funzionare, e circa 10 ore alla ricerca del codice giusto! Se si utilizza esattamente il codice sopra, dovrebbe funzionare correttamente. Sentitevi liberi di commentare se avete domande a tutti .

Spero che questo aiuti a tutti coloro che sperano di aggiungere un acquisto in-app alla loro applicazione iOS. Saluti!


1
ma se non aggiungo quella riga, quando faccio clic sul pulsante di ripristino non succede nulla .. comunque grazie mille per questo tutorial;)
Ilario

1
"if ( * transazione * == SKPaymentTransactionStateRestored) {" dovrebbe essere if ( * transazione.transactionState * == SKPaymentTransactionStateRestored) {
Massmaker

13
Le migliori pratiche di Apple consigliano di aggiungere l'osservatore delle transazioni all'AppDelegate, non le azioni del controller di visualizzazione. developer.apple.com/library/ios/technotes/tn2387/_index.html
Craig Pickering

3
Ricevo un conteggio di 0 prodotti, ma ho già verificato i 3 possibili motivi che hai elencato. L'unica cosa che viene in mente se non ho impostato le informazioni di contatto, le informazioni bancarie e le informazioni fiscali sul "contratto di app a pagamento iOS" all'interno di iTunes iTunes, potrebbe essere questo il motivo?
Christopher Francisco,

4
Dovresti spiegare che al passaggio 9, il nome visualizzato è ciò che viene presentato all'utente. E viene presentato in questo modo: "Vuoi acquistare un NOME DISPLAY per $ 0,99?". Questo è importante perché ho creato il mio nome visualizzato "Rimuovi annunci" e quindi la mia app è stata respinta perché nel pop-up utilizzavo una grammatica errata! Ho dovuto cambiare il mio nome visualizzato in "Pacchetto rimozione annunci".
Alan Scarpa,

13

Basta tradurre il codice Jojodmo in 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

Risposta rapida

Questo ha lo scopo di integrare la mia risposta Objective-C per gli utenti Swift, per evitare che la risposta Objective-C diventi troppo grande.

Impostare

Innanzitutto, imposta l'acquisto in-app su appstoreconnect.apple.com . Segui la parte iniziale della mia risposta Objective-C (passaggi 1-13, sotto l' intestazione App Store Connect ) per istruzioni su come farlo.

Potrebbero essere necessarie alcune ore per la registrazione dell'ID prodotto in App Store Connect, quindi sii paziente.

Ora che hai impostato le informazioni di acquisto in-app su App Store Connect, dobbiamo aggiungere il framework di Apple per gli acquisti in-app StoreKitall'app.

Vai al tuo progetto Xcode e vai al gestore dell'applicazione (icona blu simile a una pagina nella parte superiore della barra di sinistra in cui si trovano i file della tua app). Fai clic sulla tua app sotto i target a sinistra (dovrebbe essere la prima opzione), quindi vai su "Funzionalità" in alto. Nell'elenco, dovresti vedere un'opzione "Acquisto in-app". Attiva questa funzionalità e Xcode verrà aggiunto StoreKital tuo progetto.

Coding

Ora inizieremo a scrivere codice!

Innanzitutto, crea un nuovo file rapido che gestirà tutti i tuoi acquisti in-app. Lo chiamerò IAPManager.swift.

In questo file, creeremo una nuova classe, chiamata IAPManagerthat is a SKProductsRequestDelegatee SKPaymentTransactionObserver. Nella parte superiore, assicurati di importare FoundationeStoreKit

import Foundation
import StoreKit

public class IAPManager: NSObject, SKProductsRequestDelegate,
                         SKPaymentTransactionObserver {
}

Successivamente, aggiungeremo una variabile per definire l'identificatore per il nostro acquisto in-app (potresti anche utilizzare un enum, che sarebbe più facile da mantenere se hai più 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"

Aggiungiamo quindi un inizializzatore per la nostra classe:

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

Ora, aggiungeremo le funzioni richieste per SKProductsRequestDelegatee SKPaymentTransactionObserverlavorare:

Aggiungeremo la RemoveAdsManagerlezione più tardi

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

Ora aggiungiamo alcune funzioni che possono essere utilizzate per avviare un acquisto o ripristinare gli acquisti:

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

Quindi, aggiungiamo una nuova classe di utility per gestire i nostri IAP. Tutto questo codice potrebbe essere in una classe, ma averlo multiplo lo rende un po 'più pulito. Ho intenzione di creare una nuova classe chiamata RemoveAdsManagere in essa inserire alcune funzioni

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

Le prime tre funzioni, removeAds, restoreRemoveAdse areAdsRemoved, sono funzioni che chiameremo per fare determinate azioni. Gli ultimi quattro sono uno che verrà chiamato da IAPManager.

Aggiungiamo un po 'di codice alle prime due funzioni 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 infine, aggiungiamo un po 'di codice alle ultime cinque funzioni.

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

Mettendo tutto insieme, otteniamo qualcosa del genere:

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

}

Infine, è necessario aggiungere un modo per consentire all'utente di iniziare l'acquisto e chiamare RemoveAdsManager.removeAds()e avviare un ripristino e una chiamata RemoveAdsManager.restoreRemoveAds(), come un pulsante da qualche parte! Tieni presente che, secondo le linee guida dell'App Store, devi fornire un pulsante per ripristinare gli acquisti da qualche parte.

Invio per revisione

L'ultima cosa da fare è inviare il tuo IAP per la revisione su App Store Connect! Per istruzioni dettagliate in merito, puoi seguire l'ultima parte della mia risposta Objective-C , sotto l' intestazione Inoltro per revisione .


4

RMStore è una libreria iOS leggera per gli acquisti in-app. Avvolge l'API StoreKit e offre blocchi pratici per richieste asincrone. Acquistare un prodotto è facile come chiamare un singolo metodo.

Per gli utenti esperti, questa libreria fornisce anche la verifica della ricevuta, i download di contenuti e la persistenza delle transazioni.


-1

So di essere in ritardo per pubblicare questo post, ma condivido un'esperienza simile quando ho imparato le corde del modello IAP.

L'acquisto in-app è uno dei flussi di lavoro più completi in iOS implementati dal framework Storekit. L' intera documentazione è abbastanza chiara se si ha pazienza di leggerla, ma è in qualche modo avanzata per natura di tecnicità.

Riassumere:

1 - Richiedi i prodotti: utilizza le classi SKProductRequest e SKProductRequestDelegate per inviare la richiesta di ID prodotto e riceverli dal tuo negozio itunesconnect.

Questi prodotti SKP dovrebbero essere utilizzati per popolare l'interfaccia utente del tuo negozio che l'utente può utilizzare per acquistare un prodotto specifico.

2 - Invia richiesta di pagamento: utilizzare SKPayment & SKPaymentQueue per aggiungere il pagamento alla coda delle transazioni.

3 - Monitora la coda delle transazioni per l'aggiornamento dello stato - usa il metodo aggiornato TransTransazioni del protocollo SKPaymentTransactionObserver per monitorare lo stato:

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

4 - Ripristina flusso pulsante: utilizza il ripristinoCompletedTransactions di SKPaymentQueue per eseguire questa operazione: il passaggio 3 si occuperà del resto, insieme ai seguenti metodi di SKPaymentTransactionObserver:

paymentQueueRestoreCompletedTransactionsFinished
restoreCompletedTransactionsFailedWithError

Ecco un tutorial passo passo (creato da me come risultato dei miei tentativi di comprenderlo) che lo spiega. Alla fine fornisce anche un esempio di codice che puoi usare direttamente.

Eccone un altro che ho creato per spiegare alcune cose che solo il testo potrebbe descrivere in modo migliore.


21
StackOverflow è un sito Web per aiutare gli altri e non per cercare di guadagnare da loro. Dovresti rimuovere il penultimo link o semplicemente pubblicare qui ciò che è stato fatto in quel tutorial, gratuitamente.
Jojodmo,

@Jojodmo puoi comprovare il tuo reclamo con eventuali linee guida di SO? Vedo molte persone commercializzare il proprio SDK (anche pagato uno) con un disclaimer, che penso sia molto presente anche qui.
Nirav Bhatt,

12
Non ci sono linee guida contro di essa, ma se sei qui per fare soldi, probabilmente sei qui per motivi sbagliati. IMO, la tua risposta sembra incentrata sul convincere le persone a iscriversi ai tuoi tutorial video e non sull'aiutare gli altri
Jojodmo,

3
Questo non è altro che fastidio.
Durazno,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.