LOCALLY का एक संपूर्ण समाधान iOS 7 पर इन-ऐप रसीदों और बंडल रसीदों को मान्य करता है


160

मैंने बहुत सारे डॉक्स और कोड पढ़े हैं जो सिद्धांत रूप में एक इन-ऐप और / या बंडल रसीद को मान्य करेंगे।

यह देखते हुए कि एसएसएल, सर्टिफिकेट, एन्क्रिप्शन आदि के बारे में मेरा ज्ञान लगभग शून्य है, मैंने जितने भी स्पष्टीकरण पढ़े हैं, इस तरह के होनहार , मुझे समझना मुश्किल है।

वे कहते हैं कि स्पष्टीकरण अपूर्ण हैं क्योंकि प्रत्येक व्यक्ति को यह पता लगाना है कि यह कैसे करना है, या हैकर्स के पास पटाखा ऐप बनाने का एक आसान काम होगा जो पैटर्न को पहचान और पहचान सकता है और एप्लिकेशन को पैच कर सकता है। ठीक है, मैं इससे एक निश्चित बिंदु तक सहमत हूं। मुझे लगता है कि वे पूरी तरह से समझा सकते हैं कि यह कैसे करना है और एक चेतावनी डालते हुए कहा कि "इस पद्धति को संशोधित करें", "इस अन्य विधि को संशोधित करें", "इस चर को मानें", "इस और उस का नाम बदलें", आदि।

क्या कुछ अच्छी आत्माएं यह समझाने के लिए पर्याप्त हो सकती हैं कि IOS 7 पर LOCALLY को वैध, बंडल रसीदें और इन-ऐप खरीदारी रसीदें कैसे बताएं क्योंकि मैं ऊपर से नीचे तक पांच साल का हूं (ठीक है, इसे 3 बनाएं), स्पष्ट रूप से?

धन्यवाद!!!


यदि आपके पास अपने ऐप्स पर काम करने वाला संस्करण है और आपकी चिंताएं हैं कि हैकर्स देखेंगे कि आपने यह कैसे किया है, तो यहां प्रकाशित करने से पहले अपने संवेदनशील तरीकों को बदल दें। आड़े तिरछे, लाइनों के क्रम को बदल दें, जिस तरह से आप लूप करते हैं (ब्लॉक गणन और इसके विपरीत का उपयोग करने से) और उस तरह की चीजों को बदल दें। जाहिर है, हर व्यक्ति जो यहां पोस्ट किए जाने वाले कोड का उपयोग करता है, उसे यही काम करना पड़ता है, न कि आसानी से हैक होने का खतरा।


1
उचित चेतावनी: इसे स्थानीय रूप से करना आपके आवेदन से इस फ़ंक्शन को पैच करने के लिए बहुत आसान बनाता है।
NinjaLikesCheez

2
ठीक है, मुझे पता है, लेकिन यहाँ बिंदु मुश्किल काम करना है और स्वचालित खुर / पैचिंग को रोकना है। सवाल यह है कि अगर कोई हैकर वास्तव में आपके ऐप को क्रैक करना चाहता है, तो वह यह करेगा कि आप जो भी तरीका इस्तेमाल करें, वह लोकल या रिमोट हो। विचार यह भी है कि आप अपने जारी किए गए हर नए संस्करण को थोड़ा बदल दें, ताकि स्वचालित पैचिंग को फिर से रोका जा सके।
डक

4
@NinjaLikesCheez - यदि कोई सर्वर पर सत्यापन किया जाता है, तो भी चेक को NOP कर सकता है।
डक

14
क्षमा करें, लेकिन यह बहाना नहीं है। लेखक को केवल इतना ही कहना है कि आईटी के रूप में कोड का उपयोग न करें। बिना किसी उदाहरण के, रॉकेट वैज्ञानिक होने के बिना इसे समझना असंभव है।
बतख

