Godot: iOS IAP in app purchase crash on purchase

Created on 15 Oct 2020  路  13Comments  路  Source: godotengine/godot

Godot version:

3.2.4.Beta

OS/device including version:
iOS iPad Pro 1st generation

Issue description:
Try to make a purchase using inappstore singleton, crash before return result

Steps to reproduce:

Try to make a purchase using inappstore singleton

This is my previously working iap code:

func purchase(sku):
print('PURCHASE IAP ', sku)
var result = inappstore.purchase({'product_id': sku})
if result == OK:
print("OK")
else:
print("FAIL")

I get the crash here: var result = inappstore.purchase({'product_id': sku})
Xcode debugger says: Thread 1: EXC_BAD_ACCESS (code=1, address=0x18)

PURCHASE IAP seurat_texture_brushes
purchasing!
(lldb) (crash)

inappstore.restore_purchases() does work as expected.

image
purchasing! is the last print before the crash

edit:
@naithar My version of Godot has autorelease. I'll try to compile with your changes and see if it works

bug crash ios porting

All 13 comments

cc @naithar

@HEAVYPOLY which methods do you call before performing inappstore.purchase?

@naithar
My Godot version is from before your changes, I'm compiling now to see if it works.
here is my code:

```extends Node

signal purchase_request_done()
var payment = null
var inappstore = null
var ready_to_purchase = false
var price = null
var is_premium = false

var subscription_cancelled = false

var reconnect_interval = 1
var purchase_token
var mode
var SKU_BRUSHES = 'seurat_texture_brushes'
var SKU_PRO = 'heavypaint_pro'

func _ready():

if Engine.has_singleton("GodotGooglePlayBilling"):
    mode = 'Android'
    print('Using Google Play Billing for IAP')
    payment = Engine.get_singleton("GodotGooglePlayBilling")
    payment.connect("connected", self, "_on_connected") # No params
    payment.connect("disconnected", self, "_on_disconnected") # No params
    payment.connect("connect_error", self, "_on_connect_error") # Response ID (int), Debug message (string)
    payment.connect("purchases_updated", self, "_on_purchases_updated") # Purchases (Dictionary[])
    payment.connect("purchase_error", self, "_on_purchase_error") # Response ID (int), Debug message (string)
    payment.connect("sku_details_query_completed", self, "_on_sku_details_query_completed") # SKUs (Dictionary[])
    payment.connect("sku_details_query_error", self, "_on_sku_details_query_error") # Response ID (int), Debug message (string), Queried SKUs (string[])
    payment.connect("purchase_acknowledged", self, "_on_purchase_acknowledged") # Purchase token (string)
    payment.connect("purchase_acknowledgement_error", self, "_on_purchase_acknowledgement_error") # Response ID (int), Debug message (string), Purchase token (string)
    payment.connect("purchase_consumed", self, "_on_purchase_consumed") # Purchase token (string)
    payment.connect("purchase_consumption_error", self, "_on_purchase_consumption_error") # Response ID (int), Debug message (string), Purchase token (string)
    payment.startConnection()
    N.Layout.find_node('Button_Restore_Brushes').hide()
elif Engine.has_singleton('InAppStore'):
    mode = 'iOS'

print('Using StoreKit for IAP')

    inappstore = Engine.get_singleton('InAppStore')
    var result = inappstore.request_product_info( { "product_ids": [SKU_BRUSHES, SKU_PRO] } )
    if result == OK:

prints('Starting InAppStore', result)

        var timer = Timer.new()
        timer.wait_time = 1
        timer.connect("timeout", self, 'check_events')
        add_child(timer)
        timer.start()
        inappstore.set_auto_finish_transaction(true)
    else:
        print('ono')
else:
    mode = 'Desktop'

print('No supported IAP provider detected')

lock_brushes()

Everybody stuff

func unlock_brushes():
S.purchases[SKU_BRUSHES] = true
S._save_purchases()
if !S.user_prefs['show_demo']:
N.ToolsInventory._style()
N.ToolsInventory.show()
N.Status.text = 'ALL BRUSHES UNLOCKED!'

func lock_brushes():
S.purchases[SKU_BRUSHES] = false
S._save_purchases()

Android stuff

func _on_connected():
print('PurchaseManager connected')
yield(get_tree().create_timer(1),"timeout")
check_purchased()
reconnect_interval = 1

payment.querySkuDetails([SKU_BRUSHES,SKU_PRO], 'inapp')

print(q)

N.DebugInfo.text = str(payment.querySkuDetails([SKU_BRUSHES,SKU_PRO], 'inapp'))

func _on_sku_details_query_completed(sku_details):
for sku in sku_details:
price = sku.price

func _on_purchase_consumption_error(code, message, purchase_token):
prints("Purchase consumption error",message)

func _on_purchase_consumed(purchase_token):
print("Purchase consumed successfully")

func _on_purchases_updated(_purchases):
prints('purchase updated', _purchases)
check_purchased()

func _on_purchase_acknowledged(_purchase_token):

prints('purchase acknowledged', _purchase_token)

N.DebugInfo.text = str('PURCHASE ACKNOWLEDGED ', _purchase_token)
check_purchased()
unlock_brushes()

if S.purchases[SKU_BRUSHES] == false:

func _on_purchase_error(code, message):
prints('Purchase error', code, message)

func _on_purchase_acknowledgement_error(code, message, purchase_token):
prints('Purchase ACK error', code, message, purchase_token)

func _on_sku_details_query_error(code, message):
print('SKU details query error %d: %s' % [code, message])

func _on_disconnected():
print('PurchaseManager disconnected')
ready_to_purchase = false
yield(get_tree().create_timer(min(reconnect_interval, 60)), 'timeout')
reconnect_interval *= 2
payment.startConnection()
#
func _notification(what):
if what == NOTIFICATION_APP_RESUMED:
check_purchased()
#
func purchase(sku):
if payment:
print('Purchase ', sku)
var response = payment.purchase(sku)
if response.status != OK:
N.Status.text = str("Purchase error ", response.debug_message)
elif inappstore:
var result = inappstore.purchase({'product_id': sku})
if result == OK:
N.Status.text = 'PURCHASING ' + sku.capitalize().to_upper() + "..."
start_load_spinner()
else:
N.Status.text = 'PURCHASE FAILED iOS'
else:
OS.shell_open('https://gumroad.com/l/heavypaint')
unlock_brushes()

func start_load_spinner():
N.LoadSpinner.show()
N.LoadSpinner.get_node('AnimationPlayer').play('Spin')
N.Dimmer.show()
func end_load_spinner():
N.LoadSpinner.hide()
N.LoadSpinner.get_node('AnimationPlayer').stop(true)
N.Dimmer.hide()
func check_purchased():
print('CHECK PURCHASED')
var query = payment.queryPurchases('inapp')
purchase_token = null
if query.status == OK:
for purchase in query.purchases:

print('PURCHASED ', purchase.sku)

        if purchase.sku in [SKU_BRUSHES, SKU_PRO]:
            if !purchase.is_acknowledged:
                print('Purchase ' + str(purchase.sku) + ' has not been acknowledged...')
                payment.acknowledgePurchase(purchase.purchase_token)
            elif purchase.purchase_state == 1:
                prints('Purchase Found', purchase.sku)
                purchase_token = purchase.purchase_token
                if S.purchases[SKU_BRUSHES] == false:
                    unlock_brushes()

subscription_cancelled = !purchase.is_auto_renewing

            break

end_load_spinner()

emit_signal('purchase_request_done')

func _on_ConsumeButton_pressed(purchase_token):
if mode == 'Android':
if !purchase_token == null:
print('consume ', purchase_token)
payment.consumePurchase(purchase_token)
lock_brushes()
else:
print('Nothing to consume')
elif mode == 'iOS':
prints("CONSUME", SKU_BRUSHES)
lock_brushes()
inappstore.consume(SKU_BRUSHES)

payment.querySkuDetails([SKU_BRUSHES,SKU_PRO], 'inapp')

iOS stuff IOSIOSIOSIOSIOSIOSISISOSISOISISO

func check_events():
while inappstore.get_pending_event_count() > 0:
var event = inappstore.pop_pending_event()

prints('CHECK EVENT FOUND \n', event)

    match event.type:
        'product_info':
                price = event.localized_prices[0]
                prints("INFO",event.ids[0], "PRICE", price)
                N.Price.text = price
        'purchase':
            if event.result == 'ok' && event.product_id in [SKU_BRUSHES, SKU_PRO]:
                prints("PURCHASE OWNED",event.product_id)
                unlock_brushes()

N.ToolsInventory.show()

payment.show_success(event.product_id)

            else:
                N.Status.text = 'PURCHASE CHECK FAILED'
        'restore':
            if event.result == "ok":

print(event.product_id.capitalize().to_upper() + ' RESTORED!')

Layout.find_node('OptionsPopup').hide()

                unlock_brushes()
                N.Status.text = event.product_id.capitalize().to_upper() + ' RESTORED!'
                N.ToolsInventory._style()

N.ToolsInventory.show()

N.OptionsPopup.hide()

payment.show_success(event.product_id)

            else:
                N.Status.text = 'RESTORE FAILED'

    end_load_spinner()
    emit_signal('purchase_request_done')

func restore_purchases():
if inappstore != null:
if inappstore.restore_purchases() != OK:
print('Could not restore purchases!')
else:
print('Purchases restored!')

#

func idk():
OS.get_singleton('InAppStore')
```

