← All extensions

Native extension

KlugBilling

com.klug.billing

NO APM NEEDED. Download and enjoy. It can’t get any simpler. Just update the Adobe AIR SDK. If you need help, we are here.

In-app purchases and subscriptions for AIR apps on the App Store and Google Play, with one ActionScript API for both stores.

iOS Android In production From €19 / year · Buy

Overview

KlugBilling sells one-time products and subscriptions from your AIR app. On iPhone and iPad it talks to the App Store, on Android to Google Play, and your ActionScript code stays the same for both.

It loads your products with localized titles and prices, opens the store's purchase sheet, reports every step of a transaction and closes it once you have delivered the product. Restoring earlier purchases, the iOS app receipt and the signed purchase data of Google Play are included, so your server can verify every sale.

On Android KlugBilling uses the current Google Play Billing Library 8. It is added automatically when the app is packaged and meets Google Play's requirements for new apps and updates.

KlugBilling sells the subscriptions of our apps on iOS and Android, our proof of concept.

Features

  • Self-contained: one ANE, no core library or dependency ANEs to add
  • One API for the App Store and Google Play
  • One-time products and auto-renewing subscriptions
  • Localized titles, descriptions and prices straight from the store
  • Subscription periods, offers and pricing phases
  • Every transaction state as an event: purchasing, purchased, failed, cancelled, restored, deferred
  • Restore purchases (iOS) and list owned purchases (Android)
  • Data for server-side checks: the iOS app receipt, Google's signed purchase data and signature
  • Android: purchases are checked against your Play public key

Supported platforms

iOS iPhone · iPad · App Store
Android phones · tablets · foldables · Google Play Billing Library 8

On other platforms InAppBilling.isSupported returns false, so the same code can stay in a cross-platform app.

Getting started

Add the extension to your application descriptor:

app.xml
<extensions>
    <extensionID>com.klug.billing</extensionID>
</extensions>

No manifest entries and no activities are needed. Create your products in App Store Connect and in the Google Play Console with the same product IDs, and copy the base64 public key of your app from the Play Console (Monetization setup).

Code examples

Four complete flows, taken from the API that runs in our apps. Every call returns at once; results arrive as events on InAppBilling.service. Full documentation →

1 · Connect to the store
Store.as
import com.klug.billing.*;
import com.klug.billing.events.*;

var store:InAppBillingService = InAppBilling.service;

if ( InAppBilling.isSupported )   // iOS, Android
{
    store.addEventListener( InAppBillingEvent.SETUP_SUCCESS, onSetup );
    store.addEventListener( InAppBillingEvent.SETUP_FAILURE, onSetupFailed );
    store.addEventListener( PurchaseEvent.PURCHASES_UPDATED, onPurchasesUpdated );   // see 3

    var service:BillingService = new BillingService();
    if ( store.platform == "android" )
        service.setServiceType( InAppBillingServiceTypes.GOOGLE_PLAY_INAPP_BILLING )
               .setGooglePlayPublicKey( "MIIBIjANBgkqh..." );   // from the Play Console, Android only
    else
        service.setServiceType( InAppBillingServiceTypes.APPLE_INAPP_PURCHASE );
    store.setup( service );
}

function onSetup( e:InAppBillingEvent ):void
{
    store.checkAvailability( function( result:String ):void {
        if ( result == InAppBillingAvailability.AVAILABLE ) loadProducts();   // see 2
        else hideShop();   // e.g. purchases disabled by Screen Time
    });
}
2 · Show products with localized prices
Products.as
function loadProducts():void
{
    store.addEventListener( ProductEvent.PRODUCTS_LOADED, onProducts );
    store.addEventListener( ProductEvent.PRODUCTS_FAILED, function( e:ProductEvent ):void {
        trace( "store not reachable: " + e.message );
    });
    // one-time products first, subscriptions second
    store.getProducts( [ "com.example.removeads" ],
                       [ "com.example.pro.monthly", "com.example.pro.yearly" ] );
}

function onProducts( e:ProductEvent ):void
{
    for each ( var p:Product in e.products )
    {
        // e.g. "Pro Yearly · 19,99 €" in the user's currency and language
        addShopButton( p.id, p.title + " · " + p.priceString );   // your app function
    }
}
3 · Buy, deliver, finish
Purchase.as
function buy( productId:String ):void
{
    var request:PurchaseRequest = new PurchaseRequest().setProductId( productId );
    if ( store.checkPurchaseRequestValid( request ) ) store.makePurchase( request );
}

