B4A Library [B4A] GPBilling_JE

This library wraps the GooglePlayBilling base library. It returns the results in structures as they are built in the Google accounting system.
The library does not display any messages; it only returns results: BillingResult, ProductResult, or PurchasedResult. It also generates the events described below.
When a ProductResult is returned, first read the ResponseCode value. If equal to CODE_OK, load the data from the list; otherwise, handle an error.
The principle of using the library is in the example application.

GPBilling_JE
Author: Jerryk
Version: 1.14

Add to the manifest editor:

CreateResourceFromFile(Macro GooglePlayBilling.GooglePlayBilling)​

Types:
to store in-app product
Type InAppProduct (
ProductId As String _​
ProductType As String _​
Title As String _​
Name As String _​
Description As String _​
PriceAmountMicros As String _​
PriceCurrencyCode As String _​
FormattedPrice As String )​

to store subscription product
Type SubsProduct (
ProductId As String _​
ProductType As String _​
Title As String _​
Name As String _​
Description As String _​
SubscriptionOfferDetails As List )​

to store SubscriptionOfferDetails
Type SubsOffer (
BasePlanId As String _​
OfferId As String _​
OfferToken As String _​
OfferTags As List _​
PricingPhaseList As List )​

to store PricingPhases
Type PricingPhase (
BillingCycleCount As String _​
BillingPeriod As String _​
FormattedPrice As String _​
PriceAmountMicros As String _​
PriceCurrencyCode As String _​
RecurrenceMode As String )​

to store returned result
Type ProductResult (
ResponseCode As Int _​
ProductsList As List)​

to store purchased items
Type PurchasedResult (
ItemPurchased As Boolean _​
ProductPurchased As Purchase)​

Events:
  • PurchaseCompleted (p As Purchase)
The event will be raised when the PurchaseInAppProducts or PurchaseSubsProducts function is called with a result of IsSuccess = True for each SKU separately. The passed parameter is p As Purchase.​
  • PurchaseError (Result As BillingResult)
The event will be raised when the PurchaseInAppProducts or PurchaseSubsProducts function is called with a result of IsSuccess = False. The parameter passed is Result As BillingResult.​

Functions:
  • Initialize (Callback As Object, EventName As String, AppPublicKey As String)
AppPublicKey = Licensing Public Key from Google Play Console​
  • GetInAppProducts (lSKUs As List) As ResumableSub
return ProductResult​
getss all available INAPP products (including disabled!!!!)​
  • GetSubscriptions (lSKUs As List) As ResumableSub
return ProductResult​
getss all available SUBS products​
  • PurchaseInAppProducts (lSKUs As List) As ResumableSub
return BillingResult​
purchase INAPP product​
  • PurchaseSubsProducts (lSKUs As List, offertoken As String) As ResumableSub
return BillingResult​
purchase SUBS product​
  • IsPurchased (skuType As String, sku As String) As ResumableSub
return PurchasedResult​
if PurchasedResult.ItemPurchased is True Sku is purchased​
  • GetPurchasedProducts As ResumableSub
return ProductResult​
gets list of all purchased products​
  • ConsumeProducts (skuType As String, sku As String) As ResumableSub
return BillingResult​
consume product​
  • AcknowledgeProducts (p As Purchase) As ResumableSub
return BillingResult​
acknowledge product VerifyPurchase is called before acknowledging​
  • ReturnResponseMessage (Result As Int) As String
return error string responded to error code defined in BillingResult​
Added two codes:​
CODE_NO_INAPP - return if no in-app SKU in Google Play Console​
CODE_NO_SUBSCRIPTION - return if no subs SKU in Google Play Console​

Properties:
  • message_xxxx (Message As String)
It is possible to localize a text message for each error.​
​
​
How to test the example on your Google Play Console?
  • Change in >Build Configurations>Package to match your application.
  • In the example, enter your ID for LICENSE_KEY and SKUs
B4X: Copy
    LICENSE_KEY = "MIIBIjAN......................"
    lSKU_INAPP.Add("pro.gpbtest2")
    lSKU_INAPP.Add("pro.gpbtest3")
    lSKU_SUBS.Add("silver.gpbtest")
    lSKU_SUBS.Add("gold.gpbtest")

    gpb.Initialize(Me, "gpb", LICENSE_KEY)