3
यदि आप DRM को लागू करने से परेशान नहीं होना चाहते हैं, तो स्थानीय सत्यापन से परेशान न हों। बस अपने ऐप से सीधे ऐप्पल को रसीद पोस्ट करें, और वे इसे आसानी से पार्स किए गए JSON फॉर्मेट में आपको फिर से भेज देंगे। यह दरार करने के लिए समुद्री डाकुओं के लिए तुच्छ है, लेकिन अगर आप सिर्फ फ्रीमियम के लिए संक्रमण कर रहे हैं और चोरी के बारे में परवाह नहीं करते हैं, तो यह बहुत आसान कोड की सिर्फ कुछ पंक्तियां हैं।
डान फेबुलिच

जवाबों:


146

यहां बताया गया है कि मैंने अपनी इन-ऐप खरीदारी लाइब्रेरी RMStore में इसे कैसे हल किया । मैं समझाऊंगा कि कैसे एक लेन-देन को सत्यापित किया जाए, जिसमें पूरी रसीद का सत्यापन करना शामिल है।

एक नजर में

रसीद प्राप्त करें और लेनदेन को सत्यापित करें। यदि यह विफल रहता है, तो रसीद को ताज़ा करें और फिर से प्रयास करें। यह सत्यापन प्रक्रिया को अतुल्यकालिक बनाता है क्योंकि रसीद को ताज़ा करना अतुल्यकालिक है।

से 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];
}];

रसीद डेटा प्राप्त करना

रसीद में है [[NSBundle mainBundle] appStoreReceiptURL]और वास्तव में एक PCKS7 कंटेनर है। मैं क्रिप्टोग्राफी में चूसता हूं इसलिए मैंने इस कंटेनर को खोलने के लिए ओपनएसएसएल का उपयोग किया। अन्य लोगों ने स्पष्ट रूप से इसे सिस्टम फ्रेमवर्क के साथ किया है ।

अपने प्रोजेक्ट में OpenSSL जोड़ना तुच्छ नहीं है। RMStore विकी मदद करनी चाहिए।

यदि आप PKCS7 कंटेनर खोलने के लिए OpenSSL का उपयोग करने का विकल्प चुनते हैं, तो आपका कोड इस तरह दिख सकता है। से 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;
}

हम सत्यापन के विवरण में बाद में आएंगे।

रसीद फ़ील्ड प्राप्त करना

रसीद ASN1 प्रारूप में व्यक्त की गई है। इसमें सामान्य जानकारी शामिल है, सत्यापन के उद्देश्यों के लिए कुछ क्षेत्र (हम बाद में आएंगे) और प्रत्येक लागू-में-खरीद की विशिष्ट जानकारी।

जब ASN1 पढ़ने की बात आती है तो ओपनएसएसएल बचाव में आता है। से RMAppReceipt , कुछ सहायक तरीकों का उपयोग कर:

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;

में app खरीद हो रही है

प्रत्येक इन-ऐप खरीदारी ASN1 में भी है। इसे प्राप्त करना सामान्य रसीद की जानकारी को पार्स करने के समान है।

से RMAppReceipt , एक ही सहायक तरीकों का उपयोग कर:

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

यह ध्यान दिया जाना चाहिए कि कुछ इन-ऐप खरीदारी, जैसे उपभोग्य और गैर-नवीकरणीय सदस्यता, केवल एक बार रसीद में दिखाई देंगे। आपको खरीद के बाद इन अधिकारों को सत्यापित करना चाहिए (फिर से, RMStore इसमें आपकी मदद करता है)।

एक नज़र में सत्यापन

अब हमें रसीद और उसके सभी इन-ऐप खरीदारी से सभी क्षेत्र मिल गए। पहले हम स्वयं रसीद सत्यापित करते हैं, और फिर हम जांचते हैं कि रसीद में लेन-देन का उत्पाद है या नहीं।

नीचे वह विधि है जिसे हमने शुरुआत में वापस बुलाया था। से 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;
}

रसीद का सत्यापन करना

