Eine Komplettlösung zur LOKALEN Validierung von In-App-Belegen und zum Bündeln von Belegen unter iOS 7


160

Ich habe viele Dokumente und Codes gelesen, die theoretisch eine In-App- und / oder Bundle-Quittung validieren.

Angesichts der Tatsache, dass mein Wissen über SSL, Zertifikate, Verschlüsselung usw. nahezu Null ist, habe ich alle Erklärungen, die ich gelesen habe, wie diese vielversprechende , als schwer verständlich empfunden.

Sie sagen, dass die Erklärungen unvollständig sind, weil jede Person herausfinden muss, wie es geht, oder die Hacker werden es leicht haben, eine Cracker-App zu erstellen, die Muster erkennen und identifizieren und die Anwendung patchen kann. OK, ich stimme dem bis zu einem gewissen Punkt zu. Ich denke, sie könnten vollständig erklären, wie es geht, und eine Warnung mit den Worten "Diese Methode ändern", "Diese andere Methode ändern", "Diese Variable verschleiern", "Den Namen von diesem und jenem ändern" usw. einfügen.

Kann eine gute Seele da draußen so freundlich sein, zu erklären, wie man Quittungen und In-App-Kaufbelege unter iOS 7 LOKAL validiert, bündelt, wenn ich fünf Jahre alt bin (ok, mach es 3), klar von oben nach unten?

Vielen Dank!!!


Wenn Sie eine Version haben, die an Ihren Apps arbeitet, und Sie befürchten, dass Hacker sehen, wie Sie es gemacht haben, ändern Sie einfach Ihre vertraulichen Methoden, bevor Sie hier veröffentlichen. Verschleiern Sie Zeichenfolgen, ändern Sie die Reihenfolge der Zeilen, ändern Sie die Art und Weise, wie Sie Schleifen ausführen (von for bis zum Blockieren der Aufzählung und umgekehrt) und ähnliches. Natürlich muss jede Person, die den hier veröffentlichten Code verwendet, dasselbe tun, um nicht zu riskieren, leicht gehackt zu werden.


1
Faire Warnung: Wenn Sie dies lokal tun, ist es verdammt viel einfacher, diese Funktion aus Ihrer Anwendung heraus zu patchen.
NinjaLikesCheez

2
OK, ich weiß, aber hier geht es darum, Dinge schwierig zu machen und automatisiertes Knacken / Patchen zu verhindern. Die Frage ist, ob ein Hacker, der Ihre App wirklich knacken möchte, dies tun wird, unabhängig davon, welche Methode Sie verwenden, lokal oder remote. Die Idee ist auch, es bei jeder neuen Version, die Sie veröffentlichen, geringfügig zu ändern, um ein erneutes automatisiertes Patchen zu verhindern.
Ente

4
@NinjaLikesCheez - man kann die Prüfung auch dann ablehnen, wenn die Überprüfung auf einem Server erfolgt.
Ente

14
Entschuldigung, aber das ist keine Entschuldigung. Das einzige, was der Autor tun muss, ist zu sagen, dass Sie den Code nicht so verwenden, wie er ist. Ohne ein Beispiel ist es unmöglich, dies zu verstehen, ohne ein Raketenwissenschaftler zu sein.
Ente

3
Wenn Sie sich nicht um die Implementierung von DRM kümmern möchten, kümmern Sie sich nicht um die lokale Überprüfung. Senden Sie die Quittung einfach direkt von Ihrer App an Apple, und sie senden sie in einem einfach zu analysierenden JSON-Format erneut an Sie zurück. Für Piraten ist es trivial, dies zu knacken, aber wenn Sie nur zu Freemium wechseln und sich nicht für Piraterie interessieren, sind es nur ein paar Zeilen sehr einfachen Codes.
Dan Fabulich

Antworten:


146

Hier ist eine exemplarische Vorgehensweise, wie ich dies in meiner In-App- Kaufbibliothek RMStore gelöst habe . Ich werde erklären, wie eine Transaktion überprüft wird, einschließlich der Überprüfung des gesamten Belegs.

Auf einen Blick

