¿Cómo se agrega una compra en la aplicación a una aplicación iOS?

257

¿Cómo se agrega una compra en la aplicación a una aplicación de iOS? ¿Cuáles son todos los detalles y hay algún código de muestra?

Esto está destinado a ser un tipo general de cómo agregar compras en la aplicación a las aplicaciones de iOS

Jojodmo
fuente
11
¿Qué pasa con la lectura de la "Guía de programación de compras en la aplicación"?
rmaddy

Respuestas:

554

Usuarios rápidos

Los usuarios de Swift pueden consultar Mi respuesta rápida para esta pregunta .
O echa un vistazo a la respuesta de Yedidya Reiss , que traduce este código de Objective-C a Swift.

Usuarios de Objective-C

El resto de esta respuesta está escrita en Objective-C

App Store Connect

  1. Vaya a appstoreconnect.apple.com e inicie sesión
  2. Haga My Appsclic y luego haga clic en la aplicación que desea agregar la compra a
  3. Haga clic en el Featuresencabezado y luego seleccione In-App Purchasesa la izquierda
  4. Haz clic en el +ícono en el medio
  5. Para este tutorial, agregaremos una compra en la aplicación para eliminar anuncios, así que elija non-consumable. Si fuera a enviar un artículo físico al usuario o le diera algo que pueda comprar más de una vez, usted elegiría consumable.
  6. Para el nombre de referencia, ponga lo que quiera (pero asegúrese de saber qué es)
  7. Para la identificación del producto, tld.websitename.appname.referencenameesto funcionará mejor, por ejemplo, podría usarcom.jojodmo.blix.removeads
  8. Elija cleared for saley luego elija el nivel de precio como 1 (99 ¢). El nivel 2 sería de $ 1.99, y el nivel 3 sería de $ 2.99. La lista completa está disponible si hace clic en view pricing matrixRecomiendo usar el nivel 1, ya que generalmente es lo máximo que alguien pagará para eliminar anuncios.
  9. Haga clic en el add languagebotón azul e ingrese la información. Todo esto se mostrará al cliente, así que no ponga nada que no quiera que vean.
  10. Para hosting content with Appleelegir no
  11. Puede dejar las notas de revisión en blanco POR AHORA .
  12. Omita el screenshot for review FOR NOW , todo lo que omitamos volveremos.
  13. Clic en Guardar'

La identificación de su producto puede demorar algunas horas en registrarse App Store Connect, así que tenga paciencia.

Configurando su proyecto

Ahora que ha configurado su información de compra en la aplicación en App Store Connect, vaya a su proyecto Xcode y vaya al administrador de la aplicación (icono en forma de página azul en la parte superior de donde están sus métodos y archivos de encabezado) haga clic en su aplicación bajo objetivos (debería ser la primera) y luego vaya a general. En la parte inferior, debería ver linked frameworks and librarieshacer clic en el pequeño símbolo más y agregar el marco. StoreKit.frameworkSi no hace esto, ¡la compra en la aplicación NO funcionará!

Si está utilizando Objective-C como lenguaje para su aplicación, debe omitir estos cinco pasos . De lo contrario, si está utilizando Swift, puede seguir My Swift Answer para esta pregunta, aquí , o, si prefiere utilizar Objective-C para el código de compra en la aplicación pero está utilizando Swift en su aplicación, puede hacer lo siguiente :

  1. Crear un nuevo .harchivo (cabecera) al ir a File> New> File...( Command ⌘+ N). Este archivo se denominará "Su .harchivo" en el resto del tutorial.

  2. Cuando se le solicite, haga clic en Crear encabezado de puente . Este será nuestro archivo de encabezado de puente. Si no se pide, vaya al paso 3. Si se le pide, omita el paso 3 y vaya directamente al paso 4.

  3. Cree otro .harchivo con el nombre Bridge.hen la carpeta principal del proyecto, luego vaya al Administrador de aplicaciones (el icono azul parecido a una página), luego seleccione su aplicación en la Targetssección y haga clic Build Settings. Busque la opción que dice Swift Compiler - Generación de código , y luego configure la opción Encabezado de puente de Objective-C enBridge.h

  4. En su archivo de encabezado de puente, agregue la línea #import "MyObjectiveCHeaderFile.h", donde MyObjectiveCHeaderFileestá el nombre del archivo de encabezado que creó en el paso uno. Entonces, por ejemplo, si nombró su archivo de encabezado InAppPurchase.h , agregaría la línea #import "InAppPurchase.h"a su archivo de encabezado de puente.

  5. Crear un nuevo Objective-C Métodos ( .marchivo) al ir a File> New> File...( Command ⌘+ N). Póngale el mismo nombre que el archivo de encabezado que creó en el paso 1. Por ejemplo, si llamó al archivo en el paso 1 InAppPurchase.h , llamaría a este nuevo archivo InAppPurchase.m . Este archivo se denominará "Su .marchivo" en el resto del tutorial.