Did you have additional logs before calling inappstore.purchase? Like CHECK EVENT FOUND... and stuff?

Yes everything else seems to be working:
.restore
.consume
.pop_pending_event()

all working fine

edit:
@naithar My version of Godot has autorelease. I'll try to compile with your changes and see if it works

Were you using older 3.2.4 version without ARC support? InAppStore module was changed two times - first when I was fixing deprecated API and the other one when ARC was implemented. So it could be that InAppStore actually doesn't crash with current 3.2. Right now I can't reproduce the crash, but I've found other problems.

@HEAVYPOLY can you test your issue with this branch: https://github.com/naithar/godot/tree/fix/in-app-store ? If it's working I'll fix it and make a PR.

Edit:
I'll also try to revert ARC implementation to try to catch the crash, just to be sure.

Reproduced the crash in pre-ARC version (could be simply fixed with latestProducts = [products retain];). But it's hardly relevant now, since ARC is merged

I'm currently compiling using Godot 3.2 branch commit 5e4a8abe2 ,would this one work?

I'll try this one first, then try your branch and report back

My computer is slow so building is taking a while with export templates

I'm currently compiling using Godot 3.2 branch commit 5e4a8abe2 ,would this one work?

I'll try this one first, then try your branch and report back

My computer is slow so building is taking a while with export templates

At least it shouldn't crash. But there is also a problem with weak referenced delegate and requests, which should be fixed in my branch.

ok, just finished build with Godot 3.2 branch commit 5e4a8ab , no crash but purchase fails.
.restore_purchases() seems to be broken in this commit also.

Going to try with @naithar s branch now

@naithar your fix from branch https://github.com/naithar/godot/tree/fix/in-app-store works! Thank you very much for the fix.

Great, I'll try finish it and make a PR soon.

Fixed by #42852.

Was this page helpful?
0 / 5 - 0 ratings