recibo recibir para notificaciones instante gratis facturar correos correo como app activar ios iphone in-app-purchase storekit

recibir - Una solución completa para validar LOCALMENTE los recibos en la aplicación y los recibos agrupados en iOS 7



recibir correos al instante en iphone (3)

Aquí hay un tutorial de cómo resolví esto en mi librería de compras en la aplicación RMStore . Explicaré cómo verificar una transacción, lo que incluye verificar el recibo completo.

De un vistazo

Obtenga el recibo y verifique la transacción. Si falla, actualice el recibo y vuelva a intentarlo. Esto hace que el proceso de verificación sea asincrónico y refrescante, el recibo es asincrónico.

Desde RMStoreAppReceiptVerifier :

RMAppReceipt *receipt = [RMAppReceipt bundleReceipt]; const BOOL verified = [self verifyTransaction:transaction inReceipt:receipt success:successBlock failure:nil]; // failureBlock is nil intentionally. See below. if (verified) return; // Apple recommends to refresh the receipt if validation fails on iOS [[RMStore defaultStore] refreshReceiptOnSuccess:^{ RMAppReceipt *receipt = [RMAppReceipt bundleReceipt]; [self verifyTransaction:transaction inReceipt:receipt success:successBlock failure:failureBlock]; } failure:^(NSError *error) { [self failWithBlock:failureBlock error:error]; }];

Obteniendo los datos del recibo

El recibo está en [[NSBundle mainBundle] appStoreReceiptURL] y en realidad es un contenedor PCKS7. Apego a la criptografía, así que usé OpenSSL para abrir este contenedor. Otros aparentemente lo han hecho puramente con marcos de sistemas .

Agregar OpenSSL a su proyecto no es trivial. El wiki de RMStore debería ayudar.

Si opta por usar OpenSSL para abrir el contenedor PKCS7, su código podría verse así. Desde RMAppReceipt :

+ (NSData*)dataFromPKCS7Path:(NSString*)path { const char *cpath = [[path stringByStandardizingPath] fileSystemRepresentation]; FILE *fp = fopen(cpath, "rb"); if (!fp) return nil; PKCS7 *p7 = d2i_PKCS7_fp(fp, NULL); fclose(fp); if (!p7) return nil; NSData *data; NSURL *certificateURL = [[NSBundle mainBundle] URLForResource:@"AppleIncRootCertificate" withExtension:@"cer"]; NSData *certificateData = [NSData dataWithContentsOfURL:certificateURL]; if ([self verifyPKCS7:p7 withCertificateData:certificateData]) { struct pkcs7_st *contents = p7->d.sign->contents; if (PKCS7_type_is_data(contents)) { ASN1_OCTET_STRING *octets = contents->d.data; data = [NSData dataWithBytes:octets->data length:octets->length]; } } PKCS7_free(p7); return data; }

Vamos a entrar en los detalles de la verificación más tarde.

Obtener los campos de recibo

El recibo se expresa en formato ASN1. Contiene información general, algunos campos para fines de verificación (veremos más adelante) e información específica de cada compra en la aplicación aplicable.

Nuevamente, OpenSSL viene al rescate cuando se trata de leer ASN1. Desde RMAppReceipt , usando algunos métodos de ayuda:

NSMutableArray *purchases = [NSMutableArray array]; [RMAppReceipt enumerateASN1Attributes:asn1Data.bytes length:asn1Data.length usingBlock:^(NSData *data, int type) { const uint8_t *s = data.bytes; const NSUInteger length = data.length; switch (type) { case RMAppReceiptASN1TypeBundleIdentifier: _bundleIdentifierData = data; _bundleIdentifier = RMASN1ReadUTF8String(&s, length); break; case RMAppReceiptASN1TypeAppVersion: _appVersion = RMASN1ReadUTF8String(&s, length); break; case RMAppReceiptASN1TypeOpaqueValue: _opaqueValue = data; break; case RMAppReceiptASN1TypeHash: _hash = data; break; case RMAppReceiptASN1TypeInAppPurchaseReceipt: { RMAppReceiptIAP *purchase = [[RMAppReceiptIAP alloc] initWithASN1Data:data]; [purchases addObject:purchase]; break; } case RMAppReceiptASN1TypeOriginalAppVersion: _originalAppVersion = RMASN1ReadUTF8String(&s, length); break; case RMAppReceiptASN1TypeExpirationDate: { NSString *string = RMASN1ReadIA5SString(&s, length); _expirationDate = [RMAppReceipt formatRFC3339String:string]; break; } } }]; _inAppPurchases = purchases;

Obtener las compras integradas en la aplicación

Cada compra en la aplicación también está en ASN1. El análisis es muy similar al análisis de la información general del recibo.

Desde RMAppReceipt , utilizando los mismos métodos de ayuda:

