#ios #objective-c
#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:amp;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:amp;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 amp;amp; transaction.transactionReceipt amp;amp; [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 amp;amp; 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:@»уникальный идентификатор»];
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:amp;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:amp;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_resu<
if ((noErr == SecTrustEvaluate(trust, amp;trust_result)) amp;amp; (trust_result == kSecTrustResultUnspecified))
{
NSDictionary *trust_info = (__bridge_transfer NSDictionary *)SecTrustCopyInfo(trust);
id hasEV = [trust_info objectForKey:(__bridge NSString *)kSecTrustInfoExtendedValidationKey];
trusted = [hasEV isKindOfClass:[NSValue class]] amp;amp; [hasEV boolValue];
}
if (trust)
{
if (!trusted amp;amp; 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] amp;amp;
[signature_string canBeConvertedToEncoding:NSASCIIStringEncoding], outLabel);
size_t purchase_info_length;
uint8_t *purchase_info_bytes = base64_decode([purchase_info_string cStringUsingEncoding:NSASCIIStringEncoding],
amp;purchase_info_length);
size_t signature_length;
uint8_t *signature_bytes = base64_decode([signature_string cStringUsingEncoding:NSASCIIStringEncoding],
amp;signature_length);
require(purchase_info_bytes amp;amp; 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:
Прошло очень много времени, но я надеюсь, что смогу решить вашу проблему.
Ну, код 21007 означает, что вы пытаетесь проверить квитанцию из изолированной среды через URL-адрес проверки производства.
Вот как я поступил со своим кодом: вызвал метод, приведенный ниже.
[self validarTransacao:transacao naStoreURL:@"https://buy.itunes.apple.com/verifyReceipt"];
1 — пытается подтвердить получение через URL-адрес проверки производства, а затем, если он возвращает 21007, я пробую вместо этого URL-адрес изолированной среды.
-(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:amp;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"];
}
}];
}
Вот и все!