27 votes

Toute (première) expérience avec les abonnements auto-renouvelables pour iOS

Apple a finalement introduit le soi-disant abonnements auto-renouvelables hier. Comme je n'ai que peu d'expériences (uniquement en bac à sable) avec les achats en ligne, je ne suis pas sûr d'avoir tout compris. Il semble qu'une besoins une vérification des reçus côté serveur. Il semble que le seul moyen de savoir si l'abonnement est toujours valable soit de stocker les données de la transaction originale sur le serveur. Le guide de programmation d'Apple à ce sujet est très obscur pour moi.

Je m'attendais à pouvoir travailler avec un client iOS uniquement, en demandant simplement à iTunes, via l'api store kit, s'il a déjà acheté ce produit (d'abonnement) et en recevant une réponse oui/non. ensemble avec une date d'expiration.

Quelqu'un a-t-il des expériences avec des abonnements auto-renouvelables ou (parce qu'ils semblent quelque peu similaires) des produits non consommables ? Existe-t-il de bons tutoriels à ce sujet ?

Merci.

46voto

kitschmaster Points 798

Je l'ai fait tourner dans le bac à sable, presque en direct...

on doit utiliser un serveur pour vérifier les reçus.

sur le serveur, vous pouvez enregistrer l'udid du périphérique avec les données du reçu, puisque les reçus sont toujours fraîchement générés. et cela fonctionnera sur plusieurs périphériques, puisque les reçus sont toujours fraîchement générés.

sur l'appareil, il n'est pas nécessaire de stocker des données sensibles. et il ne faut pas le faire :)

il faut vérifier le dernier reçu auprès du magasin chaque fois que l'application est lancée. l'application appelle le serveur et le serveur valide avec le magasin. tant que le magasin renvoie un reçu valide, l'application sert la fonction.

J'ai développé une application Rails3.x pour gérer le côté serveur, le code actuel pour la vérification ressemble à ceci :

APPLE_SHARED_PASS = "enter_yours"
APPLE_RECEIPT_VERIFY_URL = "https://sandbox.itunes.apple.com/verifyReceipt" #test
# APPLE_RECEIPT_VERIFY_URL = "https://buy.itunes.apple.com/verifyReceipt"     #real
def self.verify_receipt(b64_receipt)
  json_resp = nil
  url = URI.parse(APPLE_RECEIPT_VERIFY_URL)
  http = Net::HTTP.new(url.host, url.port)
  http.use_ssl = true
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
  json_request = {'receipt-data' => b64_receipt, 'password' => APPLE_SHARED_PASS}.to_json
  resp, resp_body = http.post(url.path, json_request.to_s, {'Content-Type' => 'application/x-www-form-urlencoded'})
  if resp.code == '200'
    json_resp = JSON.parse(resp_body)
    logger.info "verify_receipt response: #{json_resp}"
  end
  json_resp
end
#App Store error responses
#21000 The App Store could not read the JSON object you provided.
#21002 The data in the receipt-data property was malformed.
#21003 The receipt could not be authenticated.
#21004 The shared secret you provided does not match the shared secret on file for your account.
#21005 The receipt server is not currently available.
#21006 This receipt is valid but the subscription has expired.

UPDATE

mon application a été rejetée, parce que le méta-données n'indiquait pas clairement certaines informations sur les abonnements auto-renouvelables.

Dans vos méta-données sur iTunes Connect (dans la description de votre application) : Vous devez divulguer clairement et de manière visible aux utilisateurs les informations concernant votre abonnement à renouvellement automatique :  

  • Titre de la publication ou du service
  • Durée de l'abonnement (période et/ou nombre de livraisons pendant chaque période d'abonnement)
  • Prix de l'abonnement, et prix par numéro le cas échéant
  • Le paiement sera débité du compte iTunes lors de la confirmation de l'achat.
  • L'abonnement est automatiquement renouvelé, sauf si le renouvellement automatique est désactivé au moins 24 heures avant la fin de la période en cours.
  • Le compte sera facturé pour le renouvellement dans les 24 heures précédant la fin de la période en cours, et identifiera le coût du renouvellement.
  • Les abonnements peuvent être gérés par l'utilisateur et le renouvellement automatique peut être désactivé en allant dans les paramètres du compte de l'utilisateur après l'achat.
  • Aucune annulation de l'abonnement en cours n'est autorisée pendant la période d'abonnement active.
  • Liens vers votre politique de confidentialité et vos conditions d'utilisation
  • Toute partie non utilisée d'une période d'essai gratuite, si elle est offerte, sera perdue lorsque l'utilisateur achètera un abonnement à cette publication."

UPDATE II

L'application a encore été rejetée. Le reçu de l'abonnement n'est pas vérifié par l'url de vérification de l'AppStore de production. Je ne peux pas reproduire ce problème dans le bac à sable, mon application fonctionne parfaitement. La seule façon de déboguer ce problème est de soumettre à nouveau l'application pour examen et de regarder le journal du serveur.

UPDATE III

Un autre rejet. Pendant ce temps, Apple a documenté deux autres statuts :

