iOS Question [solved] Auto_Renewable subscriptions suddenly are not recognized at some customers

fredo

Well-Known Member
Licensed User
Longtime User
The underlying question is at the bottom and is addressed to B4i developers who have practical experience with implementing Auto_Renewable subscriptions.

The situation
An app has been active in the App Store for months and has many "Auto_Renewable" subscribers with whom everything works without problems.
However, a few users have reported in between that their active subscription is no longer recognized by the app.

Technical framework
The app was built with the Hosted Builder, Certificate and Provisionfile were newly created.
Purchase and validation are performed as described here: https://www.b4x.com/android/forum/t...-auto-renewable-subscriptions.132432/#content
On purchase, the receipt is stored in the keychain and read on app startup and validated by our own server (https://developer.apple.com/documen...tore#//apple_ref/doc/uid/TP40010573-CH104-SW1).
The purchases work via sandbox and production.

The problem
The receipt is read out of the keychain at App start for the individual affected users, just like for all others, but the validation results in the status "21006", i.e. "expired" (https://developer.apple.com/documentation/appstorereceipts/status).

Further procedure
The issue could not be reproduced by the developer yet, which makes troubleshooting more difficult.

One approach would have been "store.RestoreTransactions", in the hope that a result with the last valid "receipt" is delivered.

However, this call does not return a result:
B4X:
Dim receiptobject As NativeObject = no.GetField("transactionReceipt")

This is probably because this field is deprecated (https://developer.apple.com/documentation/storekit/skpaymenttransaction/1617722-transactionreceipt).

A way is now being looked for to get a new receipt for future server side receipt validation.

The question
What specifically needs to be done to get a valid "receipt" object for an active Auto_Renewable subscription subsequently?


Maybe I missed something or Apple changed something recently...
 

Segga

Member
Licensed User
Longtime User
I'm using the "latest_receipt_info" for expiry the date (which only seems to be available when a subscription is active), otherwise I only get the initial subscription end date returned.
B4X:
If responsebodymap.ContainsKey("latest_receipt_info") Then
        Dim receiptmap As Map = responsebodymap.Get("latest_receipt_info")
        ActiveProductMap.Put("receipt_product_id", receiptmap.GetDefault("product_id", "?"))
        ActiveProductMap.Put("receipt_expires_date", receiptmap.GetDefault("expires_date", "?"))
        ActiveProductMap.Put("receipt_expires_date2", $"$DateTime{receiptmap.GetDefault("expires_date", 0)}"$)         
else if responsebodymap.ContainsKey("receipt") Then   
        Dim receiptmap As Map = responsebodymap.Get("receipt")
        ActiveProductMap.Put("receipt_product_id", receiptmap.GetDefault("product_id", "?"))
        ActiveProductMap.Put("receipt_expires_date", receiptmap.GetDefault("expires_date", "?"))
        ActiveProductMap.Put("receipt_expires_date2", $"$DateTime{receiptmap.GetDefault("expires_date", 0)}"$)
End If
 
Upvote 0

fredo

Well-Known Member
Licensed User
Longtime User
With little experience in the field of InApp Auto renewable subscription functionalities, it was an interesting challenge to get closer to the result.

In case someone with a similar situation ends up here:
You get the "transactionReceipt" object in the istore_PurchaseCompleted event.

In my particular case, the user is helped by executing "Restore Subscription".
Through "store.RestoreTransaction" the "istore_PurchaseCompleted" event is raised in which the "Purchase" object is delivered.
-------------

However, now I have to solve the real problem.

After calling "istore.RestoreTransactions" the "istore_TransactionsRestored" event is called, but not "istore_PurchaseCompleted".
 
Upvote 0

Segga

Member
Licensed User
Longtime User
Looks like the SKReceiptRefreshRequest class is the way (and apparently preferred method according to Apple) to "request a new receipt if the receipt is invalid or missing".
This would allow restoring auto-renew subscriptions if the user changes their device and doesn't have their keychain synced.
We really need a ReceiptRefresh added to the iStore library!
 
Upvote 0

Segga

Member
Licensed User
Longtime User
That sounds very beneficial.

Who would have to do what?

Could this be done with inline code?
Who would have to do what?
Not sure who would do it, since it's an internal library. Maybe put it on the wish list?

Could this be done with inline code?
Way beyond my expertise! Hopefully, someone read this can provide a definitive answer.
 
Upvote 0

Segga

Member
Licensed User
Longtime User
The underlying question is at the bottom and is addressed to B4i developers who have practical experience with implementing Auto_Renewable subscriptions.

The situation
An app has been active in the App Store for months and has many "Auto_Renewable" subscribers with whom everything works without problems.
However, a few users have reported in between that their active subscription is no longer recognized by the app.

Technical framework
The app was built with the Hosted Builder, Certificate and Provisionfile were newly created.
Purchase and validation are performed as described here: https://www.b4x.com/android/forum/t...-auto-renewable-subscriptions.132432/#content
On purchase, the receipt is stored in the keychain and read on app startup and validated by our own server (https://developer.apple.com/documen...tore#//apple_ref/doc/uid/TP40010573-CH104-SW1).
The purchases work via sandbox and production.

The problem
The receipt is read out of the keychain at App start for the individual affected users, just like for all others, but the validation results in the status "21006", i.e. "expired" (https://developer.apple.com/documentation/appstorereceipts/status).

Further procedure
The issue could not be reproduced by the developer yet, which makes troubleshooting more difficult.

One approach would have been "store.RestoreTransactions", in the hope that a result with the last valid "receipt" is delivered.

However, this call does not return a result:
B4X:
Dim receiptobject As NativeObject = no.GetField("transactionReceipt")

This is probably because this field is deprecated (https://developer.apple.com/documentation/storekit/skpaymenttransaction/1617722-transactionreceipt).

A way is now being looked for to get a new receipt for future server side receipt validation.

The question
What specifically needs to be done to get a valid "receipt" object for an active Auto_Renewable subscription subsequently?


Maybe I missed something or Apple changed something recently...
I've found a way to get the receipt using iStore.RestoreTransactions :)
Restore auto-renewing subscription receipt:
Public Sub RestoreSubscription As ResumableSub
    istore.RestoreTransactions
    wait for istore_PurchaseCompleted (Success As Boolean, Product As Purchase)
    ' --- Get receipt for transaction ---
    Dim no As NativeObject = Product.RestoredPurchase
    Dim receiptobject As NativeObject = no.GetField("transactionReceipt")
    If receiptobject.IsInitialized Then
        Dim b() As Byte = receiptobject.NSDataToArray(receiptobject)
        Dim receiptraw As String = BytesToString(b, 0, b.Length, "UTF-8")
        LogColor(receiptraw, Colors.Magenta)
        keychain.KeyChainPut("receiptraw", receiptraw)
        Return True
    Else
        LogColor("receiptobject not initialized", Colors.Magenta)
        Return False
    End If
 
Upvote 0

Segga

Member
Licensed User
Longtime User
I've found a way to get the receipt using iStore.RestoreTransactions :)
Restore auto-renewing subscription receipt:
Public Sub RestoreSubscription As ResumableSub
    istore.RestoreTransactions
    wait for istore_PurchaseCompleted (Success As Boolean, Product As Purchase)
    ' --- Get receipt for transaction ---
    Dim no As NativeObject = Product.RestoredPurchase
    Dim receiptobject As NativeObject = no.GetField("transactionReceipt")
    If receiptobject.IsInitialized Then
        Dim b() As Byte = receiptobject.NSDataToArray(receiptobject)
        Dim receiptraw As String = BytesToString(b, 0, b.Length, "UTF-8")
        LogColor(receiptraw, Colors.Magenta)
        keychain.KeyChainPut("receiptraw", receiptraw)
        Return True
    Else
        LogColor("receiptobject not initialized", Colors.Magenta)
        Return False
    End If
UPDATED RestoreSubscription
After further testing... if the user had not previously purchased a subscription for the app before, the app would hang indefinitely at wait for istore_PurchaseCompleted (ie. waiting for a purchase that doesn’t exist).
The attached code uses a loop to wait for TransactionsRestored to fire, which always happens after PurchaseCompleted (regardless of any purchases being found).
The transaction receipt can only be retrieved if purchases were found.
The bRestoringPurchases flag is also used to prevent an error from occurring when the subscription auto-renews (which seems to trigger PurchaseCompleted with a null purchase).
Restore auto-renewing subscription receipt:
Sub Class_Globals
'Restoring purchases
    Private bRestoringPurchases As Boolean
    Private restoredReceiptObject As NativeObject
End Sub

'Usage
    LogColor("RestoreSubscription BEGIN ********************",Colors.Magenta)
        wait for (RestoreSubscription) complete(b As Boolean)
    LogColor("RestoreSubscription END **********************",Colors.Magenta)  
   
'***********************************************************************************
Public Sub RestoreSubscription As ResumableSub
'Set boolean flags
    bRestoringPurchases=True  
    restoredReceiptObject=""

'Begin restore transactions flow
    istore.RestoreTransactions
    'Wait until the check is complete
    Do While bRestoringPurchases
        Sleep(100)
    Loop

    LogColor("Purchases found =" & restoredReceiptObject<>"", IIf(restoredReceiptObject<>"",Colors.Green,Colors.Red))
   
    If restoredReceiptObject="" Then Return False' Exit here if no purchases were found

'Get the transaction receipt
    Dim receiptobject As NativeObject = restoredReceiptObject.GetField("transactionReceipt")
    If receiptobject.IsInitialized Then
        Dim b() As Byte = receiptobject.NSDataToArray(receiptobject)
        Dim receiptraw As String = BytesToString(b, 0, b.Length, "UTF-8")
        LogColor(receiptraw,Colors.Gray)
        keychain.KeyChainPut("receiptraw", receiptraw)
        Return True
    Else
        Log("receiptobject not initialized")
        Return False
    End If
End Sub

Private Sub iStore_TransactionsRestored (Success As Boolean)
    LogColor("iStore_TransactionsRestored (" & Success & ")", Colors.Magenta)
    bRestoringPurchases=False
End Sub

Private Sub iStore_PurchaseCompleted (Success As Boolean, Product As Purchase)
    Log("Found Transaction: " & Product.RestoredPurchase)
    If Not(bRestoringPurchases) Then Return 'Purchase complete also seems to trigger when a subscription has auto renewed.
    restoredReceiptObject=Product.RestoredPurchase
End Sub
'***********************************************************************************
 
Last edited:
Upvote 0
Top