在iOS8中的应用程序购买收据validation

我们正在关注来自http://www.raywenderlich.com/23266/in-app-purchases-in-ios-6-tutorial-consumables-and-receipt-validation的raywenderlinch教程。

但是在我们的项目中导入verificationController类时,我遇到了一些错误。 现在,我正在使用iOS8。

Error is: Implicit declaration of function 'checkReciptSecurity' is invalid in C99 

我还在apple开发者网站上搜索了Verification类的示例代码,找不到他们的页面。

请给我你的解决方案,或提供更新到iOS8的validation类链接。

在VerificationController.h中放置函数原型如下:

 - (void)verifyPurchase:(SKPaymentTransaction *)transaction completionHandler:(VerifyCompletionHandler)completionHandler; BOOL checkReceiptSecurity(NSString *purchase_info_string, NSString *signature_string, CFDateRef purchaseDate); 

这样做的原因是调用函数checkReceiptSecurity的行号是在函数声明之前。

您必须修改VerificationController.m文件代码。 我在这里修改了代码。

 #import "VerificationController.h" #import "NSData+Base64.h" static VerificationController *singleton; @implementation VerificationController { NSMutableDictionary * _completionHandlers; } + (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 = ITMS_SANDBOX_VERIFY_RECEIPT_URL; //ITMS_PROD_VERIFY_RECEIPT_URL; // 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. 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 *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]; 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 Base 64 encoding - (NSString *)encodeBase64:(const uint8_t *)input length:(NSInteger)length { NSData * data = [NSData dataWithBytes:input length:length]; return [data base64EncodedString]; } - (NSString *)decodeBase64:(NSString *)input length:(NSInteger *)length { NSData * data = [NSData dataFromBase64String:input]; return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; } char* base64_encode(const void* buf, size_t size) { size_t outputLength; return NewBase64Encode(buf, size, NO, &outputLength); } void * base64_decode(const char* s, size_t * data_len) { return NewBase64Decode(s, strlen(s), data_len); } @end #pragma mark #pragma mark Check Receipt signature #include  #include  #include  unsigned int iTS_intermediate_der_len = 1039; unsigned char iTS_intermediate_der[] = { put the hexacode here }; 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. */ #pragma pack(push, 1) struct signature_blob { uint8_t version; uint8_t signature[128]; uint32_t cert_len; uint8_t certificate[]; } *signature_blob_ptr = (struct signature_blob *)signature_bytes; #pragma pack(pop) uint32_t certificate_len; /* Make sure the signature blob is long enough to safely extract the version and cert_len fields, then perform a sanity check on the fields. */ require(signature_length > offsetof(struct signature_blob, certificate), outLabel); require(signature_blob_ptr->version == 2, outLabel); certificate_len = ntohl(signature_blob_ptr->cert_len); require(signature_length - offsetof(struct signature_blob, certificate) >= certificate_len, outLabel); /* Validate certificate chains back to valid receipt signer; policy approximation for now set intermediate as a trust anchor; current intermediate lapses in 2016. */ certificate_data = [NSData dataWithBytes:signature_blob_ptr->certificate length:certificate_len]; require(leaf = SecCertificateCreateWithData(NULL, (__bridge CFDataRef) certificate_data), outLabel); certificate_data = [NSData dataWithBytes:iTS_intermediate_der length:iTS_intermediate_der_len]; require(intermediate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef) certificate_data), outLabel); anchors = [NSArray arrayWithObject:(__bridge id)intermediate]; require(anchors, outLabel); require_noerr(SecTrustCreateWithCertificates(leaf, policy, &trust), outLabel); require_noerr(SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef) anchors), outLabel); if (purchaseDate) { require_noerr(SecTrustSetVerifyDate(trust, purchaseDate), outLabel); } SecTrustResultType trust_result; require_noerr(SecTrustEvaluate(trust, &trust_result), outLabel); require(trust_result == kSecTrustResultUnspecified, outLabel); require(2 == SecTrustGetCertificateCount(trust), outLabel); /* Chain is valid, use leaf key to verify signature on receipt by calculating SHA1(version|purchaseInfo) */ CC_SHA1_CTX sha1_ctx; uint8_t to_be_verified_data[CC_SHA1_DIGEST_LENGTH]; CC_SHA1_Init(&sha1_ctx); CC_SHA1_Update(&sha1_ctx, &signature_blob_ptr->version, sizeof(signature_blob_ptr->version)); CC_SHA1_Update(&sha1_ctx, purchase_info_bytes, purchase_info_length); CC_SHA1_Final(to_be_verified_data, &sha1_ctx); SecKeyRef receipt_signing_key = SecTrustCopyPublicKey(trust); require(receipt_signing_key, outLabel); require_noerr(SecKeyRawVerify(receipt_signing_key, kSecPaddingPKCS1SHA1, to_be_verified_data, sizeof(to_be_verified_data), signature_blob_ptr->signature, sizeof(signature_blob_ptr->signature)), outLabel); /* Optional: Verify that the receipt certificate has the 1.2.840.113635.100.6.5.1 Null OID The signature is a 1024-bit RSA signature. */ valid = YES; outLabel: if (leaf) CFRelease(leaf); if (intermediate) CFRelease(intermediate); if (trust) CFRelease(trust); if (policy) CFRelease(policy); return valid; }