Holen Sie sich die Quittung und überprüfen Sie die Transaktion. Wenn dies fehlschlägt, aktualisieren Sie die Quittung und versuchen Sie es erneut. Dies macht den Überprüfungsprozess asynchron, da das Aktualisieren des Belegs asynchron ist.

Von 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];
}];

Abrufen der Belegdaten

Die Quittung ist in [[NSBundle mainBundle] appStoreReceiptURL]und ist eigentlich ein PCKS7-Container. Ich lutsche an Kryptographie, also habe ich OpenSSL verwendet, um diesen Container zu öffnen. Andere haben es anscheinend nur mit System-Frameworks gemacht .

Das Hinzufügen von OpenSSL zu Ihrem Projekt ist nicht trivial. Das RMStore-Wiki sollte helfen.

Wenn Sie OpenSSL zum Öffnen des PKCS7-Containers verwenden, könnte Ihr Code folgendermaßen aussehen. Von 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;
}

Wir werden später auf die Details der Überprüfung eingehen.

Abrufen der Belegfelder

Die Quittung wird im ASN1-Format ausgedrückt. Es enthält allgemeine Informationen, einige Felder zu Überprüfungszwecken (wir werden später darauf zurückkommen) und spezifische Informationen zu jedem anwendbaren In-App-Kauf.

Auch hier hilft OpenSSL beim Lesen von ASN1. Von RMAppReceipt mit einigen Hilfsmethoden :

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;

In-App-Käufe abrufen

Jeder In-App-Kauf erfolgt ebenfalls in ASN1. Das Parsen ist dem Parsen der allgemeinen Beleginformationen sehr ähnlich.

In RMAppReceipt mit denselben Hilfsmethoden :

[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;
        }
    }
}]; 

Es ist zu beachten, dass bestimmte In-App-Käufe wie Verbrauchsmaterialien und nicht erneuerbare Abonnements nur einmal in der Quittung erscheinen. Sie sollten diese direkt nach dem Kauf überprüfen (auch hier hilft Ihnen RMStore).

Überprüfung auf einen Blick

Jetzt haben wir alle Felder von der Quittung und allen In-App-Käufen erhalten. Zuerst überprüfen wir die Quittung selbst und dann prüfen wir einfach, ob die Quittung das Produkt der Transaktion enthält.

Unten ist die Methode, die wir am Anfang zurückgerufen haben. Von 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;
}

Überprüfung der Quittung

Die Überprüfung der Quittung selbst läuft auf Folgendes hinaus:

  1. Überprüfen, ob die Quittung gültig ist PKCS7 und ASN1. Wir haben dies implizit bereits getan.
  2. Überprüfen, ob die Quittung von Apple signiert ist. Dies wurde vor dem Parsen der Quittung durchgeführt und wird unten detailliert beschrieben.
  3. Überprüfen Sie, ob die in der Quittung enthaltene Bundle-ID Ihrer Bundle-ID entspricht. Sie sollten Ihre Bundle-ID fest codieren, da es nicht sehr schwierig zu sein scheint, Ihr App-Bundle zu ändern und eine andere Quittung zu verwenden.
  4. Überprüfen Sie, ob die in der Quittung enthaltene App-Version Ihrer App-Versionskennung entspricht. Sie sollten die App-Version aus den oben genannten Gründen fest codieren.
  5. Überprüfen Sie den Beleg-Hash, um sicherzustellen, dass der Beleg dem aktuellen Gerät entspricht.

Die 5 Schritte im Code auf hoher Ebene von 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;
}

Lassen Sie uns einen Drilldown in die Schritte 2 und 5 durchführen.

Überprüfung der Quittungssignatur

Als wir die Daten extrahierten, warfen wir einen Blick auf die Bestätigung der Quittungssignatur. Die Quittung ist mit dem Apple Inc.-Stammzertifikat signiert, das von der Apple Root Certificate Authority heruntergeladen werden kann . Der folgende Code verwendet den PKCS7-Container und das Stammzertifikat als Daten und prüft, ob sie übereinstimmen:

+ (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;
}

Dies wurde zu Beginn durchgeführt, bevor die Quittung analysiert wurde.

Überprüfen des Quittungs-Hash

Der in der Quittung enthaltene Hash ist ein SHA1 der Geräte-ID, ein undurchsichtiger Wert in der Quittung und die Bundle-ID.

