Một giải pháp hoàn chỉnh để xác thực xác thực một biên lai trong ứng dụng và biên lai bó trên iOS 7


160

Tôi đã đọc rất nhiều tài liệu và mã mà trên lý thuyết sẽ xác nhận một biên nhận trong ứng dụng và / hoặc gói.

Cho rằng kiến ​​thức về SSL, chứng chỉ, mã hóa, v.v. của tôi gần như bằng không, tất cả những lời giải thích tôi đã đọc, như bài viết đầy hứa hẹn này , tôi thấy khó hiểu.

Họ nói rằng những lời giải thích là không đầy đủ bởi vì mọi người phải tìm ra cách thực hiện hoặc tin tặc sẽ có một công việc dễ dàng tạo ra một ứng dụng cracker có thể nhận dạng và xác định các mẫu và vá ứng dụng. OK, tôi đồng ý với điều này đến một điểm nhất định. Tôi nghĩ rằng họ có thể giải thích hoàn toàn cách thực hiện và đưa ra cảnh báo "sửa đổi phương thức này", "sửa đổi phương pháp khác này", "làm xáo trộn biến này", "thay đổi tên của cái này và cái kia", v.v.

Một số linh hồn tốt ngoài kia có thể đủ tử tế để giải thích làm thế nào để xác thực xác thực, biên lai gói và biên lai mua hàng trong ứng dụng trên iOS 7 khi tôi năm tuổi (ok, làm cho nó 3), từ trên xuống dưới, rõ ràng?

Cảm ơn!!!


Nếu bạn có một phiên bản hoạt động trên ứng dụng của mình và mối quan tâm của bạn là tin tặc sẽ thấy bạn đã làm như thế nào, chỉ cần thay đổi các phương pháp nhạy cảm của bạn trước khi xuất bản tại đây. Làm xáo trộn chuỗi, thay đổi thứ tự các dòng, thay đổi cách bạn thực hiện các vòng lặp (từ việc sử dụng để chặn liệt kê và ngược lại) và những thứ tương tự. Rõ ràng, mọi người sử dụng mã có thể được đăng ở đây, đều phải làm điều tương tự, để không có nguy cơ bị hack dễ dàng.


1
Cảnh báo công bằng: thực hiện tại địa phương làm cho việc vá chức năng này ra khỏi ứng dụng của bạn trở nên dễ dàng hơn rất nhiều.
NinjaLikeCheez

2
OK, tôi biết, nhưng vấn đề ở đây là làm những việc khó khăn và ngăn chặn việc bẻ khóa / vá tự động. Câu hỏi là nếu một hacker thực sự muốn bẻ khóa ứng dụng của bạn, anh ấy / cô ấy sẽ làm điều đó, bất kể phương pháp nào bạn sử dụng, cục bộ hoặc từ xa. Ý tưởng cũng là thay đổi nó một chút mỗi phiên bản mới bạn phát hành, để ngăn chặn việc vá tự động một lần nữa.
Vịt

4
@NinjaLikeCheez - người ta có thể NOP kiểm tra ngay cả khi xác minh được thực hiện trên máy chủ.
Vịt

14
xin lỗi, nhưng đây không phải là lý do Điều duy nhất mà tác giả phải làm là nói KHÔNG SỬ DỤNG MÃ NHƯ VẬY. Nếu không có bất kỳ ví dụ nào, không thể hiểu điều này nếu không trở thành một nhà khoa học tên lửa.
Vịt

3
Nếu bạn không muốn thực hiện DRM, đừng bận tâm với xác minh cục bộ. Chỉ cần gửi biên nhận trực tiếp cho Apple từ ứng dụng của bạn và họ sẽ gửi lại cho bạn theo định dạng JSON được phân tích cú pháp dễ dàng. Thật là tầm thường khi những tên cướp biển phá vỡ điều này, nhưng nếu bạn chỉ đang chuyển sang freemium và không quan tâm đến vi phạm bản quyền, thì đó chỉ là một vài dòng mã rất dễ dàng.
Dan Fabulich

Câu trả lời:


146

Đây là hướng dẫn về cách tôi giải quyết vấn đề này trong thư viện mua hàng trong ứng dụng RMStore . Tôi sẽ giải thích cách xác minh một giao dịch, bao gồm xác minh toàn bộ biên lai.

Trong nháy mắt

Nhận biên lai và xác minh giao dịch. Nếu thất bại, hãy làm mới hóa đơn và thử lại. Điều này làm cho quá trình xác minh không đồng bộ khi làm mới hóa đơn không đồng bộ.

Từ 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];
}];

Lấy dữ liệu nhận

