iOS应用内购买验证

时间:2014-06-23 13:31:42

标签: ios objective-c

我无法找到错误代码21007的帐户答案。我了解Apple使用沙盒环境测试应用内购买,但我如何在以下代码中对此进行说明。

#import "VerificationController.h"
#import "NSData+Base64.h"

static VerificationController *singleton;

@implementation VerificationController {
NSMutableDictionary * _completionHandlers;
NSString *SendURL;



}

+ (VerificationController *)sharedInstance
{
if (singleton == nil)
{
    singleton = [[VerificationController alloc] init];
}
return singleton;
}


- (id)init
{
self = [super init];
if (self != nil)
{
    transactionsReceiptStorageDictionary = [[NSMutableDictionary alloc] init];
    _completionHandlers = [[NSMutableDictionary alloc] init];
}
return self;
}


- (NSDictionary *)dictionaryFromPlistData:(NSData *)data
{
NSError *error;
NSDictionary *dictionaryParsed = [NSPropertyListSerialization propertyListWithData:data
                                                                                        options:NSPropertyListImmutable
                                                                            format:nil
                                                                             error:&error];
if (!dictionaryParsed)
{
    if (error)
    {
        NSLog(@"Error parsing plist");
    }
    return nil;
}
return dictionaryParsed;
}


- (NSDictionary *)dictionaryFromJSONData:(NSData *)data
{
NSError *error;
NSDictionary *dictionaryParsed = [NSJSONSerialization JSONObjectWithData:data
                                                                 options:0
                                                                   error:&error];
if (!dictionaryParsed)
{
    if (error)
    {
        NSLog(@"Error parsing dictionary");
    }
    return nil;
}
return dictionaryParsed;
}


#pragma mark Receipt Verification

// This method should be called once a transaction gets to the     SKPaymentTransactionStatePurchased or SKPaymentTransactionStateRestored state
// Call it with the SKPaymentTransaction.transactionReceipt
- (void)verifyPurchase:(SKPaymentTransaction *)transaction completionHandler:    (VerifyCompletionHandler)completionHandler
{    
BOOL isOk = [self isTransactionAndItsReceiptValid:transaction];
if (!isOk)
{
        // There was something wrong with the transaction we got back, so no need to call    verifyReceipt.
    NSLog(@"Invalid transacion");
    completionHandler(FALSE);
    return;
    }

    // The transaction looks ok, so start the verify process.

// Encode the receiptData for the itms receipt verification POST request.
NSString *jsonObjectString = [self encodeBase64:(uint8_t *)transaction.transactionReceipt.bytes
                                         length:transaction.transactionReceipt.length];

// Create the POST request payload.
NSString *payload = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\", \"password\" :    \"%@\"}",
                     jsonObjectString, ITC_CONTENT_PROVIDER_SHARED_SECRET];

NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding];

#warning Check for the correct itms verify receipt URL
// Use ITMS_SANDBOX_VERIFY_RECEIPT_URL while testing against the sandbox.

NSString *serverURL = SendURL;

// Create the POST request to the server.
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL  URLWithString:serverURL]];
[request setHTTPMethod:@"POST"];
[request setHTTPBody:payloadData];
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];

_completionHandlers[[NSValue valueWithNonretainedObject:conn]] = completionHandler;

[conn start];
// The transation receipt has not been validated yet.  That is done from the NSURLConnection callback.

}

// Check the validity of the receipt.  If it checks out then also ensure the transaction is something
// we haven't seen before and then decode and save the purchaseInfo from the receipt for later receipt validation.
- (BOOL)isTransactionAndItsReceiptValid:(SKPaymentTransaction *)transaction
{
if (!(transaction && transaction.transactionReceipt && [transaction.transactionReceipt length] > 0))
{
    // Transaction is not valid.
    return NO;
}

// Pull the purchase-info out of the transaction receipt, decode it, and save it for later so
// it can be cross checked with the verifyReceipt.
NSDictionary *receiptDict       = [self dictionaryFromPlistData:transaction.transactionReceipt];
NSString *transactionPurchaseInfo = [receiptDict objectForKey:@"purchase-info"];
NSString *decodedPurchaseInfo   = [self decodeBase64:transactionPurchaseInfo length:nil];
NSDictionary *purchaseInfoDict  = [self dictionaryFromPlistData:[decodedPurchaseInfo   dataUsingEncoding:NSUTF8StringEncoding]];

NSString *transactionId         = [purchaseInfoDict objectForKey:@"transaction-id"];
NSString *purchaseDateString    = [purchaseInfoDict objectForKey:@"purchase-date"];
NSString *signature             = [receiptDict objectForKey:@"signature"];

// Convert the string into a date
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:@"yyyy-MM-dd HH:mm:ss z"];

