Permalink
Browse files

Add InAppPurchaseManager plugin

  • Loading branch information...
1 parent 3ded3a0 commit d3b4a0ac8606904e5324310c6583a999f02ec7fc @ascorbic ascorbic committed Feb 23, 2011
@@ -0,0 +1,38 @@
+//
+// InAppPurchaseManager.h
+// beetight
+//
+// Created by Matt Kane on 20/02/2011.
+// Copyright 2011 Matt Kane. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import <StoreKit/StoreKit.h>
+#import "PhoneGapCommand.h"
+#import "NSData+Base64.h"
+
+#import "SKProduct+LocalizedPrice.h"
+
+@interface InAppPurchaseManager : PhoneGapCommand <SKPaymentTransactionObserver> {
+
+}
+- (void) setup:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;
+- (void) makePurchase:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;
+- (void) requestProductData:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;
+- (void) paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions;
+
+@end
+
+@interface ProductsRequestDelegate : NSObject <SKProductsRequestDelegate>{
+ NSString* successCallback;
+ NSString* failCallback;
+
+ InAppPurchaseManager* command;
+}
+
+@property (nonatomic, copy) NSString* successCallback;
+@property (nonatomic, copy) NSString* failCallback;
+@property (nonatomic, retain) InAppPurchaseManager* command;
+
+@end;
+
@@ -0,0 +1,153 @@
+/**
+ * A plugin to enable iOS In-App Purchases.
+ *
+ * Copyright (c) Matt Kane 2011
+ */
+
+var InAppPurchaseManager = function() {
+ PhoneGap.exec('InAppPurchaseManager.setup');
+}
+
+/**
+ * Makes an in-app purchase.
+ *
+ * @param {String} productId The product identifier. e.g. "com.example.MyApp.myproduct"
+ * @param {int} quantity
+ */
+
+InAppPurchaseManager.prototype.makePurchase = function(productId, quantity) {
+ var q = parseInt(quantity);
+ if(!q) {
+ q = 1;
+ }
+ return PhoneGap.exec('InAppPurchaseManager.makePurchase', productId, q);
+}
+
+/**
+ * Asks the payment queue to restore previously completed purchases.
+ * The restored transactions are passed to the onRestored callback, so make sure you define a handler for that first.
+ *
+ */
+
+InAppPurchaseManager.prototype.restoreCompletedTransactions = function() {
+ return PhoneGap.exec('InAppPurchaseManager.restoreCompletedTransactions');
+}
+
+
+/**
+ * Retrieves the localised product data, including price (as a localised string), name, description.
+ * You must call this before attempting to make a purchase.
+ *
+ * @param {String} productId The product identifier. e.g. "com.example.MyApp.myproduct"
+ * @param {Function} successCallback Called once for each returned product id. Signature is function(productId, title, description, price)
+ * @param {Function} failCallback Called once for each invalid product id. Signature is function(productId)
+ */
+
+InAppPurchaseManager.prototype.requestProductData = function(productId, successCallback, failCallback) {
+ var key = 'f' + this.callbackIdx++;
+ window.plugins.inAppPurchaseManager.callbackMap[key] = {
+ success: function(productId, title, description, price ) {
+ if (productId == '__DONE') {
+ delete window.plugins.fileUploader.callbackMap[key]
+ return;
+ }
+ successCallback(productId, title, description, price);
+ },
+ fail: failCallback
+ }
+ var callback = 'window.plugins.inAppPurchaseManager.callbackMap.' + key;
+ PhoneGap.exec('InAppPurchaseManager.requestProductData', productId, callback + '.success', callback + '.fail');
+}
+
+/* function(transactionIdentifier, productId, transactionReceipt) */
+InAppPurchaseManager.prototype.onPurchased = null;
+
+/* function(originalTransactionIdentifier, productId, originalTransactionReceipt) */
+InAppPurchaseManager.prototype.onRestored = null;
+
+/* function(errorCode, errorText) */
+InAppPurchaseManager.prototype.onFailed = null;
+
+
+/* This is called from native.*/
+
+InAppPurchaseManager.prototype.updatedTransactionCallback = function(state, errorCode, errorText, transactionIdentifier, productId, transactionReceipt) {
+ switch(state) {
+ case "PaymentTransactionStatePurchased":
+ if(this.onPurchased) {
+ this.onPurchased(transactionIdentifier, productId, transactionReceipt);
+ } else {
+ this.eventQueue.push(arguments);
+ this.watchQueue();
+ }
+ return;
+
+ case "PaymentTransactionStateFailed":
+ if(this.onFailed) {
+ this.onFailed(errorCode, errorText);
+ } else {
+ this.eventQueue.push(arguments);
+ this.watchQueue();
+ }
+ return;
+
+ case "PaymentTransactionStateRestored":
+ if(this.onRestored) {
+ this.onRestored(transactionIdentifier, productId, transactionReceipt);
+ } else {
+ this.eventQueue.push(arguments);
+ this.watchQueue();
+ }
+ return;
+ }
+}
+
+/*
+ * This queue stuff is here because we may be sent events before listeners have been registered. This is because if we have
+ * incomplete transactions when we quit, the app will try to run these when we resume. If we don't register to receive these
+ * right away then they may be missed. As soon as a callback has been registered then it will be sent any events waiting
+ * in the queue.
+ */
+
+InAppPurchaseManager.prototype.runQueue = function() {
+ if(!this.eventQueue.length || (!this.onPurchased && !this.onFailed && !this.onRestored)) {
+ return;
+ }
+ var args;
+ /* We can't work directly on the queue, because we're pushing new elements onto it */
+ var queue = this.eventQueue.slice();
+ this.eventQueue = [];
+ while(args = queue.shift()) {
+ this.updatedTransactionCallback.apply(this, args);
+ }
+ if(!this.eventQueue.length) {
+ this.unWatchQueue();
+ }
+}
+
+InAppPurchaseManager.prototype.watchQueue = function() {
+ if(this.timer) {
+ return;
+ }
+ this.timer = setInterval("window.plugins.inAppPurchaseManager.runQueue()", 10000);
+}
+
+InAppPurchaseManager.prototype.unWatchQueue = function() {
+ if(this.timer) {
+ clearInterval(this.timer);
+ this.timer = null;
+ }
+}
+
+
+InAppPurchaseManager.prototype.callbackMap = {};
+InAppPurchaseManager.prototype.callbackIdx = 0;
+InAppPurchaseManager.prototype.eventQueue = [];
+InAppPurchaseManager.prototype.timer = null;
+
+PhoneGap.addConstructor(function() {
+ if(!window.plugins) {
+ window.plugins = {};
+ }
+ window.plugins.inAppPurchaseManager = InAppPurchaseManager.manager = new InAppPurchaseManager();
+});
@@ -0,0 +1,150 @@
+//
+// InAppPurchaseManager.m
+// beetight
+//
+// Created by Matt Kane on 20/02/2011.
+// Copyright 2011 Matt Kane. All rights reserved.
+//
+
+#import "InAppPurchaseManager.h"
+
+
+@implementation InAppPurchaseManager
+
+-(void) setup:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options {
+ [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
+}
+
+- (void) requestProductData:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options
+{
+ if([arguments count] < 3) {
+ return;
+ }
+ NSLog(@"Getting product data");
+ NSSet *productIdentifiers = [NSSet setWithObject:[arguments objectAtIndex:0]];
+ SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
+
+ ProductsRequestDelegate* delegate = [[[ProductsRequestDelegate alloc] init] retain];
+ delegate.command = self;
+ delegate.successCallback = [arguments objectAtIndex:1];
+ delegate.failCallback = [arguments objectAtIndex:2];
+
+ productsRequest.delegate = delegate;
+ [productsRequest start];
+
+}
+
+- (void) makePurchase:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options
+{
+ NSLog(@"About to do IAP");
+ if([arguments count] < 1) {
+ return;
+ }
+
+ SKMutablePayment *payment = [SKMutablePayment paymentWithProductIdentifier:[arguments objectAtIndex:0]];
+
+ if([arguments count] > 1) {
+ id quantity = [arguments objectAtIndex:1];
+ if ([quantity respondsToSelector:@selector(integerValue)]) {
+ payment.quantity = [quantity integerValue];
+ }
+ }
+ [[SKPaymentQueue defaultQueue] addPayment:payment];
+}
+
+- (void) restoreCompletedTransactions:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options
+{
+ [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
+}
+
+// SKPaymentTransactionObserver methods
+// called when the transaction status is updated
+//
+- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
+{
+ NSString *state, *error, *transactionIdentifier, *transactionReceipt, *productId;
+ NSInteger errorCode;
+
+ for (SKPaymentTransaction *transaction in transactions)
+ {
+ error = state = transactionIdentifier = transactionReceipt = productId = @"";
+ errorCode = 0;
+
+ switch (transaction.transactionState)
+ {
+ case SKPaymentTransactionStatePurchased:
+ state = @"PaymentTransactionStatePurchased";
+ transactionIdentifier = transaction.transactionIdentifier;
+ transactionReceipt = [[transaction transactionReceipt] base64EncodedString];
+ productId = transaction.payment.productIdentifier;
+ break;
+
+ case SKPaymentTransactionStateFailed:
+ state = @"PaymentTransactionStateFailed";
+ error = transaction.error.localizedDescription;
+ errorCode = transaction.error.code;
+ NSLog(@"error %d %@", errorCode, error);
+
+ break;
+
+ case SKPaymentTransactionStateRestored:
+ state = @"PaymentTransactionStateRestored";
+ transactionIdentifier = transaction.originalTransaction.transactionIdentifier;
+ transactionReceipt = [[[transaction originalTransaction] transactionReceipt] base64EncodedString];
+ productId = transaction.originalTransaction.payment.productIdentifier;
+ break;
+
+ default:
+ NSLog(@"Invalid state");
+ continue;
+ }
+ NSLog(@"state: %@", state);
+ NSString *js = [NSString stringWithFormat:@"InAppPurchaseManager.manager.updatedTransactionCallback('%@',%d, '%@','%@','%@','%@')", state, errorCode, error, transactionIdentifier, productId, transactionReceipt ];
+ NSLog(@"js: %@", js);
+ [self writeJavascript: js];
+ [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
+
+ }
+}
+
+
+@end
+
+@implementation ProductsRequestDelegate
+
+@synthesize successCallback, failCallback, command;
+
+
+- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
+{
+ NSLog(@"got iap product response");
+ for (SKProduct *product in response.products) {
+ NSLog(@"sending js for %@", product.productIdentifier);
+ NSString *js = [NSString stringWithFormat:@"%@('%@','%@','%@','%@')", successCallback, product.productIdentifier, product.localizedTitle, product.localizedDescription, product.localizedPrice];
+ NSLog(@"js: %@", js);
+ [command writeJavascript: js];
+ }
+
+ for (NSString *invalidProductId in response.invalidProductIdentifiers) {
+ NSLog(@"sending fail (%@) js for %@", failCallback, invalidProductId);
+
+ [command writeJavascript: [NSString stringWithFormat:@"%@('%@')", failCallback, invalidProductId]];
+ }
+ NSLog(@"done iap");
+
+ [command writeJavascript: [NSString stringWithFormat:@"%@('__DONE')", successCallback]];
+
+ [request release];
+ [self release];
+}
+
+- (void) dealloc
+{
+ [successCallback release];
+ [failCallback release];
+ [command release];
+ [super dealloc];
+}
+
+
+@end
Oops, something went wrong.

0 comments on commit d3b4a0a

Please sign in to comment.