Codificación

Ahora vamos a entrar en la codificación real. Agregue el siguiente código en su .harchivo:

BOOL areAdsRemoved;

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

A continuación, debe importar el StoreKitmarco en su .marchivo, así como agregar SKProductsRequestDelegatey SKPaymentTransactionObserverdespués de su @interfacedeclaración:

#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

y ahora agregue lo siguiente en su .marchivo, esta parte se complica, por lo que le sugiero que lea los comentarios en el 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;
        }
    }
}

Ahora desea agregar su código para lo que sucederá cuando el usuario finalice la transacción, para este tutorial, usamos la eliminación de adiciones, tendrá que agregar su propio código para lo que sucede cuando se carga la vista de 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];
}

Si no tiene anuncios en su aplicación, puede usar cualquier otra cosa que desee. Por ejemplo, podríamos hacer que el color del fondo sea azul. Para hacer esto nos gustaría 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];
}

Ahora, en algún lugar de su viewDidLoadmétodo, querrá agregar el siguiente 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
}

Ahora que ha agregado todo el código, vaya a su archivo .xibo storyboard, y agregue dos botones, uno que dice comprar y el otro que dice restaurar. Conecte el tapsRemoveAds IBActionbotón de compra que acaba de hacer y el restore IBActionbotón de restauración. La restoreacción verificará si el usuario ha comprado previamente la compra en la aplicación, y le dará la compra en la aplicación de forma gratuita si aún no la tiene.

Enviar para revisión

A continuación, vaya a App Store Connect y haga clic, Users and Accessluego haga clic en el Sandbox Testersencabezado y luego haga clic en el +símbolo de la izquierda donde dice Testers. Puede poner cosas al azar para el nombre y apellido, y el correo electrónico no tiene que ser real, solo debe poder recordarlo. Ingrese una contraseña (que deberá recordar) y complete el resto de la información. Le recomendaría que establezca Date of Birthuna fecha que haga que el usuario tenga 18 años o más. App Store Territory Tiene que estar en el país correcto. A continuación, cierre sesión en su cuenta de iTunes existente (puede volver a iniciar sesión después de este tutorial).

Ahora, ejecute su aplicación en su dispositivo iOS, si intenta ejecutarla en el simulador, la compra siempre será un error, DEBE ejecutarla en su dispositivo iOS. Una vez que la aplicación se esté ejecutando, toque el botón de compra. Cuando se le solicite que inicie sesión en su cuenta de iTunes, inicie sesión como el usuario de prueba que acabamos de crear. A continuación, cuando le pida que confirme la compra de 99 ¢ o lo que sea que establezca también en el nivel de precio, TOME UNA PANTALLA INSTANTÁNEA. Esto es lo que va a utilizar para su screenshot for reviewApp Store Connect. Ahora cancele el pago.

Ahora, ir a la tienda de aplicaciones Conectar , y luego ir a My Apps> the app you have the In-app purchase on> In-App Purchases. Luego haga clic en su compra en la aplicación y haga clic en editar debajo de los detalles de compra en la aplicación. Una vez que haya hecho eso, importe la foto que acaba de tomar en su iPhone a su computadora y cárguela como captura de pantalla para su revisión, luego, en las notas de revisión, coloque su correo electrónico y contraseña del USUARIO DE LA PRUEBA . Esto ayudará a Apple en el proceso de revisión.

Después de haber hecho esto, regrese a la aplicación en su dispositivo iOS, aún conectado como la cuenta de usuario de prueba, y haga clic en el botón de compra. Esta vez, confirme el pago No se preocupe, esto NO le cobrará a su cuenta NINGÚN dinero, pruebe que las cuentas de usuario obtengan todas las compras en la aplicación de forma gratuita Después de haber confirmado el pago, asegúrese de lo que sucede cuando el usuario compra su producto realmente sucede Si no es así, eso será un error con su doRemoveAdsmétodo. Nuevamente, recomiendo usar cambiar el fondo a azul para probar la compra en la aplicación, sin embargo, esta no debería ser su compra real en la aplicación. ¡Si todo funciona y listo! ¡Solo asegúrese de incluir la compra en la aplicación en su nuevo binario cuando la cargue en App Store Connect!