NSDate *purchaseDate = [dateFormat dateFromString:[purchaseDateString   stringByReplacingOccurrencesOfString:@"Etc/" withString:@""]];


if (![self isTransactionIdUnique:transactionId])
{
    // We've seen this transaction before.
    // Had [transactionsReceiptStorageDictionary objectForKey:transactionId]
    // Got purchaseInfoDict
    return NO;
}

// Check the authenticity of the receipt response/signature etc.

BOOL result = checkReceiptSecurity(transactionPurchaseInfo, signature,
                                   (__bridge CFDateRef)(purchaseDate));

if (!result)
{
    return NO;
}

// Ensure the transaction itself is legit
if (![self doTransactionDetailsMatchPurchaseInfo:transaction withPurchaseInfo:purchaseInfoDict])
{
    return NO;
}

// Make a note of the fact that we've seen the transaction id already
[self saveTransactionId:transactionId];

// Save the transaction receipt's purchaseInfo in the transactionsReceiptStorageDictionary.
[transactionsReceiptStorageDictionary setObject:purchaseInfoDict forKey:transactionId];

return YES;
}

// Make sure the transaction details actually match the purchase info
- (BOOL)doTransactionDetailsMatchPurchaseInfo:(SKPaymentTransaction *)transaction withPurchaseInfo:(NSDictionary *)purchaseInfoDict

{
if (!transaction || !purchaseInfoDict)
{
    return NO;
}

int failCount = 0;

if (![transaction.payment.productIdentifier isEqualToString:[purchaseInfoDict objectForKey:@"product-id"]])
{

    failCount++;
}

if (transaction.payment.quantity != [[purchaseInfoDict objectForKey:@"quantity"] intValue])
{
    failCount++;
}

if (![transaction.transactionIdentifier isEqualToString:[purchaseInfoDict objectForKey:@"transaction-id"]])
{
    failCount++;
}

// Optionally check the bid and bvrs match this app's current bundle ID and bundle version.
// Optionally check the requestData.
// Optionally check the dates.

if (failCount != 0)
{
    return NO;
}

// The transaction and its signed content seem ok.
return YES;
}



- (BOOL)isTransactionIdUnique:(NSString *)transactionId
{
NSString *transactionDictionary = KNOWN_TRANSACTIONS_KEY;
// Save the transactionId to the standardUserDefaults so we can check against that later
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults synchronize];

if (![defaults objectForKey:transactionDictionary])
{
    [defaults setObject:[[NSMutableDictionary alloc] init] forKey:transactionDictionary];
    [defaults synchronize];
}

if (![[defaults objectForKey:transactionDictionary] objectForKey:transactionId])
{
    return YES;
}
// The transaction already exists in the defaults.
return NO;
}


- (void)saveTransactionId:(NSString *)transactionId
{
// Save the transactionId to the standardUserDefaults so we can check against that later
// If dictionary exists already then retrieve it and add new transactionID
// Regardless save transactionID to dictionary which gets saved to NSUserDefaults
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *transactionDictionary = KNOWN_TRANSACTIONS_KEY;
NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithDictionary:
                                   [defaults objectForKey:transactionDictionary]];
if (!dictionary)
{
    dictionary = [[NSMutableDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithInt:1], transactionId, nil];
} else {
    [dictionary setObject:[NSNumber numberWithInt:1] forKey:transactionId];
}
[defaults setObject:dictionary forKey:transactionDictionary];
[defaults synchronize];

}


