In-App Purchases en iOS # Parte II #

http://www.ingens-networks.com/blog/post/2012/06/04/In-App-Purchases-en-iOS-Parte-2.aspx

En la primera parte se vió una introducción a las In-App Purchases, categorías y tipos. Ahora veremos un ejemplo práctico, en concreto, desarrollaremos una aplicación que comprará un archivo de audio (In-App) que no se encuentra en el bundle. Una vez comprada y descargada, podremos reproducirla en nuestro dispositivo.

 

Antes de entrar a “picar código”, necesitamos 3 cosas: App ID / Provisioning Profile, Usuario de pruebas para las “In-App Purchases” y productos que vender.

 


APP ID / PROVISIONING PROFILE


Como ya he comentado en anteriores artículos. Para poder hacer uso de las In-App Purchases, notificaciones Push y/o Game Center, necesitamos que el App ID que estemos utilizando para firmar nuestra app no contenga comodines (wildcard).

 

Incorrecto: com.miempresa.*

 

 

Correcto: com.miempresa.miapp

 

 

Para este ejemplo utilizaremos el App ID creado anteriormente “PushTest”. Si tienes alguna duda de como crearlo en este post se explica con más detenimiento.

 


USUARIO DE PRUEBAS PARA LAS “IN-APP PURCHASES”

 

Debemos crear al menos una cuenta de prueba para cada región que la aplicación esté localizada. Las cuentas de prueba deben ser nuevas y únicas. No se pueden reutilizar las ya existentes. Solo los usuarios administradores y técnicos están autorizados para crearlos. Estos usuarios no tienen acceso a iTunes Connect, pero serán capaces de probar las In-App Purchases en un entorno de desarrollo de un dispositivo de prueba registrado.

 

Para crearlo seguiremos los siguientes pasos:

 

  • Logarnos en iTunes Connect.
  • En la página principal, seleccionamos “Manage Users”.
  • Seleccionamos el tipo de usuario “Test User”.
  • Ahora nos aparecerá el listado de usuarios, si no hemos creado ninguno previamente estará vacío. Para añadirlo tendremos que hacer click en “Add new user”.
  • Metemos toda la información en el formulario de registro y guardamos.
Tenemos que asegurarnos que el correo de la nueva cuenta no está asociada a ninguna cuenta ya configurada.

 


PRODUCTOS PARA VENDER


Las compras a través de una aplicación (In-App) se pueden hacer en aplicaciones gratuitas o de pago, tanto en iOS como en OSX. Para registrarlas deberemos añadir antes dicha aplicación en iTunes Connect y nos aparecerá un menú como este:

 

Una vez que hayamos accedido al apartado que nos interesa (Manage In-App Purchases -> Create new), deberemos elegir el tipo que será, en este caso será “No Consumible”. El siguiente apartado nos pedirá toda la información del producto, lo rellenamos todo a excepción del apartado “Screenshot for review” que no será necesario para entornos de desarrollo pero una vez que lo queramos publicar en la AppStore, sí que deberemos incluir este apartado.

 

Una vez creado, deberemos apuntar el campo Product ID que es el que utilizaremos con StoreKit para realizar las transacciones. Podemos crear hasta 10000 productos por aplicación, número más que suficiente para la inmensa mayoría.

 

Ya tenemos todo lo necesario para entrar en materia.

 


SHOW ME THE CODE


Primero de todo deberemos importar los frameworks “StoreKit” (para trabajar con las transacciones de las In-App Purchase) y “AVFoundation” (es el que nos permitirá trabajar con audio).

 

1
2
#import <StoreKit/StoreKit.h>
#import <AVFoundation/AVFoundation.h>


Nuestro .xib tendrá 3 UIButtons: uno para hacer la petición de compra, otra para reproducir la canción comprada y la última para detener la reproducción de esta.

 

 

Desde Interface Builder vamos a hacer que los UIButtons de “Play” y “Stop” tengan la propiedad de “hidden” activada (esto es porque solo aparecerá dichos botones cuando hayamos realizado la compra y descarga de la canción). En el UIButton que utilizaremos para realizar la acción de comprar, pondremos de texto predeterminado “Esperando a conectar…” y lo modificaremos por código una vez que hayamos recibido respuesta de la AppStore y también pondremos la propiedad “Enabled” deshabilitada (mientras no recibamos respuesta de la AppStore no podremos hacer dicha petición de compra).

 

Tenemos que hacer lo siguiente:

 

  • Crear los IBAction / IBOutlet de los 3 UIButtons.
  • Crear dos propiedades del tipo SKProduct y AVAudioPlayer.
  • Implementar los protocolos SKProductsRequestDelegate SKPaymentTransactionObserver.
De esta forma nuestro ViewController.h quedaría así:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@interface ViewController : UIViewController <SKProductsRequestDelegate, SKPaymentTransactionObserver>
{
    SKProduct *cancion;
    AVAudioPlayer *player;
}
@property (nonatomic, strong) SKProduct *cancion;
@property (nonatomic, strong) AVAudioPlayer *player;
@property (strong, nonatomic) IBOutlet UIButton *boton;
@property (strong, nonatomic) IBOutlet UIButton *botonStop;
@property (strong, nonatomic) IBOutlet UIButton *botonPlay;
- (IBAction)compra:(id)sender;
- (IBAction)play:(id)sender;
- (IBAction)stop:(id)sender;
@end

 

