概览
KlugBilling 让您的 AIR 应用销售一次性商品和订阅。在 iPhone 和 iPad 上它与 App Store 通信,在 Android 上与 Google Play 通信,而您的 ActionScript 代码在两者中保持不变。
它会加载带有本地化标题和价格的商品,打开商店的购买界面,报告交易的每一步,并在您交付商品后关闭交易。还包括恢复以前的购买、iOS 应用收据以及 Google Play 的已签名购买数据,因此您的服务器可以验证每一笔销售。
在 Android 上,KlugBilling 使用当前的 Google Play Billing Library 8。它会在打包应用时自动添加,并满足 Google Play 对新应用和更新的要求。
KlugBilling 负责销售我们的应用在 iOS 和 Android 上的订阅,这就是我们的概念验证。
特性
- 自成一体:一个 ANE,无需添加核心库或依赖 ANE
- App Store 和 Google Play 共用一套 API
- 一次性商品和自动续订订阅
- 直接来自商店的本地化标题、描述和价格
- 订阅周期、优惠和价格阶段
- 每种交易状态均以事件形式报告:购买中、已购买、失败、已取消、已恢复、已延迟
- 恢复购买(iOS)和列出已拥有的购买(Android)
- 用于服务器端校验的数据:iOS 应用收据、Google 的已签名购买数据及签名
- Android:使用您的 Play 公钥校验购买
支持的平台
在其他平台上,InAppBilling.isSupported 返回 false,因此同一份代码可以保留在跨平台应用中。
入门
将扩展添加到您的应用描述文件中:
<extensions>
<extensionID>com.klug.billing</extensionID>
</extensions>
无需任何清单条目和 Activity。在 App Store Connect 和 Google Play Console 中使用相同的商品 ID 创建商品,并从 Play Console(创收设置)复制应用的 base64 公钥。
代码示例
四个完整流程,取自在我们应用中运行的 API。每次调用都会立即返回;结果以 InAppBilling.service 上的事件形式到达。完整文档 →
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 }); }
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 } }
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; } } }
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 概览
| InAppBilling.isSupported | 在 iOS 和 Android 上为 True,其他平台为 false。 |
| InAppBilling.service | 唯一的商店服务;以下所有调用和事件均基于它。 |
| setup(service) | 连接到由 BillingService 描述的商店。结果:SETUP_SUCCESS 或 SETUP_FAILURE。 |
| checkAvailability(callback) | AVAILABLE、NOT_AVAILABLE 或 STORE_UPGRADE_REQUIRED。 |
| canMakePayments | 设备上禁用购买时为 False。 |
| getProducts(productIds, subscriptionIds) | 加载商店数据。结果:PRODUCTS_LOADED、INVALID_PRODUCT 或 PRODUCTS_FAILED。 |
| getProduct(id) | 已加载的 Product:title, description, price, priceString, currencyCode, subscriptionPeriod, subscriptionOffers。 |
| makePurchase(request) | 为 PurchaseRequest 打开商店的购买界面;可选订阅优惠。 |
| finishPurchase(purchase) | 交付后关闭交易。结果:FINISH_SUCCESS 或 FINISH_FAILED。 |
| getPendingPurchases() | 商店中仍未关闭的交易。 |
| restorePurchases() | iOS:恢复该 Apple ID 以前的购买。 |
| getPurchases() | Android:列出该 Google 账户的购买。 |
| applicationReceipt | iOS:供服务器使用的 base64 格式应用收据,可通过 refresh() 刷新。 |
| dispose() | 断开与商店的连接。 |
事件
| InAppBillingEvent | SETUP_SUCCESS, SETUP_FAILURE, RESTORE_PURCHASES_SUCCESS, RESTORE_PURCHASES_FAILED |
| ProductEvent | PRODUCTS_LOADED (products), INVALID_PRODUCT, PRODUCTS_FAILED |
| PurchaseEvent | PURCHASES_UPDATED(data = Purchase 对象), PURCHASE_FAILED, FINISH_SUCCESS, FINISH_FAILED, RESTORE_PURCHASES_SUCCESS, RESTORE_PURCHASES_FAILED, GET_PURCHASES_COMPLETE, GET_PURCHASES_FAILED |
| AvailabilityEvent | COMPLETE,附带可用性结果 |
| ApplicationReceiptEvent | REFRESH_SUCCESS, REFRESH_FAILED |
购买 KlugBilling
一个 AIR 应用的年度许可,含 12 个月内的所有更新。付款后,您将通过电子邮件收到扩展、ActionScript 库和文档,通常在一个工作日内。许可与条款
要求
| AIR SDK | 51.x (HARMAN) |
| iOS | iOS 13 或更高版本,在 App Store Connect 中创建商品 |
| Android | Android 6.0 (API 23) 或更高版本,在 Google Play Console 中创建商品 |
| Google Play | Play Billing Library 8.3,在打包时添加 |
| 服务器(推荐) | 校验 iOS 应用收据或 Google 的已签名购买数据 |