IAP not working correctly with Corona-2019.3528 and IOS 13

One question because this all is confusing…

Is the following usage still working?

local store = require( "store" ) local json = require( "json" ) -- Transaction listener function local function transactionListener( event )     if not ( event.transaction.state == "failed" ) then  -- Successful transaction         print( json.prettify( event ) )         print( "event.transaction: " .. json.prettify( event.transaction ) )     else  -- Unsuccessful transaction; output error details         print( event.transaction.errorType )         print( event.transaction.errorString )     end     -- Tell the store that the transaction is finished     store.finishTransaction( event.transaction ) end -- Initialize Apple IAP store.init( transactionListener )

And inside the not failed state:

if event.transaction.state == "purchased" or event.transaction.state == "restored" then                   if productID=="PERSONAL PRODUCT NAME" then                          --- do stuff                      end end

I just wonder if this code still can be used with the new build version and if it is working fine on all current iOS versions?

Any help welcome.
 

In store.receipt encrypted().in_app for each subscription there is an expires_date parameter that is the expiration time of the recipe. Tell me in what time zone does expires_date come? What is the best way to check the end time? Can I find out to Google time and compare it?

It’s supposed to be Unix time. It has no time zone, it is rather point in universal time- how much seconds passed from Jan 1970. There are convenience functions to get or translate it. But most of the time you can get and compare it directly, like
os.time(os.date("!*t"))

The difference between UNIX time (from Jan 1970) and purchase date is 82 hours. There is a 78 hour difference between os.time(os.date("!*t")) and purchase_date. I can’t figure out how to check the time with such a difference. Maybe time is distorted due to testing in TestFlight?

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.

One question because this all is confusing…

Is the following usage still working?

local store = require( "store" ) local json = require( "json" ) -- Transaction listener function local function transactionListener( event )     if not ( event.transaction.state == "failed" ) then  -- Successful transaction         print( json.prettify( event ) )         print( "event.transaction: " .. json.prettify( event.transaction ) )     else  -- Unsuccessful transaction; output error details         print( event.transaction.errorType )         print( event.transaction.errorString )     end     -- Tell the store that the transaction is finished     store.finishTransaction( event.transaction ) end -- Initialize Apple IAP store.init( transactionListener )

And inside the not failed state:

if event.transaction.state == "purchased" or event.transaction.state == "restored" then                   if productID=="PERSONAL PRODUCT NAME" then                          --- do stuff                      end end

I just wonder if this code still can be used with the new build version and if it is working fine on all current iOS versions?

Any help welcome.
 

In store.receipt encrypted().in_app for each subscription there is an expires_date parameter that is the expiration time of the recipe. Tell me in what time zone does expires_date come? What is the best way to check the end time? Can I find out to Google time and compare it?

It’s supposed to be Unix time. It has no time zone, it is rather point in universal time- how much seconds passed from Jan 1970. There are convenience functions to get or translate it. But most of the time you can get and compare it directly, like
os.time(os.date("!*t"))

The difference between UNIX time (from Jan 1970) and purchase date is 82 hours. There is a 78 hour difference between os.time(os.date("!*t")) and purchase_date. I can’t figure out how to check the time with such a difference. Maybe time is distorted due to testing in TestFlight?

Hi everybody! @vlads I launch your example, it seems that when the plugin is initialized, goods come into the listener and are locally checked. Does the products check when the plugin starts?
Hope for help

No, it doesn’t. It just checks for trivial consumable/permanent purchase. I didn’t look into the subscriptions, etc. Just ‘Purchase was made, great’

I have some older apps (2years+) in the store using this kind of transaction listener:

transactionListener = function( event )
	 
	    local productID= event.transaction.productIdentifier;

        if event.transaction.state == "purchased" or event.transaction.state == "restored" then

	            -- we have to look up which product we get here to purchase:
	            if productID=="XXX" then

                    -- DO STUFF HERE
                    -- LIKE REMOVE ADS OR UNLOCK CONTENT

	            end


        elseif event.transaction.state == "cancelled" then

            removeShopPopup()
        elseif event.transaction.state == "failed" then        

            removeShopPopup()
        else

            removeShopPopup()
        end

        local doitnow=performWithDelay(2200,function() store.finishTransaction( event.transaction ) end,1)

	end

I have noticed a drop in sales over the last days and wonder if something has changed regarding purchases? Is this code (in older games in the store) not working correctly anymore on new iOS?

Any help welcome!

Hi @vlad, for the server side validation of apple IAP, need your help.

The pay is finished, but it did not send the validation request “User:doIAPPayment” in the code below, it seems even not enter the branch “if transaction.state == “purchased””

Is there something I made wrong?

I’m using corona Version 2019.3565 (2019.12.27).

Thanks for your concern.

The code below:

MobileStore.store = require( “store” )
currentProductList = appleProductList
MobileStore.store.init(appleTransactionCallbak)

function appleTransactionCallbak(event)
local transaction = event.transaction

if transaction.state == "purchased" then  -- Successful transaction
    local receipt = MobileStore.store.receiptRawData()

    local receipt2 = MobileStore.store.receiptBase64Data()
    --local receipt = transaction.receipt

    local payload = {receipt=receipt, receipt2=receipt2}
    
    User:doIAPPayment(json.encode(payload))

elseif transaction.state == "cancelled" then
    LOG("User cancelled transaction")

    NoticeView:show(Lang['word_sorry'], 
                    Lang['sentence_user_cancel_transaction'], 
                    NoticeView.STYLE_YES)

elseif transaction.state == "failed" then
    LOG( transaction.errorType )
    LOG( transaction.errorString)

    local errorMsg = transaction.errorString

    NoticeView:show(Lang['word_sorry'], 
                    Lang['sentence_transaction_failed'].." "..errorMsg,
                    NoticeView.STYLE_YES)
end

-- Tell the store that the transaction is finished
MobileStore.store.finishTransaction( transaction )

end

I think at some point Apple broke the way receipts are returned. I highly recommend using newest Corona or Solar2D build, it should be fixed there.

Great, I fixed it.
Here is the points:

  1. use the latest solar2d build 2020.3609

  2. use require( “plugin.apple.iap” ) instead of require( “store” ) ,
    don’t forget add the info in the build.settings:
    [“plugin.apple.iap”] =
    {
    publisherId = “com.coronalabs”
    },

  3. code example:

    local store = require( “plugin.apple.iap” )

    store.init(“apple”, appleTransactionCallbak)

    function appleTransactionCallbak(event)
        
    local transaction = event.transaction
    
        if transaction.state == "purchased" then  -- Successful transaction
           
            local receipt = transaction.receipt   
    

NOTICE here* the transaction.receipt is base64 code yet after the iOS13 SDK, so you need to change your validation code in the server side.
the old transaction.receipt is something like <8bit code…>

            local payload = {receipt=receipt}
            
            User:doIAPPayment(json.encode(payload))

        elseif transaction.state == "cancelled" then
            LOG("User cancelled transaction")
        elseif transaction.state == "failed" then
            LOG( transaction.errorType )
            LOG( transaction.errorString)
        end
     
        -- Tell the store that the transaction is finished
        store.finishTransaction( transaction )
    end