Tenemos que controlar que no existe la restricción (control parental) de las “Compras integradas” en el dispositivo como también inicializar la solicitud con los productos que nos interesan. Para ello modificamos el método viewDidLoad del archivo ViewController.m:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // Comprobamos si hay alguna restricción configurada en el device respecto a las In-App Purchases.
    if ([SKPaymentQueue canMakePayments]) {
        
        NSLog(@"Puedo hacer pagos In-App");
        
        // Inicializamos la solicitud con los productos que nos interesen. En este caso solo mandamos el único que tenemos creado.
        SKProductsRequest *productRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:@"com.miempresa.miapp.prueba"]];
        productRequest.delegate = self;
        [productRequest start];
    }
    else {
        NSLog(@"Control parental activado");
    }
}

 

El identificador que le pasamos, se corresponde con el “Product ID” que creamos anteriormente en iTunes Connect.

 

Ahora debemos implementar los métodos delegados correspondiente al protocolo SKProductsRequestDelegate:

 

  • productsRequest:didReceiveResponse: -> Obtiene la respuesta de la AppStore (obligatorio).
  • request:didFailWithError: -> Este método es llamado cuando falla la solicitud a la AppStore.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#pragma mark SKProductsRequestDelegate
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
    NSArray *misProductos = response.products;
    
    NSLog(@"Número de productos devueltos: %i", misProductos.count);
    
    if (misProductos.count > 0) {
        cancion = [misProductos objectAtIndex:0];
        [boton setTitle:@"Comprar canción 0.99€" forState:UIControlStateNormal];
        // Habilitamos el botón de comprar
        [boton setEnabled:YES];
    }
    else{
        NSLog(@"Productos no disponibles");
    }
    
}
-(void)request:(SKRequest *)request didFailWithError:(NSError *)error
{
    NSLog(@"Error al conectar a la AppStore para las In-App Purchase: %@", error);
}

 

Controlamos el número de productos que recibimos en la respuesta ya que podríamos solicitar un producto que ya no esté en venta o lo hayamos borrado por cualquier causa.

 

Ahora tenemos que añadir a la cola de pago la transacción para adquirir nuestra canción. Para ello tendremos que añadir lo siguiente en el IBAction de nuestro botón:

 

1
2
3
4
5
6
7
- (IBAction)compra:(id)sender {
    // Añadimos el producto que recibimos en el método delegado productsRequest:didReceiveResponse:
    SKPayment *pago = [SKPayment paymentWithProduct:cancion];
    // Nos añadimos a nosotros mismos como observadores de la transacción.
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    [[SKPaymentQueue defaultQueue] addPayment:pago];
}

 

Antes implementamos el protocolo delegado SKPaymentTransactionObserver y algunos os preguntareis ¿Exactamente qué hacemos con él?. Es el que se encarga de ver como ha ido el proceso de la transacción de compra: si se ha comprado satisfactoriamente, si ha habido algún error en el proceso de compra…. Exactamente tenemos 5 estados en una transacción en StoreKit pero para este ejemplo solo utilizaremos SKPaymentTransactionStateFailed y SKPaymentTransactionStatePurchased.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#pragma mark SKPaymentTransactionObserver
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchasing:
                
                break;
                
            case SKPaymentTransactionStatePurchased:
                
                // Nos descargamos la canción
                [self downloadFromUrl:[NSURL URLWithString:@"http://miweb.com/pruebas/cancion.mp3"]];
                
                NSLog(@"Comprado");
                
                // mostramos los botones
                [botonPlay setHidden:NO];
                [botonStop setHidden:NO];
                
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                
                break;
                
                
            case SKPaymentTransactionStateRestored:
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                
                break;
            
            case SKPaymentTransactionStateFailed:
                
                if (transaction.error.code != SKErrorPaymentCancelled) {
                    NSLog(@"Error en la transacción: %@", transaction.error.localizedDescription);
                }
                
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                
                break;
        }
    }
}

 

Ya solo faltaría implementar los métodos que nos permitirá descargarnos la canción desde un servidor externo, reproducir la canción y detener su reproducción.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
-(void)downloadFromUrl:(NSURL *)url
{
    NSData *datos = [NSData dataWithContentsOfURL:url];
    NSArray *rutas = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentDirectory = [rutas objectAtIndex:0];
    NSString *fichero = [NSString stringWithFormat:@"%@/cancion.mp3", documentDirectory];
    
    [datos writeToFile:fichero atomically:YES];
}
- (IBAction)play:(id)sender {
    NSArray *rutas = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentDirectory = [rutas objectAtIndex:0];
    NSString *fichero = [NSString stringWithFormat:@"%@/cancion.mp3", documentDirectory];
    
    NSError *error;
    
    player = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:fichero] error:&error];
    
    if (player == nil) {
        NSLog(@"Error al reproducir: %@", [error description]);
    }
    else {
        [player play];
    }
}
- (IBAction)stop:(id)sender {
    NSArray *rutas = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentDirectory = [rutas objectAtIndex:0];
    NSString *fichero = [NSString stringWithFormat:@"%@/cancion.mp3", documentDirectory];
    
    NSError *error;
    
    player = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:fichero] error:&error];
    
    if (player == nil) {
        NSLog(@"Error al reproducir: %@", [error description]);
    }
    else {
        [player stop];
    }
}

 

El resultado final quedaría algo así: