iOS InApp Purchases

Previous topic - Next topic

ampos

How to use InApp Purchases in Apple's iOS devices.

This code completly has been done by MATCHY, so you have to cheer him, not me  :nw: :enc: :nw: :enc: :nw: :enc:

These are the steps I have followed, as explained in a online guide (http://troybrant.net/blog/2010/01/in-app-purchases-a-full-walkthrough/). Some steps may be necesaries or not, but I have follow this.

As far as I know, it works in all iOS version and devices. In some jailbroken devices it does not work, but on others it does.

The file InApp.mm has been modified by Michael Werren, and it now works with user cancelled purchases.

Please, follow STRICTLY the following procedure to have InApp purchases working. It is known that it works if you follow it.

-Create your apple app ID as always in Apple Itunes Connect page. In this example, I will use "MyApp", with ID "com.me.myapp"

-Upload your test binary program to itunes connect. Once uploaded, reject the binary.

-Go to your app itunes connect and "create a purchase".I have not created/tested (yet) anything but non-consumable purchase...

--Reference Name: common name for the product. I used "Full version". This name is non-editable, and it will not be displayed in the App Store.

--Product ID: unique id for your app. Typically of the form com.company.appname.product, but it can be whatever you want. It does not need to have your app's App ID as a prefix. I will use "myapp.full"

--Type: You have 3 choices:

        Non-consumable: only pay once (use this if you want a free-to-pro-upgrade product)
        Consumable: pay for every download
        Subscription: recurring payment

--Price Tier: price of the product. See the price matrix for the different tiers.

--Cleared for Sale: check this now. If you don't, you will get back an invalid product ID during testing.

--Language to Add: Pick one. The following two fields will appear:

--Displayed Name: Name of your product shown to your user. I chose "Upgrade to Full".

--Description: What the product does. The text you enter here is sent along with the Displayed Name and Price when you fetch an SKProduct in code.

--Screenshot: Your feature in action. Despite the text on the screen about the screenshot submission triggering the product review process (a very sloppy design choice, IMHO), you can safely add the screenshot now without the product being submitted for review. After saving the product, just choose the "Submit with app binary" option. This will tie the product to the app binary, so when you finally submit the 100% complete app binary, the product will be submitted as well.

-Create a Test User for your program in the iTunes Connect page (manage users -> test user -> create). Before trying to check the purchase process on the iOS, you should  logoff from your itunes appstore account (settings -> store -> logoff).

Back to GLBasic and your program...

Write code for managing you InApp purchases.

On the XCode project, you have to make something:

-Add "StoreKit.framework" in the frameworks windows.

-Add in the resources window the files "inapp.mm" and "inapp.h"

Follow the GLB source code to understand how it works.

In XCode keep open the console window to see all the msgs that Apple sent to the iOSthing and the debug msgs from the source code.


GLBasic sample source code

Code (glbasic) Select
// --------------------------------- //
// Project: inapptest
// Start: Tuesday, April 26, 2011
// IDE Version: 9.077

FUNCTION main_test:

// First we have to check if iOS InApp has been cracked...

glb_inapp_buy("pirata")
local request_data$ = "NO_DATA"
REPEAT
showscreen
request_data$ = UCASE$(glb_inapp_product_request())
UNTIL request_data$="TRANSACTION_SUCCESSFUL" OR request_data$="TRANSACTION_FAILED"

IF request_data$="TRANSACTION_SUCCESSFUL"
                debug "InApp purchase is cracked!"
SLEEP 3000
RETURN
ENDIF

LOCAL id_list$ = "myapp.full,myapp.extras"  // product id, myapp.extras does not exist in our example
request_data$ = "NO_DATA"

LOCAL is_online$ = glb_inapp_is_online()
CLEARSCREEN 0xffffff
PRINT "Is online: " + is_online$, 10, 10, TRUE
SHOWSCREEN
MOUSEWAIT

CLEARSCREEN 0xffffff
glb_inapp_init(id_list$)
WHILE request_data$ = "NO_DATA"
PRINT PLATFORMINFO$("TIME"), 0, 0, TRUE
PRINT "Requesting product listing...", 10, 10, TRUE
SHOWSCREEN
request_data$ = glb_inapp_product_request()
WEND

debu("[LISTING]:" + request_data$)

LOCAL item$[]
LOCAL item_count = SPLITSTR(request_data$, item$[], "##")
LOCAL i

CLEARSCREEN 0xffffff
PRINT "Products retreived", 10, 10, TRUE
PRINT "Tap screen to BUY first product (upgrade)", 10, 30, TRUE
IF item_count > 3
FOR i = 0 TO item_count - 1 STEP 4
PRINT i + ". " + item$[i + 1], 10, 50 + (i * 14)
PRINT item$[i + 2], 20, 60 + (i * 14)
PRINT item$[i + 3], 20, 70 + (i * 14)
NEXT
ELSE
PRINT 0 + ". " + "NO PRODUCTS or ERROR.", 20, 50 + (i * 14)
ENDIF
SHOWSCREEN
MOUSEWAIT
SHOWSCREEN

CLEARSCREEN 0xffffff
glb_inapp_buy("myapp.full")
request_data$ = "NO_DATA"
WHILE request_data$ <> "TRANSACTION_SUCCESSFUL" OR request_data$ <> "TRANSACTION_FAILED"
PRINT PLATFORMINFO$("TIME"), 0, 0, TRUE
PRINT "Transacting purchase...", 10, 10, TRUE
SHOWSCREEN
request_data$ = glb_inapp_product_request()   //I changed this to =UCASE$(glb...())
WEND
debu("[TRANSACTION]:" + request_data$)
IF request_data$ = "TRANSACTION_SUCCESSFULL"
debu("Product flagged locally") // eg. dat file store local purchased flag for product (upgrade)
ELSEIF request_data$ = "TRANSACTION_FAILED"
debu("User probably cancled or no funds") // eg. dat file ignore
ENDIF

END
ENDFUNCTION

SUB GLB_ON_PAUSE:
pausado = 1
debu("MAIN PAUSE")
ENDSUB

SUB GLB_ON_RESUME:
pausado = 0
debu("MAIN RESUME")
ENDSUB

SUB GLB_ON_QUIT:
pausado = -1
debu("MAIN QUIT")
ENDSUB

FUNCTION debu: data_in$
?IFDEF IPHONE
STDOUT "[GLB]->" + data_in$ + "\n"
?ELSE
DEBUG "[GLB]->" + data_in$ + "\n"
?ENDIF
ENDFUNCTION

?IFDEF IPHONE
IMPORT "C" char* glb_inapp_is_online()
IMPORT "C" int glb_inapp_can_pay()
IMPORT "C" void glb_inapp_init(const char* id_list)
IMPORT "C" char* glb_inapp_product_request()
IMPORT "C" void glb_inapp_buy(const char* product_id)
?ELSE
FUNCTION glb_inapp_is_online:
ENDFUNCTION

FUNCTION glb_inapp_can_pay:
ENDFUNCTION

FUNCTION glb_inapp_init: id_list
ENDFUNCTION

FUNCTION glb_inapp_product_request:
ENDFUNCTION

FUNCTION glb_inapp_buy: product_id
ENDFUNCTION
?ENDIF


NOTE: In iTunes connect, when you are ready to sent your app to review, on the page where you upload the graphics, at the bottom, there is a paragraph titled "In App purchases". Edit it and enable the InApp purchases that you want Apple's review team to review, or your app will not have InApp purchases availables.


[attachment deleted by admin]
check my web and/or my blog :D
http://diniplay.blogspot.com (devblog)
http://www.ampostata.org
http://ampostata.blogspot.com
I own PC-Win, MacBook 13", iPhone 3G/3GS/4G and iPAC-WinCE

ampos


And here is the code used on my current source code:

Code (glbasic) Select
FUNCTION compra:
LOCAL ys=110*appzoom

debu("Comprando...")

LOCAL id_list$ = "glowingsky.full"  // product id
LOCAL request_data$ = "NO_DATA"

glb_inapp_init(id_list$)
WHILE request_data$ = "NO_DATA"
PRINT PLATFORMINFO$("TIME"), 0, ys, TRUE
PRINT "Getting into AppStore", 0, ys+(30*appzoom), TRUE
SHOWSCREEN
request_data$ = glb_inapp_product_request()
WEND
debu("[LISTING]:" + request_data$+" [END]")

LOCAL item$[]
LOCAL item_count = SPLITSTR(request_data$, item$[], "##")
LOCAL i

IF item_count > 3
PRINT "Access granted", 10, ys, TRUE
PRINT "Tap to upgrade", 10, ys+(30*appzoom), TRUE
FOR i = 0 TO item_count - 1 STEP 4
PRINT i+" ID:"+item$[i],0,ys+(180*appzoom);debu(i+" ID:"+item$[i])
PRINT "Short:"+item$[i+1],0,ys+(90*appzoom);debu("Short:"+item$[i+1])
PRINT "Price:"+item$[i+2],0,ys+(120*appzoom);debu("Price:"+item$[i+2])
PRINT "Longg:"+item$[i+3],0,ys+(150*appzoom);debu("Longg:"+item$[i+3])
NEXT
ELSE
PRINT "NO ACCESS or ERROR.", 0,ys
ENDIF
SHOWSCREEN
MOUSEWAIT

glb_inapp_buy("glowingsky.full")
request_data$ = "NO_DATA"
REPEAT    // THIS IS THE LOOP THAT DOES NOT DETECT THE USER CANCELLATION
PRINT PLATFORMINFO$("TIME"), 0, 0, TRUE
PRINT "Transacting purchase...", 0, 30, TRUE
SHOWSCREEN
request_data$ = UCASE$(glb_inapp_product_request())
IF request_data$<>"NO_DATA" THEN debu("[MSG]->"+request_data$+"<-")
UNTIL request_data$="TRANSACTION_SUCCESSFUL" OR request_data$="TRANSACTION_FAILED"

debu("[TRANSACTION]:" + request_data$)
IF request_data$ = "TRANSACTION_SUCCESSFUL"
debu("DONE") // eg. dat file store local purchased flag for product (upgrade)
ELSEIF request_data$ = "TRANSACTION_FAILED"
debu("ERROR!") // eg. dat file ignore
ENDIF

END
ENDFUNCTION
check my web and/or my blog :D
http://diniplay.blogspot.com (devblog)
http://www.ampostata.org
http://ampostata.blogspot.com
I own PC-Win, MacBook 13", iPhone 3G/3GS/4G and iPAC-WinCE

ampos

Just checked with "consumable" purchases and it works, also.
check my web and/or my blog :D
http://diniplay.blogspot.com (devblog)
http://www.ampostata.org
http://ampostata.blogspot.com
I own PC-Win, MacBook 13", iPhone 3G/3GS/4G and iPAC-WinCE

ampos

My app with InApp purchases in action...

[attachment deleted by admin]
check my web and/or my blog :D
http://diniplay.blogspot.com (devblog)
http://www.ampostata.org
http://ampostata.blogspot.com
I own PC-Win, MacBook 13", iPhone 3G/3GS/4G and iPAC-WinCE

ampos

More screenshots.


[attachment deleted by admin]
check my web and/or my blog :D
http://diniplay.blogspot.com (devblog)
http://www.ampostata.org
http://ampostata.blogspot.com
I own PC-Win, MacBook 13", iPhone 3G/3GS/4G and iPAC-WinCE

matchy

Well done! I'm glad you got around to using my uncredited wrapper.  :P

I had issues with the app store, so I'll be keen to see it working live.

spicypixel

Just glanced at this but it uses TRANSACTION_SUCCESSFUL and TRANSACTION_SUCCESSFULL ??
http://www.spicypixel.net | http://www.facebook.com/SpicyPixel.NET

Comps Owned - ZX.81, ZX.48K, ZX.128K+2, Vic20, C64, Atari-ST, A500.600.1200, PC, Apple Mini-Mac.

Crivens

Nice one guys, this looks brilliant.

A couple of questions though:-
1. When you say it doesn't work for cancellations, does that mean if they cancel they still get the upgrade or what?

2. What happens if the user re-installs the app, or installs on another machine? How do they keep their purchases (ie. do we have to handle this like keep a flag on a website)? And if they don't then is this just a standard Apple thing?

3. For an upgrade from free to pro, for example, then does this method mean we store a local flag or is there an alternative method where apple can overwrite the free binary with a different binary? I'm just thinking that if it's just a flag then a free one is basically a full version without a flag and is open more to piracy, and also you have to include all extra data (eg. extra levels) in the free game. Or do we have to handle that? Eg. store extra level data on our own site.

For example my original game is much smaller than the full version because I don't include all the higher def pictures for the additional levels. If it's just a flag then a free one would still be 4 or 5 times the size. Then again, I guess it's easier this way and space is less important.

Cheers

Current fave quote: Cause you like musicians and I like people with boobs.

ampos

LOL, sorry Matchy, I wrote this post with my wife yelling "work and don't lost your time!" at my back... I will fix it.

It is SUCESFUL with 1 L, but Matchy's original code had 2 LL, forgot to fix it.

1.- No, if he cancels the purchase, the program will not detect it and will not go outside the loop. It is just looking for an OK or ERROR, not CANCEL. This CANCEL msg is not sent back by the xcode source.

2.- If you reinstall the app and press the BUY button, iTunes just say "you had bought this item previously, press OK to download it again" and GLB just get a OK msg. In fact, you can not know if it is a new or old purchase.

3.- In any case you have to store somewhere the "upgrade" flag. When I get the OK msg I do this:

Code (glbasic) Select
iniopen "prefs.txt"
iniput "main","full",encrypt("password",platforminfo$("ID"))
full=true


and at the start I set this

Code (glbasic) Select
iniopen "prefs.txt"
a$=iniget("main","full")
a$=decrypt("password",a$)
if a$=platforminfo("ID")
   full=true
else
   full=false
endif


Perhaps you have to download the extra gfx once it is bought.
check my web and/or my blog :D
http://diniplay.blogspot.com (devblog)
http://www.ampostata.org
http://ampostata.blogspot.com
I own PC-Win, MacBook 13", iPhone 3G/3GS/4G and iPAC-WinCE

Crivens

#9
Ah ok, so there isn't a way to query iTunes to see what you have previously bought? Bummer if installing on a second device I guess. Still better for us when you look at it that way :)

I take it though that all info will be remembered in iTunes, so if the device needs a wipe and restore then all your old created files will be still there, including things like upgrade flags and the like.

Unless of course iTunes remembers the purchases (at least Non-consumable) like it now does with normal purchases, and then if you do select to purchase on a 2nd device (or re-installed one) then it will go through the process but not actually charge you.

Cheers
Current fave quote: Cause you like musicians and I like people with boobs.

ampos

The upgrade flag is not saved, as it is writing by your program. The user has to bought it again, but itunes will return OK/BOUGHT although there was not really a bought.

Also, any itunes email account can be used upto 5 iOS devices, so a user can bought 1 app/InApp and use/install it in his iphone, his wife's, his ipad and in the children iPod at no extra cost.
check my web and/or my blog :D
http://diniplay.blogspot.com (devblog)
http://www.ampostata.org
http://ampostata.blogspot.com
I own PC-Win, MacBook 13", iPhone 3G/3GS/4G and iPAC-WinCE

Crivens

I get that with a whole application (I do it between my iPods and my iPhone), but not with in-app purchases.

This article sounds interesting:-
http://www.tuaw.com/2011/03/08/dear-aunt-tuaw-do-i-have-to-re-buy-apps-for-all-my-devices/

Particularly this paragraph:-
QuoteAs for in-app purchases, it depends. Non-consumable purchases, i.e. buy once, use-forever purchases are good for all devices registered to an account. Examples of nonconsumable purchases include adding game levels, enabling ad-removal, and unlocking features. Once unlocked, they remain available for all devices. Application developers are able to check for prior purchase and apply that purchase to new devices through a process known as restoring purchases.
And especially the last line about restoring purchases.

Cheers
Current fave quote: Cause you like musicians and I like people with boobs.

ampos

As I said, once you press BUY, iOS will tell you it was already bought, asking to press OK for a free download. In fact, you can see the (spanish) advise in capture 6.jpg of my previous post.
check my web and/or my blog :D
http://diniplay.blogspot.com (devblog)
http://www.ampostata.org
http://ampostata.blogspot.com
I own PC-Win, MacBook 13", iPhone 3G/3GS/4G and iPAC-WinCE

Crivens

Ah ok, sorry I didn't understand what you said. I thought you meant just on the main purchases from the app store.

So basically any non-consumable in-app purchases then if you install on another device, attempting to purchase the in-app purchase again will just tell you have done this already and to download for nothing (I assume this is what the Spanish text says), but for consumable purchases (eg. extra ammo) then tough you have to buy again? (fair enough as is a different device).

Cheers
Current fave quote: Cause you like musicians and I like people with boobs.

ampos

Yes, consumables purchases have to be bought any time.

-Non-consumable: buy a new fish for your aquarium.

-Consumable: buy food for your fish.

-Suscription: each month, receive 10 liters of fresh water!

Actually, there are 2 suscription modes avaiable: auto and manual. Quoted from Apple:

QuoteConsumable

A consumable In-App Purchase must be purchased every time the user downloads it. One-time services, such as fish food in a fishing app, are usually implemented as consumables.

Non-Consumable

A non-consumable In-App Purchase only needs to be purchased once by the user. Services that do not expire or decrease with use, such as a new race track for a game app, are usually implemented as non-consumables.

Auto-Renewable Subscriptions

An auto-renewable In-App Purchase subscription allows the user to purchase in-app content for a set duration of time. At the end of that duration the subscription will renew itself, unless the user opts out. An example of an auto-renewable subscription would be a magazine or newspaper that takes advantage of the auto-renewing functionality built into iOS.

Auto-renewable subscriptions will be delivered to all devices associated with the user's Apple ID. When you create an auto-renewable subscription in iTunes Connect, you begin by selecting the duration(s) that you will offer. When a duration ends, the App Store will automatically renew the subscription. Note that if the user has opted out of this functionality, the subscription will expire at the end of that duration. You must make sure that your app can determine whether a subscription is currently active and renewable.

Non-Renewing Subscription

In the past, a non-renewing In-App Purchase subscription has been used for services with a limited duration. An example of this would be a magazine or newspaper that requires users to renew their own subscriptions. Non-renewing subscriptions can still be offered, but auto-renewable subscriptions are now preferred for the following reasons:

    When creating an auto-renewable subscription, you can easily set up the various durations that you want to offer. Non-renewing subscriptions do not have this feature, so you must provide the information some other way. As this is often done in the display name, you end up with a separate listing for every possible duration. By contrast, auto-renewable subscriptions allow you to have a single listing where the user simply chooses one of the durations that you offer.

    Because a non-renewing subscription requires a user to renew each time, your app must contain code that recognizes when the subscription is due to expire. It must also prompt the user to purchase a new subscription. An auto-renewable subscription eliminates these steps.

    As part of iOS, an auto-renewable subscription will automatically be delivered to all devices associated with the user's Apple ID. To make device-syncing available for a non-renewing subscription, you would have to create your own delivery system.

check my web and/or my blog :D
http://diniplay.blogspot.com (devblog)
http://www.ampostata.org
http://ampostata.blogspot.com
I own PC-Win, MacBook 13", iPhone 3G/3GS/4G and iPAC-WinCE