2012年7月,一位俄罗斯黑客发现 In-App Purchase 系统中的漏洞并将其公之于众。Apple 响应了“iOS 在应用内购买收据验证”(TP40012484),其中包含了用于执行验证的 mostly-complete 单例类 VerificationController。
Apple 的 VerificationController 需要在代码中做出多项更改,并添加您自己的回调和 base64 实现。这个小型类集合以此为基础,提供了一个基于代理的简单系统来执行验证。
它在公共领域,RRBase64Manager 中没有定义 USE_CODE_REQUIRING_ATTRIBUTION。如果是定义的(这是默认情况),将使用更有效的需要提供引用的 base64 实现;详细信息请参阅该文件。
需要 iOS 5.x。在 iOS 4 上,所有购买都将进行验证(就像您完全没有使用该类一样)。另外,一定要添加链接器标志 "-fobjc-arc -weak-lSystem.B -weaklobjc" 来避免在启用 ARC 时运行 iOS 4 时崩溃。
iOS 5 的需求仅是因为使用了 NSJSONSerialization;欢迎使用用 iOS 4 兼容实现替换 NSJSONSerialization 的补丁(在 NSData+RRTransactionParsingAdditions 中)。
此示例假设您的 SKPaymentTransactionObserver 符合类还将实现 RRVerificationControllerDelegate。
您需要您的 iTunes Connect In App Purchase Shared Secret,您可以在 iTunes Connect -> Manage Apps -> 您的应用 -> Manage In App Purchases 中找到/生成。
#define MY_SHARED_SECRET @"1234567890abcdef1234567890abcdef"
- (void)paymentQueue:(id)queue updatedTransactions:(NSArray *)transactions
{
[RRVerificationController sharedInstance].itcContentProviderSharedSecret = MY_SHARED_SECRET;
for (SKPaymentTransaction *transaction in transactions)
{
switch ([transaction transactionState])
{
case SKPaymentTransactionStatePurchasing:
break;
case SKPaymentTransactionStatePurchased:
/* If verification is successful, the delegate's verificationControllerDidVerifyPurchase:isValid: method will be called to take appropriate action and complete the transaction */
if ([[RRVerificationController sharedInstance] verifyPurchase:transaction
withDelegate:self
error:NULL] == FALSE) {
[self failedTransaction:transaction];
}
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
/* If verification is successful, the delegate's verificationControllerDidVerifyPurchase:isValid: method will be called to take appropriate action and complete the transaction */
if ([[RRVerificationController sharedInstance] verifyPurchase:transaction
withDelegate:self
error:NULL] == FALSE) {
[self failedTransaction:transaction];
}
default:
break;
}
}
}
RRVerificationControllerDelegate 的实现如下
- (void)completeTransaction:(SKPaymentTransaction *)transaction
{
/* Handled a completed and verified new transcation */
}
- (void)restoreTransaction:(SKPaymentTransaction *)transaction
{
/* Handled a completed and verified restored transcation.
* For many use cases this is simply [self completeTransaction:transaction]
*/
}
/*!
* @brief Verification with Apple's server completed successfully
*
* @param transaction The transaction being verified
* @param isValid YES if Apple reported the transaction was valid; NO if Apple said it was not valid or if the server's validation reply was inconsistent with validity
*/
- (void)verificationControllerDidVerifyPurchase:(SKPaymentTransaction *)transaction isValid:(BOOL)isValid
{
if (isValid) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
[self restoreTransaction:transaction];
default:
break;
}
} else
[self displayFailureMessage];
if (transaction.transactionState != SKPaymentTransactionStatePurchasing)
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
/*!
* @brief The attempt at verification could not be completed
*
* This does not mean that Apple reported the transaction was invalid, but
* rather indicates a communication failure, a server error, or the like.
*
* @param transaction The transaction being verified
* @param error An NSError describing the error. May be nil if the cause of the error was unknown (or if nobody has written code to report an NSError for that failure...)
*/
- (void)verificationControllerDidFailToVerifyPurchase:(SKPaymentTransaction *)transaction error:(NSError *)error
{
NSString *message = NSLocalizedString(@"Your purchase could not be verified with Apple's servers. Please try again later.", nil);
if (error) {
message = [message stringByAppendingString:@"\n\n"];
message = [message stringByAppendingFormat:NSLocalizedString(@"The error was: %@.", nil), error.localizedDescription];
}
[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Purchase Verification Failed", nil)
message:message
delegate:nil
cancelButtonTitle:NSLocalizedString(@"Dismiss", nil)
otherButtonTitles:nil] show];
}
其他所有事情都会自动处理;只需将您自己的逻辑放入 performUpgrade、failedTransaction: 和 displayFailureMessage 中,无需更改验证控制器代码。
这些类旨在启用 ARC 进行编译。如果您没有在项目范围内启用 ARC,请确保在每个文件的“编译源”构建阶段中都指定 -fobjc-arc
您需要链接对 Security.framework 的请求才能使用这些类。