// every step of every transaction, also open ones from earlier sessions
function onPurchasesUpdated( e:PurchaseEvent ):void
{
    for each ( var p:Purchase in e.data )
    {
        switch ( p.transactionState )
        {
            case Purchase.STATE_PURCHASED:
            case Purchase.STATE_RESTORED:
                // iOS: p.transactionReceipt (app receipt)
                // Android: p.originalMessage + p.signature (Google's signed purchase data)
                verifyOnServer( p, function():void {   // your server call
                    unlock( p.productId );              // your app function
                    store.finishPurchase( p );          // close it, or the store sends it again
                });
                break;
            case Purchase.STATE_FAILED:
            case Purchase.STATE_CANCELLED:
                store.finishPurchase( p );
                break;
            case Purchase.STATE_DEFERRED:
                showHint( "Waiting for approval" );   // e.g. Ask to Buy
                break;
        }
    }
}
4 · Restore earlier purchases
Restore.as
function restore():void
{
    if ( store.isGetPurchasesSupported )   // Android: list what the Google account owns
    {
        store.addEventListener( PurchaseEvent.GET_PURCHASES_COMPLETE, function( e:PurchaseEvent ):void {
            for each ( var p:Purchase in e.data ) unlock( p.productId );
        });
        store.getPurchases();
    }
    else   // iOS: restored transactions arrive in onPurchasesUpdated with STATE_RESTORED
    {
        store.addEventListener( PurchaseEvent.RESTORE_PURCHASES_SUCCESS, function( e:PurchaseEvent ):void {
            showHint( "Purchases restored" );
        });
        store.restorePurchases();
    }
}

API overview

InAppBilling.isSupportedTrue on iOS and Android, false elsewhere.
InAppBilling.serviceThe single store service; all calls and events below.
setup(service)Connects to the store described by a BillingService. Result: SETUP_SUCCESS or SETUP_FAILURE.
checkAvailability(callback)AVAILABLE, NOT_AVAILABLE or STORE_UPGRADE_REQUIRED.
canMakePaymentsFalse when purchases are disabled on the device.
getProducts(productIds, subscriptionIds)Loads store data. Result: PRODUCTS_LOADED, INVALID_PRODUCT or PRODUCTS_FAILED.
getProduct(id)A loaded Product: title, description, price, priceString, currencyCode, subscriptionPeriod, subscriptionOffers.
makePurchase(request)Opens the store's purchase sheet for a PurchaseRequest; optional subscription offer.
finishPurchase(purchase)Closes a transaction after delivery. Result: FINISH_SUCCESS or FINISH_FAILED.
getPendingPurchases()Transactions the store still holds open.
restorePurchases()iOS: restores earlier purchases of the Apple ID.
getPurchases()Android: lists the purchases of the Google account.
applicationReceiptiOS: the app receipt as base64 for your server, with refresh().
dispose()Disconnects from the store.

Events

InAppBillingEventSETUP_SUCCESS, SETUP_FAILURE, RESTORE_PURCHASES_SUCCESS, RESTORE_PURCHASES_FAILED
ProductEventPRODUCTS_LOADED (products), INVALID_PRODUCT, PRODUCTS_FAILED
PurchaseEventPURCHASES_UPDATED (data = Purchase objects), PURCHASE_FAILED, FINISH_SUCCESS, FINISH_FAILED, RESTORE_PURCHASES_SUCCESS, RESTORE_PURCHASES_FAILED, GET_PURCHASES_COMPLETE, GET_PURCHASES_FAILED
AvailabilityEventCOMPLETE with the availability result
ApplicationReceiptEventREFRESH_SUCCESS, REFRESH_FAILED

Buy KlugBilling

A yearly licence for one AIR application, with all updates for 12 months. After payment you receive the extension, the ActionScript library and the documentation by e-mail, usually within one business day. Licence and terms

Simple
€19 / year
  • Licence for one AIR application
  • All updates for 12 months
  • No support
Buy Simple
Standard
€149 / year
  • Licence for one AIR application
  • All updates for 12 months
  • Answer by e-mail within 7 days
Buy Standard
Priority
€299 / year
  • Licence for one AIR application
  • All updates for 12 months
  • Answer by e-mail within 48 hours
Buy Priority
Taxes are calculated at checkout by Link (Stripe). You enter the application ID of your AIR app during checkout.

Requirements

AIR SDK51.x (HARMAN)
iOSiOS 13 or later, products in App Store Connect
AndroidAndroid 6.0 (API 23) or later, products in the Google Play Console
Google PlayPlay Billing Library 8.3, added at packaging
Server (recommended)Verifies the iOS app receipt or Google's signed purchase data