रसीद को सत्यापित करना स्वयं को उबलता है:

  1. जाँच कर रहा है कि रसीद PKCS7 और ASN1 है। यह हमने पहले ही किया है।
  2. सत्यापित किया जा रहा है कि रसीद Apple द्वारा हस्ताक्षरित है। रसीद को पार्स करने से पहले यह किया गया था और नीचे विस्तृत होगा।
  3. जाँच करना कि रसीद में शामिल बंडल पहचानकर्ता आपके बंडल पहचानकर्ता से मेल खाता है। आपको अपने बंडल पहचानकर्ता को हार्डकोड करना चाहिए, क्योंकि यह आपके ऐप बंडल को संशोधित करने और किसी अन्य रसीद का उपयोग करने के लिए बहुत मुश्किल नहीं लगता है।
  4. जाँच रहा है कि रसीद में शामिल ऐप संस्करण आपके ऐप संस्करण पहचानकर्ता से मेल खाता है। ऊपर बताए गए समान कारणों से आपको ऐप संस्करण को हार्डकोड करना चाहिए।
  5. रसीद की जांच करें कि रसीद वर्तमान डिवाइस के अनुरूप है या नहीं।

RMStoreAppReceiptVerificator से उच्च-स्तर पर कोड में 5 चरण :

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

चलो चरण 2 और 5 में ड्रिल-डाउन करें।

रसीद हस्ताक्षर का सत्यापन

जब हमने रसीद हस्ताक्षर सत्यापन पर नज़र डाली तो हमने डेटा निकाला। रसीद पर ऐप्पल इंक रूट सर्टिफिकेट के साथ हस्ताक्षर किए गए हैं, जिसे ऐप्पल रूट सर्टिफिकेट अथॉरिटी से डाउनलोड किया जा सकता है । निम्न कोड PKCS7 कंटेनर और रूट प्रमाणपत्र को डेटा के रूप में लेता है और जांचता है कि क्या वे मेल खाते हैं:

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

रसीद के पार्स होने से पहले, यह शुरुआत में वापस किया गया था।

रसीद हैश का सत्यापन करना

रसीद में शामिल हैश डिवाइस आईडी का SHA1 है, रसीद और बंडल आईडी में शामिल कुछ अपारदर्शी मूल्य।

यह है कि आप iOS पर रसीद हैश का सत्यापन कैसे करेंगे। से 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];
}

और यही इसका सार है। मुझे यहाँ या वहाँ कुछ याद आ रहा है, इसलिए मैं बाद में इस पद पर वापस आ सकता हूं। किसी भी स्थिति में, मैं अधिक विवरण के लिए पूरा कोड ब्राउज़ करने की सलाह देता हूं।


2
सुरक्षा अस्वीकरण: खुले स्रोत कोड का उपयोग करने से आपका ऐप अधिक असुरक्षित हो जाता है। यदि सुरक्षा चिंता का विषय है, तो आप RMStore और उपरोक्त कोड को केवल एक गाइड के रूप में उपयोग करना चाह सकते हैं।
hpique

6
यह शानदार होगा यदि भविष्य में आप ओपनएसएसएल से छुटकारा पाएं और सिर्फ सिस्टम फ्रेमवर्क का उपयोग करके अपनी लाइब्रेरी को कॉम्पैक्ट बनाएं।
बतख 14

2
@RubberDuck देखें github.com/robotmedia/RMStore/issues/16 । में झंकार, या योगदान करने के लिए स्वतंत्र महसूस करें। :)
hpique

1
@ रबरडक I के पास ओपनएसएसएल का शून्य ज्ञान था। कौन जानता है, आप भी इसे पसंद कर सकते हैं। : P
hpique

2
यह मध्य हमले में एक आदमी के लिए अतिसंवेदनशील है, जहां अनुरोध और / या प्रतिक्रिया को अवरोधन और संशोधित किया जा सकता है। उदाहरण के लिए, अनुरोध को तीसरे पक्ष के सर्वर पर पुनर्निर्देशित किया जा सकता है, और एक झूठी प्रतिक्रिया वापस की जा सकती है, ऐप को यह सोचकर कि किसी उत्पाद को खरीदा गया था, जब यह नहीं था, और मुफ्त में कार्यक्षमता को सक्षम करना।
जसरीन

13

मुझे आश्चर्य है कि किसी ने भी यहां रेसीगेन का उल्लेख नहीं किया । यह एक ऐसा उपकरण है जो स्वचालित रूप से obfuscated रसीद सत्यापन कोड उत्पन्न करता है, हर बार एक अलग; यह GUI और कमांड लाइन ऑपरेशन दोनों का समर्थन करता है। अत्यधिक सिफारिशित।

(रेसीगेन से संबद्ध नहीं, सिर्फ एक खुशहाल उपयोगकर्ता।)

