Working on first iOS in-app purchase app... questions

Seeking the assistance of the IAP gurus!

I am in the process of working on my first in-app purchase app, and I’ll be posting a few questions in the coming days/weeks. There’s your warning. I am reading along with the Ray Wenderlich tutorials, so I am learning the steps but will need some Xojo assistance

First, is there a good sample Xojo project to follow along? Second, can anyone share the link to the latest StoreKit extension from Jason King?

Simple first post, and I’ll be posting more questions in time. Just needing a starting point

Thanks!

The classes are contained within my iOSKit: https://github.com/kingj5/iOSKit/tree/master/Modules/StoreKit

They are Based on the Ray Wenderlich tutorials.

There is a complete example project like you create in those tutorials. I think it’s here? But there might be a newer one that I didn’t find with a quick search.

https://xojo.io/xrawfiles/dropboxdl/223f96c5b4da.xojo_binary_project

There is a lot of info on using StoreKit on the forum, I recommend you do a search through for possible solutions as well.

@Ryan Hartz As the information is kind of spread out around the forum, and some code might need updating here and there, perhaps you would considering taking notes as you go? It would make a nice blog or forum post, because this topic comes up now and then.

Thank you for the suggestion Gavin. I am happy to report that I have already begun the process of compiling notes and will definitely share either on here or send to Xojo for a blog post. I agree that a step-by-step process is warranted, and I would be more than happy to share to hopefully help others. But first, gotta get it to work on my end :slight_smile:

And @Jason King thank you for responding and for making StoreKit for us. Question. In the sample project you sent, I wanted to run the project to see what happens. I know I can’t do an actual “purchase” in simulator but wanted to see the table loaded with the products/prices. However, when I ran it, I got the following error on the RowData method

Not sure what to do with that. Ideas?

Ok, I found a post with a sample project from Jeremie, and I was able to tweak it a bit to get my IAP product listed. It’s a start!
http://www.jeremieleroy.com/upload/Storekit%20Example.xojo_binary_project.zip

Follow up questions:

  • What do you do to make a purchase? Fancy Buy button of course, but what goes in that button? I didn’t see anything in the above example that covers this. The Ray Wenderlich tutorial is daunting since it’s not in Xojo. I know I need to cover the Restore piece too, but that question will come later
  • When a purchase is made, what is the best approach to tell the app to unlock the feature? Do you have a database table and store something in here?
  • I’m assuming that you build the app as a full-feature app, then make some features as unavailable as they are only part of the “free” version, and when the purchase is made and is successful, you set the “premium” features to unlock, correct?

Hi Ryan,

[code]Using StoreKit

Const productID = “com.company.appname.inappID”

if SKPaymentQueue.CanMakePayments = False then

MsgBox(“Error”, “In-app payments disabled”)

Else

Dim Helper as StoreKit.InAppPurchaseHelper = StoreKit.InAppPurchaseHelper.GetInstance

For Each p As StoreKit.SKProduct In products

if p.productIdentifier = productID then
  
  Helper.BuyProduct(p)
  
  Return
  
end if

Next

end if[/code]

Using Jason’s StoreKit module, if the purchase is successful, the following property will be set to true

Const productID = "com.company.appname.inappID" Dim productPurchased as Boolean = Foundation.NSUserDefaults.StandardUserDefaults.BoolForKey(productID)

Yes exactly.

I have a UserRights class with several methods based on username/userID and also based on the fact that Foundation.NSUserDefaults.StandardUserDefaults.BoolForKey(productID) will return True or False for some features.

This is the code I use in the Restore button

[code]Redim StoreKit.InAppPurchaseHelper.productIDArray(-1)

StoreKit.InAppPurchaseHelper.productIDArray.Append “com.company.xxx.premium”
StoreKit.InAppPurchaseHelper.productIDArray.Append “com.company.xxx.premiumpromo”
StoreKit.InAppPurchaseHelper.productIDArray.Append “com.company.xxx.weather”

StoreKit.InAppPurchaseHelper.GetInstance.Setup
StoreKit.InAppPurchaseHelper.GetInstance.RestoreCompletedTransactions[/code]

Hi Jeremie. Thank you! I’m trying to implement these now and making sure everything at least runs. Not yet at the point of testing a purchase on a device

Couple things I’m hoping you can help with

  • In your first part for making the purchase. Where do you advise having this code? I tried to put this in a separate button, but it does not seem to recognize “products”. I have “products” as a parameter as Foundation.NSArray in a LoadTable method. Is it possible to have this accessible to be loaded in the method and then called elsewhere such as in the button. When I tried to make a property called SomeProducts to use in place of “products” it actually crashed Xojo. Not sure if a property is the right solution

[code] For Each p As StoreKit.SKProduct In products

if p.productIdentifier = productID then
  
  Helper.BuyProduct(p)
  
  Return
  
end if[/code]
  • In your last code for restore, the “setup” is not recognized. Is this in a newer version of StoreKit that I might not have in mine?
StoreKit.InAppPurchaseHelper.GetInstance.Setup

Sorry I forgot to mention I have a Property products() as StoreKit.SKProduct in the iOSView.

In the method LoadProduct called by StoreKit.InAppPurchaseHelper.GetInstance.RequestProductsWithCompletionHandler(WeakAddressOf LoadProduct)

I keep an array of all SKProduct loaded by the helper for future reference:

Using StoreKit
Using Foundation

Dim prod As SKProduct

For i as Integer = 0 to products.Count-1
  
  prod = new SKProduct(products.Value(i))
  
    Self.products.append prod
Next i

I actually added the Setup method, it re-initializes the Singleton class and reloads product identifiers. You might not need this, but I needed sometimes to dynamically reload all product identifiers.

[code]Public Sub Setup()

dim set as new Foundation.NSMutableSet
for each t as Text in productIDArray
set.AddObject( new NSString(t) )
next

productIdentifiers = set

purchasedProductIdentifiers = Foundation.NSMutableSet.Set

dim productIDsArray as Foundation.NSArray = productIdentifiers.allObjects
dim count as UInteger = productIDsArray.count
for i as Integer = 0 to count - 1
dim productID as new Foundation.NSString(productIDsArray.Value(i))
dim productPurchased as Boolean = Foundation.NSUserDefaults.StandardUserDefaults.BoolForKey(productID.StringValue)
if productPurchased then
purchasedProductIdentifiers.AddObject productID
end if
next

StoreKit.SKPaymentQueue.DefaultQueue.AddTransactionObserver(self)
End Sub
[/code]

Thank you so much Jeremie!

Ok, I am putting the final touches on the app and am trying to get it on an actual device to test the purchase. In ITC, I created the necessary profiles for development and distribution and also for the ad hoc to test on my iPhone. However, when I go to build it, I am getting a gross looking message with this

This was the ad hoc provisioning profile I created. Thinking I may have messed something up in ITC, I deleted that one and created a new ad hoc profile with a different name. Downloaded it, downloaded manually in Xcode, even restarted Xojo, but I am still getting that message with the old ad hoc distribution profile as above. Is this located in KeyChain that I need to delete?

Edit: Might it have anything to do with the fact that my Xcode version is 10.1 (haven’t jumped to 11 yet) and that in Xcode devices, it says

I am downloading a new version of Xcode now to see if that helps. But still looking for advice if I am on the right path. Thanks!

Getting there

You do not need ad hoc to test on your own devices.

You just need to add them as test devices in your development provisioning profile and then send the app to the device using the devices view in Xcode

Thanks. But when I went to build in xojo, I got that error message about the profile, and it didn’t build completely. Kinda wondering if I messed something up when making that ad hoc profile. Do you know what I can try? Delete profiles and create again without the ad hoc, or delete something in KeyChain and then add back?

Update, and good news everyone! (Futurama shtick if anyone picked up on that) First, to fix the issue above with the error message when building the app, I saw in the long message a file path to the provisioning profile it was saying was not valid (Library > MobileDevice > Provisioning Profiles), and I deleted the couple with yesterday’s date. Back in ITC, I re-downloaded only the Distribution and Development provisioning profiles. Did the build, and no errors!

