Home Tutorials Download Beta Store Forum Documentation KnowledgeBase Wiki Blog

ShiVa3D

Return to Code Snippets

In-App Purchase system integrated! (iOS)

Paste down any little snippets or request a new one.

In-App Purchase system integrated! (iOS)

Postby vklymenko » 20 Sep 2011, 15:59

Good day fellows,

[INTRO]

I believe anyone who is doing apps/games for iOS at some point wants to integrate IAP into his game or freelance project. There are a lot of tutorials over web "how to integrate IAP" in common, but I've not founded one special for Shiva3d, so i'll be the one, who will share the integration - special for shiva's users ; )
I must say, most part of the in-app integration code (everything that is outside modifications in shiva's code) I took somewhere from web, from objc devs' tutorials, I can't find exectly sources, but for sure I should mention:
Dave Wooldridge
http://www.ebutterfly.com/books/iphonebusiness/
if I'll find/remember other sources, I'll add them as well.

[KNOWN ISSUES]
If internet is off when player is trying to buy a pack - probably the game will stuck on "shop" screen in waiting for processing request. All that I can propose currently is to use a timer e.g. 30 seconds, so if player read info about IAP and clicked "buy" without internet access - the in-game's shop should be auto-closed.

[WHAT IS]
In-app purchase system is nothing more then 1 button click. For example you have a tower defense game. You're releasing it as paid version, then adding in-app and by changing to free you're getting hundreds of thousands downloads : ) So often you have better chance to make some money with in-app instead always be down in lists with paid version. Or if you have good selling paid version, in-app will just double your incomings.
So how it works: e.g. you have 10 basic levels in your game. You can propose to player buy another for example 10 levels for 0.99. These levels (art, code etc) should be already in the game (its not some downloading from ftp or something), and user will just click "buy the IAP" and your code need to handle this event and save somewhere in local file on device that player has bought the IAP and these levels are open for him to play any time. It calls NON-CONSUMABLE in-app type. We will implement this type of IAPs now. So its something that player bought once and even if he will delete the app/game - he will be able to re-download it any amount of times for free after once paid. There is also consumable type of IAPs. It can be for example "coins" or "credits" that player will buy as pack again and again, and each time he will need to pay for.

[HOW TO]
[iTunes connect part]
First of all it needs to create game's template in itunes connect and the IAP.
1. Once you created game's template you should open it and click "Manage In-App Purchases"
2. click "Create New"
3. select "Non-Consumable"
4. fill usual reference and id (product id is something that will be used in code)
5. be sure you've selected "Cleared for Sale"
Important: its recommended to wait 24h to let apple's servers be updated, so your code will not return an error. So at this step you should leave the implementation and wait 24h.

[ObjC part]
Now we need to write some code into ObjC part of the game, later we will link it with shiva's scripts.
Open your xCode project of the game and:

1. Add "StoreKit" framework to the project (select the project in tree->Build Phases->"Link Binaries with libraries" (find and add this framework)

2. Create InAppPurchaseManager.h and add to the project (root of files-tree):
Code: Select all
#import <StoreKit/StoreKit.h>

#import "S3DEngine_EAGLView.h"

#define kInAppPurchaseManagerProductsFetchedNotification @"kInAppPurchaseManagerProductsFetchedNotification"
#define kInAppPurchaseManagerTransactionFailedNotification @"kInAppPurchaseManagerTransactionFailedNotification"
#define kInAppPurchaseManagerTransactionSucceededNotification @"kInAppPurchaseManagerTransactionSucceededNotification"

@interface InAppPurchaseManager : NSObject <SKProductsRequestDelegate>
{
    SKProduct *proUpgradeProduct;
    SKProductsRequest *productsRequest;
}

- (void)loadStore;
- (BOOL)canMakePurchases;
- (void)purchaseProUpgrade;

@end


3. Create InAppPurchaseManager.m and add to the project:
Code: Select all

#import "InAppPurchaseManager.h"
@implementation InAppPurchaseManager

#define kInAppPurchaseProUpgradeProductId @"1"

#pragma -
#pragma Public methods

- (void)loadStore
{
    // restarts any purchases if they were interrupted last time the app was open
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
   
    // get the product description (defined in early sections)
    [self requestProUpgradeProductData];
   
}

//
// call this before making a purchase
//
- (BOOL)canMakePurchases
{
    return [SKPaymentQueue canMakePayments];
}

//
// kick off the upgrade transaction
//
- (void)purchaseProUpgrade
{
   
    NSString *sPackNum = [NSString stringWithFormat:@"%d", S3DEngine_GetPackNum()];
   
    if ([sPackNum isEqualToString:@"1"])
    {
        NSString *sPackName = [NSString stringWithFormat:@"%s", "MyPackName1"];
        sPackNum = sPackName;
    }

    SKPayment *payment = [SKPayment paymentWithProductIdentifier:sPackNum];
    [[SKPaymentQueue defaultQueue] addPayment:payment];

}

#pragma -
#pragma Purchase helpers

//
// saves a record of the transaction by storing the receipt to disk
//
- (void)recordTransaction:(SKPaymentTransaction *)transaction
{
   
    NSString *sPackNum = [NSString stringWithFormat:@"%d", S3DEngine_GetPackNum()];
   
    if ([sPackNum isEqualToString:@"1"])
    {
        NSString *sPackName = [NSString stringWithFormat:@"%s", "MyPackName1"];
        sPackNum = sPackName;
    }
   
    if ([transaction.payment.productIdentifier isEqualToString:sPackNum])
    {
        // save the transaction receipt to disk
        [[NSUserDefaults standardUserDefaults] setValue:transaction.transactionReceipt forKey:@"proUpgradeTransactionReceipt" ];
        [[NSUserDefaults standardUserDefaults] synchronize];
    }
}

//
// enable pro features
//
- (void)provideContent:(NSString *)productId
{
    NSString *sPackNum = [NSString stringWithFormat:@"%d", S3DEngine_GetPackNum()];
   
    if ([sPackNum isEqualToString:@"1"])
    {
        NSString *sPackName = [NSString stringWithFormat:@"%s", "MyPackName1"];
        sPackNum = sPackName;
    }
   
    if ([productId isEqualToString:sPackNum])
    {
        // enable the pro features
        [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"isProUpgradePurchased" ];
        [[NSUserDefaults standardUserDefaults] synchronize];
    }
}

//
// removes the transaction from the queue and posts a notification with the transaction result
//
- (void)finishTransaction:(SKPaymentTransaction *)transaction wasSuccessful:(BOOL)wasSuccessful
{
    // remove the transaction from the payment queue.
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
   
    NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:transaction, @"transaction" , nil];
    if (wasSuccessful)
    {
        // send out a notification that we’ve finished the transaction
        [[NSNotificationCenter defaultCenter] postNotificationName:kInAppPurchaseManagerTransactionSucceededNotification object:self userInfo:userInfo];
       
        NSString *sRealPackNum = [NSString stringWithFormat:@"%d", S3DEngine_GetPackNum()];
        int num = [sRealPackNum intValue];
        S3DEngine_PurchasePack(num);

    }
    else
    {
        // send out a notification for the failed transaction
        [[NSNotificationCenter defaultCenter] postNotificationName:kInAppPurchaseManagerTransactionFailedNotification object:self userInfo:userInfo];
       
    }
    S3DClient_SendEventToCurrentUser("MainAI","onCloseShop",NULL);
}

//
// called when the transaction was successful
//
- (void)completeTransaction:(SKPaymentTransaction *)transaction
{
    [self recordTransaction:transaction];
    [self provideContent:transaction.payment.productIdentifier];
    [self finishTransaction:transaction wasSuccessful:YES];
}

//
// called when a transaction has been restored and and successfully completed
//
- (void)restoreTransaction:(SKPaymentTransaction *)transaction
{
    [self recordTransaction:transaction.originalTransaction];
    [self provideContent:transaction.originalTransaction.payment.productIdentifier];
    [self finishTransaction:transaction wasSuccessful:YES];
}

//
// called when a transaction has failed
//
- (void)failedTransaction:(SKPaymentTransaction *)transaction
{
    if (transaction.error.code != SKErrorPaymentCancelled)
    {
        // error!
        [self finishTransaction:transaction wasSuccessful:NO];
    }
    else
    {
        // this is fine, the user just cancelled, so don’t notify
        [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
    }
    S3DClient_SendEventToCurrentUser("MainAI","onCloseShop",NULL);
}

#pragma mark -
#pragma mark SKPaymentTransactionObserver methods

//
// called when the transaction status is updated
//
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
    for (SKPaymentTransaction *transaction in transactions)
    {
        switch (transaction.transactionState)
        {
            case SKPaymentTransactionStatePurchased:
                [self completeTransaction:transaction];
                break;
            case SKPaymentTransactionStateFailed:
                [self failedTransaction:transaction];
                break;
            case SKPaymentTransactionStateRestored:
                [self restoreTransaction:transaction];
                break;
            default:
                break;
        }
    }
}


