本机Android计费SDK
Catappult Android Billing SDK是一种实施Catappult计费的简单解决方案。 它包括一个与AppCoins Wallet通信的计费客户端,允许您从Catappult获取产品并处理这些商品的购买。
####总结 具有该SDK的应用中的计费流程如下:
- 与Catappult Billing SDK建立连接;
- 通过Catappult Billing SDK查询应用内产品;
- 最终用户想在您的应用上购买产品;
- 应用通过Catappult Billing SDK启动AppCoins Wallet;
- AppCoins Wallet处理付款并在完成后回调您的应用;
- 应用请求Catappult Billing SDK验证交易数据;
- 应用将产品提供给最终用户。

接下来在您的应用中实施该SDK,您的第一个目标是实例化客户端并与其建立连接。 连接后,此计费客户端可用于获取Catappult中的注册产品、开始购买和处理购买。
因此,实施包括以下4个步骤:
- 与Catappult Billing SDK建立连接;
- 查询应用内产品;
- 启动Appcoins Wallet;
- 处理购买并将该商品提供给用户。
1. 与Catappult Billing SDK建立连接
在实例化和连接到Catappult之前,您需要在应用中添加依赖项和权限才能使用Catappult SDK。
依赖项和权限
在您的项目build.gradle
中,请确保您拥有以下存储库:
allprojects {
repositories {
google()
mavenCentral()
}
}
在您应用的build.gradle中,将AppCoins计费客户端添加到依赖项中。 要获取最新版本,请访问以下链接:
android-appcoins-billing
dependencies {
<...other dependencies..>
implementation 'io.catappult:android-appcoins-billing:0.6.7.0'
}
在AndroidManifest.xml中,您需要在查询标签中添加2个权限和1个程序包,以便该SDK可以与AppCoins Wallet通信。
<manifest>
...
<queries>
<!-- Required to work with Android 11 and above -->
<package android:name="com.appcoins.wallet" />
...
</queries>
...
<uses-permission android:name="com.appcoins.BILLING" />
<uses-permission android:name="android.permission.INTERNET" />
...
</manifest>
启动服务连接
一旦权限和依赖项全部添加完毕,您需要初始化一个AppcoinsBillingClient实例。 这是用于与Catappult Billing Library通信的实例。 您在任何时候都应该只有一个活动实例。 要初始化计费客户端,必须使用PurchasesUpdatedListener,要启动连接,则必须使用AppCoinsBillingStateListener。 本节将说明如何创建这两个必需的实例,以及如何实例化并连接AppcoinsBillingClient。 PurchaseUpdatedListner将在步骤4中展开说明。
AppCoinsBillingStateListener
这是适用于计费设置过程和状态的回调。 此侦听程序使用两种不同的方法:
名称 | 定义 |
---|---|
onBillingSetupFinished(responseCode) | 计费设置过程完成时调用此方法。 |
onBillingServiceDisconnected() | 计费连接丢失时调用此方法。 |
如果安装了钱包,服务将立即启动并调用计费状态侦听器。 否则,系统将提示用户下载Appcoins Wallet、安装Appcoins Wallet并设置新钱包。
class MainActivity : Activity() {
...
val appCoinsBillingStateListener: AppCoinsBillingStateListener =
object : AppCoinsBillingStateListener {
override fun onBillingSetupFinished(responseCode: Int) {
if (responseCode != ResponseCode.OK.value) {
Log.d(TAG, "Problem setting up in-app billing: $responseCode")
return
}
// Check for pending and/or owned purchases
checkPurchases()
// Query in-app sku details
queryInapps()
// Query subscriptions sku details
querySubs()
Log.d(TAG, "Setup successful. Querying inventory.")
}
override fun onBillingServiceDisconnected() {
Log.d("Message: ", "Disconnected")
}
}
...
}
class MainActivity extends Activity {
...
AppCoinsBillingStateListener appCoinsBillingStateListener = new AppCoinsBillingStateListener() {
@Override public void onBillingSetupFinished(int responseCode) {
if (responseCode != ResponseCode.OK.getValue()) {
Log.d(TAG, "Problem setting up in-app billing: " + responseCode);
return;
}
// Check for pending and/or owned purchases
checkPurchases();
// Query in-app sku details
queryInapps();
// Query subscriptions sku details
querySubs();
Log.d(TAG, "Setup successful. Querying inventory.");
}
@Override public void onBillingServiceDisconnected() {
Log.d("Message: ", "Disconnected");
}
};
...
}
AppcoinsBillingClient
下面的示例展示了如何通过将AppCoinsBillingStateListener、PurchasesUpdatedListener和公钥当作参数传递来构建和启动Appcoins IAB。
要从Catappult获取公钥,请单击此处。
class MainActivity : Activity() {
...
private lateinit var cab: AppcoinsBillingClient
private val purchasesUpdatedListener =
PurchasesUpdatedListener { responseCode: Int, purchases: List<Purchase> -> {
//Defined in step 4
}}
...
override fun onCreate(savedInstanceState: Bundle ?) {
...
val base64EncodedPublicKey = MY_KEY // Key obtained in Catappult's console
cab = CatapultBillingAppCoinsFactory.BuildAppcoinsBilling(
this,
base64EncodedPublicKey,
purchasesUpdatedListener
)
cab.startConnection(appCoinsBillingStateListener)
...
}
...
}
class MainActivity extends Activity {
...
private AppcoinsBillingClient cab;
PurchasesUpdatedListener purchaseUpdatedListener = (responseCode, purchases) -> {
// Defined in step 4
};
..
protected void onCreate(Bundle savedInstanceState) {
...
String base64EncodedPublicKey = MY_KEY // Key obtained in Catappult's console
cab = CatapultBillingAppCoinsFactory.BuildAppcoinsBilling(
this,
base64EncodedPublicKey,
purchasesUpdatedListener
);
cab.startConnection(appCoinsBillingStateListener);
...
}
...
}
成功完成设置后,您应该立即检查是否存在待处理和/或已拥有的订阅。 如果有待处理的购买,您应该进行消费。 消费将在步骤4中予以说明。
下面的示例展示了如何查看待处理或已拥有的购买:
void fun checkPurchases() {
val purchasesResult = cab.queryPurchases(SkuType.inapp.toString())
val purchases = purchasesResult.purchases
// queryPurchases of subscriptions will always return active and to consume subscription
val subsResult = cab.queryPurchases(SkuType.subs.toString())
val subs = subsResult.purchases
}
private void checkPurchases() {
PurchasesResult purchasesResult = cab.queryPurchases(SkuType.inapp.toString());
List<Purchase> purchases = purchasesResult.getPurchases();
// queryPurchases of subscriptions will always return active and to consume subscription
PurchaseResult subsResult = cab.queryPurchases(SkuType.subs.toString());
List<Purchase> subs = subsResult.getPurchases();
}
2. 查询应用内产品
启动连接后,您可以向Catappult查询可购买的产品,以便将其显示给用户。 此查询不仅包括产品的标题,还包括描述、价值等… 要查询产品,您可以使用querySkuDetailsAsync,它需要SkuDetailsResponseListener来处理Catappult的响应。
SkuDetailsResponseListener
名称 | 定义 |
---|---|
onSkuDetailsResponse(responseCode, skuDetailsList) | 此方法接收SKU详细信息查询的结果以及所查询SKU详细信息的列表 |
class MainActivity : Activity() {
...
val skuDetailsResponseListener = SkuDetailsResponseListener {responseCode, skuDetailsList ->
Log.d(TAG, "Received skus $responseCode $skuDetailsList")
for (sku in skuDetailsList) {
Log.d(TAG, "sku details: $sku")
// You can add these details to a list in order to update
// UI or use it in any other way
}
}
...
}
class MainActivity extends Activity {
...
SkuDetailsResponseListener skuDetailsResponseListener = (responseCode, skuDetailsList) -> {
Log.d(TAG, "Received skus " + responseCode)
for (SkuDetails sku: skuDetailsList) {
Log.d(TAG, "sku details: " + sku)
// You can add these details to a list in order to update
// UI or use it in any other way
}
}
...
}
创建侦听程序后,您可以将其与参数一起传递给querySkuDetailsAsync,如下所示:
private fun queryInapps() {
cab.querySkuDetailsAsync(
SkuDetailsParams().apply{
itemType = SkuType.inapp.toString()
moreItemSkus = mutableListOf<String>() // Fill with the skus of items
},
skuDetailsResponseListener
)
}
private fun querySubs() {
cab.querySkuDetailsAsync(
SkuDetailsParams().apply{
itemType = SkuType.subs.toString()
moreItemSkus = mutableListOf<String>() // Fill with the skus of subscriptions
},
skuDetailsResponseListener
)
}
private void queryInapps() {
List<String> inapps = ArrayList<String>();
// Fill the inapps with the skus of items
SkuDetailsParams skuDetailsParams = SkuDetailsParams();
skuDetailsParams.setItemType(SkuType.inapp.toString());
skuDetailsParams.setMoreItemSkus(inapps);
cab.querySkuDetailsAsync(skuDetailsParams, skuDetailsResponseListener);
}
private void querySubs() {
List<String> subs = ArrayList<String>();
// Fill the subs with the skus of subscriptions
SkuDetailsParams skuDetailsParams = SkuDetailsParams();
skuDetailsParams.setItemType(SkuType.subs.toString());
skuDetailsParams.setMoreItemSkus(subs);
cab.querySkuDetailsAsync(skuDetailsParams, skuDetailsResponseListener);
}
3. 启动Appcoins Wallet
要启动购买流程,请使用函数lauchBillingFlow。 这将引入一个BillingFlowParams实例,其中包括SKU、购买类型(应用内购买或应用内订阅)以及开发人员要使用的数据。 以下代码片段显示了与“购买”按钮相关联的可能函数:
private fun startPurchase(sku: String, developerPayload: String) {
Log.d(TAG, "Launching purchase flow.");
// Your sku type, can also be SkuType.subs.toString()
val skuType = SkuType.inapp.toString()
val billingFlowParams = BillingFlowParams(
sku,
skuType,
"orderId=" + System.currentTimeMillis(),
developerPayload,
"BDS"
)
if (!cab.isReady) {
cab.startConnection(appCoinsBillingStateListener)
}
val activity: Activity = this
val thread = Thread {
val responseCode = cab.launchBillingFlow(activity, billingFlowParams)
runOnUiThread {
if (responseCode != ResponseCode.OK.value) {
val builder =
AlertDialog.Builder(this)
builder.setMessage("Error purchasing with response code : $responseCode")
builder.setNeutralButton("OK", null)
Log.d(TAG, "Error purchasing with response code : $responseCode")
builder.create().show()
}
}
}
thread.start()
}
private void startPurchase(String sku, String developerPayload) {
Log.d(TAG, "Launching purchase flow.");
// Your sku type, can also be SkuType.subs.toString()
String skuType = SkuType.inapp.toString();
BillingFlowParams billingFlowParams =
new BillingFlowParams(
Skus.YOUR_SKU_ID,
skuType,
"orderId=" +System.currentTimeMillis(),
developerPayload,
"BDS"
);
//Make sure that the billing service is ready
if (!cab.isReady()) {
startConnection();
}
final Activity activity = this;
Thread thread = new Thread(() -> {
final int responseCode = cab.launchBillingFlow(activity, billingFlowParams);
runOnUiThread(() -> {
if (responseCode != ResponseCode.OK.getValue()) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("Error purchasing with response code : " + responseCode);
builder.setNeutralButton("OK", null);
Log.d(TAG, "Error purchasing with response code : " + responseCode);
builder.create().show();
}
});
});
thread.start();
}
为了让SDK接收购买数据,您需要添加一个函数调用到Catappult Billing SDK的onActivityResult。 这样一来,该SDK将处理并验证购买,然后通过PurchasesUpdatedListener通知您。
以下代码片段展示了如何实施该操作:
class MainActivity : Activity() {
...
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
cab.onActivityResult(requestCode, resultCode, data)
}
...
}
class MainActivity extends Activity {
...
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
cab.onActivityResult(requestCode, resultCode, data);
}
...
}
4. 处理购买并将商品提供给用户。
在Appcoins Wallet处理购买后,它会将在该意图下所得的数据返回到您的应用。 在通过上一步骤对onActivityResult进行更改后,该SDK将收到包含数据的通知,然后使用您的公钥验证购买信息。
在该SDK处理并验证购买后,它会通过PurchasesUpdatedListener将购买数据通知给您。 此侦听程序正是在第1步中注册的侦听程序,包含购买更新时的回调,您可以在该回调中获取购买的详细信息并将商品归于用户。
PurchasesUpdatedListener
下面是PurchasesUpdatedListener的定义和示例片段:
Name | Definition |
---|---|
onPurchasesUpdated(responseCode,listPurchases) | This method receives the notifications for purchases updates. |
class MainActivity : Activity() {
...
private var purchasesUpdatedListener =
PurchasesUpdatedListener { responseCode: Int, purchases: List<Purchase> ->
if (responseCode == ResponseCode.OK.value) {
for (purchase in purchases) {
token = purchase.token
// After validating and attributing the product, consumePurchase should be called
// to allow the user to purchase the item again and change the purchase's state.
// Also consume subscriptions to make them active, there will be no issue in consuming more than once
cab.consumeAsync(token, consumeResponseListener);
}
} else {
AlertDialog.Builder(this).setMessage(
String.format(
Locale.ENGLISH, "response code: %d -> %s", responseCode,
ResponseCode.values()[responseCode].name
)
)
.setPositiveButton(android.R.string.ok) { dialog, which -> dialog.dismiss() }
.create()
.show()
}
}
...
}
class MainActivity extends Activity {
...
PurchasesUpdatedListener purchaseUpdatedListener = (responseCode, purchases) -> {
if (responseCode == ResponseCode.OK.getValue()) {
for (Purchase purchase : purchases) {
token = purchase.getToken();
// After validating and attributing consumePurchase may be called
// to allow the user to purchase the item again and change the purchase's state.
// Also consume subscriptions to make them active, there will be no issue in consuming more than once
cab.consumeAsync(token, consumeResponseListener);
}
} else {
new AlertDialog.Builder(this).setMessage(
String.format(Locale.ENGLISH, "response code: %d -> %s", responseCode,
ResponseCode.values()[responseCode].name()))
.setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.dismiss())
.create()
.show();
}
};
...
}
消费购买
购买完成后,需要消费购买。 要消费购买,请使用函数consumeAsync。 此函数显示在PurchaseUpdatedListener片段中,需要ConsumeResponseListener来处理Catappult的消费响应。
ConsumeResponseListener
当商品消费操作结束时,回调将通知应用。
名称 | 定义 |
---|---|
onConsumeResponse(responseCode,purchaseToken) | 通知消费操作是否结束的回调。 |
您可以在下面找到ConsumeResponseListener实施的示例。
class MainActivity : Activity() {
...
val consumeResponseListener = ConsumeResponseListener {responseCode, purchaseToken ->
Log.d(TAG, "Consumption finished. Purchase: $purchaseToken, result: $responseCode")
if (responseCode == ResponseCode.OK.value) {
Log.d(TAG, "Consumption successful. Provisioning.");
//Your SKU logic goes here
} else {
complain("Error while consuming token: $purchaseToken");
}
Log.d(TAG, "End consumption flow.");
}
...
}
class MainActivity extends Activity {
...
ConsumeResponseListener consumeResponseListener = new ConsumeResponseListener() {
@Override public void onConsumeResponse(int responseCode, String purchaseToken) {
Log.d(TAG, "Consumption finished. Purchase: " + purchaseToken + ", result: " + responseCode);
if (responseCode == ResponseCode.OK.getValue()) {
Log.d(TAG, "Consumption successful. Provisioning.");
//Your SKU logic goes here
} else {
complain("Error while consuming token: " + purchaseToken);
}
Log.d(TAG, "End consumption flow.");
}
};
...
}
常见问题解答
是否存在任何帮助程序来实施该SDK?
是,有个AndroidStudio插件可以指导您逐步完成实施。Catappult计费集成

如何将用户链接到购买?
如果您需要将购买链接到用户,您可以通过在开发人员负载中传递userId来实现。 以下展示了已传递到购买函数的UserId的示例。
startPurchase(sku, "user12345")
startPurchase(sku, "user12345");
您可以在Purchase对象中检索此负载。 以下示例展示了如何提取PurchasesUpdatedListener中的负载并进行条件处理:
private var purchasesUpdatedListener =
PurchasesUpdatedListener { responseCode: Int, purchases: List<Purchase> ->
if (responseCode == ResponseCode.OK.value) {
for (purchase in purchases) {
token = purchase.token
val developerPayload = purchase.developerPayload
if (developerPayload == "user12345") {
...
}
...
}
} else {
...
}
}
PurchasesUpdatedListener purchaseUpdatedListener = (responseCode, purchases) -> {
if (responseCode == ResponseCode.OK.getValue()) {
for (Purchase purchase : purchases) {
token = purchase.getToken();
String developerPayload = purchase.getDeveloperPayload();
if (developerPayload.equals("user12345")) {
...
}
...
}
} else {
...
}
};
如何在没有认证应用的情况下测试购买流程?
出于测试目的,您可以使用以下数据来测试应用计费。
applicationId:
com.appcoins.sample
IAB_KEY:
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyEt94j9rt0UvpkZ2jPMZZ16yUrBOtjpIQCWi/
F3HN0+iwSAeEJyDw7xIKfNTEc0msm+m6ud1kJpLK3oCsK61syZ8bYQlNZkUxTaWNof1nMnbw3Xu5nuY
MuowmzDqNMWg5jNooy6oxwIgVcdvbyGi5RIlxqbo2vSAwpbAAZE2HbUrysKhLME7IOrdRR8MQbSbKE
y/9MtfKz0uZCJGi9h+dQb0b69H7Yo+/BN/ayBSJzOPlaqmiHK5lZsnZhK+ixpB883fr+PgSczU7qGoktqoe
6Fs+nhk9bLElljCs5ZIl9/NmOSteipkbplhqLY7KwapDmhrtBgrTetmnW9PU/eCWQIDAQAB
您还可以在此处获取我们已实施计费的 Google Trivial Drive 版本,以获得已经生效的示例。
Updated 2 months ago