Aquí hay algunos errores comunes:

Registrado: No Products Available

Esto podría significar cuatro cosas:

  • No colocó la ID de compra correcta en la aplicación en su código (para el identificador kRemoveAdsProductIdentifieren el código anterior
  • No borró su compra en la aplicación para la venta en App Store Connect
  • No esperaste a que se registrara el ID de compra en la aplicación en App Store Connect . Espere un par de horas desde la creación de la ID y su problema debería resolverse.
  • No completó el llenado de sus acuerdos, impuestos e información bancaria.

Si no funciona la primera vez, ¡no te frustres! ¡No te rindas! ¡Me tomó cerca de 5 horas seguidas antes de poder hacer que esto funcionara, y alrededor de 10 horas buscando el código correcto! Si usa el código anterior exactamente, debería funcionar bien. Siéntase libre de comentar si tiene alguna pregunta en absoluto .

Espero que esto ayude a todos aquellos que esperan agregar una compra en la aplicación a su aplicación iOS. ¡Salud!

Jojodmo
fuente
1
pero si no agrego esa línea, cuando hago clic en el botón restaurar no pasa nada ... de todos modos muchas gracias por este tutorial;)
Ilario
1
"if ( * transaction * == SKPaymentTransactionStateRestored) {" debería ser if ( * transaction.transactionState * == SKPaymentTransactionStateRestored) {
Massmaker
13
Las mejores prácticas de Apple recomiendan agregar el observador de transacciones al AppDelegate, no las acciones del controlador de vista. developer.apple.com/library/ios/technotes/tn2387/_index.html
Craig Pickering
3
Recibo un recuento de 0 productos, pero ya verifiqué los 3 posibles motivos que enumeró. Lo único que me viene a la mente si no he configurado la información de contacto, la información bancaria y la información fiscal en el "contrato de aplicación pagada ios" dentro de iTunes Connect, ¿podría ser esta la razón?
Christopher Francisco
44
Debe explicar que en el Paso 9, el nombre para mostrar es lo que se presenta al usuario. Y se presenta de esta manera: "¿Desea comprar un NOMBRE DE PANTALLA por $ 0.99?". Esto es importante porque hice mi nombre para mostrar "Eliminar anuncios" y luego mi aplicación fue rechazada porque estaba usando una gramática incorrecta en la ventana emergente. Tuve que cambiar mi nombre para mostrar a "Paquete de eliminación de anuncios".
Alan Scarpa
13

Simplemente traduzca el código de Jojodmo a 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
fuente
6

Respuesta rápida

Esto está destinado a complementar mi respuesta de Objective-C para los usuarios de Swift, para evitar que la respuesta de Objective-C sea demasiado grande.

Preparar

Primero, configure la compra en la aplicación en appstoreconnect.apple.com . Siga la parte inicial de mi respuesta de Objective-C (pasos 1-13, debajo del encabezado App Store Connect ) para obtener instrucciones sobre cómo hacerlo.

La identificación de su producto puede demorar algunas horas en registrarse en App Store Connect, así que sea paciente.

Ahora que ha configurado su información de compra en la aplicación en App Store Connect, necesitamos agregar el marco de Apple para compras StoreKiten la aplicación, a la aplicación.

Ingrese a su proyecto Xcode y vaya al administrador de la aplicación (icono azul parecido a una página en la parte superior de la barra izquierda donde están los archivos de su aplicación). Haga clic en su aplicación debajo de los objetivos a la izquierda (debería ser la primera opción), luego vaya a "Capacidades" en la parte superior. En la lista, debería ver una opción "Compra en la aplicación". Active esta capacidad y Xcode se agregará StoreKita su proyecto.

Codificación

¡Ahora vamos a comenzar a codificar!

Primero, cree un nuevo archivo rápido que gestionará todas sus compras en la aplicación. Voy a llamarlo IAPManager.swift.

En este archivo, vamos a crear una nueva clase, llamada IAPManagereso es una SKProductsRequestDelegatey SKPaymentTransactionObserver. En la parte superior, asegúrese de importar FoundationyStoreKit

import Foundation
import StoreKit

public class IAPManager: NSObject, SKProductsRequestDelegate,
                         SKPaymentTransactionObserver {
}

A continuación, agregaremos una variable para definir el identificador de nuestra compra en la aplicación (también puede usar una enum, que sería más fácil de mantener si tiene múltiples 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"

A continuación, agreguemos un inicializador para nuestra clase:

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

Ahora, vamos a agregar las funciones requeridas para SKProductsRequestDelegatey SKPaymentTransactionObserverpara trabajar:

Añadiremos la RemoveAdsManagerclase más 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
        }
    }
}

