trouble mixing ObjC and C++ for in app purchase

Hi All,

I’ve been tying to implement in app purchases using this tutorial:
http://xcodenoobies.blogspot.com/2012/04/implementing-inapp-purchase-in-xcode.html

I can get as far as inviting user to purchase the item and even make the payment but I can’t integrate the updatedTransactions delegate which is supposed to trigger upon purchase.

In other words I can’t tell my app that the item has been purchased!

here is the code that I need to paste:

  
  
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {  
    for (SKPaymentTransaction *transaction in transactions) {  
        switch (transaction.transactionState) {  
            case SKPaymentTransactionStatePurchasing:  
                  
                // show wait view here  
                statusLabel.text = @"Processing...";  
                break;  
                  
            case SKPaymentTransactionStatePurchased:  
              
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];  
                // remove wait view and unlock feature 2  
                statusLabel.text = @"Done!";  
                UIAlertView *tmp = [[UIAlertView alloc]   
                                    initWithTitle:@"Complete"   
                                    message:@"You have unlocked Feature 2!"  
                                    delegate:self   
                                    cancelButtonTitle:nil   
                                    otherButtonTitles:@"Ok", nil];   
                [tmp show];  
                [tmp release];  
                  
                  
                NSError *error = nil;  
                [SFHFKeychainUtils storeUsername:@"IAPNoob01" andPassword:@"whatever" forServiceName:kStoredData updateExisting:YES error:&error];  
                  
                // apply purchase action  - hide lock overlay and  
                [feature2Btn setBackgroundImage:nil forState:UIControlStateNormal];  
  
                // do other thing to enable the features  
                  
                break;  
                  
            case SKPaymentTransactionStateRestored:  
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];  
                // remove wait view here  
                statusLabel.text = @"";  
                break;  
                  
            case SKPaymentTransactionStateFailed:  
                  
                if (transaction.error.code != SKErrorPaymentCancelled) {  
                    NSLog(@"Error payment cancelled");  
                }  
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];  
                // remove wait view here  
                statusLabel.text = @"Purchase Error!";  
                break;  
                  
            default:  
                break;  
        }  
    }  
}  
  
  
  

Xcode won’t accept it, is there a trick or special place it must go?

Any advice would be greatly appreciated as I feel so close!!!

Thanks
Alex

hi Alex, nice post, I don’t know the answer I’ve never tried to implement in-app purchases

let us know of your progress, maybe paste some code.?

…hm…
So you basically want to use that function inside testApp?

Have you tried something cheap…

…like just baptise the obj-c paymentQueue function to a c++ function so that you can use it inside the test-App class…

  
  
  
  
void testApp::paymentQ(SKPaymentQueue *queue ,NSArray *transactions){  
  
    for (SKPaymentTransaction *transaction in transactions) {  
        switch (transaction.transactionState) {  
            case SKPaymentTransactionStatePurchasing:  
                  
                // show wait view here  
                statusLabel.text = @"Processing...";  
                break;  
                  
            case SKPaymentTransactionStatePurchased:  
                  
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];  
                // remove wait view and unlock feature 2  
                statusLabel.text = @"Done!";  
                UIAlertView *tmp = [[UIAlertView alloc]   
                                    initWithTitle:@"Complete"   
                                    message:@"You have unlocked Feature 2!"  
                                    delegate:self   
                                    cancelButtonTitle:nil   
                                    otherButtonTitles:@"Ok", nil];   
                [tmp show];  
                [tmp release];  
                  
                  
                NSError *error = nil;  
                [SFHFKeychainUtils storeUsername:@"IAPNoob01" andPassword:@"whatever" forServiceName:kStoredData updateExisting:YES error:&error];  
                  
                // apply purchase action  - hide lock overlay and  
                [feature2Btn setBackgroundImage:nil forState:UIControlStateNormal];  
                  
                // do other thing to enable the features  
                  
                break;  
                  
            case SKPaymentTransactionStateRestored:  
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];  
                // remove wait view here  
                statusLabel.text = @"";  
                break;  
                  
            case SKPaymentTransactionStateFailed:  
                  
                if (transaction.error.code != SKErrorPaymentCancelled) {  
                    NSLog(@"Error payment cancelled");  
                }  
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];  
                // remove wait view here  
                statusLabel.text = @"Purchase Error!";  
                break;  
                  
            default:  
                break;  
        }  
    }  
}  
  
  
  

