Información general de StoreKit y recuperación de información del producto en Xamarin.iOS
La interfaz de usuario de una compra desde la aplicación se muestra en las capturas de pantalla siguientes. Antes de que se produzca cualquier transacción, la aplicación debe recuperar el precio y la descripción del producto para su presentación. Posteriormente, cuando el usuario presiona Comprar, la aplicación realiza una solicitud a StoreKit que administra el cuadro de diálogo de confirmación y el inicio de sesión del id. de Apple. Suponiendo que la transacción se realiza correctamente, StoreKit notifica al código de la aplicación, que debe almacenar el resultado de la transacción y proporcionar al usuario acceso a su compra.
Clases
La implementación de compras desde la aplicación requiere las siguientes clases del marco StoreKit:
SKProductsRequest: Una solicitud a StoreKit para vender productos aprobados (App Store). Se puede configurar con varios identificadores de producto.
- SKProductsRequestDelegate: Declara métodos para manejar solicitudes y respuestas de productos.
- SKProductsResponse: Se devuelve al delegado desde StoreKit (App Store). Contiene los SKProducts que coinciden con los identificadores de producto enviados con la solicitud.
- SKProduct: Un producto recuperado de StoreKit (que haya configurado en iTunes Connect). Contiene información sobre el producto, como id. de producto, título, descripción y precio.
- SKPayment: Creado con un id. de producto y agregado a la cola de pagos para realizar una compra.
- SKPaymentQueue: Solicitudes de pago en cola que se enviarán a Apple. Las notificaciones se desencadenan como resultado de cada pago que se está procesando.
- SKPaymentTransaction: Representa una transacción completa (una solicitud de compra que ha sido procesada por la App Store y enviada de vuelta a su aplicación mediante StoreKit). La transacción puede ser comprar, restaurar o error.
- SKPaymentTransactionObserver: Subclase personalizada que responde a eventos generados por la cola de pagos de StoreKit.
- Las operaciones de StoreKit son asincrónicas: Después de iniciarse SKProductRequest o se agrega SKPayment a la cola, el control vuelve a su código. StoreKit llamará a métodos en su subclase SKProductsRequestDelegate o SKPaymentTransactionObserver cuando reciba datos de los servidores de Apple.
En el siguiente diagrama se muestran las relaciones entre las distintas clases StoreKit (las clases abstractas deben implementarse en la aplicación):
Estas clases se explican con más detalle más adelante en este documento.
Prueba
La mayoría de las operaciones de StoreKit requieren un dispositivo real para las pruebas. La recuperación de información del producto (es decir, precio y descripción) funcionará en el simulador, pero las operaciones de compra y restauración devolverán un error (como FailedTransaction Code=5002 Se ha producido un error desconocido).
Nota: StoreKit no funciona en el simulador de iOS. Al ejecutar la aplicación en el simulador de iOS, StoreKit registra una advertencia si la aplicación intenta recuperar la cola de pago. La prueba de almacenar debe realizarse en dispositivos reales.
Importante: No inicie sesión con su cuenta de prueba en la aplicación Configuración. Puede usar la aplicación Configuración para cerrar sesión en cualquier cuenta de ID de Apple existente, luego debe esperar a que se le solicite dentro de la secuencia de compras desde la aplicación para iniciar sesión con una id. de Apple de prueba.
Si intenta iniciar sesión en la tienda real con una cuenta de prueba, se convertirá automáticamente en un id. de Apple real. Esa cuenta ya no se podrá usar para las pruebas.
Para probar el código StoreKit, debe cerrar sesión en su cuenta de prueba habitual de iTunes e iniciar sesión con una cuenta de prueba especial (creada en iTunes Connect) que esté vinculada a la tienda de prueba. Para cerrar sesión en la cuenta actual, vaya a la Configuración > de iTunes y App Store como se muestra aquí:
a continuación, inicie sesión con una cuenta de prueba cuando lo solicite StoreKit en la aplicación:
Para crear usuarios de prueba en iTunes Connect, haga clic en Usuarios y roles en la página principal.
Seleccione Pruebas de espacio aislado
Se muestra la lista de usuarios existentes. Puede agregar un nuevo usuario o eliminar un registro existente. El portal no le permite (actualmente) ver ni editar usuarios de prueba existentes, por lo que se recomienda que mantenga un buen registro de cada usuario de prueba que se crea (especialmente la contraseña que asigna). Una vez eliminado un usuario de prueba, la dirección de correo electrónico no se puede volver a usar para otra cuenta de prueba.
Los nuevos usuarios de prueba tienen atributos similares a los de una id. de Apple real (como nombre, contraseña, pregunta y respuesta secretas). Mantenga un registro de todos los detalles especificados aquí. El campo Seleccionar iTunes Store determinará qué moneda e idioma utilizarán las compras dentro de la aplicación cuando inicie sesión como ese usuario.
Recuperar información del producto
El primer paso para vender un producto comprado dentro de la aplicación es mostrarlo: Recuperar el precio actual y la descripción de la App Store para mostrarlos.
Independientemente del tipo de productos que vende una aplicación (Consumible, No Consumible o un tipo de Suscripción), el proceso de recuperación de información del producto para la presentación es el mismo. El código InAppPurchaseSample que acompaña a este artículo contiene un proyecto denominado Consumables que muestra cómo recuperar información de producción para su visualización. Muestra cómo:
- Cree una implementación de
SKProductsRequestDelegate
e implemente el método abstractoReceivedResponse
. El código de ejemplo llama a esto la claseInAppPurchaseManager
. - Consulte con StoreKit para ver si se permiten pagos (mediante
SKPaymentQueue.CanMakePayments
). - Cree una instancia de un
SKProductsRequest
con los identificadores de producto que se han definido en iTunes Connect. Esto se hace en el métodoInAppPurchaseManager.RequestProductData
de ejemplo. - Llame al método Start en el
SKProductsRequest
. Esto desencadena una llamada asincrónica a los servidores de App Store. Se llamará al delegado (InAppPurchaseManager
) con los resultados. - El método del delegado (
InAppPurchaseManager
)ReceivedResponse
actualiza la interfaz de usuario con los datos devueltos por la App Store (precios y descripciones de productos o mensajes sobre productos no válidos).
La interacción general tiene este aspecto ( StoreKit está integrado en iOS y App Store representa los servidores de Apple):
Mostrar el ejemplo de información del producto
El código de ejemplo Consumables muestra cómo se puede recuperar la información del producto. La pantalla principal del ejemplo muestra información de dos productos recuperados de App Store:
El código de ejemplo para recuperar y mostrar información del producto se explica con más detalle a continuación.
Métodos ViewController
La clase ConsumableViewController
administrará la visualización de los precios de dos productos cuyos identificadores de producto están codificados de forma dura en la clase.
public static string Buy5ProductId = "com.xamarin.storekit.testing.consume5credits",
Buy10ProductId = "com.xamarin.storekit.testing.consume10credits";
List<string> products;
InAppPurchaseManager iap;
public ConsumableViewController () : base()
{
// two products for sale on this page
products = new List<string>() {Buy5ProductId, Buy10ProductId};
iap = new InAppPurchaseManager();
}
En el nivel de clase también debe haber un NSObject declarado que se usará para configurar un observador de NSNotificationCenter
:
NSObject priceObserver;
En el método ViewWillAppear, el observador se crea y asigna mediante el centro de notificaciones predeterminado:
priceObserver = NSNotificationCenter.DefaultCenter.AddObserver (
InAppPurchaseManager.InAppPurchaseManagerProductsFetchedNotification,
(notification) => {
// display code goes here, to handle the response from the App Store
}
Al final del método ViewWillAppear
, llama al método RequestProductData
para iniciar la solicitud StoreKit. Una vez realizada esta solicitud, StoreKit se pondrá en contacto de forma asincrónica con los servidores de Apple para obtener la información y devolverla a la aplicación. Esto se logra mediante la subclase SKProductsRequestDelegate
(InAppPurchaseManager
) que se explica en la sección siguiente.
iap.RequestProductData(products);
El código para mostrar el precio y la descripción simplemente recupera la información del SKProduct y la asigna a los controles UIKit (observe que mostramos LocalizedTitle
y LocalizedDescription
: StoreKit resuelve automáticamente el texto y los precios correctos según la configuración de la cuenta del usuario). El código siguiente pertenece a la notificación que creamos anteriormente:
priceObserver = NSNotificationCenter.DefaultCenter.AddObserver (
InAppPurchaseManager.InAppPurchaseManagerProductsFetchedNotification,
(notification) => {
// display code goes here, to handle the response from the App Store
var info = notification.UserInfo;
if (info.ContainsKey(NSBuy5ProductId)) {
var product = (SKProduct) info.ObjectForKey(NSBuy5ProductId);
buy5Button.Enabled = true;
buy5Title.Text = product.LocalizedTitle;
buy5Description.Text = product.LocalizedDescription;
buy5Button.SetTitle("Buy " + product.Price, UIControlState.Normal); // price display should be localized
}
}
Por último, el método ViewWillDisappear
debe asegurarse de que el observador se quita:
NSNotificationCenter.DefaultCenter.RemoveObserver (priceObserver);
Métodos SKProductRequestDelegate (InAppPurchaseManager)
Se llama al método RequestProductData
cuando la aplicación desea recuperar los precios de los productos y otra información. Analiza la colección de id. de producto en el tipo de datos correcto y, a continuación, crea un SKProductsRequest
con esa información. Llamar al método Start hace que se realice una solicitud de red a los servidores de Apple. La solicitud se ejecutará de forma asincrónica y llamará al método ReceivedResponse
del delegado cuando se haya completado correctamente.
public void RequestProductData (List<string> productIds)
{
var array = new NSString[productIds.Count];
for (var i = 0; i < productIds.Count; i++) {
array[i] = new NSString(productIds[i]);
}
NSSet productIdentifiers = NSSet.MakeNSObjectSet<NSString>(array);
productsRequest = new SKProductsRequest(productIdentifiers);
productsRequest.Delegate = this; // for SKProductsRequestDelegate.ReceivedResponse
productsRequest.Start();
}
iOS enrutará automáticamente la solicitud a la versión "espacio aislado" o "producción" de la App Store dependiendo del perfil de aprovisionamiento con el que se esté ejecutando la aplicación, de modo que cuando esté desarrollando o probando su aplicación, la solicitud tendrá acceso a todos los productos configurados en iTunes Connect (incluso aquellos que aún no han sido enviados ni aprobados por Apple). Cuando la aplicación está en producción, las solicitudes de StoreKit solo devolverán información de productos aprobados.
Se llama al método ReceivedResponse
invalidado después de que los servidores de Apple hayan respondido con datos. Debido a que esto se llama en segundo plano, el código debe analizar los datos válidos y usar una notificación para enviar la información del producto a cualquier ViewControllers que esté "escuchando" esa notificación. A continuación se muestra el código para recopilar información válida del producto y enviar una notificación:
public override void ReceivedResponse (SKProductsRequest request, SKProductsResponse response)
{
SKProduct[] products = response.Products;
NSDictionary userInfo = null;
if (products.Length > 0) {
NSObject[] productIdsArray = new NSObject[response.Products.Length];
NSObject[] productsArray = new NSObject[response.Products.Length];
for (int i = 0; i < response.Products.Length; i++) {
productIdsArray[i] = new NSString(response.Products[i].ProductIdentifier);
productsArray[i] = response.Products[i];
}
userInfo = NSDictionary.FromObjectsAndKeys (productsArray, productIdsArray);
}
NSNotificationCenter.DefaultCenter.PostNotificationName (InAppPurchaseManagerProductsFetchedNotification, this, userInfo);
}
Aunque no se muestra en el diagrama, el método RequestFailed
también debe invalidarse para que pueda proporcionar algunos comentarios al usuario en caso de que los servidores de App Store no sean accesibles (o se produce algún otro error). El código de ejemplo simplemente escribe en la consola, pero una aplicación real podría optar por consultar la propiedad error.Code
e implementar un comportamiento personalizado (como una alerta al usuario).
public override void RequestFailed (SKRequest request, NSError error)
{
Console.WriteLine (" ** InAppPurchaseManager RequestFailed() " + error.LocalizedDescription);
}
En esta captura de pantalla se muestra la aplicación de ejemplo inmediatamente después de la carga (cuando no hay información del producto disponible):
Productos no válidos
Un SKProductsRequest
también puede devolver una lista de identificadores de producto no válidos. Normalmente, los productos no válidos se devuelven debido a cualquiera de lo siguiente:
El id. de producto está mal escrito: Solo se aceptan identificadores de producto válidos.
Producto no aprobado: Durante las pruebas, todos los productos que estén autorizados para la venta deben ser devueltos por un SKProductsRequest
; sin embargo, en producción solo se devuelven productos aprobados.
El id. de la aplicación no es explícito: Los identificadores de aplicación comodín (con un asterisco) no permiten compras dentro de la aplicación.
El perfil de aprovisionamiento no es correcto: Si realiza cambios en la configuración de su aplicación en el portal de aprovisionamiento (como habilitar compras dentro de la aplicación), recuerde volver a generar y utilizar el perfil de aprovisionamiento correcto al crear la aplicación.
El contrato de aplicaciones pagas de iOS no está vigente: Las funciones de StoreKit no funcionarán en absoluto a menos que exista un contrato válido para su cuenta de desarrollador de Apple.
El binario está en estado Rechazado: Si hay un binario enviado previamente en el estado Rechazado (ya sea por el equipo de App Store o por el desarrollador), las características de StoreKit no funcionarán.
El método ReceivedResponse
del código de ejemplo genera los productos no válidos en la consola:
public override void ReceivedResponse (SKProductsRequest request, SKProductsResponse response)
{
// code removed for clarity
foreach (string invalidProductId in response.InvalidProducts) {
Console.WriteLine("Invalid product id: " + invalidProductId );
}
}
Mostrar precios localizados
Los planes de tarifa especifican un precio específico para cada producto en todas las tiendas de aplicaciones internacionales. Para asegurarse de que los precios se muestran correctamente para cada moneda, use el siguiente método de extensión (definido en SKProductExtension.cs
) en lugar de la propiedad Price de cada SKProduct
:
public static class SKProductExtension {
public static string LocalizedPrice (this SKProduct product)
{
var formatter = new NSNumberFormatter ();
formatter.FormatterBehavior = NSNumberFormatterBehavior.Version_10_4;
formatter.NumberStyle = NSNumberFormatterStyle.Currency;
formatter.Locale = product.PriceLocale;
var formattedString = formatter.StringFromNumber(product.Price);
return formattedString;
}
}
El código que establece el título del botón utiliza un método de extensión como este:
string Buy = "Buy {0}"; // or a localizable string
buy5Button.SetTitle(String.Format(Buy, product.LocalizedPrice()), UIControlState.Normal);
El uso de dos cuentas de prueba de iTunes diferentes (una para la tienda americana y otra para la tienda japonesa) da como resultado las capturas de pantalla siguientes:
Observe que el almacén afecta al idioma que se usa para la información del producto y la moneda del precio, mientras que la configuración de idioma del dispositivo afecta a las etiquetas y a otro contenido localizado.
Recuerde que para usar otra cuenta de prueba de la tienda debe Cerrar sesión en la Configuración de > iTunes y App Store e iniciar de nuevo la aplicación para iniciar sesión con una cuenta diferente. Para cambiar el idioma del dispositivo, vaya a Configuración > General > Internacional > Idioma.