Ahora agreguemos algunas funciones que pueden usarse para comenzar una compra o 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()
}

A continuación, agreguemos una nueva clase de utilidades para administrar nuestros IAP. Todo este código podría estar en una clase, pero tenerlo múltiple lo hace un poco más limpio. Voy a hacer una nueva clase llamada RemoveAdsManager, y en ella, poner algunas funciones

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

Las tres primeras funciones, removeAds, restoreRemoveAdsy areAdsRemoved, son funciones que se van a llamar para hacer ciertas acciones. Los últimos cuatro son uno que será llamado por IAPManager.

Agreguemos código a las dos primeras funciones removeAdsy 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()
}

Y, por último, agreguemos algo de código a las últimas cinco funciones.

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

Poniendo todo junto, obtenemos algo como esto:

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 último, debe agregar alguna forma para que el usuario comience la compra y llame RemoveAdsManager.removeAds()y comience una restauración y llamada RemoveAdsManager.restoreRemoveAds(), ¡como un botón en alguna parte! Tenga en cuenta que, según las pautas de la tienda de aplicaciones, debe proporcionar un botón para restaurar las compras en algún lugar.

Enviar para revisión

¡Lo último que debe hacer es enviar su IAP para su revisión en App Store Connect! Para obtener instrucciones detalladas sobre cómo hacerlo, puede seguir la última parte de mi respuesta de Objective-C , debajo del encabezado Enviar para revisión .

Jojodmo
fuente
4

RMStore es una biblioteca liviana de iOS para compras en la aplicación. Envuelve la API de StoreKit y le proporciona bloques útiles para solicitudes asincrónicas. Comprar un producto es tan fácil como llamar a un solo método.

Para los usuarios avanzados, esta biblioteca también proporciona verificación de recibos, descargas de contenido y persistencia de transacciones.

Vladimir Grigorov
fuente
-1

Sé que llego bastante tarde para publicar esto, pero comparto una experiencia similar cuando aprendí las cuerdas del modelo IAP.

La compra en la aplicación es uno de los flujos de trabajo más completos en iOS implementado por el marco de Storekit. La documentación completa es bastante clara si tiene paciencia para leerla, pero es algo avanzada en cuanto a tecnicidad.

Para resumir:

1 - Solicite los productos: use las clases SKProductRequest y SKProductRequestDelegate para emitir una solicitud de ID de producto y recibirlos en su propia tienda itunesconnect.

Estos SKProducts deben usarse para completar la interfaz de usuario de su tienda que el usuario puede usar para comprar un producto específico.

2 - Emitir solicitud de pago: use SKPayment & SKPaymentQueue para agregar el pago a la cola de transacciones.

3 - Monitoree la cola de transacciones para la actualización del estado - use el método updatedTransactions del Protocolo SKPaymentTransactionObserver para monitorear el estado:

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

4: restablezca el flujo del botón: utilice el método restoreCompletedTransactions de SKPaymentQueue para lograr esto; el paso 3 se encargará del resto, junto con los siguientes métodos de SKPaymentTransactionObserver:

paymentQueueRestoreCompletedTransactionsFinished
restoreCompletedTransactionsFailedWithError

Aquí hay un tutorial paso a paso (escrito por mí como resultado de mis propios intentos de entenderlo) que lo explica. Al final, también proporciona una muestra de código que puede usar directamente.

Aquí hay otro que creé para explicar ciertas cosas que solo el texto podría describir de una mejor manera.

Nirav Bhatt
fuente
21
StackOverflow es un sitio web para ayudar a otros, y no para tratar de ganar dinero con ellos. Debe eliminar el penúltimo enlace o simplemente publicar lo que se hace en ese tutorial aquí, de forma gratuita.
Jojodmo
@Jojodmo, ¿puede justificar su reclamo con alguna guía de SO? Veo a muchas personas comercializando su propio SDK (incluso pagado) con un descargo de responsabilidad, que creo que también está muy presente aquí.
Nirav Bhatt
12
No hay pautas en su contra, pero si estás aquí para ganar dinero, probablemente estés aquí por las razones equivocadas. En mi opinión, su respuesta parece centrarse en lograr que las personas se inscriban en sus tutoriales en video, y no en ayudar a los demás
Jojodmo
3
Esto no es más que molestia.
durazno