In app purchasing - subscription testing on iOS

Hi there,

I’m new to working with IAP and am trying to get an auto-renew subscription for iOS working for testing purposes initially. Having spent 2 days reading countless online tutorials and the Corona docs, I’ve got a certain way through it, but I’m probably now more confused than I was at the start so I’m reaching out to anyone who has been through the process of setting up auto-renewing subscriptions before and has them working.

The steps I have successfully taken so far:

  • Set up my app in iTunes Connect (and ensured banking info all up to date)
  • Set up 2 x auto-renewing in-app purchases in iTunes Connect (both in the same group), one a monthly subscription (£1.99) and one a yearly subscription (£19.99).
  • Created an Ad-Hoc distribution certificate and installed the app on my test iPad using that certificate.
  • Set myself up as a sandbox tester for the app under a completely new Apple account.
  • Followed the Corona IAP setup guide for iOS and reread it a lot of times in case I missed anything!
  • Created some code to both 1.) allow the purchase of either subscription and 2.) restore previously purchased subscriptions in the event the app is deleted. Have included this code at the foot of this post.

I have my app pulling back and displaying the correct 2 products from the store, with the correct prices, descriptions and product identifiers.

If I open the app and “buy” one of the subscriptions, all is well and the purchase appears to go through successfully.

But then the problems begin… any ideas gratefully welcomed and I will gladly write all this up as a tutorial if I can get through the process and have it working!!

  • Problem 1: Failed Transaction Issue… If I buy the monthly membership (£1.99) and then try to upgrade to the yearly membership (£19.99), I get a message that tells me I’m already subscribed and offering me the chance to change my subscription. All good. Except that when I agree to that, I get a “Failed Transaction” returned to my transactionListener function whilst the native iOS popup that is displayed says “You’re all set” and seems to indicate success. This also happens if I start with the yearly and try to change to the monthly. Why the transaction is coming back as “Failed” when iOS tells me success is baffling me and makes it impossible for me to gracefully handle what happens next as the information is contradictory. Anyone else experience this? Am I missing something?

  • Problem 2: Restored Purchases Issue… I delete the app and then load it again and run store.restore() - I get every purchase I ever made during the testing restored - hundreds of subscriptions to the same two test products from every time I upgraded / downgraded. I may not be understanding what restore is all about. They’re all linked to the same original purchase - I can tell that from the originalIdentifier being the same for each. So how do I know which one to restore (the monthly or the yearly) as there is no way to specify restoring a specific purchase from what I can tell - it just restores everything.

  • Problem 3: View Existing Subscriptions… I would like to be able to view existing subscriptions associated with my test account somewhere so I can tell when the upgrade / downgrade requests are working properly.

  • Finally: Testing Cancelling Subscriptions… is there any way of testing the process of a user cancelling a subscription mid way through it running? I can’t find any store.cancelPurchase(id) method or anything like that. How can we allow customers to cancel subscriptions from within the app if required or do they have to do that via Apple directly?

So, if anyone can shed some light on any of the above, thank you in advance enormously. And I am sorry for the length of this post!!!

Thanks,

Ian

Code:

I’ve included the current (pretty clunky test code, so please ignore the short cuts I’ve taken!!) code below but I’ve left out the button creation code etc. as that isn’t relevant. The two functions right at the bottom are called from the buttons to subscribe to and restore products.

-- The transaction listener for initialising the store local transactionListener = function( e ) local transaction = e.transaction; if ( transaction.isError ) then Debug:writeToConsole("TRANSACTION ERROR:"); Debug:writeToConsole( transaction.errorType ); Debug:writeToConsole( transaction.errorString ); else -- No errors; proceed if ( transaction.state == "purchased" ) then -- Handle a normal purchase here Debug:writeToConsole( "SUCCESSFUL TRANSACTION FOR:" ); Debug:writeToConsole( transaction.productIdentifier ); elseif ( transaction.state == "restored" ) then Debug:writeToConsole( "TRANSACTION RESTORED FOR:" ); Debug:writeToConsole( transaction.productIdentifier ); elseif ( transaction.state == "cancelled" ) then -- Handle a cancelled transaction here Debug:writeToConsole( "CANCELLED TRANSACTION FOR:" ); Debug:writeToConsole( transaction.productIdentifier ); elseif ( transaction.state == "failed" ) then Debug:writeToConsole( "FAILED TRANSACTION FOR:" ); Debug:writeToConsole( transaction.productIdentifier ); else Debug:writeToConsole( "OTHER TRANSACTION EVENT FOR:" ); Debug:writeToConsole( transaction.productIdentifier ); Debug:writeToConsole( transaction.state ); end -- Tell the store that the transaction is complete -- If you are providing downloadable content, do not call this until the download has completed store.finishTransaction( transaction ); Debug:writeToConsole("----------------------"); Debug:writeToConsole(transaction.identifier); Debug:writeToConsole("----------------------"); Debug:writeToConsole("----------------------"); end end -- Init the store store.init( transactionListener ); if store.isActive == true and store.canLoadProducts == true then local productIdentifiers = { "uk.co.myTestCoronaApp.membershipMonthly", "uk.co.myTestCoronaApp.membershipYearly", } local function productListener( e ) for i=1 ,#e.products do local p = e.products[i]; Debug:writeToConsole("PRODUCT "..i..":"); Debug:writeToConsole(p.productIdentifier); Debug:writeToConsole(p.title); Debug:writeToConsole(p.description); Debug:writeToConsole(p.localizedPrice); Interface:addButton({ pId=p.productIdentifier, px=400, py=30+((i-1)\*60), txt="Buy "..i, bType=1, }); end end -- Load store products; store must be properly initialized by this point! store.loadProducts( productIdentifiers, productListener ); -- Add a restore button Interface:addButton({ pId=nil, px=400, py=150, txt="Restore", bType=2, ​ }); else Debug:writeToConsole("PROBLEMS LOADING PRODUCTS"); end

function subscribeToProduct(productIdentifier) -- Called from the "Buy" button for a particular product store.purchase( productIdentifier ); end

function restorePurchases() -- Called from the "Restore" button     store.restore() end