- (BOOL)doesTransactionInfoMatchReceipt:(NSString*) receiptString
{
// Convert the responseString into a dictionary and pull out the receipt data.
NSDictionary *verifiedReceiptDictionary = [self dictionaryFromJSONData:[receiptString dataUsingEncoding:NSUTF8StringEncoding]];

// Check the status of the verifyReceipt call
id status = [verifiedReceiptDictionary objectForKey:@"status"];
if (!status)
{
    return NO;
}
int verifyReceiptStatus = [status integerValue];
// 21006 = This receipt is valid but the subscription has expired.
NSLog(@"Status Code: %@", status);
if (0 != verifyReceiptStatus && 21006 != verifyReceiptStatus)

{
    return NO;
}
 // The receipt is valid, so checked the receipt specifics now.

NSDictionary *verifiedReceiptReceiptDictionary  = [verifiedReceiptDictionary objectForKey:@"receipt"];
NSString *verifiedReceiptUniqueIdentifier       = [verifiedReceiptReceiptDictionary objectForKey:@"unique_identifier"];
NSString *transactionIdFromVerifiedReceipt      = [verifiedReceiptReceiptDictionary objectForKey:@"transaction_id"];

// Get the transaction's receipt data from the transactionsReceiptStorageDictionary
NSDictionary *purchaseInfoFromTransaction = [transactionsReceiptStorageDictionary objectForKey:transactionIdFromVerifiedReceipt];

if (!purchaseInfoFromTransaction)
{
    // We didn't find a receipt for this transaction.
    return NO;
}


// NOTE: Instead of counting errors you could just return early.
int failCount = 0;

// Verify all the receipt specifics to ensure everything matches up as expected
if (![[verifiedReceiptReceiptDictionary objectForKey:@"bid"]
      isEqualToString:[purchaseInfoFromTransaction objectForKey:@"bid"]])
{
    failCount++;
}

if (![[verifiedReceiptReceiptDictionary objectForKey:@"product_id"]
      isEqualToString:[purchaseInfoFromTransaction objectForKey:@"product-id"]])
{
    failCount++;
}

if (![[verifiedReceiptReceiptDictionary objectForKey:@"quantity"]
      isEqualToString:[purchaseInfoFromTransaction objectForKey:@"quantity"]])
{
    failCount++;
}

if (![[verifiedReceiptReceiptDictionary objectForKey:@"item_id"]
      isEqualToString:[purchaseInfoFromTransaction objectForKey:@"item-id"]])
{
    failCount++;
}

if ([[UIDevice currentDevice] respondsToSelector:NSSelectorFromString(@"identifierForVendor")])     // iOS 6?
{
#if IS_IOS6_AWARE
    // iOS 6 (or later)
    NSString *localIdentifier                   = [[[UIDevice currentDevice]    identifierForVendor] UUIDString];
    NSString *purchaseInfoUniqueVendorId        = [purchaseInfoFromTransaction objectForKey:@"unique-vendor-identifier"];
    NSString *verifiedReceiptVendorIdentifier   = [verifiedReceiptReceiptDictionary objectForKey:@"unique_vendor_identifier"];


    if(verifiedReceiptVendorIdentifier)
    {
        if (![purchaseInfoUniqueVendorId isEqualToString:verifiedReceiptVendorIdentifier]
            || ![purchaseInfoUniqueVendorId isEqualToString:localIdentifier])
        {
            // Comment this line out to test in the Simulator.
            failCount++;
        }
    }
#endif
} else {
    // Pre iOS 6 

// NSString * localIdentifier = [UIDevice currentDevice] .uniqueIdentifier;        NSString * localIdentifier = [[[[UIDevice currentDevice] identifierForVendor] UUIDString];         NSString * purchaseInfoUniqueId = [purchaseInfoFromTransaction objectForKey:@" unique-identifier"];

    if (![purchaseInfoUniqueId isEqualToString:verifiedReceiptUniqueIdentifier]
        || ![purchaseInfoUniqueId isEqualToString:localIdentifier])
    {
        // Comment this line out to test in the Simulator.
        failCount++;
    }        
}


// Do addition time checks for the transaction and receipt.

if(failCount != 0)
{
    return NO;
}

return YES;
}


#pragma mark NSURLConnectionDelegate (for the verifyReceipt connection)

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {

NSLog(@"Connection failure: %@", error);

VerifyCompletionHandler completionHandler = _completionHandlers[[NSValue valueWithNonretainedObject:connection]];
[_completionHandlers removeObjectForKey:[NSValue valueWithNonretainedObject:connection]];
completionHandler(FALSE);

}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

// So we got some receipt data. Now does it all check out?
BOOL isOk = [self doesTransactionInfoMatchReceipt:responseString];

VerifyCompletionHandler completionHandler = _completionHandlers[[NSValue   valueWithNonretainedObject:connection]];
[_completionHandlers removeObjectForKey:[NSValue valueWithNonretainedObject:connection]];
if (isOk)
{
    //Validation suceeded. Unlock content here.
    NSLog(@"Validation successful");
    completionHandler(TRUE);

} else {
    NSLog(@"Validation failed");
    completionHandler(FALSE);
}
}


- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge: (NSURLAuthenticationChallenge *)challenge
{
if ([[[challenge protectionSpace] authenticationMethod] isEqualToString:NSURLAuthenticationMethodServerTrust])
{
    SecTrustRef trust = [[challenge protectionSpace] serverTrust];
    NSError *error = nil;
    BOOL didUseCredential = NO;
    BOOL isTrusted = [self validateTrust:trust error:&error];
    if (isTrusted)
    {
        NSURLCredential *trust_credential = [NSURLCredential credentialForTrust:trust];
        if (trust_credential)
        {
            [[challenge sender] useCredential:trust_credential  forAuthenticationChallenge:challenge];
            didUseCredential = YES;
        }
    }
    if (!didUseCredential)
    {
        [[challenge sender] cancelAuthenticationChallenge:challenge];
    }
} else {
    [[challenge sender] performDefaultHandlingForAuthenticationChallenge:challenge];
}
}

// NOTE: These are needed for 4.x (as willSendRequestForAuthenticationChallenge: is not supported)
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace: (NSURLProtectionSpace *)protectionSpace
{
return [[protectionSpace authenticationMethod]  isEqualToString:NSURLAuthenticationMethodServerTrust];
}


- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([[[challenge protectionSpace] authenticationMethod] isEqualToString:NSURLAuthenticationMethodServerTrust])
{
    SecTrustRef trust = [[challenge protectionSpace] serverTrust];
    NSError *error = nil;
    BOOL didUseCredential = NO;
    BOOL isTrusted = [self validateTrust:trust error:&error];
    if (isTrusted)
    {
        NSURLCredential *credential = [NSURLCredential credentialForTrust:trust];
        if (credential)
        {
            [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
            didUseCredential = YES;
        }
    }
    if (! didUseCredential) {
        [[challenge sender] cancelAuthenticationChallenge:challenge];
    }
  } else {
    [[challenge sender] performDefaultHandlingForAuthenticationChallenge:challenge];
}
}


#pragma mark
#pragma mark NSURLConnection - Trust validation

- (BOOL)validateTrust:(SecTrustRef)trust error:(NSError **)error
 {

// Include some Security framework SPIs
extern CFStringRef kSecTrustInfoExtendedValidationKey;
extern CFDictionaryRef SecTrustCopyInfo(SecTrustRef trust);

BOOL trusted = NO;
SecTrustResultType trust_result;
if ((noErr == SecTrustEvaluate(trust, &trust_result)) && (trust_result == kSecTrustResultUnspecified))
{
    NSDictionary *trust_info = (__bridge_transfer NSDictionary *)SecTrustCopyInfo(trust);
    id hasEV = [trust_info objectForKey:(__bridge NSString *)kSecTrustInfoExtendedValidationKey];
    trusted =  [hasEV isKindOfClass:[NSValue class]] && [hasEV boolValue];
}

if (trust)
{
    if (!trusted && error)
    {
        *error = [NSError errorWithDomain:@"kSecTrustError" code:(NSInteger)trust_result userInfo:nil];
    }
    return trusted;
}
return NO;
}


#pragma mark
#pragma mark Check Receipt signature

#include <CommonCrypto/CommonDigest.h>
#include <Security/Security.h>
#include <AssertMacros.h>
unsigned int iTS_intermediate_der_len = 1039;