I mean if you can access those SKPaymentQueue objects from the Storekit framework it should kind of work…

Hi,8bit-k!

Thanks for the help!

Hi again!

So It will compile without errors using the code below, but doesn’t seem to respond to the purchase. Should I be calling it manually given that it’s c++ now?

Also I noticed that we renamed paymentQueue to paymentQ. Could that be a problem?

We’ve also dropped queue updatedTransactions: from the original obj-C funtion.

  
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {   

I’m unable to put that back in without getting errors, probably because I don’t know how to declare it in testApp.h

Sorry I’m a bit of a Noob, hacking my way through delicate territory!

Here’s the code so far (compiles ok but doesn’t respond to the purchase):

testApp.h:

  
  void paymentQ(SKPaymentQueue *queue ,NSArray *transactions);  

TestApp.m:

  
  
void testApp::paymentQ(SKPaymentQueue *queue ,NSArray *transactions){  
      
    for (SKPaymentTransaction *transaction in transactions) {  
        switch (transaction.transactionState) {  
            case SKPaymentTransactionStatePurchasing:  
                  
                // show wait view here  
           
                break;  
                  
            case SKPaymentTransactionStatePurchased:  
                  
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];  
                // remove wait view and unlock feature 2  
                                
                // do other thing to enable the features  
                  
                break;  
                  
           
        }  
    }  
}  
  

Thanks for the support!
Alex

yes if you don’t call it inside TestApp it won’t do anything…

the name shouldn’t matter but rename it paymentQueue just in case…

queue updatedTransactions: is not one word… queue is the name of the SKPaymentQueue pointer that our function takes, and updatedTransactions: is just the name of the next section of the obj-c function, you don’t need it in c++ it’s like the comma (,) it’s there just for readability reasons… (I think)//

Thanks for the enlightenment
unfortunately I still can’t get it to work. I’m pretty sure that it should be called by the storekit framework. Calling it manually wont help if I’m understanding this correctly.

I’m going to see if I can the in app purchase function natively in paralel using the ios native example…

yes you are correct…, after having a look at the whole code it’s pretty obvious that it should be called by the framework.

You need to write a wrapper class. that will basically call those things that the two buttons do

so you need first an obj-c class

similar to this code that will handle all the code you have when called:

  
  
  
@interface Storekit: NSObject<<SKProductsRequestDelegate, SKPaymentTransactionObserver>{  
    SKProduct *proUpgradeProduct;  
    SKProductsRequest *productsRequest;  
}  
  
+ (StoreController*)getInstance;  
  
- (void)initializeWithStoreAssets:(id<IStoreAsssets>)storeAssets;  
  
  
- (void)buyCurrencyPackWithProcuctId:(NSString*)productId;  
  
  
- (void)buyVirtualGood:(NSString*)itemId;  
  
  
- (void)storeOpening;  
  
  
- (void)storeClosing;  
  
- (void) equipVirtualGood:(NSString*) itemId;  
  
- (void) unequipVirtualGood:(NSString*) itemId;  
  
@end  
  

so basically the implementation of this interface should do everything

Correct me if I am wrong, I am doing it for the first time as well…

so like with the gamecenter you need to first create the objects to buy in itunes connect - I haven’t done that yet- :confused: … the way you access those is with their stringnames. you send their string names along with the password to the app-store and take a yes or no answer back…

so… we need to write a c++ wrapper around that implementation

so we will end up with a pointer to a storekitHandler inside the TestApp.

  • initialaze it:

  • openThe store

  • send stringname

  • password

  • do stuff

-close shop

