Skip to content
This repository has been archived by the owner on Mar 4, 2020. It is now read-only.

Commit

Permalink
Mac Compatibility warning fixed, receipt validation pending
Browse files Browse the repository at this point in the history
  • Loading branch information
MugunthKumar committed Dec 7, 2011
1 parent bd40370 commit 22347ae
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 73 deletions.
3 changes: 0 additions & 3 deletions Externals/SFHFKeychainUtils.h
Expand Up @@ -27,9 +27,6 @@
// OTHER DEALINGS IN THE SOFTWARE.
//

#import <UIKit/UIKit.h>


@interface SFHFKeychainUtils : NSObject {

}
Expand Down
89 changes: 71 additions & 18 deletions MKSKProduct.m
Expand Up @@ -44,6 +44,75 @@ @implementation MKSKProduct
@synthesize theConnection;
@synthesize dataFromConnection;

+(NSString*) deviceId {

#if TARGET_OS_IPHONE
UIDevice *dev = [UIDevice currentDevice];
NSString *uniqueID;
if ([dev respondsToSelector:@selector(uniqueIdentifier)])
uniqueID = [dev valueForKey:@"uniqueIdentifier"];
else {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
id uuid = [defaults objectForKey:@"uniqueID"];
if (uuid)
uniqueID = (NSString *)uuid;
else {
CFStringRef cfUuid = CFUUIDCreateString(NULL, CFUUIDCreate(NULL));
uniqueID = (__bridge NSString *)cfUuid;
CFRelease(cfUuid);
[defaults setObject:uniqueID forKey:@"uniqueID"];
}
}
#elif TARGET_OS_MAC

kern_return_t kernResult;
mach_port_t master_port;
CFMutableDictionaryRef matchingDict;
io_iterator_t iterator;
io_object_t service;
CFDataRef macAddress = nil;

kernResult = IOMasterPort(MACH_PORT_NULL, &master_port);
if (kernResult != KERN_SUCCESS) {
printf("IOMasterPort returned %d\n", kernResult);
return nil;
}

matchingDict = IOBSDNameMatching(master_port, 0, "en0");
if(!matchingDict) {
printf("IOBSDNameMatching returned empty dictionary\n");
return nil;
}

kernResult = IOServiceGetMatchingServices(master_port, matchingDict, &iterator);
if (kernResult != KERN_SUCCESS) {
printf("IOServiceGetMatchingServices returned %d\n", kernResult);
return nil;
}

while((service = IOIteratorNext(iterator)) != 0)
{
io_object_t parentService;

kernResult = IORegistryEntryGetParentEntry(service, kIOServicePlane, &parentService);
if(kernResult == KERN_SUCCESS)
{
if(macAddress)
CFRelease(macAddress);
macAddress = IORegistryEntryCreateCFProperty(parentService, CFSTR("IOMACAddress"), kCFAllocatorDefault, 0);
IOObjectRelease(parentService);
}
else {
printf("IORegistryEntryGetParentEntry returned %d\n", kernResult);
}

IOObjectRelease(service);
}

return [[NSString alloc] initWithData:(__bridge NSData*) macAddress encoding:NSASCIIStringEncoding];
#endif
}

-(id) initWithProductId:(NSString*) aProductId receiptData:(NSData*) aReceipt
{
if((self = [super init]))
Expand All @@ -54,7 +123,6 @@ -(id) initWithProductId:(NSString*) aProductId receiptData:(NSData*) aReceipt
return self;
}


#pragma mark -
#pragma mark In-App purchases promo codes support
// This function is only used if you want to enable in-app purchases for free for reviewers
Expand All @@ -68,23 +136,8 @@ +(void) verifyProductForReviewAccess:(NSString*) productId
{
onReviewRequestVerificationSucceeded = [completionBlock copy];
onReviewRequestVerificationFailed = [errorBlock copy];

UIDevice *dev = [UIDevice currentDevice];
NSString *uniqueID;
if ([dev respondsToSelector:@selector(uniqueIdentifier)])
uniqueID = [dev valueForKey:@"uniqueIdentifier"];
else {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
id uuid = [defaults objectForKey:@"uniqueID"];
if (uuid)
uniqueID = (NSString *)uuid;
else {
CFStringRef cfUuid = CFUUIDCreateString(NULL, CFUUIDCreate(NULL));
uniqueID = (__bridge NSString *)cfUuid;
CFRelease(cfUuid);
[defaults setObject:uniqueID forKey:@"uniqueID"];
}
}

NSString *uniqueID = [self deviceId];
// check udid and featureid with developer's server

NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@/%@", OWN_SERVER, @"featureCheck.php"]];
Expand Down
72 changes: 53 additions & 19 deletions MKStoreManager.m
Expand Up @@ -383,6 +383,28 @@ - (NSMutableDictionary *)pricesDictionary {
return priceDict;
}

-(void) showAlertWithTitle:(NSString*) title message:(NSString*) message {

#if TARGET_OS_IPHONE
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title
message:message
delegate:nil
cancelButtonTitle:NSLocalizedString(@"Dismiss", @"")
otherButtonTitles:nil];
[alert show];
#elif TARGET_OS_MAC
NSAlert *alert = [[NSAlert alloc] init];
[alert addButtonWithTitle:NSLocalizedString(@"Dismiss", @"")];

[alert setMessageText:title];
[alert setInformativeText:message];
[alert setAlertStyle:NSInformationalAlertStyle];

[alert runModal];

#endif
}

