web-dev-qa-db-fra.com

Vérifier le reçu pour l'achat via l'application

Je joue avec les achats intégrés depuis quelques jours, tout fonctionne bien jusqu'au moment où j'essaie de valider le reçu avec l'App Store, car je retrouve constamment un statut non valide.

Je transmets les données de réception à mon PHP puis je les transfère à l'App Store et une fois que j'aurai une réponse valide, j'ai l'intention d'ajouter les données de réception à ma base de données.

Le guide de programmation du kit de magasin et les références de classe sont moins qu'utiles pour ce domaine particulier car ils ne vous donnent pas vraiment d'exemple, j'en ai trouvé un utile article qui m'a aidé un peu mais quelque chose ne va toujours pas.

Fondamentalement, je me demande si quelqu'un qui travaille sur la validation des reçus serait disposé à partager son code car je ne vais nulle part.

Merci

40
Andy

Tout d'abord, il y a quelques fautes de frappe dans le code affiché. Essaye ça. (Avertissement: Refactoring et al. Est laissé comme un exercice pour le lectorat!)

- (BOOL)verifyReceipt:(SKPaymentTransaction *)transaction {
    NSString *jsonObjectString = [self encode:(uint8_t *)transaction.transactionReceipt.bytes length:transaction.transactionReceipt.length];      
    NSString *completeString = [NSString stringWithFormat:@"http://url-for-your-php?receipt=%@", jsonObjectString];               
    NSURL *urlForValidation = [NSURL URLWithString:completeString];       
    NSMutableURLRequest *validationRequest = [[NSMutableURLRequest alloc] initWithURL:urlForValidation];              
    [validationRequest setHTTPMethod:@"GET"];         
    NSData *responseData = [NSURLConnection sendSynchronousRequest:validationRequest returningResponse:nil error:nil];  
    [validationRequest release];
    NSString *responseString = [[NSString alloc] initWithData:responseData encoding: NSUTF8StringEncoding];
    NSInteger response = [responseString integerValue];
    [responseString release];
    return (response == 0);
}

- (NSString *)encode:(const uint8_t *)input length:(NSInteger)length {
    static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

    NSMutableData *data = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
    uint8_t *output = (uint8_t *)data.mutableBytes;

    for (NSInteger i = 0; i < length; i += 3) {
        NSInteger value = 0;
        for (NSInteger j = i; j < (i + 3); j++) {
            value <<= 8;

            if (j < length) {
                value |= (0xFF & input[j]);
            }
        }

        NSInteger index = (i / 3) * 4;
        output[index + 0] =                    table[(value >> 18) & 0x3F];
        output[index + 1] =                    table[(value >> 12) & 0x3F];
        output[index + 2] = (i + 1) < length ? table[(value >> 6)  & 0x3F] : '=';
        output[index + 3] = (i + 2) < length ? table[(value >> 0)  & 0x3F] : '=';
    }

    return [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease];
}

Vous pouvez créer ces méthodes internes sur la classe qui gère vos messages SKPaymentTransactionObserver:

@interface YourStoreClass (Internal)
- (BOOL)verifyReceipt:(SKPaymentTransaction *)transaction;
- (NSString *)encode:(const uint8_t *)input length:(NSInteger)length;
@end

Remarque: vous pourriez utiliser quelque chose comme libcrypto pour gérer l'encodage base64, mais vous examinez ensuite les restrictions d'exportation et les étapes supplémentaires au moment de l'approbation de l'application. Mais je m'égare ...

Ensuite, partout où vous avez l'intention de lancer l'enregistrement de la transaction sur votre serveur distant, appelez verifyReceipt: avec votre transaction et assurez-vous qu'elle revient positive.

Pendant ce temps, sur votre serveur, voici quelques super-dépouillé PHP pour gérer les choses:

$receipt = json_encode(array("receipt-data" => $_GET["receipt"]));
// NOTE: use "buy" vs "sandbox" in production.
$url = "https://sandbox.iTunes.Apple.com/verifyReceipt";
$response_json = call-your-http-post-here($url, $receipt);
$response = json_decode($response_json);

// Save the data here!

echo $response->status;

call-your-http-post-here est votre mécanisme de publication HTTP préféré. (cURL est un choix possible. YMMV. PHP.net a le scoop!)

Une chose qui m'inquiète un peu est la longueur de la charge utile dans l'URL allant de l'application au serveur (via GET). J'oublie s'il y a un problème de longueur dans les RFC. Peut-être que c'est OK, ou peut-être que c'est spécifique au serveur. (Lecteurs: Avis de bienvenue sur cette partie!)

Il peut également être difficile de faire de cette demande une demande synchrone. Vous pouvez le publier de manière asynchrone et mettre en place l'ol 'IActivityIndicatorView ou un autre HUD. Exemple: cet appel initWithData: encoding: prend beaucoup de temps pour moi. Quelques secondes, ce qui est une petite éternité dans l'iPhone (ou n'importe où ailleurs en ligne, d'ailleurs). Il peut être souhaitable de montrer une sorte d'indicateur de progrès indéterminé.