Biên lai là trong [[NSBundle mainBundle] appStoreReceiptURL]và thực sự là một container PCKS7. Tôi hút mật mã vì vậy tôi đã sử dụng OpenSSL để mở container này. Những người khác dường như đã làm điều đó hoàn toàn với khung hệ thống .

Thêm OpenSSL vào dự án của bạn không phải là chuyện nhỏ. Các wiki RMStore nên giúp đỡ.

Nếu bạn chọn sử dụng OpenSSL để mở bộ chứa PKCS7, mã của bạn có thể trông như thế này. Từ 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;
}

Chúng ta sẽ đi vào chi tiết xác minh sau.

Lấy các trường nhận

Biên lai được thể hiện dưới định dạng ASN1. Nó chứa thông tin chung, một số trường cho mục đích xác minh (chúng ta sẽ đến đó sau) và thông tin cụ thể của từng giao dịch mua trong ứng dụng.

Một lần nữa, OpenSSL đến cứu nguy khi đọc ASN1. Từ RMAppReceipt , sử dụng một vài phương thức trợ giúp:

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;

Nhận mua hàng trong ứng dụng

Mỗi lần mua trong ứng dụng cũng bằng ASN1. Phân tích cú pháp nó rất giống với phân tích thông tin nhận chung.

Từ RMAppReceipt , sử dụng các phương thức trợ giúp tương tự:

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

Cần lưu ý rằng một số giao dịch mua trong ứng dụng, chẳng hạn như hàng tiêu dùng và đăng ký không thể gia hạn, sẽ chỉ xuất hiện một lần trong biên lai. Bạn nên xác minh những điều này ngay sau khi mua hàng (một lần nữa, RMStore giúp bạn điều này).

Xác minh trong nháy mắt

Bây giờ chúng tôi đã nhận được tất cả các lĩnh vực từ biên lai và tất cả các giao dịch mua trong ứng dụng. Đầu tiên chúng tôi xác minh hóa đơn, và sau đó chúng tôi chỉ cần kiểm tra xem biên nhận có chứa sản phẩm của giao dịch hay không.

Dưới đây là phương pháp mà chúng tôi đã gọi lại từ đầu. Từ 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;
}

Xác nhận hóa đơn

Xác minh hóa đơn tự rút xuống:

  1. Kiểm tra xem biên nhận có hợp lệ PKCS7 và ASN1 không. Chúng tôi đã thực hiện điều này ngầm.
  2. Xác minh rằng biên nhận được ký bởi Apple. Điều này đã được thực hiện trước khi phân tích hóa đơn và sẽ được chi tiết dưới đây.
  3. Kiểm tra xem số nhận dạng gói có trong biên nhận có tương ứng với số nhận dạng gói của bạn không. Bạn nên mã hóa số nhận dạng gói của mình, vì dường như không khó để sửa đổi gói ứng dụng của bạn và sử dụng một số biên lai khác.
  4. Kiểm tra xem phiên bản ứng dụng có trong biên lai có tương ứng với số nhận dạng phiên bản ứng dụng của bạn không. Bạn nên mã hóa phiên bản ứng dụng, vì những lý do tương tự được nêu ở trên.
  5. Kiểm tra băm nhận để đảm bảo biên nhận tương ứng với thiết bị hiện tại.

5 bước trong mã ở mức cao, từ 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;
}

Hãy đi sâu vào bước 2 và 5.

Xác nhận chữ ký nhận

Quay lại khi chúng tôi trích xuất dữ liệu, chúng tôi liếc qua xác minh chữ ký nhận. Biên lai được ký với Chứng chỉ gốc của Apple Inc., có thể được tải xuống từ Cơ quan cấp chứng chỉ gốc của Apple . Đoạn mã sau lấy bộ chứa PKCS7 và chứng chỉ gốc làm dữ liệu và kiểm tra xem chúng có khớp không:

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

Điều này đã được thực hiện lại từ đầu, trước khi nhận được phân tích cú pháp.

Xác minh băm nhận

Giá trị băm có trong biên nhận là SHA1 của id thiết bị, một số giá trị mờ bao gồm trong hóa đơn và id gói.

Đây là cách bạn sẽ xác minh băm nhận trên iOS. Từ 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];
}

Và đó là ý chính của nó. Tôi có thể đang thiếu một cái gì đó ở đây hoặc ở đó, vì vậy tôi có thể quay lại bài này sau. Trong mọi trường hợp, tôi khuyên bạn nên duyệt mã hoàn chỉnh để biết thêm chi tiết.


2
Từ chối trách nhiệm bảo mật: sử dụng mã nguồn mở làm cho ứng dụng của bạn dễ bị tổn thương hơn. Nếu bảo mật là một mối quan tâm, bạn có thể muốn sử dụng RMStore và mã ở trên chỉ như một hướng dẫn.
hpique