Auf diese Weise würden Sie den Beleg-Hash unter iOS überprüfen. Von 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];
}

Und das ist der Kern davon. Möglicherweise fehlt mir hier oder da etwas, sodass ich später auf diesen Beitrag zurückkommen kann. In jedem Fall empfehle ich, den vollständigen Code nach weiteren Details zu durchsuchen.


2
Sicherheitsausschluss: Die Verwendung von Open Source Code macht Ihre App anfälliger. Wenn die Sicherheit ein Problem darstellt, sollten Sie RMStore und den obigen Code nur als Leitfaden verwenden.
Hpique

6
Es wäre fantastisch, wenn Sie in Zukunft OpenSSL loswerden und Ihre Bibliothek kompakt machen würden, indem Sie nur System-Frameworks verwenden.
Ente

2
@RubberDuck Siehe github.com/robotmedia/RMStore/issues/16 . Fühlen Sie sich frei, sich einzuschalten oder einen Beitrag zu leisten. :)
Hpique

1
@ RubberDuck Ich hatte bis dahin keine Kenntnisse über OpenSSL. Wer weiß, vielleicht gefällt es dir sogar. : P
hpique

2
Es ist anfällig für einen Mann im mittleren Angriff, bei dem die Anfrage und / oder Antwort abgefangen und geändert werden kann. Beispielsweise könnte die Anfrage an einen Server eines Drittanbieters umgeleitet und eine falsche Antwort zurückgegeben werden, was die App dazu verleitet, zu glauben, dass ein Produkt gekauft wurde, wenn dies nicht der Fall war, und die Funktionalität kostenlos aktiviert.
Jasarien

13

Ich bin überrascht, dass hier niemand Receigen erwähnt hat. Es ist ein Tool, das automatisch einen verschleierten Belegvalidierungscode generiert, der jedes Mal anders ist. Es unterstützt sowohl die GUI als auch den Befehlszeilenbetrieb. Sehr empfehlenswert.

(Nicht mit Receigen verbunden, nur ein glücklicher Benutzer.)

Ich verwende ein solches Rakefile, um Receigen automatisch erneut auszuführen (da dies bei jeder Versionsänderung durchgeführt werden muss), wenn ich Folgendes eingebe 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

1
Für diejenigen, die sich für Receigen interessieren, ist dies eine kostenpflichtige Lösung, die im App Store für 29,99 $ erhältlich ist. Obwohl es seit September 2014 nicht aktualisiert wurde.
DevGansta

Der Mangel an Updates ist zwar sehr alarmierend. Es funktioniert jedoch immer noch; FWIW, ich verwende es in meinen Apps.
Andrey Tarantsov

Überprüfen Sie Ihre App in Instrumenten auf Lecks, mit Receigen bekomme ich sie oft.
der Reverend

Receigen ist die Schneide, aber ja, es ist eine Schande, dass es fallen gelassen wurde.
Fattie

1
Sieht aus wie es noch nicht fallen gelassen wird. Vor drei Wochen aktualisiert!
Oleg Korzhukov

2

Hinweis: Es wird nicht empfohlen, diese Art der Überprüfung auf der Clientseite durchzuführen

Dies ist eine Swift 4- Version zur Validierung des In-App-Kaufbelegs ...

Erstellen Sie eine Aufzählung, um die möglichen Fehler der Belegvalidierung darzustellen

enum ReceiptValidationError: Error {
    case receiptNotFound
    case jsonResponseIsNotValid(description: String)
    case notBought
    case expired
}

Erstellen wir dann die Funktion, die die Quittung validiert. Sie gibt einen Fehler aus, wenn sie nicht validiert werden kann.

