Il existe de nombreux exemples sur la manière de tester la validation des reçus d'achat dans l'application à l'aide d'un compte de testeur de sandbox.
Mais comment est le reçu pour l'application payante elle-même? Comment pouvons-nous obtenir le reçu d'application dans l'environnement de développement?
Il y a deux choses que je veux faire:
Pour empêcher la copie illégale de notre application en cours d'exécution par l'utilisateur qui n'a pas acheté l'application. Comme j'ai vu une application qui a détecté que le compte iTune était connecté, elle ne la possédait pas (elle avertit l'utilisateur qui ne la possédait pas, mais ne réussit pas à empêcher l'utilisateur de continuer à utiliser l'application)
Envoyez le reçu d'achat de l'application à notre serveur. Nous voulons savoir quand ils achètent notre application, quelle version de l'application ils ont apportée.
La plupart des réponses peuvent être trouvées ici dans la documentation Apple. Mais il y a des lacunes et le code objective-c utilise des méthodes obsolètes.
Ce code Swift 3 montre comment obtenir le reçu d’application et l’envoyer à l’app store pour validation. Avant de sauvegarder les données souhaitées, vous devez absolument valider le reçu de l’application avec App Store. L'avantage de demander à l'app store de valider est qu'il répond avec des données que vous pouvez facilement sérialiser en JSON et extraire ensuite les valeurs des clés souhaitées. Aucune cryptographie requise.
Comme Apple le décrit dans cette documentation, le flux préféré est le suivant ...
device -> your trusted server -> app store -> your trusted server -> device
Lorsque l'App Store retourne sur votre serveur, en cas de succès, vous pourrez sérialiser et extraire les données dont vous avez besoin et les enregistrer à votre guise. Voir le JSON ci-dessous. Et vous pouvez envoyer le résultat et tout ce que vous voulez à l'application.
Dans validateAppReceipt()
ci-dessous, pour en faire un exemple de travail, il utilise simplement ce flux ...
device -> app store -> device
Pour que cela fonctionne avec votre serveur, il suffit de changer validationURLString
pour qu'il pointe vers votre serveur et ajoutez ce que vous souhaitez à requestDictionary
.
Pour tester cela en développement, vous devez:
Voici le code. Le chemin heureux coule très bien. Les erreurs et les points d'échec sont simplement imprimés ou commentés. Traitez avec ceux que vous avez besoin.
Cette partie récupère le reçu de l'application. Si ce n'est pas là (ce qui se passera lorsque vous testez), il demande à l'App Store pour se rafraîchir.
let receiptURL = Bundle.main.appStoreReceiptURL
func getAppReceipt() {
guard let receiptURL = receiptURL else { /* receiptURL is nil, it would be very weird to end up here */ return }
do {
let receipt = try Data(contentsOf: receiptURL)
validateAppReceipt(receipt)
} catch {
// there is no app receipt, don't panic, ask Apple to refresh it
let appReceiptRefreshRequest = SKReceiptRefreshRequest(receiptProperties: nil)
appReceiptRefreshRequest.delegate = self
appReceiptRefreshRequest.start()
// If all goes well control will land in the requestDidFinish() delegate method.
// If something bad happens control will land in didFailWithError.
}
}
func requestDidFinish(_ request: SKRequest) {
// a fresh receipt should now be present at the url
do {
let receipt = try Data(contentsOf: receiptURL!) //force unwrap is safe here, control can't land here if receiptURL is nil
validateAppReceipt(receipt)
} catch {
// still no receipt, possible but unlikely to occur since this is the "success" delegate method
}
}
func request(_ request: SKRequest, didFailWithError error: Error) {
print("app receipt refresh request did fail with error: \(error)")
// for some clues see here: https://samritchie.net/2015/01/29/the-operation-couldnt-be-completed-sserrordomain-error-100/
}
Cette partie valide le reçu de l'application. Ce n'est pas une validation locale. Voir les notes 1 et 2 dans les commentaires.
func validateAppReceipt(_ receipt: Data) {
/* Note 1: This is not local validation, the app receipt is sent to the app store for validation as explained here:
https://developer.Apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html#//Apple_ref/doc/uid/TP40010573-CH104-SW1
Note 2: Refer to the url above. For good reasons Apple recommends receipt validation follow this flow:
device -> your trusted server -> app store -> your trusted server -> device
In order to be a working example the validation url in this code simply points to the app store's sandbox servers.
Depending on how you set up the request on your server you may be able to simply change the
structure of requestDictionary and the contents of validationURLString.
*/
let base64encodedReceipt = receipt.base64EncodedString()
let requestDictionary = ["receipt-data":base64encodedReceipt]
guard JSONSerialization.isValidJSONObject(requestDictionary) else { print("requestDictionary is not valid JSON"); return }
do {
let requestData = try JSONSerialization.data(withJSONObject: requestDictionary)
let validationURLString = "https://sandbox.iTunes.Apple.com/verifyReceipt" // this works but as noted above it's best to use your own trusted server
guard let validationURL = URL(string: validationURLString) else { print("the validation url could not be created, unlikely error"); return }
let session = URLSession(configuration: URLSessionConfiguration.default)
var request = URLRequest(url: validationURL)
request.httpMethod = "POST"
request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringCacheData
let task = session.uploadTask(with: request, from: requestData) { (data, response, error) in
if let data = data , error == nil {
do {
let appReceiptJSON = try JSONSerialization.jsonObject(with: data)
print("success. here is the json representation of the app receipt: \(appReceiptJSON)")
// if you are using your server this will be a json representation of whatever your server provided
} catch let error as NSError {
print("json serialization failed with error: \(error)")
}
} else {
print("the upload task returned an error: \(error)")
}
}
task.resume()
} catch let error as NSError {
print("json serialization failed with error: \(error)")
}
}
Vous devriez vous retrouver avec quelque chose comme ça. Dans votre cas, c’est ce que vous utiliseriez sur votre serveur.
{
environment = Sandbox;
receipt = {
"adam_id" = 0;
"app_item_id" = 0;
"application_version" = "0"; // for me this was showing the build number rather than the app version, at least in testing
"bundle_id" = "com.yourdomain.yourappname"; // your app's actual bundle id
"download_id" = 0;
"in_app" = (
);
"original_application_version" = "1.0"; // this will always return 1.0 when testing, the real thing in production.
"original_purchase_date" = "2013-08-01 07:00:00 Etc/GMT";
"original_purchase_date_ms" = 1375340400000;
"original_purchase_date_pst" = "2013-08-01 00:00:00 America/Los_Angeles";
"receipt_creation_date" = "2016-09-21 18:46:39 Etc/GMT";
"receipt_creation_date_ms" = 1474483599000;
"receipt_creation_date_pst" = "2016-09-21 11:46:39 America/Los_Angeles";
"receipt_type" = ProductionSandbox;
"request_date" = "2016-09-22 18:37:41 Etc/GMT";
"request_date_ms" = 1474569461861;
"request_date_pst" = "2016-09-22 11:37:41 America/Los_Angeles";
"version_external_identifier" = 0;
};
status = 0;
}
Je suppose que vous savez comment effectuer l'achat InApp.
Nous devons valider un reçu une fois la transaction terminée.
- (void)completeTransaction:(SKPaymentTransaction *)transaction
{
NSLog(@"completeTransaction...");
[appDelegate setLoadingText:VALIDATING_RECEIPT_MSG];
[self validateReceiptForTransaction];
}
Une fois que le produit a été acheté avec succès, il doit être validé. Le serveur le fait pour nous, nous juste besoin de transmettre les données de réception renvoyées par le serveur Apple.
-(void)validateReceiptForTransaction
{
/* Load the receipt from the app bundle. */
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
if (!receipt) {
/* No local receipt -- handle the error. */
}
/* ... Send the receipt data to your server ... */
NSData *receipt; // Sent to the server by the device
/* Create the JSON object that describes the request */
NSError *error;
NSDictionary *requestContents = @{
@"receipt-data": [receipt base64EncodedStringWithOptions:0]
};
NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents
options:0
error:&error];
if (!requestData) {
/* ... Handle error ... */
}
// Create a POST request with the receipt data.
NSURL *storeURL = [NSURL URLWithString:@"https://buy.iTunes.Apple.com/verifyReceipt"];
NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
[storeRequest setHTTPMethod:@"POST"];
[storeRequest setHTTPBody:requestData];
/* Make a connection to the iTunes Store on a background queue. */
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:storeRequest queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError) {
/* ... Handle error ... */
} else {
NSError *error;
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (!jsonResponse) {
/* ... Handle error ...*/
}
/* ... Send a response back to the device ... */
}
}];
}
La charge utile de la réponse est un objet JSON contenant les clés et les valeurs suivantes:
statut:
Soit 0 si le reçu est valide, soit l’un des codes d’erreur mentionnés ci-dessous:
Pour les reçus de transaction de style iOS 6, le code de statut reflète le statut du reçu de la transaction spécifique.
Pour les reçus d'applications de style iOS 7, le code d'état reflète l'état du reçu d'application dans son ensemble. Par exemple, si vous envoyez un accusé de réception d'application valide contenant un abonnement arrivé à expiration, la réponse est 0 car le reçu dans son ensemble est valide.
le reçu:
Une représentation JSON du reçu envoyé pour vérification.
Rappelles toi:
Nous obtiendrons le code d’arrêt 21007 pour une validation de réception réussie, Dans l’environnement Sandbox.
Dans l'environnement de test, utilisez https://sandbox.iTunes.Apple.com/verifyReceipt comme URL. Dans Production, utilisez https://buy.iTunes.Apple.com/verifyReceipt comme URL
EDIT 1
transactionReceipt
est obsolète: d'abord déconseillé dans iOS 7.0
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) {
// iOS 6.1 or earlier.
// Use SKPaymentTransaction's transactionReceipt.
} else {
// iOS 7 or later.
NSURL *receiptFileURL = nil;
NSBundle *bundle = [NSBundle mainBundle];
if ([bundle respondsToSelector:@selector(appStoreReceiptURL)]) {
// Get the transaction receipt file path location in the app bundle.
receiptFileURL = [bundle appStoreReceiptURL];
// Read in the contents of the transaction file.
} else {
/* Fall back to deprecated transaction receipt,
which is still available in iOS 7.
Use SKPaymentTransaction's transactionReceipt. */
}
}
si vous souhaitez tester l'application in-app, validez les réceptions dans l'environnement en sandbox et tenez compte du fait que les intervalles de renouvellement dans le sandbox sont
1 semaine 3 minutes 1 mois 5 minutes 2 mois 10 minutes 3 mois 15 minutes 6 mois 30 minutes 1 an 1 heure
Le meilleur moyen de valider la réception est de communiquer votre serveur avec le serveur Apple pour validation.