- (void)requestProUpgradeProductData
{
   
    NSString *sPackNum = [NSString stringWithFormat:@"%d", S3DEngine_GetPackNum()];
   
    if ([sPackNum isEqualToString:@"1"])
    {
        NSString *sPackName = [NSString stringWithFormat:@"%s", "MyPackName1"];
        sPackNum = sPackName;
    }
   
    NSSet *productIdentifiers = [NSSet setWithObject:sPackNum ];
    productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
    productsRequest.delegate = self;
    [productsRequest start];
   
    // we will release the request object in the delegate callback
}

#pragma mark -
#pragma mark SKProductsRequestDelegate methods

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
    NSArray *products = response.products;
    proUpgradeProduct = [products count] == 1 ? [[products firstObject] retain] : nil;
    if (proUpgradeProduct)
    {
        NSLog(@"Product title: %@" , proUpgradeProduct.localizedTitle);
        NSLog(@"Product description: %@" , proUpgradeProduct.localizedDescription);
        NSLog(@"Product price: %@" , proUpgradeProduct.price);
        NSLog(@"Product id: %@" , proUpgradeProduct.productIdentifier);
    }
   
    for (NSString *invalidProductId in response.invalidProductIdentifiers)
    {
        NSLog(@"Invalid product id: %@" , invalidProductId);
    }
   
    // finally release the reqest we alloc/init’ed in requestProUpgradeProductData
    [productsRequest release];
   
    [[NSNotificationCenter defaultCenter] postNotificationName:kInAppPurchaseManagerProductsFetchedNotification object:self userInfo:nil];
}

@end


in few places in the code you can see:
NSString *sPackNum = [NSString stringWithFormat:@"%d", S3DEngine_GetPackNum()];
if ([sPackNum isEqualToString:@"1"])
{
NSString *sPackName = [NSString stringWithFormat:@"%s", "MyPackName1"];
sPackNum = sPackName;
}

so for simple using: from scripts Im sending integer variables: 1,2,3,4,5 - packs' numbers. Currently we have setup only for 1 pack, but you can add any amount by changing the "if" block. I know this way is not professional, but it works and as far most games haven't more then 1 IAP - it should be ok : ) Sure, as you understand you must change "MyPackName1" to the product id that you've entered in itunes connect(!).

4. Create InAppPurchaseObserver.h and add to the project:
Code: Select all
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
@interface InAppPurchaseObserver : NSObject <SKPaymentTransactionObserver> {

}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions;
@end


5. Create InAppPurchaseObserver.m and add to the project:
Code: Select all
#import "InAppPurchaseObserver.h"

@implementation InAppPurchaseObserver

