Skip to content

Commit

Permalink
Add examples
Browse files Browse the repository at this point in the history
  • Loading branch information
code-with-max committed Jun 15, 2024
1 parent 84af5cf commit e31e66c
Show file tree
Hide file tree
Showing 6 changed files with 368 additions and 1 deletion.
13 changes: 13 additions & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# These are supported funding model platforms

github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: codewithmax
open_collective: # Replace with a single Open Collective username
ko_fi: codewithmax
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: ['https://boosty.to/codewithmax']
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# AndroidIAPP Godot Plugin

AndroidIAPP is a [plugin] (<https://docs.godotengine.org/en/stable/tutorials/plugins/editor/installing_plugins.html#installing-a-plugin>) for the Godot game engine. It provides an interface to work with Google Play Billing Library version 7. The plugin supports all public functions of the library, passes all error codes, and can work with different subscription plans.
AndroidIAPP is a [plugin](<https://docs.godotengine.org/en/stable/tutorials/plugins/editor/installing_plugins.html#installing-a-plugin>) for the Godot game engine. It provides an interface to work with Google Play Billing Library version 7. The plugin supports all public functions of the library, passes all error codes, and can work with different subscription plans.

## Features

Expand All @@ -16,3 +16,7 @@ AndroidIAPP is a [plugin] (<https://docs.godotengine.org/en/stable/tutorials/plu
1. Download the plugin from [GitHub](https://github.com/your-repo-url).
2. Place the plugin folder in the `res://addons/` directory.
3. Enable the plugin in the project settings.

## Examples

- Example of a script for working with a plugin
261 changes: 261 additions & 0 deletions examples/billing_example.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
# AndroidIAPP is a plugin for the Godot game engine.
# It provides an interface to work with Google Play Billing Library version 7.
# The plugin supports all public functions of the library, passes all error codes, and can work with different subscription plans.
# https://developer.android.com/google/play/billing
#
# You can use this plugin with any node in Godot.
# But, I recommend adding this script as a singleton (autoload).
# This makes it easier to access and use its functions from anywhere in your project.
#
# An example of working with a plugin:


extends Node


# https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchaseState
enum purchaseState {
UNSPECIFIED_STATE = 0,
PURCHASED = 1,
PENDING = 2,
}


# https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode
enum billingResponseCode {
SERVICE_TIMEOUT = -3,
FEATURE_NOT_SUPPORTED = -2,
SERVICE_DISCONNECTED = -1,
OK = 0,
USER_CANCELED = 1,
SERVICE_UNAVAILABLE = 2,
BILLING_UNAVAILABLE = 3,
ITEM_UNAVAILABLE = 4,
DEVELOPER_ERROR = 5,
ERROR = 6,
ITEM_ALREADY_OWNED = 7,
ITEM_NOT_OWNED = 8,
NETWORK_ERROR = 12
}


const ITEM_CONSUMATED: Array = [
"additional_life_v1",
]

const ITEM_ACKNOWLEDGED: Array = [
"red_skin_v1",
"blue_skin_v1",
"yellow_skin_v1",
]

const SUBSCRIPTIONS: Array = [
"remove_ads_sub_01",
"test_iapp_v7",
]


var billing = null


# Called when the node enters the scene tree for the first time.
func _ready() -> void:
await get_tree().create_timer(1).timeout
run_iapp_billing()


func run_iapp_billing():
if Engine.has_singleton("AndroidIAPP"):
# Get the singleton instance of AndroidIAPP
billing = Engine.get_singleton("AndroidIAPP")
print("AndroidIAPP singleton loaded")

# Connection information

# Handle the response from the helloResponse signal
billing.helloResponse.connect(_on_hello_response)
# Handle the startConnection signal
billing.startConnection.connect(_on_start_connection)
# Handle the connected signal
billing.connected.connect(_on_connected)
# Handle the disconnected signal
billing.disconnected.connect(_on_disconnected)

# Querying purchases

# Handle the response from the query_purchases signal
billing.query_purchases.connect(_on_query_purchases)
# Handle the query_purchases_error signal
billing.query_purchases_error.connect(_on_query_purchases_error)

# Querying products details

# Handle the response from the query_product_details signal
billing.query_product_details.connect(query_product_details)
# Handle the query_product_details_error signal
billing.query_product_details_error.connect(_on_query_product_details_error)

# Purchase processing

# Handle the purchase signal
billing.purchase.connect(_on_purchase)
# Handle the purchase_error signal
billing.purchase_error.connect(_on_purchase_error)

# Purchase updating

# Handle the purchase_updated signal
billing.purchase_updated.connect(_on_purchase_updated)
# Handle the purchase_cancelled signal
billing.purchase_cancelled.connect(_on_purchase_cancelled)
# Handle the purchase_update_error signal
billing.purchase_update_error.connect(_on_purchase_update_error)

# Purchase consuming

# Handle the purchase_consumed signal
billing.purchase_consumed.connect(_on_purchase_consumed)
# Handle the purchase_consumed_error signal
billing.purchase_consumed_error.connect(_on_purchase_consumed_error)

# Purchase acknowledging

# Handle the purchase_acknowledged signal
billing.purchase_acknowledged.connect(_on_purchase_acknowledged)
# Handle the purchase_acknowledged_error signal
billing.purchase_acknowledged_error.connect(_on_purchase_acknowledged_error)

# Connection
billing.startConnection()
else:
printerr("AndroidIAPP singleton not found")


func _on_start_connection() -> void:
print("Billing: start connection")


func _on_connected() -> void:
print("Billing successfully connected")
await get_tree().create_timer(0.4).timeout
if billing.isReady():
# billing.sayHello("Hello from Godot Google IAPP plugin :)")
# Show products available to buy
# https://developer.android.com/google/play/billing/integrate#show-products
billing.queryProductDetails(ITEM_ACKNOWLEDGED, "inapp")
billing.queryProductDetails(ITEM_CONSUMATED, "inapp")
billing.queryProductDetails(SUBSCRIPTIONS, "subs")
# Handling purchases made outside your app
# https://developer.android.com/google/play/billing/integrate#ooap
billing.queryPurchases("subs")
billing.queryPurchases("inapp")


func _on_disconnected() -> void:
print("Billing disconnected")


func _on_hello_response(response) -> void:
print("Hello signal response: " + response)


func query_product_details(response) -> void:
for i in response["product_details_list"].size():
var product = response["product_details_list"][i]
print(JSON.stringify(product["product_id"], " "))
#
# Handle avaible for purchase product details here
#


func _on_query_purchases(response) -> void:
print("on_query_Purchases_response: ")
for purchase in response["purchases_list"]:
process_purchase(purchase)


func _on_purchase_updated(response):
for purchase in response["purchases_list"]:
process_purchase(purchase)


# Processing incoming purchase
func process_purchase(purchase):
for product in purchase["products"]:
if (product in ITEM_ACKNOWLEDGED) or (product in SUBSCRIPTIONS):
# Acknowledge the purchase
if not purchase["is_acknowledged"]:
print("Acknowledging: " + purchase["purchase_token"])
billing.acknowledgePurchase(purchase["purchase_token"])
#
# Here, process the use of the product in your game.
#
else:
print("Already acknowledged")
elif product in ITEM_CONSUMATED:
# Consume the purchase
print("Consuming: " + purchase["purchase_token"])
billing.consumePurchase(purchase["purchase_token"])
#
# Here, process the use of the product in your game.
#
else:
print("Product not found: " + str(product))


# Purchase
func do_purchase(id: String, is_personalized: bool = false):
billing.purchase([id], is_personalized)

# Subscriptions
func do_subsciption(subscription_id: String, base_plan_id: String , is_personalized: bool = false):
billing.subscribe([subscription_id], [base_plan_id], is_personalized)


func print_purchases(purchases):
for purchase in purchases:
print(JSON.stringify(purchase, " "))


func _on_purchase(response) -> void:
print("Purchase started:")
print(JSON.stringify(response, " "))


func _on_purchase_cancelled(response) -> void:
print("Purchase_cancelled:")
print(JSON.stringify(response, " "))


func _on_purchase_consumed(response) -> void:
print("Purchase_consumed:")
print(JSON.stringify(response, " "))


func _on_purchase_acknowledged(response) -> void:
print("Purchase_acknowledged:")
print(JSON.stringify(response, " "))


func _on_purchase_update_error(error) -> void:
print(JSON.stringify(error, " "))


func _on_purchase_error(error) -> void:
print(JSON.stringify(error, " "))


func _on_purchase_consumed_error(error) -> void:
print(JSON.stringify(error, " "))


func _on_purchase_acknowledged_error(error) -> void:
print(JSON.stringify(error, " "))


func _on_query_purchases_error(error) -> void:
print(JSON.stringify(error, " "))


func _on_query_product_details_error(error) -> void:
print(JSON.stringify(error, " "))
14 changes: 14 additions & 0 deletions examples/details_inapp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"description": "Beautiful sky-blue skin for circle catcher.",
"hash_code": 1042382311,
"name": "Blue skin",
"one_time_purchase_offer_details": {
"formatted_price": "1,29 CA$",
"price_amount_micros": 1290000,
"price_currency_code": "CAD"
},
"product_id": "blue_skin_v1",
"product_type": "inapp",
"title": "Blue skin (Circle catcher 2)",
"to_string": "ProductDetails{jsonString='{\"productId\":\"blue_skin_v1\",\"type\":\"inapp\",\"title\":\"Blue skin (Circle catcher 2)\",\"name\":\"Blue skin\",\"description\":\"Beautiful sky-blue skin for circle catcher.\",\"localizedIn\":[\"en-US\"],\"skuDetailsToken\":\"AEuhp4Ik3jZtacec7suJ2k1UwRNw1TAURMDxhuBdJVAvUszGtiXhi5IouYzz023Wz_3n9L-VCkaEOvo=\",\"oneTimePurchaseOfferDetails\":{\"priceAmountMicros\":1290000,\"priceCurrencyCode\":\"CAD\",\"formattedPrice\":\"1,29 CA$\",\"offerIdToken\":\"AbNbjn6qd8NV87Re\\/Gu8BaDG15I1ttYTPGQklCYCEKswhA2AYrbJgEaLxvr8PzAhsQYGGMuXkxbh\\/8kb2wySL7bDGg==\"}}', parsedJson={\"productId\":\"blue_skin_v1\",\"type\":\"inapp\",\"title\":\"Blue skin (Circle catcher 2)\",\"name\":\"Blue skin\",\"description\":\"Beautiful sky-blue skin for circle catcher.\",\"localizedIn\":[\"en-US\"],\"skuDetailsToken\":\"AEuhp4Ik3jZtacec7suJ2k1UwRNw1TAURMDxhuBdJVAvUszGtiXhi5IouYzz023Wz_3n9L-VCkaEOvo=\",\"oneTimePurchaseOfferDetails\":{\"priceAmountMicros\":1290000,\"priceCurrencyCode\":\"CAD\",\"formattedPrice\":\"1,29 CA$\",\"offerIdToken\":\"AbNbjn6qd8NV87Re\\/Gu8BaDG15I1ttYTPGQklCYCEKswhA2AYrbJgEaLxvr8PzAhsQYGGMuXkxbh\\/8kb2wySL7bDGg==\"}}, productId='blue_skin_v1', productType='inapp', title='Blue skin (Circle catcher 2)', productDetailsToken='AEuhp4Ik3jZtacec7suJ2k1UwRNw1TAURMDxhuBdJVAvUszGtiXhi5IouYzz023Wz_3n9L-VCkaEOvo=', subscriptionOfferDetails=null}"
}
55 changes: 55 additions & 0 deletions examples/details_subscription.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"description": "Remove all ADs from app one one year.",
"hash_code": -1341575204,
"name": "Remove all ADs from app",
"product_id": "remove_ads_sub_01",
"product_type": "subs",
"subscription_offer_details": [
{
"base_plan_id": "remove-ads-on-mounth",
"installment_plan_details": {

},
"offer_id": null,
"offer_tags": [
"ads",
"mounth"
],
"offer_token": "AbNbjn6TaIQy+qPjOjdLgFDKeSMVXjCx1DDgdhYNjVblNZ0d2GRCLJ/FWAFnIsPUe8NER3mp64JbMnUXORLfCMKLmc046GWIIdZy9jc=",
"pricing_phases": [
{
"billing_cycle_count": 0,
"billing_period": "P1M",
"formatted_price": "1,39 CA$",
"price_amount_micros": 1390000,
"price_currency_code": "CAD",
"recurrence_mode": 1
}
]
},
{
"base_plan_id": "remove-ads-on-year",
"installment_plan_details": {

},
"offer_id": null,
"offer_tags": [
"ads",
"year"
],
"offer_token": "AbNbjn4L6u7g6/hdXadrh8DN5gpEJ6Df9qwu9zvrwSSNpDZ/xwktiJ1668rLTaBQcC/cZsh6964WTWIjXPgIeoy7BACAMtaSi5nL",
"pricing_phases": [
{
"billing_cycle_count": 0,
"billing_period": "P1Y",
"formatted_price": "12,99 CA$",
"price_amount_micros": 12990000,
"price_currency_code": "CAD",
"recurrence_mode": 1
}
]
}
],
"title": "Remove all ADs from app (Circle catcher 2)",
"to_string": "ProductDetails{jsonString='{\"productId\":\"remove_ads_sub_01\",\"type\":\"subs\",\"title\":\"Remove all ADs from app (Circle catcher 2)\",\"name\":\"Remove all ADs from app\",\"description\":\"Remove all ADs from app one one year.\",\"localizedIn\":[\"en-US\"],\"skuDetailsToken\":\"AEuhp4ILup4nhJvTjaewXqded-BbLooEPhm9MvUQWBuQ27NI1U6D1cN7E6Yi_E8pMzqc\",\"subscriptionOfferDetails\":[{\"offerIdToken\":\"AbNbjn6TaIQy+qPjOjdLgFDKeSMVXjCx1DDgdhYNjVblNZ0d2GRCLJ\\/FWAFnIsPUe8NER3mp64JbMnUXORLfCMKLmc046GWIIdZy9jc=\",\"basePlanId\":\"remove-ads-on-mounth\",\"pricingPhases\":[{\"priceAmountMicros\":1390000,\"priceCurrencyCode\":\"CAD\",\"formattedPrice\":\"1,39 CA$\",\"billingPeriod\":\"P1M\",\"recurrenceMode\":1}],\"offerTags\":[\"ads\",\"mounth\"]},{\"offerIdToken\":\"AbNbjn4L6u7g6\\/hdXadrh8DN5gpEJ6Df9qwu9zvrwSSNpDZ\\/xwktiJ1668rLTaBQcC\\/cZsh6964WTWIjXPgIeoy7BACAMtaSi5nL\",\"basePlanId\":\"remove-ads-on-year\",\"pricingPhases\":[{\"priceAmountMicros\":12990000,\"priceCurrencyCode\":\"CAD\",\"formattedPrice\":\"12,99 CA$\",\"billingPeriod\":\"P1Y\",\"recurrenceMode\":1}],\"offerTags\":[\"ads\",\"year\"]}]}', parsedJson={\"productId\":\"remove_ads_sub_01\",\"type\":\"subs\",\"title\":\"Remove all ADs from app (Circle catcher 2)\",\"name\":\"Remove all ADs from app\",\"description\":\"Remove all ADs from app one one year.\",\"localizedIn\":[\"en-US\"],\"skuDetailsToken\":\"AEuhp4ILup4nhJvTjaewXqded-BbLooEPhm9MvUQWBuQ27NI1U6D1cN7E6Yi_E8pMzqc\",\"subscriptionOfferDetails\":[{\"offerIdToken\":\"AbNbjn6TaIQy+qPjOjdLgFDKeSMVXjCx1DDgdhYNjVblNZ0d2GRCLJ\\/FWAFnIsPUe8NER3mp64JbMnUXORLfCMKLmc046GWIIdZy9jc=\",\"basePlanId\":\"remove-ads-on-mounth\",\"pricingPhases\":[{\"priceAmountMicros\":1390000,\"priceCurrencyCode\":\"CAD\",\"formattedPrice\":\"1,39 CA$\",\"billingPeriod\":\"P1M\",\"recurrenceMode\":1}],\"offerTags\":[\"ads\",\"mounth\"]},{\"offerIdToken\":\"AbNbjn4L6u7g6\\/hdXadrh8DN5gpEJ6Df9qwu9zvrwSSNpDZ\\/xwktiJ1668rLTaBQcC\\/cZsh6964WTWIjXPgIeoy7BACAMtaSi5nL\",\"basePlanId\":\"remove-ads-on-year\",\"pricingPhases\":[{\"priceAmountMicros\":12990000,\"priceCurrencyCode\":\"CAD\",\"formattedPrice\":\"12,99 CA$\",\"billingPeriod\":\"P1Y\",\"recurrenceMode\":1}],\"offerTags\":[\"ads\",\"year\"]}]}, productId='remove_ads_sub_01', productType='subs', title='Remove all ADs from app (Circle catcher 2)', productDetailsToken='AEuhp4ILup4nhJvTjaewXqded-BbLooEPhm9MvUQWBuQ27NI1U6D1cN7E6Yi_E8pMzqc', subscriptionOfferDetails=[com.android.billingclient.api.ProductDetails$SubscriptionOfferDetails@e3d71fe, com.android.billingclient.api.ProductDetails$SubscriptionOfferDetails@1591d5f]}"
}
20 changes: 20 additions & 0 deletions examples/purchase_updated_inapp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"account_identifiers": null,
"developer_payload": "",
"hash_code": 347836291,
"is_acknowledged": false,
"is_auto_renewing": false,
"order_id": "GPA.3386-6160-2931-85348",
"original_json": "{\"orderId\":\"GPA.3386-6160-2931-85348\",\"packageName\":\"org.godotengine.circlecatcher\",\"productId\":\"blue_skin_v1\",\"purchaseTime\":1718385548694,\"purchaseState\":0,\"purchaseToken\":\"hbecfldgjckddhaefmoomgdm.AO-J1OxmTkHVIaH0xGRLKk53AvFGuj2gwKmldZ6YLAdfKTigvqE306j3cW38a_H8zd-DTr5-ZB-rWSbqUZFRuDpT3rGQyiicFN4e8VNd6qv4NWjc7L1opSU\",\"quantity\":1,\"acknowledged\":false}",
"package_name": "org.godotengine.circlecatcher",
"pending_purchase_update": null,
"products": [
"blue_skin_v1"
],
"purchase_state": 1,
"purchase_time": 1718385548694,
"purchase_token": "hbecfldgjckddhaefmoomgdm.AO-J1OxmTkHVIaH0xGRLKk53AvFGuj2gwKmldZ6YLAdfKTigvqE306j3cW38a_H8zd-DTr5-ZB-rWSbqUZFRuDpT3rGQyiicFN4e8VNd6qv4NWjc7L1opSU",
"quantity": 1,
"signature": "A0NkdhKPTSubDkUgu9HzVvJ33G0bZ18a/LX5NoK5WHXZmO3qznf8GESw/bda1A76CbX0PpiDaDIWGLK8b0huTmmdxSL+2wjw+LnaABc+hSf6KjD8Zmu/doMA6ScihP9Ilv9/t18S8JFpJdvUOUvKMs//EP0bxvmxJbAin+Y8QUdKuL7Bgj4NY75Vka4UWbqApyGdRYOkU5R4DZXV8zhaeRbbbevOmWd4AgLctJv+ZhVSeWyY9g5ONlPutVPPg3nwz4RBFL49yIarXGKvaZicM1weqJX5rtV/H/2fTSTUqp/vqigZEDYGI/MzLQw8M6GjI2qUSuFf8MMu+Pvl5zPW4A==",
"to_string": "Purchase. Json: {\"orderId\":\"GPA.3386-6160-2931-85348\",\"packageName\":\"org.godotengine.circlecatcher\",\"productId\":\"blue_skin_v1\",\"purchaseTime\":1718385548694,\"purchaseState\":0,\"purchaseToken\":\"hbecfldgjckddhaefmoomgdm.AO-J1OxmTkHVIaH0xGRLKk53AvFGuj2gwKmldZ6YLAdfKTigvqE306j3cW38a_H8zd-DTr5-ZB-rWSbqUZFRuDpT3rGQyiicFN4e8VNd6qv4NWjc7L1opSU\",\"quantity\":1,\"acknowledged\":false}"
}

0 comments on commit e31e66c

Please sign in to comment.