#21007 This receipt is a sandbox receipt, but it was sent to the production service for verification.
#21008 This receipt is a production receipt, but it was sent to the sandbox service for verification.

Avant de soumettre l'application pour révision, il ne faut pas basculer le serveur en production. Si on le fait, on obtient le statut 21007 renvoyé lors de la vérification.

Cette fois, le refus se lit comme suit :

L'application lance le processus d'achat In App d'une manière non standard. Nous avons inclus les détails suivants pour aider à expliquer le problème et nous espérons que vous envisagerez de réviser et de soumettre à nouveau votre application.

Le nom d'utilisateur et le mot de passe iTunes sont demandés immédiatement au lancement de l'application. Veuillez vous référer à la capture d'écran ci-jointe pour plus informations.

Je n'ai aucune idée de la raison pour laquelle cela se produit. La boîte de dialogue du mot de passe s'affiche-t-elle parce qu'une transaction précédente est en cours de restauration ? ou s'affiche-t-elle au moment de la demande d'informations sur les produits auprès de l'App Store ?

UPDATE IV

je l'ai eu après 5 rejets. mon code faisait l'erreur la plus évidente. on devrait vraiment s'assurer de toujours terminer les transactions lorsqu'elles sont livrées à l'application.

si les transactions ne sont pas terminées, elles sont renvoyées à l'application et les choses se passent étrangement mal.

il faut d'abord initier un paiement, comme ceci :

//make the payment
SKPayment *payment = [SKPayment paymentWithProductIdentifier:productIdentifier];
[[SKPaymentQueue defaultQueue] addPayment:payment];

alors l'application abandonnera rapidement son état actif et cette méthode du délégué de l'application sera appelée :

- (void)applicationWillResignActive:(UIApplication *)application

lorsque l'application est inactive, l'AppStore fait apparaître ses boîtes de dialogue. lorsque l'application redevient active :

- (void)applicationDidBecomeActive:(UIApplication *)application

le système d'exploitation effectue la transaction :

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{

  for (SKPaymentTransaction *transaction in transactions)
  {

    switch (transaction.transactionState)
    {
        case SKPaymentTransactionStatePurchased: {
            [self completeTransaction:transaction];
            break;
        }
        case SKPaymentTransactionStateFailed: {
            [self failedTransaction:transaction];
            break;
        }
        case SKPaymentTransactionStateRestored: {
            [self restoreTransaction:transaction];
            break;
        }
        default:
            break;
      }
  }
}

et ensuite on complète la transaction :

//a fresh purchase
- (void) completeTransaction: (SKPaymentTransaction *)transaction
{
    [self recordTransaction: transaction];
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; 
}

voir, comment on appelle la méthode finishTransaction juste après avoir transmis la transaction reçue à recordTransaction qui appelle ensuite le serveur d'applications et effectue la vérification du reçu de l'abonnement auprès de l'appstore, comme ceci :

- (void)recordTransaction: (SKPaymentTransaction *)transaction 
{
    [self subscribeWithTransaction:transaction];
}

- (void)subscribeWithTransaction:(SKPaymentTransaction*)transaction {

    NSData *receiptData = [transaction transactionReceipt];
    NSString *receiptEncoded = [Kriya base64encode:(uint8_t*)receiptData.bytes length:receiptData.length];//encode to base64 before sending

    NSString *urlString = [NSString stringWithFormat:@"%@/api/%@/%@/subscribe", [Kriya server_url], APP_ID, [Kriya deviceId]];

    NSURL *url = [NSURL URLWithString:urlString];
    ASIFormDataRequest *request = [[[ASIFormDataRequest alloc] initWithURL:url] autorelease];
    [request setPostValue:[[transaction payment] productIdentifier] forKey:@"product"];
    [request setPostValue:receiptEncoded forKey:@"receipt"];
    [request setPostValue:[Kriya deviceModelString] forKey:@"model"];
    [request setPostValue:[Kriya deviceiOSString] forKey:@"ios"];
    [request setPostValue:[appDelegate version] forKey:@"v"];

    [request setDidFinishSelector:@selector(subscribeWithTransactionFinished:)];
    [request setDidFailSelector:@selector(subscribeWithTransactionFailed:)];
    [request setDelegate:self];

    [request startAsynchronous];

}

Auparavant, mon code essayait d'appeler finishTransaction seulement après que mon serveur ait vérifié le reçu, mais à ce moment-là, la transaction avait déjà été perdue d'une manière ou d'une autre. finishTransaction dès que possible.

un autre problème que l'on peut rencontrer est le fait que lorsque l'application est dans le bac à sable, elle appelle l'url de vérification de l'appstore du bac à sable, mais lorsqu'elle est en révision, elle est en quelque sorte entre les deux mondes. j'ai donc dû changer le code de mon serveur comme ceci :