[RMAppReceipt enumerateASN1Attributes:asn1Data.bytes length:asn1Data.length usingBlock:^(NSData *data, int type) { const uint8_t *p = data.bytes; const NSUInteger length = data.length; switch (type) { case RMAppReceiptASN1TypeQuantity: _quantity = RMASN1ReadInteger(&p, length); break; case RMAppReceiptASN1TypeProductIdentifier: _productIdentifier = RMASN1ReadUTF8String(&p, length); break; case RMAppReceiptASN1TypeTransactionIdentifier: _transactionIdentifier = RMASN1ReadUTF8String(&p, length); break; case RMAppReceiptASN1TypePurchaseDate: { NSString *string = RMASN1ReadIA5SString(&p, length); _purchaseDate = [RMAppReceipt formatRFC3339String:string]; break; } case RMAppReceiptASN1TypeOriginalTransactionIdentifier: _originalTransactionIdentifier = RMASN1ReadUTF8String(&p, length); break; case RMAppReceiptASN1TypeOriginalPurchaseDate: { NSString *string = RMASN1ReadIA5SString(&p, length); _originalPurchaseDate = [RMAppReceipt formatRFC3339String:string]; break; } case RMAppReceiptASN1TypeSubscriptionExpirationDate: { NSString *string = RMASN1ReadIA5SString(&p, length); _subscriptionExpirationDate = [RMAppReceipt formatRFC3339String:string]; break; } case RMAppReceiptASN1TypeWebOrderLineItemID: _webOrderLineItemID = RMASN1ReadInteger(&p, length); break; case RMAppReceiptASN1TypeCancellationDate: { NSString *string = RMASN1ReadIA5SString(&p, length); _cancellationDate = [RMAppReceipt formatRFC3339String:string]; break; } } }];

Cabe señalar que ciertas compras integradas en la aplicación, como los consumibles y las suscripciones no renovables, aparecerán una sola vez en el recibo. Debes verificar esto justo después de la compra (de nuevo, RMStore te ayuda con esto).

Verificación de un vistazo

Ahora tenemos todos los campos del recibo y todas sus compras en la aplicación. Primero verificamos el recibo y luego simplemente verificamos si el recibo contiene el producto de la transacción.

A continuación se muestra el método que llamamos al principio. Desde RMStoreAppReceiptVerificator :

- (BOOL)verifyTransaction:(SKPaymentTransaction*)transaction inReceipt:(RMAppReceipt*)receipt success:(void (^)())successBlock failure:(void (^)(NSError *error))failureBlock { const BOOL receiptVerified = [self verifyAppReceipt:receipt]; if (!receiptVerified) { [self failWithBlock:failureBlock message:NSLocalizedString(@"The app receipt failed verification", @"")]; return NO; } SKPayment *payment = transaction.payment; const BOOL transactionVerified = [receipt containsInAppPurchaseOfProductIdentifier:payment.productIdentifier]; if (!transactionVerified) { [self failWithBlock:failureBlock message:NSLocalizedString(@"The app receipt doest not contain the given product", @"")]; return NO; } if (successBlock) { successBlock(); } return YES; }

Verificando el recibo

Verificar el recibo en sí se reduce a:

  1. Verificando que el recibo sea válido PKCS7 y ASN1. Ya lo hemos hecho implícitamente.
  2. Verificando que el recibo esté firmado por Apple. Esto se hizo antes de analizar el recibo y se detallará a continuación.
  3. Verificando que el identificador del paquete incluido en el recibo corresponde a su identificador de paquete. Debe codificar el identificador de su paquete, ya que no parece ser muy difícil modificar el paquete de su aplicación y utilizar algún otro recibo.
  4. Verificando que la versión de la aplicación incluida en el recibo corresponda con el identificador de la versión de tu aplicación. Debe codificar la versión de la aplicación, por las mismas razones indicadas anteriormente.
  5. Verifique el hash de recibo para asegurarse de que el recibo corresponda al dispositivo actual.

Los 5 pasos en el código en un nivel alto, de RMStoreAppReceiptVerificator :