v1.1 - clear unnessery files
 

Attachments

Last edited:
GPBilling_JE - update to version 1.14

New versions of the Google Play Billing wrapper are available. If you are on 1.11, please update - 1.14 fixes two crashes that can happen in production. The public API is unchanged (all existing subs, types, events and constants keep their names and signatures), so it should be a drop-in replacement: just replace the .jar and .xml and recompile.

1.14 - crash fix: uninitialized Purchases list
Reported from a production app (Crashlytics): a user redeemed a promo code in Google Play while the app was running in the background and the app crashed with
Code: Copy
java.lang.RuntimeException: Object should first be initialized (List).
    at anywheresoftware.b4a.objects.collections.List.getSize
    at ...gpbilling_je._billing_purchasesupdated
Cause: the underlying BillingClient wrapper only calls setObject on the list when Google passes a non-null list. With Result.IsSuccess = True and a null list, the event receives an uninitialized List, and For Each over it kills the process. The same applies to QueryPurchases, not only PurchasesUpdated. An IsInitialized check was added to billing_PurchasesUpdated, IsPurchased, ConsumeProducts and GetPurchasedProducts.
Note: this only stops the crash. A purchase made this way (promo code redeemed outside of PurchaseInAppProducts) is not acknowledged by the wrapper and _PurchaseCompleted is not raised. Your app learns about it when it queries (GetPurchasedProducts / IsPurchased). Remember that Google refunds purchases that are not acknowledged within 3 days, so acknowledge them yourself with AcknowledgeProducts.

1.13 - crash fix: empty SKU list
QuerySkuDetails throws IllegalArgumentException: Product list cannot be empty when it gets an empty list. This was not guarded before, so calling GetInAppProducts, GetSubscriptions, PurchaseInAppProducts or PurchaseSubsProducts with an empty (or uninitialized) list crashed the app. They now return CODE_DEVELOPER_ERROR ("Incorrect usage of the API.") without calling the store.

1.12 - internal refactoring and bug fixes
  • ConsumeProducts: checked the result of the connection instead of the consume result, and returned an unassigned (null) result when the connection failed. It now returns the real consume result, and CODE_ITEM_NOT_OWNED when the SKU is not found.
  • PurchaseSubsProducts: an exception while launching the billing flow was logged and then reported as success. It now returns CODE_ERROR with the exception message.
  • PurchaseInAppProducts / PurchaseSubsProducts: when the query returns no products, CODE_NO_INAPP / CODE_NO_SUBSCRIPTION is returned instead of a "successful" result.
  • GetInAppProducts and GetPurchasedProducts: ResponseCode came from the connection result instead of the query result. GetPurchasedProducts now reports the first failing query, otherwise CODE_OK.
  • AcknowledgeProducts: a failed signature check returned the (successful) connection result. It now returns CODE_DEVELOPER_ERROR.
  • Null results from the Billing library when parsing products, offers and pricing phases are handled, ProductsList is always initialized, and a failed acknowledge is logged (_PurchaseCompleted is still raised as before).
  • Parsing was split into private subs and the resumable subs use guard clauses. No change in behavior.
Added (additive only): PRODUCT_TYPE_INAPP, PRODUCT_TYPE_SUBS, CODE_NETWORK_ERROR (now handled in ReturnResponseMessage), message_NetworkError and message_ServiceTimeout.

Behavior changes to be aware of
If your code relied on the old (wrong) results - for example treating a failed consume as success - it will now see the real error code. Please check your handling of ConsumeProducts, PurchaseSubsProducts and AcknowledgeProducts results.

Testing
Tested on a real device (Android, Play Billing 8.0.0): product and subscription queries, purchase of an already owned item, user-cancelled purchase, consume, and the empty SKU list case. The 1.14 fix was verified by compiling and by decompiling the Billing wrapper, because the promo-code scenario cannot be reproduced without a real Play promo code - feedback is welcome.
 
Back
Top