APPLE_SHARED_PASS = "83f1ec5e7d864e89beef4d2402091cd0" #you can get this in iTunes Connect
APPLE_RECEIPT_VERIFY_URL_SANDBOX    = "https://sandbox.itunes.apple.com/verifyReceipt"
APPLE_RECEIPT_VERIFY_URL_PRODUCTION = "https://buy.itunes.apple.com/verifyReceipt"

  def self.verify_receipt_for(b64_receipt, receipt_verify_url)
    json_resp = nil
    url = URI.parse(receipt_verify_url)
    http = Net::HTTP.new(url.host, url.port)
    http.use_ssl = true
    http.verify_mode = OpenSSL::SSL::VERIFY_NONE
    json_request = {'receipt-data' => b64_receipt, 'password' => APPLE_SHARED_PASS}.to_json
    resp, resp_body = http.post(url.path, json_request.to_s, {'Content-Type' => 'application/x-www-form-urlencoded'})
    if resp.code == '200'
      json_resp = JSON.parse(resp_body)
    end
    json_resp
end

def self.verify_receipt(b64_receipt)
    json_resp = Subscription.verify_receipt_for(b64_receipt, APPLE_RECEIPT_VERIFY_URL_PRODUCTION)
    if json_resp!=nil
      if json_resp.kind_of? Hash
        if json_resp['status']==21007 
          #try the sandbox then
          json_resp = Subscription.verify_receipt_for(b64_receipt, APPLE_RECEIPT_VERIFY_URL_SANDBOX)
        end
      end
    end
    json_resp
end

En gros, on vérifie toujours avec l'URL de production, mais s'il renvoie le code 21007, cela signifie qu'un reçu de sandbox a été envoyé à l'URL de production et il suffit alors de réessayer avec l'URL de sandbox. De cette façon, votre application fonctionne de la même manière en mode sandbox et en mode production.

et enfin, Apple m'a demandé d'ajouter un bouton RESTORE à côté des boutons d'abonnement, pour gérer le cas de plusieurs appareils appartenant à un même utilisateur. ce bouton appelle alors [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; et l'application sera livrée avec les transactions restaurées (s'il y en a).

De plus, il arrive parfois que les comptes d'utilisateur de test soient contaminés et que les choses cessent de fonctionner. Vous pouvez alors obtenir un message "Can not connect to iTunes store" lors de l'inscription. il est utile de créer un nouvel utilisateur de test.

voici le reste du code correspondant :

- (void) restoreTransaction: (SKPaymentTransaction *)transaction
{
    [self recordTransaction: transaction];
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; 
}

- (void) failedTransaction: (SKPaymentTransaction *)transaction
{
    if (transaction.error.code == SKErrorPaymentCancelled)
    {
        //present error to user here 
    }
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];    

}

je vous souhaite une bonne expérience de programmation InAppPurchase :-)

10voto

lxt Points 22990

Pour déterminer si un utilisateur dispose d'un abonnement valide, vous devez soit a) valider le reçu existant comme décrit dans le document dont vous avez donné le lien, soit b) demander à l'utilisateur de racheter l'abonnement et obtenir une réponse d'Apple.

Cette dernière solution ne nécessite aucune interaction côté serveur de votre part, mais elle est erronée et risque de vous faire rejeter, car vous devrez demander à l'utilisateur de "racheter" votre produit chaque fois que vous voudrez vérifier son abonnement.

Ainsi, la seule option est - comme Apple le recommande - d'aller en magasin puis de vérifier le reçu du magasin.

Maintenant, je suppose en théorie vous pourriez enregistrer le reçu du magasin sur l'appareil, et le vérifier de cette façon. Cependant, je pense qu'il faudrait être fou pour faire cela, car le nouveau système de vérification nécessite un secret partagé qu'il faudrait intégrer à l'application elle-même (une très mauvaise idée).

En d'autres termes, la réponse à votre question "puis-je travailler uniquement avec un client iOS" est "techniquement oui", mais il serait très déconseillé de le faire en raison d'un certain nombre de problèmes de sécurité. Heureusement, l'architecture côté serveur que vous devez mettre en place est très simple : il suffit de relier les reçus iTunes aux UDID des appareils, et de créer une API simple pour communiquer avec eux. Si vous n'y arrivez pas, je suis sûr que, très bientôt, les assistants d'achat in app existants, comme Urban Airship, ajouteront des abonnements à renouvellement automatique à leurs produits.

L'association de l'UDID et du reçu fonctionne bien car lorsque l'utilisateur effectue un achat sur un autre appareil, Apple rétablit automatiquement ses achats précédents. Vous pouvez donc enregistrer à nouveau le reçu, cette fois lié à un nouvel UDID.

2voto

Adam Libby Points 21

Peut-être que les serveurs d'achat de sandbox auto-renouvelables sont en panne ? Les achats d'articles de bac à sable consommables/non consommables/abonnements fonctionnent, mais l'achat auto-renouvelable renvoie cette erreur :

Erreur Domain=SKErrorDomain Code=0 "Impossible de se connecter à l'iTunes Store". UserInfo=0x15b600 {NSLocalizedDescription=Canot connect to iTunes Store}

0voto

Mr Q.C. Points 385

Il n'est pas nécessaire de le stocker sur le serveur. vous pouvez le vérifier localement sur le client. nous codons actuellement un script auto renouvelable.

mais actuellement, il semble que les serveurs soient en panne ou quelque chose comme ça. la vérification avec les serveurs d'apple ne fonctionne pas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X