unsigned char iTS_intermediate_der[] = {
0x30, 0x82, 0x04, 0x0b, 0x30, 0x82, 0x02, 0xf3, 0xa0, 0x03, 0x02, 0x01,
0x02, 0x02, 0x01, 0x1a, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x62, 0x31, 0x0b, 0x30,
0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13,
0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0a, 0x41, 0x70, 0x70,
0x6c, 0x65, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x26, 0x30, 0x24, 0x06,
0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20,
0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31,
0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x0d, 0x41, 0x70,
0x70, 0x6c, 0x65, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30,
0x1e, 0x17, 0x0d, 0x30, 0x39, 0x30, 0x35, 0x31, 0x39, 0x31, 0x38, 0x33,
0x31, 0x33, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x36, 0x30, 0x35, 0x31, 0x38,
0x31, 0x38, 0x33, 0x31, 0x33, 0x30, 0x5a, 0x30, 0x7f, 0x31, 0x0b, 0x30,
0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13,
0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0a, 0x41, 0x70, 0x70,
0x6c, 0x65, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x26, 0x30, 0x24, 0x06,
0x03, 0x55, 0x04, 0x0b, 0x0c, 0x1d, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20,
0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31,
0x33, 0x30, 0x31, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x2a, 0x41, 0x70,
0x70, 0x6c, 0x65, 0x20, 0x69, 0x54, 0x75, 0x6e, 0x65, 0x73, 0x20, 0x53,
0x74, 0x6f, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f,
0x72, 0x69, 0x74, 0x79, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09,
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03,
0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01,
0x00, 0xa4, 0xbc, 0xaf, 0x32, 0x94, 0x43, 0x3e, 0x0b, 0xbc, 0x37, 0x87,
0xcd, 0x63, 0x89, 0xf2, 0xcc, 0xd9, 0xbe, 0x20, 0x4d, 0x5a, 0xb4, 0xfe,
0x87, 0x67, 0xd2, 0x9a, 0xde, 0x1a, 0x54, 0x9d, 0xa2, 0xf3, 0xdf, 0x87,
0xe4, 0x4c, 0xcb, 0x93, 0x11, 0x78, 0xa0, 0x30, 0x8f, 0x34, 0x41, 0xc1,
0xd3, 0xbe, 0x66, 0x6d, 0x47, 0x6c, 0x98, 0xb8, 0xec, 0x7a, 0xd5, 0xc9,
0xdd, 0xa5, 0xe4, 0xea, 0xc6, 0x70, 0xf4, 0x35, 0xd0, 0x91, 0xf7, 0xb3,
0xd8, 0x0a, 0x11, 0x99, 0xab, 0x3a, 0x62, 0x3a, 0xbd, 0x7b, 0xf4, 0x56,
0x4f, 0xdb, 0x9f, 0x24, 0x93, 0x51, 0x50, 0x7c, 0x20, 0xd5, 0x66, 0x4d,
0x66, 0xf3, 0x18, 0xa4, 0x13, 0x96, 0x22, 0x16, 0xfd, 0x31, 0xa7, 0xf4,
0x39, 0x66, 0x9b, 0xfb, 0x62, 0x69, 0x5c, 0x4b, 0x9f, 0x94, 0xa8, 0x4b,
0xe8, 0xec, 0x5b, 0x64, 0x5a, 0x18, 0x79, 0x8a, 0x16, 0x75, 0x63, 0x42,
0xa4, 0x49, 0xd9, 0x8c, 0x33, 0xde, 0xad, 0x7b, 0xd6, 0x39, 0x04, 0xf4,
0xe2, 0x9d, 0x0a, 0x69, 0x8c, 0xeb, 0x4b, 0x12, 0x28, 0x4b, 0x34, 0x48,
0x07, 0x9b, 0x0e, 0x59, 0xf9, 0x1f, 0x62, 0xb0, 0x03, 0x9f, 0x36, 0xb8,
0x4e, 0xa3, 0xd3, 0x75, 0x59, 0xd4, 0xf3, 0x3a, 0x05, 0xca, 0xc5, 0x33,
0x3b, 0xf8, 0xc0, 0x06, 0x09, 0x08, 0x93, 0xdb, 0xe7, 0x4d, 0xbf, 0x11,
0xf3, 0x52, 0x2c, 0xa5, 0x16, 0x35, 0x15, 0xf3, 0x41, 0x02, 0xcd, 0x02,
0xd1, 0xfc, 0xf5, 0xf8, 0xc5, 0x84, 0xbd, 0x63, 0x6a, 0x86, 0xd6, 0xb6,
0x99, 0xf6, 0x86, 0xae, 0x5f, 0xfd, 0x03, 0xd4, 0x28, 0x8a, 0x5a, 0x5d,
0xaf, 0xbc, 0x65, 0x74, 0xd1, 0xf7, 0x1a, 0xc3, 0x92, 0x08, 0xf4, 0x1c,
0xad, 0x69, 0xe8, 0x02, 0x4c, 0x0e, 0x95, 0x15, 0x07, 0xbc, 0xbe, 0x6a,
0x6f, 0xc1, 0xb3, 0xad, 0xa1, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81,
0xae, 0x30, 0x81, 0xab, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x0f, 0x06, 0x03,
0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01,
0xff, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
0x36, 0x1d, 0xe8, 0xe2, 0x9d, 0x82, 0xd2, 0x01, 0x18, 0xb5, 0x32, 0x6b,
0x0e, 0xd7, 0x43, 0x0b, 0x91, 0x58, 0x43, 0x3a, 0x30, 0x1f, 0x06, 0x03,
0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x2b, 0xd0, 0x69,
0x47, 0x94, 0x76, 0x09, 0xfe, 0xf4, 0x6b, 0x8d, 0x2e, 0x40, 0xa6, 0xf7,
0x47, 0x4d, 0x7f, 0x08, 0x5e, 0x30, 0x36, 0x06, 0x03, 0x55, 0x1d, 0x1f,
0x04, 0x2f, 0x30, 0x2d, 0x30, 0x2b, 0xa0, 0x29, 0xa0, 0x27, 0x86, 0x25,
0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x61,
0x70, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x70, 0x70,
0x6c, 0x65, 0x63, 0x61, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72,
0x6c, 0x30, 0x10, 0x06, 0x0a, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x63, 0x64,
0x06, 0x02, 0x02, 0x04, 0x02, 0x05, 0x00, 0x30, 0x0d, 0x06, 0x09, 0x2a,
0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82,
0x01, 0x01, 0x00, 0x75, 0xa6, 0x90, 0xe6, 0x9a, 0xa7, 0xdb, 0x65, 0x70,
0xa6, 0x09, 0x93, 0x6f, 0x08, 0xdf, 0x2c, 0xdb, 0xe9, 0x28, 0x8d, 0x40,
0x1b, 0x57, 0x5e, 0xa0, 0xea, 0xf4, 0xec, 0x13, 0x65, 0x1b, 0x71, 0x4a,
0x4d, 0xdc, 0x80, 0x48, 0x4f, 0xf2, 0xe5, 0xa9, 0xfb, 0x85, 0x6c, 0xb7,
0x1e, 0x9d, 0xdb, 0xf4, 0x18, 0x48, 0x10, 0x79, 0x17, 0xea, 0xc3, 0x3d,
0x87, 0xd8, 0xb4, 0x79, 0x6d, 0x14, 0x50, 0xad, 0xd2, 0xbf, 0x3d, 0x4e,
0xfc, 0x0d, 0xe2, 0xc5, 0x03, 0x94, 0x75, 0x80, 0x73, 0x4d, 0xa5, 0xa1,
0x91, 0xfe, 0x1c, 0xde, 0x15, 0x17, 0xac, 0x89, 0x71, 0x2a, 0x6f, 0x0f,
0x67, 0x0a, 0xd3, 0x9c, 0x30, 0xa1, 0x68, 0xfb, 0xcf, 0x70, 0x17, 0xca,
0xd9, 0x40, 0xfc, 0xf8, 0x1b, 0xbf, 0xce, 0xb0, 0xc4, 0xae, 0xf4, 0x4a,
0x2d, 0xa9, 0x99, 0x87, 0x06, 0x42, 0x09, 0x86, 0x22, 0x6a, 0x84, 0x40,
0x39, 0xf4, 0xbb, 0xac, 0x56, 0x18, 0xf7, 0x9a, 0x1c, 0x01, 0x81, 0x5c,
0x8c, 0x6e, 0x41, 0xf2, 0x5d, 0x19, 0x2c, 0x17, 0x1c, 0x49, 0x46, 0xd9,
0x1c, 0x7e, 0x93, 0x12, 0x13, 0xc8, 0x67, 0x99, 0xc2, 0xea, 0x83, 0xe3,
0xa2, 0x8c, 0x0e, 0xb8, 0x3b, 0x2a, 0xdf, 0x1c, 0xbf, 0x4b, 0x8b, 0x6f,
0x1a, 0xb8, 0xee, 0x97, 0x67, 0x4a, 0xd8, 0xab, 0xaf, 0x8b, 0xa4, 0xda,
0x5c, 0x87, 0x1e, 0x20, 0xb8, 0xc5, 0xf3, 0xb1, 0xc4, 0x98, 0xa2, 0x37,
0xf8, 0x9e, 0xc6, 0x9a, 0x6b, 0xa5, 0xad, 0xf6, 0x78, 0x96, 0x0e, 0x82,
0x8f, 0x04, 0x46, 0x1c, 0xb2, 0xa5, 0xfd, 0x9a, 0x30, 0x51, 0x28, 0xfd,
0x52, 0x04, 0x15, 0x03, 0xd5, 0x3c, 0xad, 0xfe, 0xf6, 0x78, 0xe0, 0xea,
0x35, 0xef, 0x65, 0xb5, 0x21, 0x76, 0xdb, 0xa4, 0xef, 0xcb, 0x72, 0xef,
0x54, 0x6b, 0x01, 0x0d, 0xc7, 0xdd, 0x1a
};