// The transaction status of the SKPaymentQueue is sent here.
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
   for(SKPaymentTransaction *transaction in transactions) {
      switch (transaction.transactionState) {
            
         case SKPaymentTransactionStatePurchasing:
            // Item is still in the process of being purchased
            break;
            
         case SKPaymentTransactionStatePurchased:
            // Item was successfully purchased!
            
            // --- UNLOCK FEATURE OR DOWNLOAD CONTENT HERE ---
            // The purchased item ID is accessible via
            // transaction.payment.productIdentifier
            
            // After customer has successfully received purchased content,
            // remove the finished transaction from the payment queue.
            [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
            break;
            
         case SKPaymentTransactionStateRestored:
            // Verified that user has already paid for this item.
            // Ideal for restoring item across all devices of this customer.
            
            // --- UNLOCK FEATURE OR DOWNLOAD CONTENT HERE ---
            // The purchased item ID is accessible via
            // transaction.payment.productIdentifier
            
            // After customer has restored purchased content on this device,
            // remove the finished transaction from the payment queue.
            [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
            break;
            
         case SKPaymentTransactionStateFailed:
            // Purchase was either cancelled by user or an error occurred.
            
            if (transaction.error.code != SKErrorPaymentCancelled) {
               // A transaction error occurred, so notify user.
            }
            // Finished transactions should be removed from the payment queue.
            [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
            break;
      }
   }
}

@end


5. Now please open S3DEngine_EAGLView.h that already exists in tree and add this as first lines
Code: Select all
#import "InAppPurchaseManager.h"
#import <StoreKit/StoreKit.h>


now in S3DEngine_EAGLView.m add:
Code: Select all
            if (S3DEngine_GetWantBuy())
            {
                S3DEngine_DisableWantBuy();
                InAppPurchaseManager *iap = [[InAppPurchaseManager alloc] init];
                if ([iap canMakePurchases])
                {
                    [iap loadStore];
                    [iap purchaseProUpgrade];
                }
            }

inside "- (void)drawView" function
just after these lines:
if ( bVirtualKeyboardVisible && ! S3DEngine_iPhone_IsVirtualKeyboardNeeded ( ) )
{
[self hideVirtualKeyboard];
}
else if ( ! bVirtualKeyboardVisible && S3DEngine_iPhone_IsVirtualKeyboardNeeded ( ) )
{
[self showVirtualKeyboard];
}

If you was using my tutorial for GameCenter integration you'll probably understand that we're using the same method:
drawView is a loop function and calling each frame. We're creating a variable that switchs to true/false. Once we have changed the variable to "true" (by sending an event from scripts to objC) - loop will process it and change back to "false", so the functions will be called only once.

6. Open S3DEngine_Wrapper.cpp and add
Code: Select all
static bool bAskPackBuy = false;
static int nPackNum = 1;

before line
"extern "C" bool S3DEngine_iPhone_Init ( const char *_pLibraryFolder, const char *_pDocumentsFolder )", to common list of variables of this file.

7. In the same file, before function
"extern "C" bool S3DEngine_iPhone_LoadPack ( const char *_pStartupPackPath, const char *_pPackPath )}

add this:
Code: Select all
void BuyPackCallback ( unsigned char _iArgumentCount, const void *_pArguments, void *_pUserData )
{
   const S3DX::AIVariable *pVariables = (const S3DX::AIVariable *)_pArguments ;
   nPackNum = pVariables[0].GetNumberValue ( );
   bAskPackBuy = true;
}


8. Also inside this function (S3DEngine_iPhone_LoadPack), just before "return true;" add this:
Code: Select all
   S3DClient_InstallCurrentUserEventHook   ( "MainAI", "onLoadInAppStore",  BuyPackCallback, NULL ) ;


9. Also at the end of this file (before "admob support" block") add:
Code: Select all
extern "C" void S3DEngine_PurchasePack( int packNum)
{
    static S3DX::AIVariable variables[1];
    variables[1].SetNumberValue(packNum);
    S3DClient_SendEventToCurrentUser("MainAI","onPackBought",1,variables);
}

extern "C" int S3DEngine_GetPackNum()
{
    return nPackNum;
}

extern "C" bool S3DEngine_GetWantBuy()
{
    return bAskPackBuy;
}

extern "C" void S3DEngine_DisableWantBuy()
{
    bAskPackBuy = false;
}


10. Open S3DEngine_Wrapper.h
add this to top:
Code: Select all
#import "InAppPurchaseManager.h"
#import <StoreKit/StoreKit.h>


and this before last line ("#endif"):
Code: Select all
extern int S3DEngine_GetPackNum();
extern bool S3DEngine_GetWantBuy();
extern void S3DEngine_DisableWantBuy();
extern void S3DEngine_PurchasePack(int packNum);


11. Create and add to the project these 2 files:
SKProduct+LocalizedPrice.h:
Code: Select all
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
@interface SKProduct (LocalizedPrice)
@property (nonatomic, readonly) NSString *localizedPrice;
@end

SKProduct+LocalizedPrice.m:
Code: Select all
#import "SKProduct+LocalizedPrice.h"

@implementation SKProduct (LocalizedPrice)

- (NSString *)localizedPrice
{
    NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
    [numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
    [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
    [numberFormatter setLocale:self.priceLocale];
    NSString *formattedString = [numberFormatter stringFromNumber:self.price];
    [numberFormatter release];
    return formattedString;
}

@end

Im not sure we're using last two files in the project, but as you know its impossible to take away words from a song, so lets leave them : )

[Shiva3d scripts side]
Now probably we did everything in objC code, so lets change some scripts. Plz open your project in shiva editor and do following:

12. Add these handlers with code:
(important: if you have another AI name - you should use everywhere instead my "MainAI", also change it in objc code (by search and replace)

how this all works in my game (Zombilution):
Once player clicked "play" he see a list of available levels/stages. Then instantly a window with IAP propose sliding in and covering whole screen with two buttons "buy" or "ignore". If player clicks "buy" - Im blocking game's input, showing half black screen with label "loading..." and objc and handlers will handle everything else. Once player bought the in-app Im remembering it in a game's variable, unlocking pack's levels and saving this into xml localy, so on next startup player will see the unlocked/bought levels already without any fullscreen proposition of iap buying.



MainAI.onCloseShop:
Code: Select all
-- Im my case Im just deleting fullscreen window with IAP buying propose


MainAI.onLoadInAppStore (num)
Code: Select all
-- This can be empty

MainAI.onPackBought ( num )
Code: Select all
-- Pack was bought successfuly, so we're closing the "shop", unlocking levels and saving this info into xml file localy for next game startups.


MainAI.onTryToBuyPack ( num )
Code: Select all
-- here you should block screen input and something will happens
-- from objC code: success or failed purchasing, we wil handle it by other handlers
this.postEvent ( 0, "onLoadInAppStore", num )


Seems its done. As you can find from researching the code, that we're sending events from scripts to objC and then receiving events for success or faild transactions.
MainAI.onTryToBuyPack ( num ) is main handler, that you should call when someone click "buy" the IAP inside game (and its number if you have few IAPs).



-------------------------------------------------
---- MODIFICATION: "RESTORE" FEATURE (thanks to feng3d)
-------------------------------------------------

So now its a very important thing. Cause your game could be just rejected if you'll not implement this posibility for a player. Restore-feature allows you to restore previously bought in-app purchase on same or another device. For example, you have deleted the game and re-installed, or running from your another device. So there should be a button near "buy", which says "restore" and will execute next:

in your lua-scripts (lets say inside MainAI ai-model) you should add handler:

Code: Select all
MainAI.onRestoreIAP ( num )


you need to call it when user clicks "restore" button from your HUD.

now lets go to objC part:

inside S3DEngine_Wrapper.h:
Code: Select all
extern bool S3DEngine_GetWantRestoreIAP();
extern void S3DEngine_SetWantRestoreIAPFalse();


inside S3DEngine_Wrapper.cpp:
Code: Select all
void RestoreIAPCallback ( unsigned char _iArgumentCount, const void *_pArguments, void *_pUserData )
{
   
   bWantRestoreIAP = true;
   
   const S3DX::AIVariable *pVariables = (const S3DX::AIVariable *)_pArguments ;
   
   nPackNum = pVariables[0].GetNumberValue ( );
   
}


as you see we're using same nPackNum variable that we were using for buying iaps to avoid creating new ones.

than, inside extern "C" bool S3DEngine_iPhone_LoadPack ( const char *_pStartupPackPath, const char *_pPackPath )
:

Code: Select all
S3DClient_InstallCurrentUserEventHook   ( "MainAI", "onRestoreIAP",  RestoreIAPCallback, NULL ) ;


and at the end of the file:
Code: Select all
extern "C" bool S3DEngine_GetWantRestoreIAP()
{
    return bWantRestoreIAP;
}

extern "C" void S3DEngine_SetWantRestoreIAPFalse()
{
    bWantRestoreIAP = false;
}


now lets go to S3DEngine_EAGLView.m:
after this (not inside):
if (S3DEngine_GetWantBuy())
{
...
}
we're adding:
Code: Select all
            if (S3DEngine_GetWantRestoreIAP())
            {
                S3DEngine_SetWantRestoreIAPFalse();
               
                InAppPurchaseManager *iap = [[InAppPurchaseManager alloc] init];
               
                if ([iap canMakePurchases])
                {
                    [iap restoreIAP];
                }
               
            }


now we're going to InAppPurchaseManager.h:
and adding
Code: Select all
- (void)restoreIAP;

right before "@end"

and inside InAppPurchaseManager.m:
Code: Select all
- (void)restoreIAP
{
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];   
}

//implement cancel event..
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {
    NSLog(@"%s","User Cancel.");
    S3DClient_SendEventToCurrentUser("MainAI","onCloseShop",NULL);
}

//implement TransactionsFinished event..
- (void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
    NSLog(@"received restored transactions: %i", queue.transactions.count);
   
    int n = queue.transactions.count;
    if (n>0)
    {
       // you need to handle this by reading in-app IDs if you have few in-app purchases, please check replies on this topic
       S3DEngine_PurchasePack(1);
    }
    else
    {
        S3DClient_SendEventToCurrentUser("MainAI","onCloseShop",NULL);
    }

}


that's all. Scripts will receive "onCloseShop" if user will cancel restoring, and will receive "onPackBought" that we've implemented previously if everything cool.

(offtop: I wrote also a tutorial for GameCenter integration, so if you need it you can find at http://www.stonetrip.com/developer/forum/viewtopic.php?f=40&t=22363&hilit=game+center+integrated
Last edited by vklymenko on 05 Aug 2012, 17:28, edited 4 times in total.
User avatar
vklymenko
Platinum Boarder
Platinum Boarder
 
Posts: 758
Location: Europe

Re: In-App Purchase system integrated! (iOS)

Postby marciokoko » 17 Nov 2011, 03:32

I get:

NSArray may not respond to firstObject warning in InAppPurchaseManager...


I also got:

SKPaymentWithProductIdentifier is deprecated. I found I had to now use SKPaymentWithProduct instead, but now I get:

Incompatible pointer type in:

SKPayment *payment = [SKPayment paymentWithProduct:kInAppPurchaseProUpgradeProductId];

help pls
marciokoko
Fresh Boarder
Fresh Boarder
 
Posts: 2

Re: In-App Purchase system integrated! (iOS)

Postby vklymenko » 17 Nov 2011, 11:03

@marciokoko:
- have you added StoreKit to frameworks?
- can you send me your "Classes" folder, I'll do a quick merge with own, maybe some syntax mistakes? (vklymenkowork@gmail.com, just "Classes" folder, don't send me whole game/app sources, I'll steal it :twisted: )
User avatar
vklymenko
Platinum Boarder
Platinum Boarder
 
Posts: 758
Location: Europe

Re: In-App Purchase system integrated! (iOS)

Postby feng3d » 03 Feb 2012, 10:36

Thank you very much! It works. :D
User avatar
feng3d
Expert Boarder
Expert Boarder
 
Posts: 130
Location: Taiwan

Re: In-App Purchase system integrated! (iOS)

Postby vklymenko » 03 Feb 2012, 11:48

feng3d wrote:Thank you very much! It works. :D

great! :D
User avatar
vklymenko
Platinum Boarder
Platinum Boarder
 
Posts: 758
Location: Europe

Re: In-App Purchase system integrated! (iOS)

Postby Jhenna » 22 Feb 2012, 11:46

feng3d wrote:Thank you very much! It works. :D



Same here. Good Job man!
get stretch mark cream
Everyone has photographic memory; some just don't have the film.
Jhenna
Fresh Boarder
Fresh Boarder
 
Posts: 1


Re: In-App Purchase system integrated! (iOS)

Postby feng3d » 28 Jun 2012, 02:26

Now if the app is a Non-Consumable IAP APP.
No restore button for in app purchase causes rejection.

My new app was rejected yesterday :(

How can I implement this feature in ShiVa and Xcode?

Thanks!
User avatar
feng3d
Expert Boarder
Expert Boarder
 
Posts: 130
Location: Taiwan

Re: In-App Purchase system integrated! (iOS)

Postby marciokoko » 28 Jun 2012, 03:23

I changed the code and it seems to work. But I want to know something, if I want to make an inApp purchase, I understand how it pays and everything, but how do I make the original app execute the new code?

Let's say I want to open up a new level?
marciokoko
Fresh Boarder
Fresh Boarder
 
Posts: 2

Re: In-App Purchase system integrated! (iOS)

Postby feng3d » 28 Jun 2012, 03:49

Now I can restore the item ,but has error on cancel restore...

Trying.....
User avatar
feng3d
Expert Boarder
Expert Boarder
 
Posts: 130
Location: Taiwan

Re: In-App Purchase system integrated! (iOS)

Postby feng3d » 28 Jun 2012, 05:27

Ok...Maybe I finished it....
The restore is work.

//implement cancel event..
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {
NSLog(@"%s","User Cancel.");
S3DClient_SendEventToCurrentUser("MainAI","onCloseShop",NULL);
}

//implement TransactionsFinished event..
- (void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
//......write your code...
}

//call this for restore
- (void)restorePreviousTransaction
{
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];

}
User avatar
feng3d
Expert Boarder
Expert Boarder
 
Posts: 130
Location: Taiwan

Re: In-App Purchase system integrated! (iOS)

Postby harold35 » 28 Jun 2012, 07:49

can you give some précision about where you put this code ?

thank you :)
harold35
Platinum Boarder
Platinum Boarder
 
Posts: 433

Re: In-App Purchase system integrated! (iOS)

Postby vklymenko » 28 Jun 2012, 09:04

I changed the code and it seems to work. But I want to know something, if I want to make an inApp purchase, I understand how it pays and everything, but how do I make the original app execute the new code?
Let's say I want to open up a new level?

For example you have 10 levels in your game. In released version you're putting all the 10 levels, but making visible only e.g. 5 (on level select screen). And when in-app purchased - you have notification saying "user bought it, lets show other 5 levels" - now you're showing this stuff for player (cause he paid for it), and saving to xml info about that (so on next game launch it will be automaticly visible).
in the code you can find
Code: Select all
S3DClient_SendEventToCurrentUser("MainAI","onPackBought",1,variables);

so you need to create handler "onPackBought" in your scripts, replace MainAI with yours, and there will be 1 number-kind variable with in-app purchase index.
You may find GameCenter integration on the forum and figure out how to make callbacks between objC and lua scripts, its pretty easy.

About Restore-button - I think "feng3d" did all the work : ) anyone can call this parts of code by using same method of callbacks i guess.
At step7 in the tutorial you can see how hooks can be created. E.g. you have an empty handler in scripts. And you're sending like
Code: Select all
user.sendEvent(hUser,"MainAI","eventName")

and in objC you can process it like:
Code: Select all
S3DClient_InstallCurrentUserEventHook   ( "MainAI", "eventName",  BuyPackCallback, NULL ) ;

and now find "BuyPackCallback" in the tutorial and watch its behaviour. Its changing a trigger/variable from false to true "bAskPackBuy". And in another file you can find this variable in a loop, where engine watching: "if this variable is true - then change it to false again, but process some ObjC code".
User avatar
vklymenko
Platinum Boarder
Platinum Boarder
 
Posts: 758
Location: Europe

Re: In-App Purchase system integrated! (iOS)

Postby vklymenko » 07 Jul 2012, 19:34

so I've added "Modification "restore" feature" to the tutorial. If someone will implement this - let us know if this works well. I've tested it and seems work - so I re-submitted my game and will know results in 7-10 days : )
User avatar
vklymenko
Platinum Boarder
Platinum Boarder
 
Posts: 758
Location: Europe

Re: In-App Purchase system integrated! (iOS)

Postby vklymenko » 05 Aug 2012, 17:25

feng3d wrote:Ok...Maybe I finished it....
The restore is work.

//implement cancel event..
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {
NSLog(@"%s","User Cancel.");
S3DClient_SendEventToCurrentUser("MainAI","onCloseShop",NULL);
}

//implement TransactionsFinished event..
- (void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
//......write your code...
}

//call this for restore
- (void)restorePreviousTransaction
{
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];

}



hi, thanks again for your code, I think it need this modification, cause currently its possible to restore in-app purchases which player even never bought:
here is a sample:

Code: Select all
- (void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
   
    NSLog(@"received restored transactions: %i", queue.transactions.count);
   
    int n = queue.transactions.count;
    if (n>0)
    {
        for (SKPaymentTransaction *transaction in queue.transactions)
        {
            NSString *productID = transaction.payment.productIdentifier;
   
            if ([productID isEqualToString:@"IAP_ID_1"])
            {
                 // restore iap with id "IAP_ID_1" (send callback to scripts)
                S3DEngine_RestoreIAP(1);
            }
            else if ([productID isEqualToString:@"IAP_ID_2"])
            {
                 // restore iap with id "IAP_ID_2" (send callback to scripts)
                S3DEngine_RestoreIAP(2);
            }
       
        }
    }
    else
    {
        S3DClient_SendEventToCurrentUser("MainAI","onCloseShop",NULL);
    }
}


in this way you can get list of previously purchased in-app purchases and restore ONLY which were bought by the player before. Hope it have sense, i've implemented so in my latest game.
User avatar
vklymenko
Platinum Boarder
Platinum Boarder
 
Posts: 758
Location: Europe

Next

Return to Code Snippets