Skip to content

[Bug] godot-iap loses available-purchase and restore failures #270

Description

@hyochan

Library

godot-iap

Library version

godot-iap 3.0.1 (also present on main at 77f91d963c17629b9b0a3c167cf47497ae3a621b)

Affected platform

Android and iOS

Store

Google Play Store and App Store

Environment

Godot 4.7.x, GDScript addon API

What happened?

get_available_purchases() collapses store and bridge failures into an empty array, so callers cannot distinguish "the user owns nothing" from "the entitlement lookup failed."

On Android, the native bridge returns [] when it is not initialized, when option parsing fails, or when store.getAvailablePurchases() throws:

The GDScript wrapper also converts Android parse failures and iOS { success: false, error: ... } payloads to []:

This makes the Android restore wrapper report success even when the lookup failed, because it ignores the lookup outcome and unconditionally sets result.success = true:

For non-consumable entitlements this is unsafe: an app may treat a transient store failure as an authoritative empty entitlement set and revoke access, while the restore UI tells the user that restoration succeeded.

Expected behavior

The Godot API should preserve an explicit success/error result for available-purchase queries on both platforms. restore_purchases() should return success = false (and an error/code) when its underlying query fails.

The existing array-returning method can remain for compatibility, but a result-bearing API should be available and used by restore. For example:

{
  "success": false,
  "purchases": [],
  "error": "Not initialized",
  "code": "not-prepared"
}

Reproduction steps

  1. Use godot-iap 3.0.1 in a Godot Android project.
  2. Instantiate the addon without a successful init_connection() (or make the billing service unavailable).
  3. Call await get_available_purchases().
  4. Observe [], which is indistinguishable from a successful query with no purchases.
  5. Call await restore_purchases().
  6. Observe a VoidResult whose success field is true, even though no successful store query occurred.

The same loss of error information occurs on iOS when _call_ios_async("getAvailablePurchases", ...) returns a failure payload.

Suggested regression coverage

  • Android not-initialized and thrown-exception paths return a structured failure.
  • iOS async failure payload survives the GDScript wrapper.
  • Android restore returns failure when its available-purchases query fails.
  • A successful empty purchase list remains distinguishable from an error.

Logs or stack trace

No exception reaches the caller. The observed values are:
get_available_purchases() -> []
restore_purchases().success -> true

Additional context

The restore documentation describes a result that should report whether restoration succeeded: https://www.openiap.dev/docs/apis/restore-purchases

Metadata

Metadata

Assignees

No one assigned

    Labels

    cross-platformCross-platform (both Android & iOS)godot-iapgodot-iap library🐛 bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions