Skip to content

Commit d3b4a0a

Browse files
committed
Add InAppPurchaseManager plugin
1 parent 3ded3a0 commit d3b4a0a

File tree

4 files changed

+438
-0
lines changed

4 files changed

+438
-0
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//
2+
// InAppPurchaseManager.h
3+
// beetight
4+
//
5+
// Created by Matt Kane on 20/02/2011.
6+
// Copyright 2011 Matt Kane. All rights reserved.
7+
//
8+
9+
#import <Foundation/Foundation.h>
10+
#import <StoreKit/StoreKit.h>
11+
#import "PhoneGapCommand.h"
12+
#import "NSData+Base64.h"
13+
14+
#import "SKProduct+LocalizedPrice.h"
15+
16+
@interface InAppPurchaseManager : PhoneGapCommand <SKPaymentTransactionObserver> {
17+
18+
}
19+
- (void) setup:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;
20+
- (void) makePurchase:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;
21+
- (void) requestProductData:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;
22+
- (void) paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions;
23+
24+
@end
25+
26+
@interface ProductsRequestDelegate : NSObject <SKProductsRequestDelegate>{
27+
NSString* successCallback;
28+
NSString* failCallback;
29+
30+
InAppPurchaseManager* command;
31+
}
32+
33+
@property (nonatomic, copy) NSString* successCallback;
34+
@property (nonatomic, copy) NSString* failCallback;
35+
@property (nonatomic, retain) InAppPurchaseManager* command;
36+
37+
@end;
38+
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/**
2+
* A plugin to enable iOS In-App Purchases.
3+
*
4+
* Copyright (c) Matt Kane 2011
5+
*/
6+
7+
var InAppPurchaseManager = function() {
8+
PhoneGap.exec('InAppPurchaseManager.setup');
9+
}
10+
11+
/**
12+
* Makes an in-app purchase.
13+
*
14+
* @param {String} productId The product identifier. e.g. "com.example.MyApp.myproduct"
15+
* @param {int} quantity
16+
*/
17+
18+
InAppPurchaseManager.prototype.makePurchase = function(productId, quantity) {
19+
var q = parseInt(quantity);
20+
if(!q) {
21+
q = 1;
22+
}
23+
return PhoneGap.exec('InAppPurchaseManager.makePurchase', productId, q);
24+
}
25+
26+
/**
27+
* Asks the payment queue to restore previously completed purchases.
28+
* The restored transactions are passed to the onRestored callback, so make sure you define a handler for that first.
29+
*
30+
*/
31+
32+
InAppPurchaseManager.prototype.restoreCompletedTransactions = function() {
33+
return PhoneGap.exec('InAppPurchaseManager.restoreCompletedTransactions');
34+
}
35+
36+
37+
/**
38+
* Retrieves the localised product data, including price (as a localised string), name, description.
39+
* You must call this before attempting to make a purchase.
40+
*
41+
* @param {String} productId The product identifier. e.g. "com.example.MyApp.myproduct"
42+
* @param {Function} successCallback Called once for each returned product id. Signature is function(productId, title, description, price)
43+
* @param {Function} failCallback Called once for each invalid product id. Signature is function(productId)
44+
*/
45+
46+
InAppPurchaseManager.prototype.requestProductData = function(productId, successCallback, failCallback) {
47+
var key = 'f' + this.callbackIdx++;
48+
window.plugins.inAppPurchaseManager.callbackMap[key] = {
49+
success: function(productId, title, description, price ) {
50+
if (productId == '__DONE') {
51+
delete window.plugins.fileUploader.callbackMap[key]
52+
return;
53+
}
54+
successCallback(productId, title, description, price);
55+
},
56+
fail: failCallback
57+
}
58+
var callback = 'window.plugins.inAppPurchaseManager.callbackMap.' + key;
59+
PhoneGap.exec('InAppPurchaseManager.requestProductData', productId, callback + '.success', callback + '.fail');
60+
}
61+
62+
/* function(transactionIdentifier, productId, transactionReceipt) */
63+
InAppPurchaseManager.prototype.onPurchased = null;
64+
65+
/* function(originalTransactionIdentifier, productId, originalTransactionReceipt) */
66+
InAppPurchaseManager.prototype.onRestored = null;
67+
68+
/* function(errorCode, errorText) */
69+
InAppPurchaseManager.prototype.onFailed = null;
70+
71+
72+
/* This is called from native.*/
73+
74+
InAppPurchaseManager.prototype.updatedTransactionCallback = function(state, errorCode, errorText, transactionIdentifier, productId, transactionReceipt) {
75+
switch(state) {
76+
case "PaymentTransactionStatePurchased":
77+
if(this.onPurchased) {
78+
this.onPurchased(transactionIdentifier, productId, transactionReceipt);
79+
} else {
80+
this.eventQueue.push(arguments);
81+
this.watchQueue();
82+
}
83+
return;
84+
85+
case "PaymentTransactionStateFailed":
86+
if(this.onFailed) {
87+
this.onFailed(errorCode, errorText);
88+
} else {
89+
this.eventQueue.push(arguments);
90+
this.watchQueue();
91+
}
92+
return;
93+
94+
case "PaymentTransactionStateRestored":
95+
if(this.onRestored) {
96+
this.onRestored(transactionIdentifier, productId, transactionReceipt);
97+
} else {
98+
this.eventQueue.push(arguments);
99+
this.watchQueue();
100+
}
101+
return;
102+
}
103+
}
104+
105+
/*
106+
* This queue stuff is here because we may be sent events before listeners have been registered. This is because if we have
107+
* incomplete transactions when we quit, the app will try to run these when we resume. If we don't register to receive these
108+
* right away then they may be missed. As soon as a callback has been registered then it will be sent any events waiting
109+
* in the queue.
110+
*/
111+
112+
InAppPurchaseManager.prototype.runQueue = function() {
113+
if(!this.eventQueue.length || (!this.onPurchased && !this.onFailed && !this.onRestored)) {
114+
return;
115+
}
116+
var args;
117+
/* We can't work directly on the queue, because we're pushing new elements onto it */
118+
var queue = this.eventQueue.slice();
119+
this.eventQueue = [];
120+
while(args = queue.shift()) {
121+
this.updatedTransactionCallback.apply(this, args);
122+
}
123+
if(!this.eventQueue.length) {
124+
this.unWatchQueue();
125+
}
126+
}
127+
128+
InAppPurchaseManager.prototype.watchQueue = function() {
129+
if(this.timer) {
130+
return;
131+
}
132+
this.timer = setInterval("window.plugins.inAppPurchaseManager.runQueue()", 10000);
133+
}
134+
135+
InAppPurchaseManager.prototype.unWatchQueue = function() {
136+
if(this.timer) {
137+
clearInterval(this.timer);
138+
this.timer = null;
139+
}
140+
}
141+
142+
143+
InAppPurchaseManager.prototype.callbackMap = {};
144+
InAppPurchaseManager.prototype.callbackIdx = 0;
145+
InAppPurchaseManager.prototype.eventQueue = [];
146+
InAppPurchaseManager.prototype.timer = null;
147+
148+
PhoneGap.addConstructor(function() {
149+
if(!window.plugins) {
150+
window.plugins = {};
151+
}
152+
window.plugins.inAppPurchaseManager = InAppPurchaseManager.manager = new InAppPurchaseManager();
153+
});
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
//
2+
// InAppPurchaseManager.m
3+
// beetight
4+
//
5+
// Created by Matt Kane on 20/02/2011.
6+
// Copyright 2011 Matt Kane. All rights reserved.
7+
//
8+
9+
#import "InAppPurchaseManager.h"
10+
11+
12+
@implementation InAppPurchaseManager
13+
14+
-(void) setup:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options {
15+
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
16+
}
17+
18+
- (void) requestProductData:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options
19+
{
20+
if([arguments count] < 3) {
21+
return;
22+
}
23+
NSLog(@"Getting product data");
24+
NSSet *productIdentifiers = [NSSet setWithObject:[arguments objectAtIndex:0]];
25+
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
26+
27+
ProductsRequestDelegate* delegate = [[[ProductsRequestDelegate alloc] init] retain];
28+
delegate.command = self;
29+
delegate.successCallback = [arguments objectAtIndex:1];
30+
delegate.failCallback = [arguments objectAtIndex:2];
31+
32+
productsRequest.delegate = delegate;
33+
[productsRequest start];
34+
35+
}
36+
37+
- (void) makePurchase:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options
38+
{
39+
NSLog(@"About to do IAP");
40+
if([arguments count] < 1) {
41+
return;
42+
}
43+
44+
SKMutablePayment *payment = [SKMutablePayment paymentWithProductIdentifier:[arguments objectAtIndex:0]];
45+
46+
if([arguments count] > 1) {
47+
id quantity = [arguments objectAtIndex:1];
48+
if ([quantity respondsToSelector:@selector(integerValue)]) {
49+
payment.quantity = [quantity integerValue];
50+
}
51+
}
52+
[[SKPaymentQueue defaultQueue] addPayment:payment];
53+
}
54+
55+
- (void) restoreCompletedTransactions:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options
56+
{
57+
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
58+
}
59+
60+
// SKPaymentTransactionObserver methods
61+
// called when the transaction status is updated
62+
//
63+
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
64+
{
65+
NSString *state, *error, *transactionIdentifier, *transactionReceipt, *productId;
66+
NSInteger errorCode;
67+
68+
for (SKPaymentTransaction *transaction in transactions)
69+
{
70+
error = state = transactionIdentifier = transactionReceipt = productId = @"";
71+
errorCode = 0;
72+
73+
switch (transaction.transactionState)
74+
{
75+
case SKPaymentTransactionStatePurchased:
76+
state = @"PaymentTransactionStatePurchased";
77+
transactionIdentifier = transaction.transactionIdentifier;
78+
transactionReceipt = [[transaction transactionReceipt] base64EncodedString];
79+
productId = transaction.payment.productIdentifier;
80+
break;
81+
82+
case SKPaymentTransactionStateFailed:
83+
state = @"PaymentTransactionStateFailed";
84+
error = transaction.error.localizedDescription;
85+
errorCode = transaction.error.code;
86+
NSLog(@"error %d %@", errorCode, error);
87+
88+
break;
89+
90+
case SKPaymentTransactionStateRestored:
91+
state = @"PaymentTransactionStateRestored";
92+
transactionIdentifier = transaction.originalTransaction.transactionIdentifier;
93+
transactionReceipt = [[[transaction originalTransaction] transactionReceipt] base64EncodedString];
94+
productId = transaction.originalTransaction.payment.productIdentifier;
95+
break;
96+
97+
default:
98+
NSLog(@"Invalid state");
99+
continue;
100+
}
101+
NSLog(@"state: %@", state);
102+
NSString *js = [NSString stringWithFormat:@"InAppPurchaseManager.manager.updatedTransactionCallback('%@',%d, '%@','%@','%@','%@')", state, errorCode, error, transactionIdentifier, productId, transactionReceipt ];
103+
NSLog(@"js: %@", js);
104+
[self writeJavascript: js];
105+
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
106+
107+
}
108+
}
109+
110+
111+
@end
112+
113+
@implementation ProductsRequestDelegate
114+
115+
@synthesize successCallback, failCallback, command;
116+
117+
118+
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
119+
{
120+
NSLog(@"got iap product response");
121+
for (SKProduct *product in response.products) {
122+
NSLog(@"sending js for %@", product.productIdentifier);
123+
NSString *js = [NSString stringWithFormat:@"%@('%@','%@','%@','%@')", successCallback, product.productIdentifier, product.localizedTitle, product.localizedDescription, product.localizedPrice];
124+
NSLog(@"js: %@", js);
125+
[command writeJavascript: js];
126+
}
127+
128+
for (NSString *invalidProductId in response.invalidProductIdentifiers) {
129+
NSLog(@"sending fail (%@) js for %@", failCallback, invalidProductId);
130+
131+
[command writeJavascript: [NSString stringWithFormat:@"%@('%@')", failCallback, invalidProductId]];
132+
}
133+
NSLog(@"done iap");
134+
135+
[command writeJavascript: [NSString stringWithFormat:@"%@('__DONE')", successCallback]];
136+
137+
[request release];
138+
[self release];
139+
}
140+
141+
- (void) dealloc
142+
{
143+
[successCallback release];
144+
[failCallback release];
145+
[command release];
146+
[super dealloc];
147+
}
148+
149+
150+
@end

0 commit comments

Comments
 (0)