Godot Google Play Billing ( I'm getting this result -> [IAP] Product not cached: coins_0.99 )
by Peter Ekela
I already implemented the logic for the in app purchases but, it appears it's not being processed at all... here is the code for my app. Is something wrong with my code? I already have my product ids set up and they are active in the play console and when I click on a button to purchase some coins it calls the "_buy_coins_10k() " function. But nothing shows, no purchase window for $0.99 for the 10k Coins I'm on Godot 4 using the Godot Google Play Billing plugin/addon version 3.2.0 extends Node # ============================================================ # SIGNALS # ============================================================ signal products_loaded(products: Array) signal product_granted(product_id: String) signal purchase_flow_started(product_id: String) # ============================================================ # PRODUCT DEFINITIONS (Consumables + Non‑Consumables) # ============================================================ const CONSUMABLES := { "coins_0.99": 10000, "coins_4.99": 60000, "coins_9.99": 150000 } const NON_CONSUMABLES := { "remove_ads": true } func is_consumable(id: String) -> bool: return CONSUMABLES.has(id) func get_reward(id: String) -> int: return CONSUMABLES.get(id, 0) # ============================================================ # INTERNAL STATE # ============================================================ var billing: BillingClient var products_cache := {} var retries := 0 const MAX_RETRIES := 5 # ============================================================ # READY # ============================================================ func _ready(): billing = BillingClient.new() add_child(billing) billing.connected.connect(_on_connected) billing.disconnected.connect(_on_disconnected) billing.query_product_details_response.connect(_on_product_details) billing.on_purchase_updated.connect(_on_purchase_updated) billing.consume_purchase_response.connect(_on_consume) billing.acknowledge_purchase_response.connect(_on_ack) billing.connect_error.connect(_on_connect_error) billing.start_connection() # ============================================================ # CONNECTION + RETRY LOGIC # ============================================================ func _on_connected(): retries = 0 _query_all() func _on_disconnected(): print("[IAP] Disconnected from Google Play.") _retry_connection() func _on_connect_error(code, msg): print("[IAP] Connection error: ", msg) _retry_connection() func _retry_connection(): if retries >= MAX_RETRIES: print("[IAP] Max retries reached. Billing unavailable.") return retries += 1 print("[IAP] Retrying connection (", retries, "/", MAX_RETRIES, ")...") await get_tree().create_timer(1.0).timeout billing.start_connection() # ============================================================ # QUERY PRODUCTS + PURCHASE HISTORY # ============================================================ func _query_all(): var all_ids = CONSUMABLES.keys() + NON_CONSUMABLES.keys() billing.query_product_details(all_ids, BillingClient.ProductType.INAPP) billing.query_purchases(BillingClient.ProductType.INAPP) # ============================================================ # PRODUCT DETAILS RECEIVED # ============================================================ func _on_product_details(response): if response.get("response_code") != BillingClient.BillingResponseCode.OK: print("[IAP] Failed to load product details.") return products_cache.clear() for p in response.get("product_details_list", []): var id = p.get("product_id") products_cache[id] = p print("[IAP] Cached products: ", products_cache.keys()) products_loaded.emit(products_cache.values()) # ============================================================ # PURCHASE FLOW # ============================================================ func buy(id: String): if not billing.is_ready(): print("[IAP] Billing not ready.") return if not products_cache.has(id): print("[IAP] Product not cached: ", id) return purchase_flow_started.emit(id) billing.purchase(id) func _on_purchase_updated(response): if response.get("response_code") != BillingClient.BillingResponseCode.OK: print("[IAP] Purchase failed: ", response.get("response_code")) return for purchase in response.get("purchases_list", []): _process_purchase(purchase) # ============================================================ # PROCESS PURCHASE (Grant → Consume/Acknowledge) # ============================================================ func _process_purchase(purchase: Dictionary): var products: Array = purchase.get("products", []) var token: String = purchase.get("purchase_token", "") var acknowledged: bool = purchase.get("is_acknowledged", false) for id in products: _grant(id) if is_consumable(id): billing.consume_purchase(token) elif not acknowledged: billing.acknowledge_purchase(token) # ============================================================ # GRANT REWARD / ENTITLEMENT # ============================================================ func _grant(id: String): if is_consumable(id): var amount = get_reward(id) GameManager._update_coin_count(amount) SignalsManager.emit_signal("coin_exchange") print("[IAP] Granted ", amount, " coins.") else: print("[IAP] Granted entitlement: ", id) product_granted.emit(id) # ============================================================ # CONSUME / ACK RESPONSES # ============================================================ func _on_consume(response): if response.get("response_code") == BillingClient.BillingResponseCode.OK: print("[IAP] Consumable consumed successfully.") else: print("[IAP] Failed to consume item: ", response.get("response_code")) func _on_ack(response): if response.get("response_code") == BillingClient.BillingResponseCode.OK: print("[IAP] Non‑consumable acknowledged.") else: print("[IAP] Failed to acknowledge item: ", response.get("response_code")) # ==================== func _coins_10k() -> String: return "coins_0.99" func _buy_coins_10k() -> void: buy(_coins_10k())