- (void) buyFeature:(NSString*) featureId
onComplete:(void (^)(NSString*)) completionBlock
onCancelled:(void (^)(void)) cancelBlock
Expand All @@ -395,12 +417,8 @@ - (void) buyFeature:(NSString*) featureId
{
if([isAllowed boolValue])
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Review request approved", @"")
message:NSLocalizedString(@"You can use this feature for reviewing the app.", @"")
delegate:self
cancelButtonTitle:NSLocalizedString(@"Dismiss", @"")
otherButtonTitles:nil];
[alert show];
[self showAlertWithTitle:NSLocalizedString(@"Review request approved", @"")
message:NSLocalizedString(@"You can use this feature for reviewing the app.", @"")];

if(self.onTransactionCompleted)
self.onTransactionCompleted(featureId);
Expand Down Expand Up @@ -433,12 +451,8 @@ -(void) addToQueue:(NSString*) productId
}
else
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"In-App Purchasing disabled", @"")
message:NSLocalizedString(@"Check your parental control settings and try again later", @"")
delegate:self
cancelButtonTitle:NSLocalizedString(@"Dismiss", @"")
otherButtonTitles: nil];
[alert show];
[self showAlertWithTitle:NSLocalizedString(@"In-App Purchasing disabled", @"")
message:NSLocalizedString(@"Check your parental control settings and try again later", @"")];
}
}

Expand Down Expand Up @@ -507,6 +521,11 @@ - (void) startVerifyingSubscriptionReceipts
}
}

-(NSData*) receiptFromBundle {

return nil;
}

#pragma mark In-App purchases callbacks
// In most cases you don't have to touch these methods
-(void) provideContent: (NSString*) productIdentifier
Expand All @@ -515,6 +534,9 @@ -(void) provideContent: (NSString*) productIdentifier
MKSKSubscriptionProduct *subscriptionProduct = [self.subscriptionProducts objectForKey:productIdentifier];
if(subscriptionProduct)
{
// MAC In App Purchases can never be a subscription product (at least as on Dec 2011)
// so this can be safely ignored.

subscriptionProduct.receipt = receiptData;
[subscriptionProduct verifyReceiptOnComplete:^(NSNumber* isActive)
{
Expand All @@ -530,6 +552,23 @@ -(void) provideContent: (NSString*) productIdentifier
}
else
{
if(!receiptData) {

// could be a mac in app receipt.
// read from receipts and verify here
receiptData = [self receiptFromBundle];
if(!receiptData) {
if(self.onTransactionCancelled)
{
self.onTransactionCancelled(productIdentifier);
}
else
{
NSLog(@"Receipt invalid");
}
}
}

if(OWN_SERVER && SERVER_PRODUCT_MODEL)
{
// ping server and get response before serializing the product
Expand Down Expand Up @@ -603,13 +642,8 @@ - (void) failedTransaction: (SKPaymentTransaction *)transaction
NSLog(@"error: %@", transaction.error);
#endif

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:[transaction.error localizedFailureReason]
message:[transaction.error localizedRecoverySuggestion]
delegate:self
cancelButtonTitle:NSLocalizedString(@"Dismiss", @"")
otherButtonTitles: nil];
[alert show];

[self showAlertWithTitle:[transaction.error localizedFailureReason] message:[transaction.error localizedRecoverySuggestion]];

if(self.onTransactionCancelled)
self.onTransactionCancelled();
}
Expand Down
15 changes: 12 additions & 3 deletions MKStoreObserver.m
Expand Up @@ -79,18 +79,27 @@ - (void) failedTransaction: (SKPaymentTransaction *)transaction
}

- (void) completeTransaction: (SKPaymentTransaction *)transaction
{
{
#if TARGET_OS_IPHONE
[[MKStoreManager sharedManager] provideContent:transaction.payment.productIdentifier
forReceipt:transaction.transactionReceipt];
#elif TARGET_OS_MAC
[[MKStoreManager sharedManager] provideContent:transaction.payment.productIdentifier
forReceipt:nil];
#endif

[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}

- (void) restoreTransaction: (SKPaymentTransaction *)transaction
{
#if TARGET_OS_IPHONE
[[MKStoreManager sharedManager] provideContent: transaction.originalTransaction.payment.productIdentifier
forReceipt:transaction.transactionReceipt];
#elif TARGET_OS_MAC
[[MKStoreManager sharedManager] provideContent: transaction.originalTransaction.payment.productIdentifier
forReceipt:transaction.transactionReceipt];
forReceipt:nil];
#endif

[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
Expand Down
51 changes: 21 additions & 30 deletions README.mdown
@@ -1,46 +1,36 @@
This is version 4.1 of MKStoreKit. It's for ARC enabled projects only.
The latest NonARC version can be obtained from a tagged branch <a href="https://github.com/MugunthKumar/MKStoreKit/zipball/NonARC">here</a>
This is version 4.1 of MKStoreKit.
Few minor bugs reported in the past two months are fixed and iCloud syncing is implemented.

---
###Todo
I'm currently working on supporting Lion. Once this is done, I guess, MKStoreKit will not have any more major updates hopefully (till Apple introduces new IAP mechanisms).
I'm working on supporting Lion. Not sure if the current version already works. (I guess it should).
Once this is done, I guess, MKStoreKit will not have any more major updates (till Apple introduces new IAP mechanisms).

Read the <a href="http://mk.sg/8j"> blog post</a> for more.
The source code, MKStoreKit, contains several new objective c files notably MKStoreKitConfigs.h among others like, MKStoreManager.h/m and MKStoreObserver.h/m and five server side files. The MKStoreManager is a singleton class that takes care of *everything*. Just include StoreKit.Framework and Security.Framework into your product and drag these four files into the project. You then have to initialize it by calling [MKStoreManager sharedManager] in your applicationDidFinishLaunching. From then on, it does the magic. The MKStoreKit automatically activates/deactivates features using your userDefaults. When a feature is purchased, it automagically records it into NSUserDefaults. For checking whether the user has purchased the feature, you can call a function like,

---

The source code, MKStoreKit, contains a config file, MKStoreKitConfigs.h and several other Objective-C code file like, MKStoreManager.h/m and MKStoreObserver.h/m and five server side files. The MKStoreManager is a singleton class that takes care of *everything*. Just include StoreKit.Framework and Security.Framework into your product and drag all these files into the project. You then have to initialize it by calling [MKStoreManager sharedManager] in your applicationDidFinishLaunching. From then on, it does the magic. The MKStoreKit automatically activates/deactivates features using your Keychain. When a feature is purchased, it automagically records it into Keychain. The Keychain is automatically synced with iCloud if you have enabled iCloud entitlements for your project.

---
For checking whether the user has purchased the feature, you can call a function like,


if([MKStoreManager isFeaturePurchased:kFeatureID])
{
//unlock it
}
if([MKStoreManager isFeaturePurchased:kFeatureID])
{
//unlock it
}

To purchase a feature, just call

[[MKStoreManager sharedManager] buyFeature:kFeatureAId
onComplete:^(NSString* purchasedFeature)
{
NSLog(@"Purchased: %@", purchasedFeature);
}
onCancelled:^
{
NSLog(@"User Cancelled Transaction");
}];
[[MKStoreManager sharedManager] buyFeature:kFeatureAId
onComplete:^(NSString* purchasedFeature)
{
NSLog(@"Purchased: %@", purchasedFeature);
}
onCancelled:^
{
NSLog(@"User Cancelled Transaction");
}];

It’s that simple with my MKStoreKit.

---
###Licensing
MKStoreKit uses zLib licensing
And so all of my source code can be used royalty-free into your app. Just make sure that you don’t remove the copyright notice from the source code if you make your app open source. You don’t have to attribute me in your app, although I would be glad if you do so.

---
###Server Setup
The database required can be created from the sql file attached.

The code that you need for setting up your server is present in the ServerCode folder.

Copy all the files to some location like
Expand All @@ -49,3 +39,4 @@ http://api.mycompany.com/inapp/
The URL which you should copy to "ownServer" variable in MKStoreManager.m is http://api.mycompany.com/inapp/featureCheck.php
Copy this URL to ownServer parameter in MKStoreManager.m


0 comments on commit 22347ae

Please sign in to comment.