IAP not working correctly with Corona-2019.3528 and IOS 13

@vlads,

Great News. Thanks for your help in this!

Just two questions:

  1. If I remain with the core and build using 3564+, is it backwards compatible? meaning, do I cover iOS13 but also iOS12 and back?

  2. There are additional methods in the Plug-in version and its not clear what each does and how they integrate to the previous workflow we had in core. can someone provide the intended behavior end to end with the new plug-in?

  1. Yes. It is backward compatible.

  2. https://docs.coronalabs.com/plugin/apple-iap/ contains list of methods and basic information about them.

But you are right, Guide is needed for sure.

Meanwhile:

This methods are wrappers around appStoreReceiptURL

Method store.receiptRequest() is a wrapper around SKReceiptRefreshRequest.

After some consideration. I removed empty “transaction” table from the Init event. Sorry, it should not be there and breaking existing code.

For your convenience, here is example of decrypted receipt. I don’t think this format can change:

https://pastebin.com/embed_iframe/UQ18tuuJ

As you can see, it is a table. Here are some important bits:

decrypted = store.receiptDecrypted() decrypted.in\_app -- array containing all in apps

Example entry of decrypted.in_app:

{ "transaction\_id":"1000000454925773", // same as store listener transaction.identifier. Unique for each purchase, restore, or renewal "is\_in\_intro\_offer\_period":0, // 1 if subscription is in free trial still, 0 otherwise "product\_id":"com.coronalabs.IapTest.goodies", // same as transaction.productIdentifier "expires\_date":1539096727, // for subscriptions, this will be time subscription ends, same as purchase time for one timers "quantity":1, // ... "purchase\_date":1539096427, // UNIX time of when purchase was made "cancellation\_date":0, // time when user cancelled auto-renewing sub, 0 otherwise "original\_transaction\_id":"1000000454914195", // transaction.originalIdentifier, identifier of original purchase "original\_purchase\_date":1539095266, "web\_order\_line\_item\_id":"1000000040727298" // seems to be some apple internal stuff }

Fields in this structure are documented on Apple website: https://developer.apple.com/documentation/appstorereceipts

Hi @vlads,

I’ve tested the new core code and have a few notes:

  1. The app now receives full receipt in iOS13 - Great!

  2. Our previous receipt validation code via Apple server now fails with code [“21002”] = “The data in the receipt-data property was malformed.” perhaps there is a difference compared to previous receipt data, but I couldn’t identify what. Not sure how to proceed in this track yet.

I then tried to switch to the plug-in. Some notes:

  1. The plugin has some differences vs the core as already noted above (change in init, new event restoreCompleted, might be others)

  2. It’s not clear if I should use for each incoming transaction event store.receiptRequest(listener) or just try store.receiptDecrypted()

  3. In both cases I get All transactions history, where our previous validation received only the latest (we used exclude_old_transactions = true on the request post data).

  4. finishTransaction now fails when using the transaction object in two events: A. init B. restoreCompleted

  5. RestoreCompleted event seem to have no useful info. only type=‘restore’ and isError=‘false’

This is all I could find so far.

Thanks for investing the time on this.

Adi

  1. Yay!

  2. Our data before was formatted like “< 01234567 89abcdef … … … >”, base64 representation in <> quotes and groupped in 8 symbols. Plugin and core now return base64 representation without <> or spaces. I hope it’ll help. Showing what code you were using to verify purchases previously might help to understand what the issue is. For now I have no idea what’s going on.

  3. Yes. I has init which can be ignored, really, and done for just compatibility with android iap, and restoreCompleted. That’s it.

  4. In all my tests receipt is ready right after purchase.

  5. No idea what it means, because you’re talking about code I never saw… We just passing what Apple APIs give us. “Before” we ware using [SKPaymentTransaction transactionReceipt] which is long deprecated. This is one returned with lua’s transaction.receipt. We still returning it. New store.receipt* functions are all receipts.

  6. I changed plugins’ finish transaction so it doesn’t fail.

  7. It is by itself. Before it was like you hit store.restore() and never know when it is finished. Now you can see it in the event.

Also, here is some verification code I threw in. It seems to work fine. 100% local verification, no need for server:

local function verifyPurchase( identifier ) &nbsp;&nbsp;&nbsp; print("Verifying", identifier) &nbsp;&nbsp;&nbsp; local iaps = (store.receiptDecrypted() or {}).in\_app &nbsp;&nbsp;&nbsp; for i=1,#iaps do &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; local iap = iaps[i] &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if iap.original\_transaction\_id == identifier then &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return true &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end &nbsp;&nbsp;&nbsp; end &nbsp;&nbsp;&nbsp; return false end store.init( "apple", function(event) &nbsp;&nbsp;&nbsp; local t = event.transaction &nbsp;&nbsp;&nbsp; if t then &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if t.state == "restored" or t.state == "purchased" then &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; local verified = verifyPurchase(t.originalIdentifier or t.identifier) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print("Verified purchase", verified) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; native.showAlert("Purchase Verified", tostring(verified), {"OK"}) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end &nbsp;&nbsp;&nbsp; end &nbsp;&nbsp;&nbsp; store.finishTransaction( t ) end)

Here is my sample’s full code: https://pastebin.com/embed_iframe/SiBkHAnC
 

@vlads,

Thats exactly what I needed! previously we had to pre-process the receipt before sending it, now we can simply use it as is for validation. The core code now works for iOS13. I’ll also verify it formats the receipt the same way for earlier OS versions.

I will also try modifying our code to use the plug-in and see if we can safely switch.

Thanks again for your help!

Any news on this?

I just have updated my iPadPro to iOS 13.3 and now can not successfully test purchases. The game is not accepting the PW for my test account. Instead even with sandbox mode shown when clicking on a product I have to enter my real Apple ID to make a purchase work. I get a window for purchase successful BUT the code for the transaction listener to notice “purchased” state is not getting called!!!

What is happening here?

How can I fix this and what does it mean for old apps and games already in the store?

Are players updating to iOS 13.3 not able anymore to purchase something???

Hello. Can you replace main.lua with code I posted, then replace iap ids in it. I just tried on iOS 13.3, everything works

Thank you!

Can you please take a look at my other problem which probably is connected (?)

https://forums.coronalabs.com/topic/77024-problem-with-inapp-purchases-on-ios-133-ipadpro/?p=406303

I mean, it is your post… Lets  move conversation there.

@vlads, any new document updated for the new interface now?

I just got rejected because of this… (feeback from Apple Review)

We found that your in-app purchase products exhibited one or more bugs when reviewed on iPad running iOS 13.3.1 on Wi-Fi.

Specifically, we cannot complete the transaction of purchasing any in-app purchase products.

Next Steps

When validating receipts on your server, your server needs to be able to handle a production-signed app getting its receipts from Apple’s test environment. The recommended approach is for your production server to always validate receipts against the production App Store first. If validation fails with the error code “Sandbox receipt used in production,” you should validate against the test environment instead.

I don’t have any idea what this means and how to solve it. Worked on the test device perfectly!

Can anybody help with this please?

d.mach-

GamingStudio17 and I both had similar trouble with App Store Review and got through by resubmitting with the following explanatory note:

"This app was built using Corona SDK. It appears that some developers are experiencing a problem where receipts are verified correctly in production, but fail in the sandbox. Could you please provide me with an error log so I can better understand what’s going wrong?"

The apps were then approved without comment, so I don’t really know what happened, but in terms of things to try, it’s pretty low-effort.

Thank you for your fast help! I must have over-read this one. Will do!

I hope it works!

Like I said, since Apple provides zero feedback, we don’t know why our apps were approved the second time through. Probably because of the note, but who knows?

Hello.
Has anyone solved the problem with purchases on iOS 13 with new IAP plugin?

April is coming and Apple requires you to publish apps with the iOS SDK 13.
We tried to update and used the new IAP plugin as it was written in the IAP Badger discussion thread on the old version of the forum.

But we have a problem. Initializing a plugin works queerly. After starting the application, the listener many times receives a table: { name = storeTransaction }.
Goods do not load. Nothing comes to the listener e = nil.

I attach the code and the console log. We tested app from TestFlight. gg.lua (1.4 KB) erroriap.txt (147.9 KB) (prints can be found by “STORE”)

Also we tried to use 3 plugins in build.settings
[“plugin.openssl”] = { publisherId = “com.coronalabs” },
[“plugin.apple.iap”] = { publisherId = “com.coronalabs” },
[“plugin.apple.iap.helper”] = { publisherId = “com.coronalabs” },
but it didn’t help us.

Maybe there is already a solution to this problem? @vlads
Hope for help

Hello.

Has anyone solved the problem with purchases on iOS 13 with new IAP plugin?

I am currently testing the new iap plugin on iOS13 (iPhone XR). I am using test flight with a sandbox tester account. It works with app in store too.

Purchase is successful with sandbox tester account.

there are 2 main data here. 

Sent to ‘https://sandbox.itunes.apple.com/verifyReceipt’ with a post json body. You can also try sending it to ‘https://buy.itunes.apple.com/verifyReceipt’ but it will most likely return ‘{“status”:21007}’ because it was using a sandbox account.

Sample body :

{"password":"...","receipt-data":"....."}

When you send it to Apple for verification :

  • event.transaction.receipt will return such data (sensitive info masked with XXX):

    { “receipt”:{ “original_purchase_date_pst”:“2020-03-20 02:05:41 America/Los_Angeles”, “purchase_date_ms”:“1584695141188”, “unique_identifier”:“00008020-000565223EE10XXX”, “original_transaction_id”:“1000000641424XXX”, “bvrs”:“2020.03.201521”, “transaction_id”:“1000000641424XXX”, “quantity”:“1”, “unique_vendor_identifier”:“BF033E1A-877C-42A0-B7B3-6E20B216CXXX”, “item_id”:“1448649XXX”, “version_external_identifier”:“0”, “bid”:“com.XXX”, “is_in_intro_offer_period”:“false”, “product_id”:“com.XXX”, “purchase_date”:“2020-03-20 09:05:41 Etc/GMT”, “is_trial_period”:“false”, “purchase_date_pst”:“2020-03-20 02:05:41 America/Los_Angeles”, “original_purchase_date”:“2020-03-20 09:05:41 Etc/GMT”, “original_purchase_date_ms”:“1584695141188”}, “status”:0 }

  • store.receipt_base64_data will return much more data :

    “receipt”:{ “receipt_type”:“ProductionSandbox”, “adam_id”:0, “app_item_id”:0, “bundle_id”:“com.XXX”, “application_version”:“2020.03.201521”, “download_id”:0, “version_external_identifier”:0, “receipt_creation_date”:“2020-03-20 09:05:41 Etc/GMT”, “receipt_creation_date_ms”:“1584695141000”, “receipt_creation_date_pst”:“2020-03-20 02:05:41 America/Los_Angeles”, “request_date”:“2020-03-20 09:39:00 Etc/GMT”, “request_date_ms”:“1584697140182”, “request_date_pst”:“2020-03-20 02:39:00 America/Los_Angeles”, “original_purchase_date”:“2013-08-01 07:00:00 Etc/GMT”, “original_purchase_date_ms”:“1375340400000”, “original_purchase_date_pst”:“2013-08-01 00:00:00 America/Los_Angeles”, “original_application_version”:“1.0”, “in_app”:[{“quantity”:“1”, “product_id”:“com.XXX”, “transaction_id”:“1000000639052XXX”, “original_transaction_id”:“1000000639052XXX”, “purchase_date”:“2020-03-14 02:53:43 Etc/GMT”, “purchase_date_ms”:“1584154423000”, “purchase_date_pst”:“2020-03-13 19:53:43 America/Los_Angeles”, “original_purchase_date”:“2020-03-14 02:53:43 Etc/GMT”, “original_purchase_date_ms”:“1584154423000”, “original_purchase_date_pst”:“2020-03-13 19:53:43 America/Los_Angeles”, “is_trial_period”:“false”}, {“quantity”:“1”, “product_id”:“com.XXX”, “transaction_id”:“1000000639056XXX”, “original_transaction_id”:“1000000639056XXX”, “purchase_date”:“2020-03-14 03:21:00 Etc/GMT”, “purchase_date_ms”:“1584156060000”, “purchase_date_pst”:“2020-03-13 20:21:00 America/Los_Angeles”, “original_purchase_date”:“2020-03-14 03:21:00 Etc/GMT”, “original_purchase_date_ms”:“1584156060000”, “original_purchase_date_pst”:“2020-03-13 20:21:00 America/Los_Angeles”, “is_trial_period”:“false”}, {“quantity”:“1”, “product_id”:“com.XXX”, “transaction_id”:“1000000639057XXX”, “original_transaction_id”:“1000000639057XXX”, “purchase_date”:“2020-03-14 03:34:22 Etc/GMT”, “purchase_date_ms”:“1584156862000”, “purchase_date_pst”:“2020-03-13 20:34:22 America/Los_Angeles”, “original_purchase_date”:“2020-03-14 03:34:22 Etc/GMT”, “original_purchase_date_ms”:“1584156862000”, “original_purchase_date_pst”:“2020-03-13 20:34:22 America/Los_Angeles”, “is_trial_period”:“false”}, {“quantity”:“1”, “product_id”:“com.XXX”, “transaction_id”:“1000000639058XXX”, “original_transaction_id”:“1000000639058XXX”, “purchase_date”:“2020-03-14 03:48:45 Etc/GMT”, “purchase_date_ms”:“1584157725000”, “purchase_date_pst”:“2020-03-13 20:48:45 America/Los_Angeles”, “original_purchase_date”:“2020-03-14 03:48:45 Etc/GMT”, “original_purchase_date_ms”:“1584157725000”, “original_purchase_date_pst”:“2020-03-13 20:48:45 America/Los_Angeles”, “is_trial_period”:“false”}, {“quantity”:“1”, “product_id”:“com.XXX”, “transaction_id”:“1000000639066XXX”, “original_transaction_id”:“1000000639066XXX”, “purchase_date”:“2020-03-14 04:56:13 Etc/GMT”, “purchase_date_ms”:“1584161773000”, “purchase_date_pst”:“2020-03-13 21:56:13 America/Los_Angeles”, “original_purchase_date”:“2020-03-14 04:56:13 Etc/GMT”, “original_purchase_date_ms”:“1584161773000”, “original_purchase_date_pst”:“2020-03-13 21:56:13 America/Los_Angeles”, “is_trial_period”:“false”}, {“quantity”:“1”, “product_id”:“com.XXX”, “transaction_id”:“1000000639068XXX”, “original_transaction_id”:“1000000639068XXX”, “purchase_date”:“2020-03-14 05:17:14 Etc/GMT”, “purchase_date_ms”:“1584163034000”, “purchase_date_pst”:“2020-03-13 22:17:14 America/Los_Angeles”, “original_purchase_date”:“2020-03-14 05:17:14 Etc/GMT”, “original_purchase_date_ms”:“1584163034000”, “original_purchase_date_pst”:“2020-03-13 22:17:14 America/Los_Angeles”, “is_trial_period”:“false”}, {“quantity”:“1”, “product_id”:“com.XXX”, “transaction_id”:“1000000639071XXX”, “original_transaction_id”:“1000000639071XXX”, “purchase_date”:“2020-03-14 05:45:42 Etc/GMT”, “purchase_date_ms”:“1584164742000”, “purchase_date_pst”:“2020-03-13 22:45:42 America/Los_Angeles”, “original_purchase_date”:“2020-03-14 05:45:42 Etc/GMT”, “original_purchase_date_ms”:“1584164742000”, “original_purchase_date_pst”:“2020-03-13 22:45:42 America/Los_Angeles”, “is_trial_period”:“false”}, {“quantity”:“1”, “product_id”:“com.XXX”, “transaction_id”:“1000000640284XXX”, “original_transaction_id”:“1000000640284XXX”, “purchase_date”:“2020-03-18 04:06:04 Etc/GMT”, “purchase_date_ms”:“1584504364000”, “purchase_date_pst”:“2020-03-17 21:06:04 America/Los_Angeles”, “original_purchase_date”:“2020-03-18 04:06:04 Etc/GMT”, “original_purchase_date_ms”:“1584504364000”, “original_purchase_date_pst”:“2020-03-17 21:06:04 America/Los_Angeles”, “is_trial_period”:“false”}, {“quantity”:“1”, “product_id”:“com.XXX”, “transaction_id”:“1000000640510XXX”, “original_transaction_id”:“1000000640510XXX”, “purchase_date”:“2020-03-18 12:04:32 Etc/GMT”, “purchase_date_ms”:“1584533072000”, “purchase_date_pst”:“2020-03-18 05:04:32 America/Los_Angeles”, “original_purchase_date”:“2020-03-18 12:04:32 Etc/GMT”, “original_purchase_date_ms”:“1584533072000”, “original_purchase_date_pst”:“2020-03-18 05:04:32 America/Los_Angeles”, “is_trial_period”:“false”}, {“quantity”:“1”, “product_id”:“com.XXX”, “transaction_id”:“1000000640572XXX”, “original_transaction_id”:“1000000640572XXX”, “purchase_date”:“2020-03-18 13:45:00 Etc/GMT”, “purchase_date_ms”:“1584539100000”, “purchase_date_pst”:“2020-03-18 06:45:00 America/Los_Angeles”, “original_purchase_date”:“2020-03-18 13:45:00 Etc/GMT”, “original_purchase_date_ms”:“1584539100000”, “original_purchase_date_pst”:“2020-03-18 06:45:00 America/Los_Angeles”, “is_trial_period”:“false”}, {“quantity”:“1”, “product_id”:“com.XXX”, “transaction_id”:“1000000641342XXX”, “original_transaction_id”:“1000000641342XXX”, “purchase_date”:“2020-03-20 06:14:08 Etc/GMT”, “purchase_date_ms”:“1584684848000”, “purchase_date_pst”:“2020-03-19 23:14:08 America/Los_Angeles”, “original_purchase_date”:“2020-03-20 06:14:08 Etc/GMT”, “original_purchase_date_ms”:“1584684848000”, “original_purchase_date_pst”:“2020-03-19 23:14:08 America/Los_Angeles”, “is_trial_period”:“false”}, {“quantity”:“1”, “product_id”:“com.XXX”, “transaction_id”:“1000000641424XXX”, “original_transaction_id”:“1000000641424XXX”, “purchase_date”:“2020-03-20 09:05:41 Etc/GMT”, “purchase_date_ms”:“1584695141000”, “purchase_date_pst”:“2020-03-20 02:05:41 America/Los_Angeles”, “original_purchase_date”:“2020-03-20 09:05:41 Etc/GMT”, “original_purchase_date_ms”:“1584695141000”, “original_purchase_date_pst”:“2020-03-20 02:05:41 America/Los_Angeles”, “is_trial_period”:“false”}]}, “status”:0, “environment”:“Sandbox”}

Hello! Just to be clear, Apple specifically says that you should be used from your servers, not from your app. Workflow is following:

You make a purchase, app sends receipt to your server. Server talks to Apple, verifying receipt and sends your app confirmation of the purchase.

If you want to do it on your device, you have to use receiptDecrypted. It will verify purchases on the device, checking cryptographic signatures. See some sample around here. It will also remove unnecessary network calls.