मैं इस तरह से एक Rakefile का उपयोग करें स्वतः प्राप्त करने के लिए फिर से चलाएँ (क्योंकि यह हर संस्करण परिवर्तन पर किए जाने की आवश्यकता है) जब मैं टाइप करता हूं 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
जो लोग रेसीगेन में रुचि रखते हैं, उनके लिए यह एक सॉल्यूशन है, जो 29.99 डॉलर में ऐप स्टोर पर उपलब्ध है। हालांकि इसे सितंबर 2014 से अपडेट नहीं किया गया है।
देवगंता

सच है, अपडेट की कमी बहुत चिंताजनक है। हालाँकि यह अभी भी काम करता है; FWIW, मैं इसे अपने ऐप्स में उपयोग कर रहा हूं।
एंड्री टारंटसोव

लीक के लिए उपकरणों में अपने ऐप की जांच करें, प्राप्तिगन के साथ मैं उन्हें बहुत कुछ देता हूं।
रेवरेंड

रिसीजन कटिंग एज है, लेकिन हां यह शर्म की बात है कि इसे गिरा दिया गया है।
फटी

1
लगता है अभी गिरा नहीं है। तीन हफ्ते पहले अपडेट किया गया!
ओलेग कोरज़ुकोव

2

नोट: ग्राहक पक्ष में इस प्रकार का सत्यापन करने की अनुशंसा नहीं की गई है

यह इन-ऐप-खरीदारी रसीद के सत्यापन के लिए एक स्विफ्ट 4 संस्करण है ...

रसीद सत्यापन की संभावित त्रुटियों का प्रतिनिधित्व करने के लिए एक एनम बनाते हैं

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

तो चलो उस फ़ंक्शन को बनाते हैं जो रसीद को मान्य करता है, यह एक त्रुटि फेंकता है यदि यह इसे सत्यापित करने में असमर्थ है।

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

आइए इस हेल्पर फ़ंक्शन का उपयोग करें, किसी विशिष्ट उत्पाद की समाप्ति तिथि प्राप्त करने के लिए। फ़ंक्शन को JSON प्रतिक्रिया और उत्पाद आईडी प्राप्त होता है। JSON प्रतिक्रिया में विभिन्न उत्पादों के लिए कई रसीद जानकारी हो सकती है, इसलिए यह निर्दिष्ट पैरामीटर के लिए अंतिम जानकारी प्राप्त करती है।

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
}

अब आप इस फ़ंक्शन को कॉल कर सकते हैं और संभावित त्रुटि मामलों को संभाल सकते हैं

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

आप ऐप स्टोर कनेक्ट से एक पासवर्ड प्राप्त कर सकते हैं । https://developer.apple.comइस लिंक पर क्लिक करें

  • 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

उस कुंजी को कॉपी करें और पासवर्ड फ़ील्ड में पेस्ट करें।

आशा है कि यह हर उस व्यक्ति के लिए मदद करेगा जो स्विफ्ट संस्करण में चाहता है।


19
आपको अपने डिवाइस से कभी भी Apple सत्यापन URL का उपयोग नहीं करना चाहिए। इसका उपयोग केवल आपके सर्वर से किया जाना चाहिए। डब्ल्यूडब्ल्यूडीसी सत्रों में इसका उल्लेख किया गया था।
पीकर

यदि उपयोगकर्ता ऐप्स हटाते हैं या लंबे समय तक नहीं खोलते हैं तो क्या होगा? क्या आपकी एक्सपायरी डेट की गणना ठीक काम कर रही है?
कार्तिकेयन

फिर आपको सर्वर साइड पर सत्यापन रखने की आवश्यकता है।
पुष्पेंद्र

1
जैसा @pechar ने कहा, आपको ऐसा कभी नहीं करना चाहिए। कृपया इसे अपने उत्तर के शीर्ष पर जोड़ें। 36:32 => पर WWDC सत्र देखें developer.apple.com/videos/play/wwdc2016/702
cicerocamargo

मुझे समझ में नहीं आता कि डिवाइस से सीधे रसीद डेटा भेजना सुरक्षित क्यों नहीं है। क्या कोई समझा पाएगा?
कोह
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.