70
Joe D'Andrea

Le code source complet, ainsi qu'un exemple hébergé d'une implémentation PHP est disponible sur: https://github.com/chrismaddern/iOS-Receipt-Validator-PHP

J'espère que cela vous aide!

7
Chris Maddern

Pour tous ceux qui se demandent comment gérer les erreurs de connexion ou de vérification qui peuvent se produire lorsque vous utilisez le modèle de serveur d'achat intégré. La validation du reçu garantit que la transaction est complète et réussie. Vous ne voulez pas le faire depuis l'iPhone car vous ne pouvez pas vraiment faire confiance au téléphone de l'utilisateur.

  1. L'utilisateur lance un achat in-app
  2. Une fois terminée, l'application demande à votre serveur de valider
  3. Vous validez le reçu auprès d'Apple: s'il est valide, vous pouvez effectuer n'importe quelle action liée à l'achat (déverrouiller/livrer du contenu, enregistrer l'abonnement ...)
  4. L'application supprime la transaction de la file d'attente (finishTransaction)

Si le serveur est en panne, vous ne devez pas terminer la transaction, mais afficher un "message d'indisponibilité" à l'utilisateur.

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

sera rappelé plus tard.

Mais si vous découvrez qu'un reçu n'est pas valide, vous devez terminer la transaction associée. Sinon, vous pouvez avoir des transactions supplémentaires vivant à jamais dans la file d'attente des transactions. Cela signifie qu'à chaque exécution de votre application, paymentQueue: updatedTransaction: sera appelé une fois par transaction ...

Dans mes applications, la validation du reçu se fait via un service Web, renvoyant un code d'erreur en cas de reçu invalide. C'est pourquoi un serveur externe est nécessaire. Si un utilisateur parvient d'une manière ou d'une autre à ignorer la validation de la réception (en simulant la réponse "succès" du service Web), il ne pourra pas déverrouiller la fonctionnalité de contenu/accès car le serveur n'a aucune trace de l'achat.

3
leviathan

Je suis surpris de ne pas avoir trouvé le tutoriel de Ray Wenderlich ici - je viens de me sauver la vie. Passe par la validation des reçus sans serveur (ce n'est pas une solution recommandée mais lourdement faite de toute façon).

http://www.raywenderlich.com/23266/in-app-purchases-in-ios-6-tutorial-consumables-and-receipt-validation

1
capikaw

Après avoir combattu avec cela pendant un certain temps, j'ai finalement trouvé une liste de codes d'état dans la documentation d'Apple, y compris le 21002 redouté (qui est "Les données dans le reçu -la propriété de données a été mal formée. "). Bien que j'aie vu des rapports sur d'autres codes d'état non inclus dans cette liste, je n'en ai pour l'instant pas vu au-delà de ce que Apple a documenté. Notez que ces codes ne sont valables que pour les abonnements à renouvellement automatique , pas d'autres types d'achats intégrés (comme le dit le document).

Le document en question se trouve ici .

1
t-dub

Vous devez envoyer le reçu sous forme de fichier à votre serveur PHP. Dans votre côté PHP, vous pouvez utiliser ce script pour valider:

<?php

$path = 'receipt'; // $_FILE['receipt-data']["tmp_name"];
$receipt = file_get_contents($path);

$json['receipt-data'] = base64_encode($receipt);

$post = json_encode($json);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,"https://buy.iTunes.Apple.com/verifyReceipt");
curl_setopt($ch, CURLOPT_POST,1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
$result=curl_exec ($ch);

curl_close ($ch);

?>

https://Gist.github.com/eduardo22i/9adc2191f71ea612a7d071342e1e4a6f

1
Eduardo Irias

Juste pour l'ouvrir à nouveau et ajouter mes 2 cents en échange de la flagellation de ces formulaires pour information.

Je viens de configurer un service IAP dans mon application et j'ai rencontré la même erreur 21002. J'ai trouvé que le 21002 se produit lorsque la publication sur votre PHP est vide (donc la requête HTTP à l'App Store est vide) ou mal formatée. Pour que la nôtre fonctionne, côté iPhone, nous définissez les données de publication dans une NSString au format base64, puis envoyez-les à notre serveur en tant que requête HTTP.

Ensuite, sur notre serveur, nous l'avons collé dans un tableau et json-ed. Comme ça:

$receipt = json_encode(array("receipt-data"=>$_POST['receipt-data']));

Vous remarquerez que c'est la même chose que ci-dessus, sauf que nous utilisons un POST au lieu d'un GET. Préférence personnelle vraiment.

Nous avons ensuite utilisé CURL pour le publier dans le bac à sable et utilisé json_decode sur la réponse.

0
Ginamin

Ceci est une excellente bibliothèque qui fait ce dont tout le monde aura besoin à ce sujet:

https://github.com/aporat/store-receipt-validator

Il va au-delà et valide:

  • iTunes
  • Play Store
  • Amazon App Store

J'espère que cela aide quelqu'un de plus m'aide.

0
Lucas