BOOL checkReceiptSecurity(NSString *purchase_info_string, NSString *signature_string, CFDateRef purchaseDate)
{
BOOL valid = NO;
SecCertificateRef leaf = NULL, intermediate = NULL;
SecTrustRef trust = NULL;
SecPolicyRef policy = SecPolicyCreateBasicX509();

NSData *certificate_data;
NSArray *anchors;

/*
 Parse inputs:
 purchase_info_string and signature_string are base64 encoded JSON blobs that need to
 be decoded.
 */

require([purchase_info_string canBeConvertedToEncoding:NSASCIIStringEncoding] &&
        [signature_string canBeConvertedToEncoding:NSASCIIStringEncoding], outLabel);

size_t purchase_info_length;
uint8_t *purchase_info_bytes = base64_decode([purchase_info_string cStringUsingEncoding:NSASCIIStringEncoding],
                                             &purchase_info_length);

size_t signature_length;
uint8_t *signature_bytes = base64_decode([signature_string cStringUsingEncoding:NSASCIIStringEncoding],
                                         &signature_length);

require(purchase_info_bytes && signature_bytes, outLabel);

/*
 Binary format looks as follows:

 RECEIPTVERSION | SIGNATURE | CERTIFICATE SIZE | CERTIFICATE
 1 byte           128         4 bytes
 big endian

 Extract version, signature and certificate(s).
 Check receipt version == 2.
 Sanity check that signature is 128 bytes.
 Sanity check certificate size <= remaining payload data.
 */

1 个答案:

答案 0 :(得分:1)

已经很长时间了,但我希望我能解决你的问题。

嗯,21007代码意味着您正在尝试通过生产验证网址验证沙箱收据。

这就是我对代码的处理方式:调用以下方法。

[self validarTransacao:transacao naStoreURL:@"https://buy.itunes.apple.com/verifyReceipt"];

1 - 尝试通过生产验证网址验证收据,然后,如果它返回21007,我会尝试使用沙箱网址。

-(void)validarTransacao:(SKPaymentTransaction *)transacao naStoreURL:(NSString *)url
{
    receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle]  appStoreReceiptURL]];

    if (!receipt)
    {
        receipt = transacao.transactionReceipt;
    }

    NSError *erro;

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];

    [request setHTTPMethod:@"POST"];

    [request setHTTPBody:[NSJSONSerialization dataWithJSONObject:@{@"receipt-data": [receipt base64EncodedStringWithOptions:0], @"password" : @"1234567890123456789"} options:0 error:&erro]];

    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError)
    {
         if (!connectionError)
         {
             NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];

             NSLog(@"%i", [[jsonResponse objectForKey:@"status"] intValue]);

             if ([[jsonResponse objectForKey:@"status"] intValue] == 0)
             {
                 // Authorized
             }
             else
             {
                 // Receipt error
                 switch ([[jsonResponse objectForKey:@"status"] intValue])
                 {
                     case 21000:
                         // Not possible to read the receipt
                         break;
                     case 21002:
                         // Receipt not sent
                         break;
                     case 21003:
                         // Not authenticated
                         break;
                     case 21004:
                         // Shared secret not found
                         break;
                     case 21005:
                         // Server not available
                         [SVProgressHUD showErrorWithStatus:@"Não foi possível conectar com a AppStore. Faremos uma nova tentativa em breve"];
                         break;
                     case 21006:
                         // Valid receipt, subsctiption expired
                         break;
                     case 21007:
                         // Test receipt try on sandbox 
                         [self validarTransacao:transacao naStoreURL:@"https://sandbox.itunes.apple.com/verifyReceipt"];
                         break;
                     case 21008:
                         // Production receipt try on buy
                         [self validarTransacao:transacao naStoreURL:@"https://buy.itunes.apple.com/verifyReceipt"];
                         break;
                 }
             }
         }
         else
         {
             [SVProgressHUD showErrorWithStatus:@"Não foi possível conectar com a AppStore. Faremos uma nova tentativa em breve"];
         }
     }];

}

就是这样!