Moved the app to the phone for sandbox testing, and it worked!! Actually had to type in the email address and password a few times. Not sure if that is normal or I spelled the password wrong in my test account. I noticed on the first two attempts, I had to put in both the email address and the password, and these were not successful “transactions”, but the 3rd time I only had to put in my test account’s password, and it worked! Could this have anything to do with the fact that in Settings, I really only signed out on my Apple ID and did not immediately sign in with the test ID? Found a quick tutorial online, and they just suggested signing out then opening the app and then signing in with test account

I have a few little mods to make before I try to submit to Apple, but this IAP is huge. And I want to make a new test account and try this all again. I will be planning on doing a Xojo tutorial on IAP with the notes I jotted down. Probably won’t get to until beginning of December.

Thank you Jeremie and Jason for your help!

Really close. Hopefully this will be my last question. This one regarding the Restore Transaction function

When in my test sandbox account, I deleted the app on my phone, reinstalled it, and clicked the Restore button. Closed out of the purchase view, and the full version worked. Good! I then deleted the app again, signed back into iTunes with my personal account for the phone, opened the app and clicked Restore, and it still restored the app to the full version. Is this correct? I would have thought since I did not “purchase” it from my IT account that it would not have given me full access. I am using the code from Jeremie in the Restore button exactly, except for the IAP string of course. Did I miss something, or is this as expected?

Also, in the Restore function, is there a way to display a message. i.e. if you bought it before and had to re-download the app and restore your purchase, then “success, your purchase has been restored”, and if you hadn’t purchased, then message “we didn’t find a record of your past purchase of this IAP”

This is a tricky question. Your iPhone might have redownloaded the settings (NSUserDefaults) of the app even though you were using a different iTunes account.

The best way to test this would be using two devices.

But you should still investigate this issue, I have seen some apps where hitting the “restore” button has given me full access although I never purchased it.

Yeah I think that before restoring it is probably best to clear all Keys from NSUserDefaults to prevent this. Definitely an edge case

It worked! Tried it out on my wife’s phone tonight. While she was still signed in to IT, tried the restore. It asked for her password and said something like invalid, so I’m happy about that. So thank you again

Being that there was a message box that showed when the restore was invalid, how do I make a message showing the restore was successful? Can I use the if productPurchased = true then show message alert? Not sure if the methods used in Restore fire the productPurchased boolean

@Jason King - your suggestion on deleting the NSUserDefaults, is this something you do In the app, specifically in the Restore function before calling restore, or are you only pointing out to do this only before testing? Not sure if you meant that to be a standard practice use in apps

Thank you!

[quote=463556:@Ryan Hartz]It worked! Tried it out on my wife’s phone tonight. While she was still signed in to IT, tried the restore. It asked for her password and said something like invalid, so I’m happy about that. So thank you again

Being that there was a message box that showed when the restore was invalid, how do I make a message showing the restore was successful? Can I use the if productPurchased = true then show message alert? Not sure if the methods used in Restore fire the productPurchased boolean

@Jason King - your suggestion on deleting the NSUserDefaults, is this something you do In the app, specifically in the Restore function before calling restore, or are you only pointing out to do this only before testing? Not sure if you meant that to be a standard practice use in apps

Thank you![/quote]
I’m not sure what event you are using now to find out something was purchased, but if you are using the notifications from my Notification_Center module included in the project then you should get a notification callback after a purchase/restore. Otherwise you might have to modify the iAPHelper class since there is a callback internally (that isn’t exposed to you) that happens when a restore occurs. If you aren’t using the notifications route it’s very easy to setup.

I might have oversimplified in my head, but it seems to be that if you call restore it should get everything so all previously set IDs are set again, and clearing all of them before calling restore would be correct. But that might be wrong. Probably worth testing before you make any changes.

Thank you Jason. I am using the full folder of iOSKit, and I do see that the Notification_Center is listed under StoreKit, so that’s good news

How is the call made to clear the NSUserDefaults? I would assume I would put this before the Restore method is called in order to wipe out what is currently there and then restore if present, yes? But what is the call to clear?

Thanks!

The call to clear should be Foundation.NSUserDefaults.StandardUserDefaults.RemoveObjectforKey("com.company.xxx.premium")

But I wouldn’t recommend removing such keys in a production app. You can however do it on your device to make sure that Restore works.
What happens if the user is offline and hits the Restore button? He will lose access to all previously unlocked features.