6
Thật tuyệt vời nếu trong tương lai bạn thoát khỏi OpenSSL và làm cho thư viện của bạn nhỏ gọn bằng cách chỉ sử dụng các khung hệ thống.
Vịt

2
@RubberDuck Xem github.com/robotmedia/RMStore/issues/16 . Hãy thoải mái để hòa nhập, hoặc đóng góp. :)
hpique

1
@RubberDuck Tôi không có kiến ​​thức về OpenSSL cho đến khi này. Ai biết được, bạn thậm chí có thể thích nó. : P
hpique

2
Nó dễ bị tấn công bởi một người đàn ông trong cuộc tấn công giữa, nơi yêu cầu và / hoặc phản hồi có thể bị chặn và sửa đổi. Ví dụ: yêu cầu có thể được chuyển hướng đến máy chủ của bên thứ 3 và phản hồi sai có thể được trả lại, lừa ứng dụng nghĩ rằng sản phẩm đã được mua, khi không và cho phép chức năng miễn phí.
Jasarien

13

Tôi ngạc nhiên không ai nhắc đến Receigen ở đây. Đó là một công cụ tự động tạo mã xác nhận hóa đơn bị xáo trộn, mỗi lần khác nhau; nó hỗ trợ cả GUI và hoạt động dòng lệnh. Rất khuyến khích.

(Không liên kết với Receigen, chỉ là một người dùng hạnh phúc.)

Tôi sử dụng Rakefile như thế này để tự động chạy lại Trình nhận (vì nó cần được thực hiện trên mỗi thay đổi phiên bản) khi tôi nhập 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
Đối với những người quan tâm đến Receigen, đây là một giải pháp trả phí, có sẵn trên App Store với giá 29,99 $. Mặc dù nó chưa được cập nhật kể từ tháng 9 năm 2014.
DevGansta

Đúng là thiếu cập nhật là rất đáng báo động. Tuy nhiên nó vẫn hoạt động; FWIW, tôi đang sử dụng nó trong các ứng dụng của mình.
Andrey Tarantsov

Kiểm tra ứng dụng của bạn trong các công cụ để biết rò rỉ, với Receigen tôi nhận được chúng rất nhiều.
Reverend

Receigen là tiên tiến, nhưng vâng, có vẻ như nó đã bị bỏ đi.
Fattie

1
Hình như nó chưa bị rơi. Cập nhật ba tuần trước!
Oleg Korzhukov

2

Lưu ý: Không nên thực hiện loại xác minh này ở phía máy khách

Đây là phiên bản Swift 4 để xác thực hóa đơn mua hàng trong ứng dụng ...

Cho phép tạo một enum để thể hiện các lỗi có thể có của xác nhận biên nhận

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

Sau đó, hãy tạo chức năng xác nhận hóa đơn, nó sẽ đưa ra lỗi nếu không thể xác thực nó.

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
    }
}

Hãy sử dụng chức năng trợ giúp này để lấy ngày hết hạn của một sản phẩm cụ thể. Hàm nhận được phản hồi JSON và id sản phẩm. Phản hồi JSON có thể chứa nhiều thông tin biên lai cho các sản phẩm khác nhau, do đó, nó nhận được thông tin cuối cùng cho tham số đã chỉ định.

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
}

Bây giờ bạn có thể gọi hàm này và xử lý các trường hợp lỗi có thể xảy ra

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).")
}

Bạn có thể nhận được Mật khẩu từ Kết nối App Store. https://developer.apple.commở liên kết này bấm vào

  • 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

Sao chép khóa đó và dán vào trường mật khẩu.

Hy vọng điều này sẽ giúp cho mọi người muốn điều đó trong phiên bản nhanh chóng.


19
Bạn không bao giờ nên sử dụng URL xác thực của Apple từ thiết bị của mình. Nó chỉ nên được sử dụng từ máy chủ của bạn. Điều này đã được đề cập trong các phiên WWDC.
pechar

Điều gì sẽ xảy ra nếu người dùng xóa các ứng dụng hoặc không mở trong một thời gian dài? Tính toán ngày hết hạn của bạn có hoạt động tốt không?
karthikeyan

Sau đó, bạn cần phải xác nhận về phía máy chủ.
Pushpendra

1
Như @pechar đã nói, bạn không bao giờ nên làm điều này. Vui lòng thêm nó vào đầu câu trả lời của bạn. Xem phiên WWDC tại 36:32 => developer.apple.com/ideo/play/wwdc2016/702
cicerocamargo

Tôi không hiểu tại sao không an toàn khi gửi dữ liệu biên nhận trực tiếp từ thiết bị. Bất cứ ai sẽ có thể giải thích?
Koh
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.