func validateReceipt() throws {
    guard let appStoreReceiptURL = Bundle.main.appStoreReceiptURL, FileManager.default.fileExists(atPath: appStoreReceiptURL.path) else {
        throw ReceiptValidationError.receiptNotFound
    }

    let receiptData = try! Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
    let receiptString = receiptData.base64EncodedString()
    let jsonObjectBody = ["receipt-data" : receiptString, "password" : <#String#>]

    #if DEBUG
    let url = URL(string: "https://sandbox.itunes.apple.com/verifyReceipt")!
    #else
    let url = URL(string: "https://buy.itunes.apple.com/verifyReceipt")!
    #endif

    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.httpBody = try! JSONSerialization.data(withJSONObject: jsonObjectBody, options: .prettyPrinted)

    let semaphore = DispatchSemaphore(value: 0)

    var validationError : ReceiptValidationError?

    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        guard let data = data, let httpResponse = response as? HTTPURLResponse, error == nil, httpResponse.statusCode == 200 else {
            validationError = ReceiptValidationError.jsonResponseIsNotValid(description: error?.localizedDescription ?? "")
            semaphore.signal()
            return
        }
        guard let jsonResponse = (try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers)) as? [AnyHashable: Any] else {
            validationError = ReceiptValidationError.jsonResponseIsNotValid(description: "Unable to parse json")
            semaphore.signal()
            return
        }
        guard let expirationDate = self.expirationDate(jsonResponse: jsonResponse, forProductId: <#String#>) else {
            validationError = ReceiptValidationError.notBought
            semaphore.signal()
            return
        }

        let currentDate = Date()
        if currentDate > expirationDate {
            validationError = ReceiptValidationError.expired
        }

        semaphore.signal()
    }
    task.resume()

    semaphore.wait()

    if let validationError = validationError {
        throw validationError
    }
}

Verwenden Sie diese Hilfsfunktion, um das Ablaufdatum eines bestimmten Produkts abzurufen. Die Funktion erhält eine JSON-Antwort und eine Produkt-ID. Die JSON-Antwort kann mehrere Beleginformationen für verschiedene Produkte enthalten, sodass die letzten Informationen für den angegebenen Parameter abgerufen werden.

func expirationDate(jsonResponse: [AnyHashable: Any], forProductId productId :String) -> Date? {
    guard let receiptInfo = (jsonResponse["latest_receipt_info"] as? [[AnyHashable: Any]]) else {
        return nil
    }

    let filteredReceipts = receiptInfo.filter{ return ($0["product_id"] as? String) == productId }

    guard let lastReceipt = filteredReceipts.last else {
        return nil
    }

    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy-MM-dd HH:mm:ss VV"

    if let expiresString = lastReceipt["expires_date"] as? String {
        return formatter.date(from: expiresString)
    }

    return nil
}

Jetzt können Sie diese Funktion aufrufen und die möglichen Fehlerfälle behandeln

do {
    try validateReceipt()
    // The receipt is valid 😌
    print("Receipt is valid")
} catch ReceiptValidationError.receiptNotFound {
    // There is no receipt on the device 😱
} catch ReceiptValidationError.jsonResponseIsNotValid(let description) {
    // unable to parse the json 🤯
    print(description)
} catch ReceiptValidationError.notBought {
    // the subscription hasn't being purchased 😒
} catch ReceiptValidationError.expired {
    // the subscription is expired 😵
} catch {
    print("Unexpected error: \(error).")
}

Sie können ein Passwort aus dem App Store Connect erhalten. https://developer.apple.comÖffnen Sie diesen Link und klicken Sie auf

  • 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

Kopieren Sie diesen Schlüssel und fügen Sie ihn in das Kennwortfeld ein.

Hoffe, dies wird jedem helfen, der das in einer schnellen Version will.


19
Sie sollten niemals die Apple-Validierungs-URL von Ihrem Gerät aus verwenden. Es sollte nur von Ihrem Server verwendet werden. Dies wurde in den WWDC-Sitzungen erwähnt.
Pechar

Was würde passieren, wenn Benutzer die Apps löschen oder nicht lange öffnen? Funktioniert Ihre Berechnung des Ablaufdatums einwandfrei?
karthikeyan

Dann müssen Sie die Validierung auf der Serverseite beibehalten.
Pushpendra

1
Wie @pechar sagte, sollten Sie dies niemals tun. Bitte fügen Sie es oben in Ihre Antwort ein. Siehe WWDC-Sitzung um 36:32 => developer.apple.com/videos/play/wwdc2016/702
cicerocamargo

Ich verstehe nicht, warum es nicht sicher ist, die Belegdaten direkt vom Gerät zu senden. Würde jemand erklären können?
Koh
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.