… the wrapper should look something like that:
(I based this code on some other inapp purchase tutorial

  
  
class StoreController{  
  
public:  
  
StoreController(){  
[[StoreController getInstance] initializeWithStoreAssets:[[MyAssets alloc] init]];  
}  
  
void buyVirtualGood(string name){  
  
//do stuff that the buttons do pass the string as a NSString to the code you have..  
}  
  
  
};  
  
  

This is how I understood the procedure…
but I can’t find a way to make things work… you said you had partial success can you share the steps you’ve made thus far?

I am trying to make a wrapper…

Can I ask you a question… you said you got this thing to work partially,

when it asks you for your username, does it allow to put your name and password?

in the code it looks like it’s hard-coded…

  
   
bool ofxINAPP::IAPItemPurchased(){  
      
    NSError *error = nil;  
    NSString *password = [SFHFKeychainUtils getPasswordForUsername:@"IAPNoob01" andServiceName:kStoredData error:&error];  
      
    if ([password isEqualToString:@"whatever"]) return true; else return false;  
}  
  

Here is my project in it’s current status:

http://alkex.com/IAPTEST.zip

I’ve added comments

Click the screen and it should prompt to buy the item. But it won’t compile unless I comment out this:

  
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];  

Pretty sure that this is why I’m not getting a response as the observer needs to report back when the transaction is successful.

Again, this tutorial is very clear, but getting it working in c++ is beyond me

http://xcodenoobies.blogspot.com/2012/04/implementing-inapp-purchase-in-xcode.html

I really appreciate you taking the dive with me! I trust and in app purchases will be a valuable addition to OF!

Cheers
Alex

ok… for now we can try create the observer object:

in your header file (testApp.h)

Add this above your testApp declaration

  
  
  
@interface PaymentObserver : NSObject <SKPaymentTransactionObserver> {    
}  
  
- (void) completeTransaction: (SKPaymentTransaction *)transaction;  
- (void) restoreTransaction: (SKPaymentTransaction *)transaction;  
- (void) failedTransaction: (SKPaymentTransaction *)transaction;  
  
@end  
  

in .cpp

add

  
  
@implementation PaymentObserver  
  
- (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions  
{  
    // handle payment cancellation  
}  
  
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions  
{  
    // handle the payment transaction actions for each state  
    for (SKPaymentTransaction *transaction in transactions)  
    {  
        switch (transaction.transactionState)  
        {  
            case SKPaymentTransactionStatePurchased:  
                [self completeTransaction:transaction];  
                cout<<"Complete Transaction";  
                break;  
            case SKPaymentTransactionStateFailed:  
                [self failedTransaction:transaction];  
                cout<<"Transaction Failed";  
  
                break;  
            case SKPaymentTransactionStateRestored:  
                cout<<"Transaction Restored";  
  
                [self restoreTransaction:transaction];          
            default:  
                break;  
        }  
    }  
}  
  
  
  
- (void) completeTransaction: (SKPaymentTransaction *)transaction;  
{  
    // Record the transaction  
    //...  
      
    // Do whatever you need to do to provide the service/subscription purchased  
    //...  
      
    // Remove the transaction from the payment queue.  
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];  
      
}  
  
- (void) restoreTransaction: (SKPaymentTransaction *)transaction  
{  
    // Record the transaction  
    //...  
      
    // Do whatever you need to do to provide the service/subscription purchased  
    //...  
      
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];  
}  
  
- (void) failedTransaction: (SKPaymentTransaction *)transaction  
{    
    if (transaction.error.code != SKErrorPaymentCancelled)  
    {  
        // Optionally, display an error here.      
    }  
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];  
}  
  
@end  
  
  
  
  

and on touchdown

  
  
  
 SKPayment *payment = [SKPayment paymentWithProductIdentifier:kStoredData];  
      
    PaymentObserver *paymentObserver = [[PaymentObserver alloc] init];  
    [[SKPaymentQueue defaultQueue] addTransactionObserver:paymentObserver];  
    [[SKPaymentQueue defaultQueue] addPayment:payment];   
  