- (BOOL)verifyAppReceipt:(RMAppReceipt*)receipt { // Steps 1 & 2 were done while parsing the receipt if (!receipt) return NO; // Step 3 if (![receipt.bundleIdentifier isEqualToString:self.bundleIdentifier]) return NO; // Step 4 if (![receipt.appVersion isEqualToString:self.bundleVersion]) return NO; // Step 5 if (![receipt verifyReceiptHash]) return NO; return YES; }

Analicemos detalladamente los pasos 2 y 5.

Verificar la firma del recibo

Cuando extrajimos los datos, echamos un vistazo a la verificación de la firma del recibo. El recibo está firmado con el certificado raíz de Apple Inc., que se puede descargar desde la Autoridad de certificación raíz de Apple . El siguiente código toma el contenedor PKCS7 y el certificado raíz como datos y verifica si coinciden:

+ (BOOL)verifyPKCS7:(PKCS7*)container withCertificateData:(NSData*)certificateData { // Based on: https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html#//apple_ref/doc/uid/TP40010573-CH1-SW17 static int verified = 1; int result = 0; OpenSSL_add_all_digests(); // Required for PKCS7_verify to work X509_STORE *store = X509_STORE_new(); if (store) { const uint8_t *certificateBytes = (uint8_t *)(certificateData.bytes); X509 *certificate = d2i_X509(NULL, &certificateBytes, (long)certificateData.length); if (certificate) { X509_STORE_add_cert(store, certificate); BIO *payload = BIO_new(BIO_s_mem()); result = PKCS7_verify(container, NULL, store, NULL, payload, 0); BIO_free(payload); X509_free(certificate); } } X509_STORE_free(store); EVP_cleanup(); // Balances OpenSSL_add_all_digests (), per http://www.openssl.org/docs/crypto/OpenSSL_add_all_algorithms.html return result == verified; }

Esto se hizo al principio, antes de que se analizara el recibo.

Verificar el hash del recibo

El hash incluido en el recibo es un SHA1 de la identificación del dispositivo, un valor opaco incluido en el recibo y la identificación del paquete.

Así es como verificarías el hash de recibo en iOS. Desde RMAppReceipt :

- (BOOL)verifyReceiptHash { // TODO: Getting the uuid in Mac is different. See: https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html#//apple_ref/doc/uid/TP40010573-CH1-SW5 NSUUID *uuid = [[UIDevice currentDevice] identifierForVendor]; unsigned char uuidBytes[16]; [uuid getUUIDBytes:uuidBytes]; // Order taken from: https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html#//apple_ref/doc/uid/TP40010573-CH1-SW5 NSMutableData *data = [NSMutableData data]; [data appendBytes:uuidBytes length:sizeof(uuidBytes)]; [data appendData:self.opaqueValue]; [data appendData:self.bundleIdentifierData]; NSMutableData *expectedHash = [NSMutableData dataWithLength:SHA_DIGEST_LENGTH]; SHA1(data.bytes, data.length, expectedHash.mutableBytes); return [expectedHash isEqualToData:self.hash]; }

Y esa es la esencia de eso. Me podría estar perdiendo algo aquí o allá, así que podría volver a este post más adelante. En cualquier caso, recomiendo navegar el código completo para más detalles.

He leído muchos documentos y códigos que, en teoría, validarán un recibo en la aplicación y / o en el paquete.

Dado que mi conocimiento de SSL, certificados, encriptación, etc. es casi nulo, todas las explicaciones que he leído, como esta prometedora , son difíciles de entender.

Dicen que las explicaciones son incompletas porque cada persona tiene que descubrir cómo hacerlo, o los hackers tendrán un trabajo fácil creando una aplicación de cracker que pueda reconocer e identificar patrones y parchear la aplicación. OK, estoy de acuerdo con esto hasta cierto punto. Creo que podrían explicar completamente cómo hacerlo y poner una advertencia diciendo "modificar este método", "modificar este otro método", "ofuscar esta variable", "cambiar el nombre de esto y aquello", etc.

¿Puede un buen alma ser tan amable de explicar cómo validar LOCALMENTE, agrupar recibos y recibos de compras en la aplicación en iOS 7 ya que tengo cinco años (vale, hazlo 3), de arriba a abajo, claramente?

¡¡¡Gracias!!!

Si tienes una versión que trabaja en tus aplicaciones y te preocupa que los hackers vean cómo lo hiciste, simplemente cambia tus métodos confidenciales antes de publicarla aquí. Ofusque cadenas, cambie el orden de las líneas, cambie la forma en que realiza los bucles (desde el uso de para para enumerar el bloque y viceversa) y cosas por el estilo. Obviamente, cada persona que usa el código que se puede publicar aquí, tiene que hacer lo mismo, para no arriesgarse a ser pirateado fácilmente.


Hola Esta es la versión de Swift 3 para la validación del recibo de compra en la aplicación ...

Llame a la función receiptValidation() desde su AppDelegate o desde donde desee hacerlo.

func receiptValidation() { if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL, FileManager.default.fileExists(atPath: appStoreReceiptURL.path) { do { let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped) let receiptString = receiptData.base64EncodedString(options: []) let dict = ["receipt-data" : receiptString, "password" : "**************************"] as [String : Any] do { let jsonData = try JSONSerialization.data(withJSONObject: dict, options: .prettyPrinted) //This Url for original Account //let url : String = "https://buy.itunes.apple.com/verifyReceipt" //This Url for Sandbox Testing Account let url : String = "https://sandbox.itunes.apple.com/verifyReceipt" if let sandboxURL = Foundation.URL(string:url) { var request = URLRequest(url: sandboxURL) request.httpMethod = "POST" request.httpBody = jsonData let session = URLSession(configuration: URLSessionConfiguration.default) let task = session.dataTask(with: request) { data, response, error in if let receivedData = data, let httpResponse = response as? HTTPURLResponse, error == nil, httpResponse.statusCode == 200 { do { if let jsonResponse = try JSONSerialization.jsonObject(with: receivedData, options: JSONSerialization.ReadingOptions.mutableContainers) as? Dictionary<String, AnyObject> { if let expirationDate: NSDate = self.expirationDateFromResponse(jsonResponse: jsonResponse as NSDictionary) { let currentDate = self.getCurrentLocalDateApp() if currentDate > expirationDate as Date { self.downgrade("1") }else{ } } } else { } } catch { } }else { print("Error=/(String(describing: error))") } } task.resume() } else { } } catch { } } catch { } } }

Ahora tenemos otra función obtener una fecha del recibo expirationDateFromResponse() . Esta función estaría en el mismo controlador o en el AppDelegate

func expirationDateFromResponse(jsonResponse: NSDictionary) -> NSDate? { if let receiptInfo: NSArray = jsonResponse["latest_receipt_info"] as? NSArray { let lastReceipt = receiptInfo.lastObject as! NSDictionary let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd HH:mm:ss VV" let expirationDate: NSDate = formatter.date(from: lastReceipt["expires_date"] as! String) as NSDate! formatter.dateStyle = .medium let stringOutput = formatter.string(from: expirationDate as Date) formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" let date = formatter.string(from: expirationDate as Date) print("Date=/(date)") self.iosNextBillingDateEntry(date) UserDefaults.standard.set(stringOutput, forKey: "PLAN_EXP_DATE") return expirationDate } else { return nil } }

Ahora tenemos otra función para obtener la hora de fecha local si es necesario para usted.

func getCurrentLocalDateApp()-> Date { var now = Date() var nowComponents = DateComponents() let calendar = Calendar.current nowComponents.year = (Calendar.current as NSCalendar).component(NSCalendar.Unit.year, from: now) nowComponents.month = (Calendar.current as NSCalendar).component(NSCalendar.Unit.month, from: now) nowComponents.day = (Calendar.current as NSCalendar).component(NSCalendar.Unit.day, from: now) nowComponents.hour = (Calendar.current as NSCalendar).component(NSCalendar.Unit.hour, from: now) nowComponents.minute = (Calendar.current as NSCalendar).component(NSCalendar.Unit.minute, from: now) nowComponents.second = (Calendar.current as NSCalendar).component(NSCalendar.Unit.second, from: now) nowComponents.timeZone = TimeZone(abbreviation: "VV") now = calendar.date(from: nowComponents)! return now }

La contraseña que obtendrás de la tienda Apple Store. https://developer.apple.com abra este enlace haga clic en

  • Account tab
  • Do Sign in
  • Open iTune Connect
  • Open My App
  • Open Feature Tab
  • Open In App Purchase
  • Click at the right side on ''View Shared Secret''
  • At the bottom you will get a secrete key

Copia esa clave y pégala en el campo de contraseña.

Espero que esto ayude a todo aquel que quiera eso en una versión rápida.


Me sorprende que nadie mencionara a Receigen aquí. Es una herramienta que genera automáticamente un código de validación de recibo ofuscado, uno diferente cada vez; es compatible tanto con la GUI como con la operación de la línea de comandos. Muy recomendable.

(No afiliado a Receigen, solo un usuario feliz).

Uso un Rakefile como este para volver a ejecutar automáticamente Receigen (porque debe hacerse en cada cambio de versión) cuando rake receigen :

desc "Regenerate App Store Receipt validation code using Receigen app (which must be already installed)" task :receigen do # TODO: modify these to match your app bundle_id = ''com.example.YourBundleIdentifierHere'' output_file = File.join(__dir__, ''SomeDir/ReceiptValidation.h'') version = PList.get(File.join(__dir__, ''YourProjectFolder/Info.plist''), ''CFBundleVersion'') command = %Q</Applications/Receigen.app/Contents/MacOS/Receigen --identifier #{bundle_id} --version #{version} --os ios --prefix ReceiptValidation --success callblock --failure callblock> puts "#{command} > #{output_file}" data = `#{command}` File.open(output_file, ''w'') { |f| f.write(data) } end module PList def self.get file_name, key if File.read(file_name) =~ %r!<key>#{Regexp.escape(key)}</key>/s*<string>(.*?)</string>! $1.strip else nil end end end