Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Always returns error object upon failure, describing the the failure …

…cases. (Based on Error Handling Programming Guide)
  • Loading branch information...
commit c34027f7b12e73e1375c4741b605c997b90f1bed 1 parent 183fa98
@lxcid authored
Showing with 105 additions and 9 deletions.
  1. +31 −0 CargoBay/CargoBay.h
  2. +74 −9 CargoBay/CargoBay.m
View
31 CargoBay/CargoBay.h
@@ -29,6 +29,37 @@
@class AFHTTPClient;
+extern NSString * const CBErrorDomain;
+
+typedef NS_ENUM(NSInteger, CBStatusCode) {
+ CBStatusOK = 0,
+
+ // Status codes for auto-renewable subscriptions
+ CBStatusCannotParseJSON = 21000,
+ CBStatusMalformedReceiptData = 21002,
+ CBStatusCannotAuthenticateReceiptData = 21003,
+ CBStatusSharedSecretDoesNotMatch = 21004,
+ CBStatusReceiptServerUnavailable = 21005,
+ CBStatusReceiptValidButSubscriptionExpired = 21006,
+ CBStatusSandboxReceiptSentToProduction = 21007,
+ CBStatusProductionReceiptSentToSandbox = 21008
+};
+
+typedef NS_ENUM(NSInteger, CBErrorCode) {
+ CBErrorUnknown = -1,
+
+ CBErrorPurchaseInfoDoesNotMatchReceipt = 1,
+ CBErrorTransactionDoesNotMatchesPurchaseInfo = 2,
+ CBErrorCannotExtractPurchaseInfoFromTransactionReceipt = 3,
+
+ // Error codes derived from status codes for auto-renewable subscriptions
+ CBErrorCannotParseJSON = CBStatusCannotParseJSON,
+ CBErrorMalformedReceiptData = CBStatusMalformedReceiptData,
+ CBErrorCannotAuthenticateReceiptData = CBStatusCannotAuthenticateReceiptData,
+ CBErrorSharedSecretDoesNotMatch = CBStatusSharedSecretDoesNotMatch,
+ CBErrorReceiptServerUnavailable = CBStatusReceiptServerUnavailable
+};
+
@interface CargoBay : NSObject <SKPaymentTransactionObserver>
@property (nonatomic) AFHTTPClient *productsHTTPClient;
View
83 CargoBay/CargoBay.m
@@ -27,6 +27,8 @@
#import "AFHTTPClient.h"
#import "AFJSONRequestOperation.h"
+NSString * const CBErrorDomain = @"me.mattt.CargoBay.ErrorDomain";
+
static NSString * const kCargoBaySandboxReceiptVerificationBaseURLString = @"https://sandbox.itunes.apple.com/";
static NSString * const kCargoBayProductionReceiptVerificationBaseURLString = @"https://buy.itunes.apple.com/";
@@ -142,14 +144,14 @@ static BOOL CBValidateTrust(SecTrustRef trust, NSError * __autoreleasing *error)
trusted = [extendedValidation isKindOfClass:[NSValue class]] && [extendedValidation boolValue];
}
- if (trust) {
- if (!trusted && error) {
+ if (!trusted) {
+ if (error != NULL) {
*error = [NSError errorWithDomain:@"kSecTrustError" code:(NSInteger)result userInfo:nil];
}
- return trusted;
+ return NO;
}
- return NO;
+ return YES;
#else
return YES;
#endif
@@ -157,12 +159,44 @@ static BOOL CBValidateTrust(SecTrustRef trust, NSError * __autoreleasing *error)
static BOOL CBValidatePurchaseInfoMatchesReceipt(NSDictionary *purchaseInfo, NSDictionary *receipt, NSError * __autoreleasing *error) {
if (![[receipt objectForKey:@"bid"] isEqual:[purchaseInfo objectForKey:@"bid"]]) {
+ if (error != NULL) {
+ NSDictionary *userInfo =
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSString stringWithFormat:@"Purchase info does not match receipt because purchase info's bundle ID (%@) does not match receipt's bundle ID (%@).", [purchaseInfo objectForKey:@"bid"], [receipt objectForKey:@"bid"]], NSLocalizedDescriptionKey,
+ [NSString stringWithFormat:@"Purchase info's bundle ID (%@) does not match receipt's bundle ID (%@).", [purchaseInfo objectForKey:@"bid"], [receipt objectForKey:@"bid"]], NSLocalizedFailureReasonErrorKey,
+ nil];
+ *error = [NSError errorWithDomain:CBErrorDomain code:CBErrorPurchaseInfoDoesNotMatchReceipt userInfo:userInfo];
+ }
return NO;
} else if (![[receipt objectForKey:@"product_id"] isEqual:[purchaseInfo objectForKey:@"product-id"]]) {
+ if (error != NULL) {
+ NSDictionary *userInfo =
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSString stringWithFormat:@"Purchase info does not match receipt because purchase info's product ID (%@) does not match receipt's product ID (%@).", [purchaseInfo objectForKey:@"product-id"], [receipt objectForKey:@"product_id"]], NSLocalizedDescriptionKey,
+ [NSString stringWithFormat:@"Purchase info's product ID (%@) does not match receipt's product ID (%@).", [purchaseInfo objectForKey:@"product-id"], [receipt objectForKey:@"product_id"]], NSLocalizedFailureReasonErrorKey,
+ nil];
+ *error = [NSError errorWithDomain:CBErrorDomain code:CBErrorPurchaseInfoDoesNotMatchReceipt userInfo:userInfo];
+ }
return NO;
} else if (![[receipt objectForKey:@"quantity"] isEqual:[purchaseInfo objectForKey:@"quantity"]]) {
+ if (error != NULL) {
+ NSDictionary *userInfo =
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSString stringWithFormat:@"Purchase info does not match receipt because purchase info's quantity (%@) does not match receipt's quantity (%@).", [purchaseInfo objectForKey:@"quantity"], [receipt objectForKey:@"quantity"]], NSLocalizedDescriptionKey,
+ [NSString stringWithFormat:@"Purchase info's quantity (%@) does not match receipt's quantity (%@).", [purchaseInfo objectForKey:@"quantity"], [receipt objectForKey:@"quantity"]], NSLocalizedFailureReasonErrorKey,
+ nil];
+ *error = [NSError errorWithDomain:CBErrorDomain code:CBErrorPurchaseInfoDoesNotMatchReceipt userInfo:userInfo];
+ }
return NO;
} else if (![[receipt objectForKey:@"item_id"] isEqual:[purchaseInfo objectForKey:@"item-id"]]) {
+ if (error != NULL) {
+ NSDictionary *userInfo =
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSString stringWithFormat:@"Purchase info does not match receipt because purchase info's item ID (%@) does not match receipt's item ID (%@).", [purchaseInfo objectForKey:@"item-id"], [receipt objectForKey:@"item_id"]], NSLocalizedDescriptionKey,
+ [NSString stringWithFormat:@"Purchase info's item ID (%@) does not match receipt's item ID (%@).", [purchaseInfo objectForKey:@"item-id"], [receipt objectForKey:@"item_id"]], NSLocalizedFailureReasonErrorKey,
+ nil];
+ *error = [NSError errorWithDomain:CBErrorDomain code:CBErrorPurchaseInfoDoesNotMatchReceipt userInfo:userInfo];
+ }
return NO;
}
@@ -176,6 +210,14 @@ static BOOL CBValidatePurchaseInfoMatchesReceipt(NSDictionary *purchaseInfo, NSD
if (![transactionUniqueVendorIdentifier isEqual:receiptVendorIdentifier] || ![transactionUniqueVendorIdentifier isEqual:deviceIdentifier])
{
#if !TARGET_IPHONE_SIMULATOR
+ if (error != NULL) {
+ NSDictionary *userInfo =
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSString stringWithFormat:@"Purchase info does not match receipt because device's identifier for vendor (%@) does not match purchase info's (%@) and receipt's unique vendor identifier (%@).", deviceIdentifier, transactionUniqueVendorIdentifier, receiptVendorIdentifier], NSLocalizedDescriptionKey,
+ [NSString stringWithFormat:@"Device's identifier for vendor (%@) does not match purchase info's (%@) and receipt's unique vendor identifier (%@).", deviceIdentifier, transactionUniqueVendorIdentifier, receiptVendorIdentifier], NSLocalizedFailureReasonErrorKey,
+ nil];
+ *error = [NSError errorWithDomain:CBErrorDomain code:CBErrorPurchaseInfoDoesNotMatchReceipt userInfo:userInfo];
+ }
return NO;
#endif
}
@@ -190,6 +232,14 @@ static BOOL CBValidatePurchaseInfoMatchesReceipt(NSDictionary *purchaseInfo, NSD
NSString *receiptUniqueIdentifier = [receipt objectForKey:@"unique_identifier"];
if (![transactionUniqueIdentifier isEqual:receiptUniqueIdentifier] || ![transactionUniqueIdentifier isEqual:deviceIdentifier])
{
+ if (error != NULL) {
+ NSDictionary *userInfo =
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSString stringWithFormat:@"Purchase info does not match receipt because device's unique identifier (%@) does not match purchase info's (%@) and receipt's unique identifier (%@).", deviceIdentifier, transactionUniqueIdentifier, receiptUniqueIdentifier], NSLocalizedDescriptionKey,
+ [NSString stringWithFormat:@"Device's unique identifier (%@) does not match purchase info's (%@) and receipt's unique identifier (%@).", deviceIdentifier, transactionUniqueIdentifier, receiptUniqueIdentifier], NSLocalizedFailureReasonErrorKey,
+ nil];
+ *error = [NSError errorWithDomain:CBErrorDomain code:CBErrorPurchaseInfoDoesNotMatchReceipt userInfo:userInfo];
+ }
return NO;
}
}
@@ -533,6 +583,14 @@ calculating SHA1(version|purchaseinfo)
// Check the authenticity of the receipt response/signature etc.
if (!CBCheckReceiptSecurity(thePurchaseInfo, theSignature, (__bridge CFDateRef)thePurchaseDate)) {
+ if (theError != NULL) {
+ NSDictionary *theUserInfo =
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ @"Cannot extract purchase info from transaction receipt because purchase info failed to validate against its signature.", NSLocalizedDescriptionKey,
+ @"Purchase info failed to validate against its signature.", NSLocalizedFailureReasonErrorKey,
+ nil];
+ *theError = [NSError errorWithDomain:CBErrorDomain code:CBErrorCannotExtractPurchaseInfoFromTransactionReceipt userInfo:theUserInfo];
+ }
return nil;
}
@@ -663,8 +721,8 @@ - (void)verifyTransactionReceipt:(NSData *)transactionReceipt
NSInteger status = [responseObject valueForKey:@"status"] ? [[responseObject valueForKey:@"status"] integerValue] : NSNotFound;
switch (status) {
- case 0: // Status 0: The receipt is valid.
- case 21006: { // Status 21006: This receipt is valid but the subscription has expired.
+ case CBStatusOK: // Status 0: The receipt is valid.
+ case CBStatusReceiptValidButSubscriptionExpired: { // Status 21006: This receipt is valid but the subscription has expired.
NSDictionary *receipt = [responseObject valueForKey:@"receipt"];
NSError *error = nil;
@@ -717,14 +775,14 @@ - (void)verifyTransactionReceipt:(NSData *)transactionReceipt
}
}
} break;
- case 21007: { // Status 21007: This receipt is a sandbox receipt, but it was sent to the production service for verification.
+ case CBStatusSandboxReceiptSentToProduction: { // Status 21007: This receipt is a sandbox receipt, but it was sent to the production service for verification.
[self verifyTransactionReceipt:transactionReceipt
client:[self sandboxReceiptVerificationClient]
password:password
success:success
failure:failure];
} break;
- case 21008: { // Status 21008: This receipt is a production receipt, but it was sent to the sandbox service for verification.
+ case CBStatusProductionReceiptSentToSandbox: { // Status 21008: This receipt is a production receipt, but it was sent to the sandbox service for verification.
[self verifyTransactionReceipt:transactionReceipt
client:[self productionReceiptVerificationClient]
password:password
@@ -736,7 +794,7 @@ - (void)verifyTransactionReceipt:(NSData *)transactionReceipt
NSString *exception = [responseObject valueForKey:@"exception"];
NSDictionary *userInfo = exception ? [NSDictionary dictionaryWithObject:exception forKey:NSLocalizedFailureReasonErrorKey] : nil;
- NSError *error = [[NSError alloc] initWithDomain:SKErrorDomain code:status userInfo:userInfo];
+ NSError *error = [[NSError alloc] initWithDomain:CBErrorDomain code:status userInfo:userInfo];
failure(error);
}
} break;
@@ -882,6 +940,13 @@ - (BOOL)isTransactionAndItsReceiptValid:(SKPaymentTransaction *)theTransaction e
// Ensure the transaction itself is legit
if (!CBValidateTransactionMatchesPurchaseInfo(theTransaction, thePurchaseInfoDictionary)) {
+ if (theError != NULL) {
+ NSDictionary *theUserInfo =
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ @"Transaction does not match purchase info", NSLocalizedDescriptionKey,
+ nil];
+ *theError = [NSError errorWithDomain:CBErrorDomain code:CBErrorTransactionDoesNotMatchesPurchaseInfo userInfo:theUserInfo];
+ }
return NO;
}
Please sign in to comment.
Something went wrong with that request. Please try again.