…I am 99.999% sure that this is not enough to make a purchase… :slight_smile:
(I would test it my self but… I don’t have a sandbox accound :frowning:

…if this by any chance works, the idea is that you add returning booleans inside the observer that will communicate with the c++ object that I haven’t create yet and affect an xml file. (to save the purchases)

ooops!!! I made a mistake while copy pasting the code here!!

the .cpp file (.mm)

I forgot to include the @end and the implementations of the 3 states!!!
I fixed it now
Can you TRY AGAIN!!!

on my machine it compiles I do the transaction I add my password and it says that my password is not a sandbox.

So it returns “transaction Failed”…

Hi!
I went to bed, sorry for the delay. So it’s compiling, but I get transaction failed even with the correct login. I did however already “purchase” the item using the tutorial so maybe it’s failing because I already own it? I’ll make a couple new test users and try again.

This is very exciting and we are a step forward since we are at least getting a response from the buy :slight_smile: :slight_smile: :slight_smile:

Thanks!!!

meh… I think it should say… “reclaimed” when you already bought it…

we must check for more steps…

…create a new object in itunes connect?

well… back to the drawing board I guess… let me know if you got any progress…

I ll have another play this evening.
The tact that paymentqeue is now triggering is very encouraging!
I ll create another product and see how that works

I noticed that we are not using “SFHFKeychainUtils” object in our paymentQueue, or anywhere else, maybe this sends an OK signal to itunesConnect before the transaction is completed?

also… maybe we need to “close” the store at the end?

or maybe we are doing that already with finishTransaction?

The keyutils thing is just a way of remembering that the purchase was made. It doesn’t affect the process. I ll be home in a couple hours And will poke around

…oh, I see, so It’s just for internal consumption, we can ignore that completely, there better ways to do what that monster does…

…we also neglected that part:
it checks for parental control before purchase

  
  
  
if (alertView==askToPurchase) {  
        if (buttonIndex==0) {  
            // user tapped YES, but we need to check if IAP is enabled or not.  
            if ([SKPaymentQueue canMakePayments]) {   
                  
                SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:@"com.emirbytes.IAPNoob.01"]];    
                  
                request.delegate = self;    
                [request start];    
                  
                  
            } else {  
                UIAlertView *tmp = [[UIAlertView alloc]   
                                    initWithTitle:@"Prohibited"   
                                    message:@"Parental Control is enabled, cannot make a purchase!"  
                                    delegate:self   
                                    cancelButtonTitle:nil   
                                    otherButtonTitles:@"Ok", nil];   
                [tmp show];  
                [tmp release];  
            }  
        }  
    }  
  
  

also I remember reading somewhere that ios6 brakes in-app purchases, so maybe they is something additional now?, I am positive our “tutorial” is quite old…

Agreed, it is pretty old but it worked fine when I tested it.

Hi,

So I’ve been playing with it for another hour with no avail! Arrgg

I tried adding new product, changing user accounts and all sorts but always get “Error payment cancelled” printed to the terminal.

Here is the project in it’s latest form:

http://www.alkex.com/IAPTEST.zip

I’m lost at this point :frowning:

I tried running the tutorial again and it still works as expected with the same product, but I can’t figure out what broke in the translation to OF. I’m happy to share the test username credentials with anyone willing to try it out.

Just PM me, I prefer not to post it publicly…

I do have a crazy idea for a possible workaround:

Given that we know that the tutorial project works, I wonder if it be possible say to temporarily exit the OF testApp, load the tutorial app (AppDelegate?), make payment then return back to appTest OF after payment successfully completed?

Here is the working tutorial project:
http://www.alkex.com/IAPNoob.zip

Thanks
Alex

…again… I can’t test that bloody example (the tutorial) it doesn’t allow me to compile…

if you want to access the tutorial from your app…

you can:

  1. add viewController.h files in scr and the assets (recreate the project basically)

  2. #include header viewController.h in test app

  3. declare a pointer ViewController *viewController; in testApp class

in .mm

4 inside setup:

  
  testApp::viewController = [[[ViewController alloc] initWithNibName:@"ViewController" bundle:nil] autorelease];  
    ofxiPhoneGetUIWindow().window.rootViewController = testApp::viewController;  
    [ofxiPhoneGetUIWindow().window makeKeyAndVisible];  
  

this should activate the tutorial… but I haven’t tested it
I can’t,

if your account is empty like mine, send me the sandbox passwords to try figure this shit out…
you can change the password after…! right?
PS (i think another guy in the forum implemented inApp purchases a while ago )