实施 Google Play 结算库版本 2
Implement Google Play Billing Library version 2
Google 发布了一个全新的版本来处理 Android 中的付款,但在搜索了一段时间后,我找不到成功实施它的人的单个示例或教程。
文档很短,只提供了一部分必要的代码:
https://developer.android.com/google/play/billing/billing_library_overview
提供的唯一示例是使用 Kotlin 制作的:
https://github.com/android/play-billing-samples
他们似乎忘记了 Java 开发人员...
有没有人知道网上的教程或者成功实现了?我当前的代码还远未发布。
这是我在 Kotlin 中使用 billing 2.1.0 的实现。如果您看到整个图片,您可以轻松地将其转换为 Java(这就是我将整个 activity 粘贴给您的原因)。
class GoPremiumActivity : AppCompatActivity(), PurchasesUpdatedListener, AcknowledgePurchaseResponseListener {
private lateinit var billingClient: BillingClient
private val skuList = listOf(CStr.PRODUCT_ADS_REMOVE.value)
private var skuDetails: SkuDetails? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.go_premium)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
setupBillingClient()
purchaseButton.setOnClickListener {
val flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.build()
billingClient.launchBillingFlow(this@GoPremiumActivity, flowParams)
}
}
private fun setupBillingClient() {
billingClient = BillingClient
.newBuilder(this@GoPremiumActivity)
.enablePendingPurchases()
.setListener(this@GoPremiumActivity)
.build()
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult?) {
if (billingResult?.responseCode == BillingClient.BillingResponseCode.OK) {
getAvailableProducts()
val purchasesResult = billingClient.queryPurchases(BillingClient.SkuType.INAPP)
val purchase = purchasesResult.purchasesList.firstOrNull { it.sku == CStr.PRODUCT_ADS_REMOVE.value}
if (purchase?.isAcknowledged == true) {
Global.prefs.adsRemovalPurchased = true
finish()
}
} else {
showGeneralError()
}
}
override fun onBillingServiceDisconnected() {
/*DO NOTHING*/
}
})
}
fun getAvailableProducts() {
if (billingClient.isReady) {
val params = SkuDetailsParams
.newBuilder()
.setSkusList(skuList)
.setType(BillingClient.SkuType.INAPP)
.build()
billingClient.querySkuDetailsAsync(params) { responseCode, skuDetailsList ->
if (responseCode.responseCode == BillingClient.BillingResponseCode.OK) {
skuDetails = skuDetailsList.firstOrNull()
skuDetails?.let {
purchaseButton.text = String.format("BUY %s", it.price)
showSuccessOrError(success = true)
} ?: run {
showSuccessOrError(success = false)
}
} else {
showGeneralError()
}
}
} else {
showGeneralError()
}
}
override fun onPurchasesUpdated(billingResult: BillingResult?, purchases: MutableList<Purchase>?) {
if (billingResult?.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
val purchase = purchases.firstOrNull { it.sku == CStr.PRODUCT_ADS_REMOVE.value}
if (purchase?.purchaseState == Purchase.PurchaseState.PURCHASED) {
if (!purchase.isAcknowledged) {
val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build()
billingClient.acknowledgePurchase(acknowledgePurchaseParams, this@GoPremiumActivity)
}
}
} else if (billingResult?.responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
Global.prefs.adsRemovalPurchased = true
finish()
} else {
Global.prefs.adsRemovalPurchased = false
showSuccessOrError(success = true)
}
}
override fun onAcknowledgePurchaseResponse(billingResult: BillingResult?) {
if (billingResult?.responseCode == BillingClient.BillingResponseCode.OK) {
showThankYouDialog(this@GoPremiumActivity)
Global.prefs.adsRemovalPurchased = true
}
}
private fun showSuccessOrError(success: Boolean) {
purchaseProgressBar.visibility = View.GONE
if (success) {
purchaseButton.visibility = View.VISIBLE
} else {
purchaseUnavailable.visibility = View.VISIBLE
}
}
private fun showGeneralError() {
purchaseProgressBar.visibility = View.GONE
purchaseUnavailable.visibility = View.VISIBLE
}
companion object {
fun newIntent(context: Context): Intent {
return Intent(context, GoPremiumActivity::class.java)
}
}
override fun onSupportNavigateUp(): Boolean {
finish()
return true
}
public override fun onDestroy() {
super.onDestroy()
}
override fun onPause() {
super.onPause()
if (isFinishing) {
finish()
}
}
private fun showThankYouDialog(context: Context) {
//Show dialog
}
}
如果您在 Java 中特别需要它,我可以删除它。
我是 Android Studio 的初学者,我正在实施计费库 2.1.0。在阅读 android studio 文档和许多关于计费库的教程一周后,我做了这个 java class,但我觉得还不够好,至少它做了它必须做的做。如果您发现任何改进方法,请发表评论。感谢:
1.- Class Pago.java:
package com.example.billing;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.Nullable;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.ConsumeParams;
import com.android.billingclient.api.ConsumeResponseListener;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;
import com.example.R;
import static com.android.billingclient.api.BillingClient.BillingResponseCode.SERVICE_TIMEOUT;
import static com.android.billingclient.api.BillingClient.BillingResponseCode.OK;
import static com.android.billingclient.api.BillingClient.BillingResponseCode.USER_CANCELED;
import static com.android.billingclient.api.BillingClient.BillingResponseCode.BILLING_UNAVAILABLE;
import static com.android.billingclient.api.BillingClient.BillingResponseCode.ITEM_UNAVAILABLE;
import static com.android.billingclient.api.BillingClient.BillingResponseCode.ERROR;
import static com.android.billingclient.api.BillingClient.SkuType.INAPP;
import java.util.ArrayList;
import java.util.List;
public class Pagos implements PurchasesUpdatedListener, BillingClientStateListener, SkuDetailsResponseListener, ConsumeResponseListener {
private BillingClient billingClient;
private Context contextPago;
private String skuId;
private List<SkuDetails> misProductos;
// Constructor de la clase Pagos
public Pagos(Context context) {
contextPago = context;
}
// Asigna el sku del producto que se quiere comprar
public void comprar(String skuId) {
this.skuId = skuId;
configurarBillingClient();
}
// Configura el Billing Client para iniciar la conexión con Google Play Console
private void configurarBillingClient() {
// 1. Configura el Billing Client
billingClient = BillingClient.newBuilder(contextPago)
.enablePendingPurchases()
.setListener(this)
.build();
// 2. Inicia la conexión y asigna los Listener
billingClient.startConnection(this);
}
@Override
// Evento salta al llamar billingClient.startConnection()
public void onBillingSetupFinished(BillingResult billingResult) {
// Busca compras en el Servidor de Google y las marca como consumidas
consumeCompras();
// Verifica que la versión de Play Store sea compatible con INAPP
if (!billingClient.isReady()) {
String mensaje = contextPago.getString(R.string.PAGOS_MENSAJE_VERSIÓN_NO_COMPATIBLE);
Toast.makeText(contextPago, mensaje, Toast.LENGTH_LONG).show();
return;
}
// Verifica que la versión de Play Store sea compatible con Suscripciones
// if (billingClient.isFeatureSupported(SUBSCRIPTIONS).getResponseCode() != OK) {
// String mensaje = contextPago.getString(R.string.PAGOS_MENSAJE_VERSIÓN_NO_COMPATIBLE);
// Toast.makeText(contextPago, mensaje, Toast.LENGTH_LONG).show();
// return; //GooglePlayNoSoportaComprasDeSuscripciones
// }
// Verifica que la Configuración se haya hecho bien, sino muestra mensaje de error
if (verificaResponseCode(billingResult.getResponseCode()) == OK) {
consultaProductos();
}
}
// Asigna los elemento que se consultarán a Google y los envía con querySkuDetailsAsync
private void consultaProductos() {
// Inicializa constantes
String ITEM_SKU_1 = "android.test.item_unavailable";
String ITEM_SKU_2 = "android.test.canceled";
String ITEM_SKU_3 = "android.test.purchased";
String ITEM_SKU_4 = "donar";
String ITEM_SKU_5 = "prueba.1";
// Agrega los productos que se consultarán a Google
List<String> skuList = new ArrayList<>();
skuList.add(ITEM_SKU_1);
skuList.add(ITEM_SKU_2);
skuList.add(ITEM_SKU_3);
skuList.add(ITEM_SKU_4);
skuList.add(ITEM_SKU_5);
// TODO Cambiar el ingreso manual de items por una consulta a servidor propio de backend seguro.
SkuDetailsParams.Builder skuDetailsParams = SkuDetailsParams
.newBuilder()
.setSkusList(skuList)
.setType(INAPP);
// Envía consulta a Google y devuelve el listado de productos mediante onSkuDetailsResponse
billingClient.querySkuDetailsAsync(skuDetailsParams.build(), this);
}
@Override
// Evento salta cuando Google envía los detalles de los Productos en Venta
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
if (verificaResponseCode(billingResult.getResponseCode()) == OK) {
if (skuDetailsList != null) {
misProductos = skuDetailsList;
muestraDialogoCompra();
} else {
String mensaje = contextPago.getString(R.string.PAGOS_MENSAJE_NO_SKUDETAILSLIST);
Toast.makeText(contextPago, mensaje, Toast.LENGTH_LONG).show();
}
}
}
// Lanza el dialogo de compra de Google
private void muestraDialogoCompra() {
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(getSkuIdDetails())
.build();
billingClient.launchBillingFlow((Activity) contextPago, flowParams);
}
// Obtiene el Producto que se comprará según el Sku ingresado mediante comprar(sku);
private SkuDetails getSkuIdDetails() {
if (misProductos == null) return null;
for (SkuDetails skuProducto : misProductos) {
if (skuId.equals(skuProducto.getSku())) return skuProducto;
}
return null;
}
@Override
// Evento salta cuando se finaliza el Proceso de compra
public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> list) {
if (verificaResponseCode(billingResult.getResponseCode()) == OK) {
// Validar compra con consulta a Google para evitar ingeniería inversa de hackers
if (validaCompra()) {
// Compra confirmada
Log.i("Pagos", "Compra encontrada en servidor");
} else {
// Compra no encontrada: Mensaje de error - Revocar privilegios
Log.i("Pagos", "Compra no encontrada posible hacker");
}
consumeCompras();
}
}
// Valida la compra y Devuelve True si encuentra la compra del usuario en el Servidor de Google
private boolean validaCompra() {
List<Purchase> purchasesList = billingClient.queryPurchases(INAPP).getPurchasesList();
if (purchasesList != null && !purchasesList.isEmpty()) {
for (Purchase purchase : purchasesList) {
if (purchase.getSku().equals(skuId)) {
return true;
}
}
}
return false;
}
// Busca compras en el Servidor de Google y las marca como consumidas
private void consumeCompras() {
Purchase.PurchasesResult queryPurchases = billingClient.queryPurchases(INAPP);
if (queryPurchases.getResponseCode() == OK) {
List<Purchase> purchasesList = queryPurchases.getPurchasesList();
if (purchasesList != null && !purchasesList.isEmpty()) {
for (Purchase purchase : purchasesList) {
ConsumeParams params = ConsumeParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
billingClient.consumeAsync(params, this);
}
}
}
}
@Override
// Evento salta cuando se ha consumido un producto, Si responseCode = 0, ya se puede volver a comprar
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
if (billingResult.getResponseCode() == OK) {
Log.i("Pagos", "Token de Compra: " + purchaseToken + " consumida");
} else {
Log.i("Pagos", "Error al consumir compra, responseCode: " + billingResult.getResponseCode());
}
}
@Override
// Evento salta cuando se pierde la conexión durante una compra
public void onBillingServiceDisconnected() {
billingClient.startConnection(this);
}
// Verifica que el estado del responseCode sea OK, si no muestra mensaje de Error
private int verificaResponseCode(int responseCode) {
if (responseCode == OK) return OK;
if (responseCode == USER_CANCELED) return USER_CANCELED;
String mensaje = "";
switch (responseCode) {
case SERVICE_TIMEOUT:
mensaje = contextPago.getString(R.string.PAGOS_MENSAJE_SERVICE_TIMEOUT);
break;
case BILLING_UNAVAILABLE:
mensaje = contextPago.getString(R.string.PAGOS_MENSAJE_BILLING_UNAVAILABLE);
break;
case ITEM_UNAVAILABLE:
mensaje = contextPago.getString(R.string.PAGOS_MENSAJE_ITEM_UNAVAILABLE);
break;
case ERROR:
mensaje = contextPago.getString(R.string.PAGOS_MENSAJE_ERROR);
break;
default:
mensaje = contextPago.getString(R.string.PAGOS_MENSAJE_ERROR) + " código: " + responseCode;
break;
}
Toast.makeText(contextPago, mensaje, Toast.LENGTH_LONG).show();
return responseCode;
}
}
3.- 清单
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="com.android.vending.BILLING" />
4.- build.gradle
// Google Play Billing Library
implementation 'com.android.billingclient:billing:2.1.0'
5.- 用法,将此代码放在要显示计费组件的任何位置:
private final String SKU_UNAVAILABLE = "android.test.item_unavailable";
private final String SKU_CANCELED = "android.test.canceled";
private final String SKU_PURCHASED = "android.test.purchased";
private final String SKU_DONAR = "donar";
private void donar() {
Pagos pagos = new Pagos(this);
pagos.comprar(SKU_DONAR);
cargandoDialogoCompra(true);
}
您可以将 SKU_DONAR 更改为 SKU_UNAVAILABLE、SKU_CANCELED、SKU_PURCHASED,因为这些是用于测试目的的项目,正如我所读,没有必要将它们添加到玩控制台
6.- Google 玩游戏机
Presencia en Google Play Store -> Productos integrados en la
aplicación -> Productos administrados:
Donación (donar) PEN 9.99
就这些了,请改进我的代码,谢谢大家。
感谢@Webfreak,你对 Kotlin 的回答指引我走向正确的方向。
以下是我为 Java 实现它的方式:
首先将'billingclient'库添加到gradle:
implementation 'com.android.billingclient:billing:X.X.X'
并在清单文件中添加所需的权限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="com.android.vending.BILLING" />
Activity 必须实现以下接口:
public class MainActivity extends AppCompatActivity implements
...
PurchasesUpdatedListener,
AcknowledgePurchaseResponseListener {
然后我在 onCreate 方法中初始化计费客户端:
/** IN-APPS PURCHASE */
private BillingClient mBillingClient;
private long mLastPurchaseClickTime = 0;
private List<String> mSkuList = new ArrayList<>();
private List<SkuDetails> mSkuDetailsList = new ArrayList<>();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// AppPrefs is just a standalone class I used to get or set shared preferences easily
mPrefs = AppPrefs.getInstance(this);
// Rest of your code ...
/** IN-APP PURCHASES */
// Initialize the list of all the in-app product IDs I use for this app
mSkuList.add(Parameters.UNIT_P1);// NoAdsPurchased
mSkuList.add(Parameters.UNIT_P2);// CustomizationPurchased
mSkuList.add(Parameters.UNIT_P3);// ChartsPurchased
// Initialize the billing client
setupBillingClient();
// Apply the upgrades on my app according to the user's purchases
applyUpgrades();
}
设置结算客户端的方法在这里,以及我用来从应用程序检索可用的应用程序内产品的方法:
private void setupBillingClient() {
mBillingClient = BillingClient
.newBuilder(MainActivity.this)
.enablePendingPurchases() // Useful for physical stores
.setListener(MainActivity.this)
.build();
mBillingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// Load the available products related to the app from Google Play
getAvailableProducts();
Purchase.PurchasesResult purchasesResult = mBillingClient.queryPurchases(BillingClient.SkuType.INAPP);// Or SkuType.SUBS if subscriptions
// Init all the purchases to false in the shared preferences (security prevention)
mPrefs.setNoAdsPurchased(false);
mPrefs.setCustomizationPurchased(false);
mPrefs.setChartsPurchased(false);
// Retrieve and loop all the purchases done by the user
// Update all the boolean related to the purchases done in the shared preferences
if (purchasesResult.getPurchasesList() != null) {
for (Purchase purchase : purchasesResult.getPurchasesList()) {
if (purchase.isAcknowledged()) {
Log.e(TAG, purchase.getSku());
switch (purchase.getSku()) {
case Parameters.UNIT_P1:
mPrefs.setNoAdsPurchased(true);
break;
case Parameters.UNIT_P2:
mPrefs.setCustomizationPurchased(true);
break;
case Parameters.UNIT_P3:
mPrefs.setChartsPurchased(true);
break;
}
}
}
}
}
}
@Override
public void onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
// TODO Note: It's strongly recommended that you implement your own connection retry policy and override the onBillingServiceDisconnected() method. Make sure you maintain the BillingClient connection when executing any methods.
Log.e(TAG, "onBillingServiceDisconnected");
}
});
}
private void getAvailableProducts() {
if (mBillingClient.isReady()) {
SkuDetailsParams params = SkuDetailsParams
.newBuilder()
.setSkusList(mSkuList)
.setType(BillingClient.SkuType.INAPP)
.build();
mBillingClient.querySkuDetailsAsync(params, new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
mSkuDetailsList = skuDetailsList;
}
}
});
}
}
当用户完成购买时(我允许在我的应用程序中的多个片段上购买),我在主 Activity 上调用此函数(使用接口):
@Override
public void purchase(String sku) {
// Mis-clicking prevention, using threshold of 3 seconds
if (SystemClock.elapsedRealtime() - mLastPurchaseClickTime < 3000){
Log.d(TAG, "Purchase click cancelled");
return;
}
mLastPurchaseClickTime = SystemClock.elapsedRealtime();
// Retrieve the SKU details
for (SkuDetails skuDetails : mSkuDetailsList) {
// Find the right SKU
if (sku.equals(skuDetails.getSku())) {
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.build();
mBillingClient.launchBillingFlow(MainActivity.this, flowParams);
break;
}
}
}
这里我实现继承的方法:
@Override
public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> purchases) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null) {
for (Purchase purchase : purchases) {
handlePurchase(purchase);
}
} else {
displayError(R.string.inapp_purchase_problem, billingResult.getResponseCode());
}
}
private void handlePurchase(Purchase purchase) {
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
// Grant entitlement to the user.
applyPurchase(purchase);
// Acknowledge the purchase if it hasn't already been acknowledged.
if (!purchase.isAcknowledged()) {
AcknowledgePurchaseParams acknowledgePurchaseParams =
AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
mBillingClient.acknowledgePurchase(acknowledgePurchaseParams, MainActivity.this);
}
}
}
@Override
public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
displayError(R.string.inapp_purchase_success, billingResult.getResponseCode());
}
}
我添加的用于在我的应用程序上确认购买的方法:
private void applyPurchase(Purchase purchase) {
switch (purchase.getSku()) {
case Parameters.UNIT_P1:
mPrefs.setNoAdsPurchased(true);
break;
case Parameters.UNIT_P2:
mPrefs.setCustomizationPurchased(true);
break;
case Parameters.UNIT_P3:
mPrefs.setChartsPurchased(true);
break;
}
// I remove the ads right away if purchases
if(mPrefs.getNoAdsPurchased()) {
destroyAds();
}
}
最后一种方法用于在应用程序上应用所有 upgrades/purchases(以移除广告为例):
private void applyUpgrades() {
// No ads
if (mPrefs.getNoAdsPurchased()) {
destroyAds();
} else {
loadAds();
}
if (mPrefs.getCustomizationPurchased()) {
// Allow customization
// ...
}
if (mPrefs.getChartsPurchased()) {
// Allow charts visualization
// ...
}
}
我想这个解决方案还不完美,但它正在工作,如果我发现改进,我会修改代码。
这是 Google Java 中的 Play Billing 版本 2 的示例应用程序:
Google 发布了一个全新的版本来处理 Android 中的付款,但在搜索了一段时间后,我找不到成功实施它的人的单个示例或教程。
文档很短,只提供了一部分必要的代码: https://developer.android.com/google/play/billing/billing_library_overview
提供的唯一示例是使用 Kotlin 制作的: https://github.com/android/play-billing-samples
他们似乎忘记了 Java 开发人员...
有没有人知道网上的教程或者成功实现了?我当前的代码还远未发布。
这是我在 Kotlin 中使用 billing 2.1.0 的实现。如果您看到整个图片,您可以轻松地将其转换为 Java(这就是我将整个 activity 粘贴给您的原因)。
class GoPremiumActivity : AppCompatActivity(), PurchasesUpdatedListener, AcknowledgePurchaseResponseListener {
private lateinit var billingClient: BillingClient
private val skuList = listOf(CStr.PRODUCT_ADS_REMOVE.value)
private var skuDetails: SkuDetails? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.go_premium)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
setupBillingClient()
purchaseButton.setOnClickListener {
val flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.build()
billingClient.launchBillingFlow(this@GoPremiumActivity, flowParams)
}
}
private fun setupBillingClient() {
billingClient = BillingClient
.newBuilder(this@GoPremiumActivity)
.enablePendingPurchases()
.setListener(this@GoPremiumActivity)
.build()
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult?) {
if (billingResult?.responseCode == BillingClient.BillingResponseCode.OK) {
getAvailableProducts()
val purchasesResult = billingClient.queryPurchases(BillingClient.SkuType.INAPP)
val purchase = purchasesResult.purchasesList.firstOrNull { it.sku == CStr.PRODUCT_ADS_REMOVE.value}
if (purchase?.isAcknowledged == true) {
Global.prefs.adsRemovalPurchased = true
finish()
}
} else {
showGeneralError()
}
}
override fun onBillingServiceDisconnected() {
/*DO NOTHING*/
}
})
}
fun getAvailableProducts() {
if (billingClient.isReady) {
val params = SkuDetailsParams
.newBuilder()
.setSkusList(skuList)
.setType(BillingClient.SkuType.INAPP)
.build()
billingClient.querySkuDetailsAsync(params) { responseCode, skuDetailsList ->
if (responseCode.responseCode == BillingClient.BillingResponseCode.OK) {
skuDetails = skuDetailsList.firstOrNull()
skuDetails?.let {
purchaseButton.text = String.format("BUY %s", it.price)
showSuccessOrError(success = true)
} ?: run {
showSuccessOrError(success = false)
}
} else {
showGeneralError()
}
}
} else {
showGeneralError()
}
}
override fun onPurchasesUpdated(billingResult: BillingResult?, purchases: MutableList<Purchase>?) {
if (billingResult?.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
val purchase = purchases.firstOrNull { it.sku == CStr.PRODUCT_ADS_REMOVE.value}
if (purchase?.purchaseState == Purchase.PurchaseState.PURCHASED) {
if (!purchase.isAcknowledged) {
val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build()
billingClient.acknowledgePurchase(acknowledgePurchaseParams, this@GoPremiumActivity)
}
}
} else if (billingResult?.responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
Global.prefs.adsRemovalPurchased = true
finish()
} else {
Global.prefs.adsRemovalPurchased = false
showSuccessOrError(success = true)
}
}
override fun onAcknowledgePurchaseResponse(billingResult: BillingResult?) {
if (billingResult?.responseCode == BillingClient.BillingResponseCode.OK) {
showThankYouDialog(this@GoPremiumActivity)
Global.prefs.adsRemovalPurchased = true
}
}
private fun showSuccessOrError(success: Boolean) {
purchaseProgressBar.visibility = View.GONE
if (success) {
purchaseButton.visibility = View.VISIBLE
} else {
purchaseUnavailable.visibility = View.VISIBLE
}
}
private fun showGeneralError() {
purchaseProgressBar.visibility = View.GONE
purchaseUnavailable.visibility = View.VISIBLE
}
companion object {
fun newIntent(context: Context): Intent {
return Intent(context, GoPremiumActivity::class.java)
}
}
override fun onSupportNavigateUp(): Boolean {
finish()
return true
}
public override fun onDestroy() {
super.onDestroy()
}
override fun onPause() {
super.onPause()
if (isFinishing) {
finish()
}
}
private fun showThankYouDialog(context: Context) {
//Show dialog
}
}
如果您在 Java 中特别需要它,我可以删除它。
我是 Android Studio 的初学者,我正在实施计费库 2.1.0。在阅读 android studio 文档和许多关于计费库的教程一周后,我做了这个 java class,但我觉得还不够好,至少它做了它必须做的做。如果您发现任何改进方法,请发表评论。感谢:
1.- Class Pago.java:
package com.example.billing;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.Nullable;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.ConsumeParams;
import com.android.billingclient.api.ConsumeResponseListener;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;
import com.example.R;
import static com.android.billingclient.api.BillingClient.BillingResponseCode.SERVICE_TIMEOUT;
import static com.android.billingclient.api.BillingClient.BillingResponseCode.OK;
import static com.android.billingclient.api.BillingClient.BillingResponseCode.USER_CANCELED;
import static com.android.billingclient.api.BillingClient.BillingResponseCode.BILLING_UNAVAILABLE;
import static com.android.billingclient.api.BillingClient.BillingResponseCode.ITEM_UNAVAILABLE;
import static com.android.billingclient.api.BillingClient.BillingResponseCode.ERROR;
import static com.android.billingclient.api.BillingClient.SkuType.INAPP;
import java.util.ArrayList;
import java.util.List;
public class Pagos implements PurchasesUpdatedListener, BillingClientStateListener, SkuDetailsResponseListener, ConsumeResponseListener {
private BillingClient billingClient;
private Context contextPago;
private String skuId;
private List<SkuDetails> misProductos;
// Constructor de la clase Pagos
public Pagos(Context context) {
contextPago = context;
}
// Asigna el sku del producto que se quiere comprar
public void comprar(String skuId) {
this.skuId = skuId;
configurarBillingClient();
}
// Configura el Billing Client para iniciar la conexión con Google Play Console
private void configurarBillingClient() {
// 1. Configura el Billing Client
billingClient = BillingClient.newBuilder(contextPago)
.enablePendingPurchases()
.setListener(this)
.build();
// 2. Inicia la conexión y asigna los Listener
billingClient.startConnection(this);
}
@Override
// Evento salta al llamar billingClient.startConnection()
public void onBillingSetupFinished(BillingResult billingResult) {
// Busca compras en el Servidor de Google y las marca como consumidas
consumeCompras();
// Verifica que la versión de Play Store sea compatible con INAPP
if (!billingClient.isReady()) {
String mensaje = contextPago.getString(R.string.PAGOS_MENSAJE_VERSIÓN_NO_COMPATIBLE);
Toast.makeText(contextPago, mensaje, Toast.LENGTH_LONG).show();
return;
}
// Verifica que la versión de Play Store sea compatible con Suscripciones
// if (billingClient.isFeatureSupported(SUBSCRIPTIONS).getResponseCode() != OK) {
// String mensaje = contextPago.getString(R.string.PAGOS_MENSAJE_VERSIÓN_NO_COMPATIBLE);
// Toast.makeText(contextPago, mensaje, Toast.LENGTH_LONG).show();
// return; //GooglePlayNoSoportaComprasDeSuscripciones
// }
// Verifica que la Configuración se haya hecho bien, sino muestra mensaje de error
if (verificaResponseCode(billingResult.getResponseCode()) == OK) {
consultaProductos();
}
}
// Asigna los elemento que se consultarán a Google y los envía con querySkuDetailsAsync
private void consultaProductos() {
// Inicializa constantes
String ITEM_SKU_1 = "android.test.item_unavailable";
String ITEM_SKU_2 = "android.test.canceled";
String ITEM_SKU_3 = "android.test.purchased";
String ITEM_SKU_4 = "donar";
String ITEM_SKU_5 = "prueba.1";
// Agrega los productos que se consultarán a Google
List<String> skuList = new ArrayList<>();
skuList.add(ITEM_SKU_1);
skuList.add(ITEM_SKU_2);
skuList.add(ITEM_SKU_3);
skuList.add(ITEM_SKU_4);
skuList.add(ITEM_SKU_5);
// TODO Cambiar el ingreso manual de items por una consulta a servidor propio de backend seguro.
SkuDetailsParams.Builder skuDetailsParams = SkuDetailsParams
.newBuilder()
.setSkusList(skuList)
.setType(INAPP);
// Envía consulta a Google y devuelve el listado de productos mediante onSkuDetailsResponse
billingClient.querySkuDetailsAsync(skuDetailsParams.build(), this);
}
@Override
// Evento salta cuando Google envía los detalles de los Productos en Venta
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
if (verificaResponseCode(billingResult.getResponseCode()) == OK) {
if (skuDetailsList != null) {
misProductos = skuDetailsList;
muestraDialogoCompra();
} else {
String mensaje = contextPago.getString(R.string.PAGOS_MENSAJE_NO_SKUDETAILSLIST);
Toast.makeText(contextPago, mensaje, Toast.LENGTH_LONG).show();
}
}
}
// Lanza el dialogo de compra de Google
private void muestraDialogoCompra() {
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(getSkuIdDetails())
.build();
billingClient.launchBillingFlow((Activity) contextPago, flowParams);
}
// Obtiene el Producto que se comprará según el Sku ingresado mediante comprar(sku);
private SkuDetails getSkuIdDetails() {
if (misProductos == null) return null;
for (SkuDetails skuProducto : misProductos) {
if (skuId.equals(skuProducto.getSku())) return skuProducto;
}
return null;
}
@Override
// Evento salta cuando se finaliza el Proceso de compra
public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> list) {
if (verificaResponseCode(billingResult.getResponseCode()) == OK) {
// Validar compra con consulta a Google para evitar ingeniería inversa de hackers
if (validaCompra()) {
// Compra confirmada
Log.i("Pagos", "Compra encontrada en servidor");
} else {
// Compra no encontrada: Mensaje de error - Revocar privilegios
Log.i("Pagos", "Compra no encontrada posible hacker");
}
consumeCompras();
}
}
// Valida la compra y Devuelve True si encuentra la compra del usuario en el Servidor de Google
private boolean validaCompra() {
List<Purchase> purchasesList = billingClient.queryPurchases(INAPP).getPurchasesList();
if (purchasesList != null && !purchasesList.isEmpty()) {
for (Purchase purchase : purchasesList) {
if (purchase.getSku().equals(skuId)) {
return true;
}
}
}
return false;
}
// Busca compras en el Servidor de Google y las marca como consumidas
private void consumeCompras() {
Purchase.PurchasesResult queryPurchases = billingClient.queryPurchases(INAPP);
if (queryPurchases.getResponseCode() == OK) {
List<Purchase> purchasesList = queryPurchases.getPurchasesList();
if (purchasesList != null && !purchasesList.isEmpty()) {
for (Purchase purchase : purchasesList) {
ConsumeParams params = ConsumeParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
billingClient.consumeAsync(params, this);
}
}
}
}
@Override
// Evento salta cuando se ha consumido un producto, Si responseCode = 0, ya se puede volver a comprar
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
if (billingResult.getResponseCode() == OK) {
Log.i("Pagos", "Token de Compra: " + purchaseToken + " consumida");
} else {
Log.i("Pagos", "Error al consumir compra, responseCode: " + billingResult.getResponseCode());
}
}
@Override
// Evento salta cuando se pierde la conexión durante una compra
public void onBillingServiceDisconnected() {
billingClient.startConnection(this);
}
// Verifica que el estado del responseCode sea OK, si no muestra mensaje de Error
private int verificaResponseCode(int responseCode) {
if (responseCode == OK) return OK;
if (responseCode == USER_CANCELED) return USER_CANCELED;
String mensaje = "";
switch (responseCode) {
case SERVICE_TIMEOUT:
mensaje = contextPago.getString(R.string.PAGOS_MENSAJE_SERVICE_TIMEOUT);
break;
case BILLING_UNAVAILABLE:
mensaje = contextPago.getString(R.string.PAGOS_MENSAJE_BILLING_UNAVAILABLE);
break;
case ITEM_UNAVAILABLE:
mensaje = contextPago.getString(R.string.PAGOS_MENSAJE_ITEM_UNAVAILABLE);
break;
case ERROR:
mensaje = contextPago.getString(R.string.PAGOS_MENSAJE_ERROR);
break;
default:
mensaje = contextPago.getString(R.string.PAGOS_MENSAJE_ERROR) + " código: " + responseCode;
break;
}
Toast.makeText(contextPago, mensaje, Toast.LENGTH_LONG).show();
return responseCode;
}
}
3.- 清单
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="com.android.vending.BILLING" />
4.- build.gradle
// Google Play Billing Library
implementation 'com.android.billingclient:billing:2.1.0'
5.- 用法,将此代码放在要显示计费组件的任何位置:
private final String SKU_UNAVAILABLE = "android.test.item_unavailable";
private final String SKU_CANCELED = "android.test.canceled";
private final String SKU_PURCHASED = "android.test.purchased";
private final String SKU_DONAR = "donar";
private void donar() {
Pagos pagos = new Pagos(this);
pagos.comprar(SKU_DONAR);
cargandoDialogoCompra(true);
}
您可以将 SKU_DONAR 更改为 SKU_UNAVAILABLE、SKU_CANCELED、SKU_PURCHASED,因为这些是用于测试目的的项目,正如我所读,没有必要将它们添加到玩控制台
6.- Google 玩游戏机
Presencia en Google Play Store -> Productos integrados en la aplicación -> Productos administrados:
Donación (donar) PEN 9.99
就这些了,请改进我的代码,谢谢大家。
感谢@Webfreak,你对 Kotlin 的回答指引我走向正确的方向。
以下是我为 Java 实现它的方式:
首先将'billingclient'库添加到gradle:
implementation 'com.android.billingclient:billing:X.X.X'
并在清单文件中添加所需的权限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="com.android.vending.BILLING" />
Activity 必须实现以下接口:
public class MainActivity extends AppCompatActivity implements
...
PurchasesUpdatedListener,
AcknowledgePurchaseResponseListener {
然后我在 onCreate 方法中初始化计费客户端:
/** IN-APPS PURCHASE */
private BillingClient mBillingClient;
private long mLastPurchaseClickTime = 0;
private List<String> mSkuList = new ArrayList<>();
private List<SkuDetails> mSkuDetailsList = new ArrayList<>();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// AppPrefs is just a standalone class I used to get or set shared preferences easily
mPrefs = AppPrefs.getInstance(this);
// Rest of your code ...
/** IN-APP PURCHASES */
// Initialize the list of all the in-app product IDs I use for this app
mSkuList.add(Parameters.UNIT_P1);// NoAdsPurchased
mSkuList.add(Parameters.UNIT_P2);// CustomizationPurchased
mSkuList.add(Parameters.UNIT_P3);// ChartsPurchased
// Initialize the billing client
setupBillingClient();
// Apply the upgrades on my app according to the user's purchases
applyUpgrades();
}
设置结算客户端的方法在这里,以及我用来从应用程序检索可用的应用程序内产品的方法:
private void setupBillingClient() {
mBillingClient = BillingClient
.newBuilder(MainActivity.this)
.enablePendingPurchases() // Useful for physical stores
.setListener(MainActivity.this)
.build();
mBillingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// Load the available products related to the app from Google Play
getAvailableProducts();
Purchase.PurchasesResult purchasesResult = mBillingClient.queryPurchases(BillingClient.SkuType.INAPP);// Or SkuType.SUBS if subscriptions
// Init all the purchases to false in the shared preferences (security prevention)
mPrefs.setNoAdsPurchased(false);
mPrefs.setCustomizationPurchased(false);
mPrefs.setChartsPurchased(false);
// Retrieve and loop all the purchases done by the user
// Update all the boolean related to the purchases done in the shared preferences
if (purchasesResult.getPurchasesList() != null) {
for (Purchase purchase : purchasesResult.getPurchasesList()) {
if (purchase.isAcknowledged()) {
Log.e(TAG, purchase.getSku());
switch (purchase.getSku()) {
case Parameters.UNIT_P1:
mPrefs.setNoAdsPurchased(true);
break;
case Parameters.UNIT_P2:
mPrefs.setCustomizationPurchased(true);
break;
case Parameters.UNIT_P3:
mPrefs.setChartsPurchased(true);
break;
}
}
}
}
}
}
@Override
public void onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
// TODO Note: It's strongly recommended that you implement your own connection retry policy and override the onBillingServiceDisconnected() method. Make sure you maintain the BillingClient connection when executing any methods.
Log.e(TAG, "onBillingServiceDisconnected");
}
});
}
private void getAvailableProducts() {
if (mBillingClient.isReady()) {
SkuDetailsParams params = SkuDetailsParams
.newBuilder()
.setSkusList(mSkuList)
.setType(BillingClient.SkuType.INAPP)
.build();
mBillingClient.querySkuDetailsAsync(params, new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
mSkuDetailsList = skuDetailsList;
}
}
});
}
}
当用户完成购买时(我允许在我的应用程序中的多个片段上购买),我在主 Activity 上调用此函数(使用接口):
@Override
public void purchase(String sku) {
// Mis-clicking prevention, using threshold of 3 seconds
if (SystemClock.elapsedRealtime() - mLastPurchaseClickTime < 3000){
Log.d(TAG, "Purchase click cancelled");
return;
}
mLastPurchaseClickTime = SystemClock.elapsedRealtime();
// Retrieve the SKU details
for (SkuDetails skuDetails : mSkuDetailsList) {
// Find the right SKU
if (sku.equals(skuDetails.getSku())) {
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.build();
mBillingClient.launchBillingFlow(MainActivity.this, flowParams);
break;
}
}
}
这里我实现继承的方法:
@Override
public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> purchases) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null) {
for (Purchase purchase : purchases) {
handlePurchase(purchase);
}
} else {
displayError(R.string.inapp_purchase_problem, billingResult.getResponseCode());
}
}
private void handlePurchase(Purchase purchase) {
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
// Grant entitlement to the user.
applyPurchase(purchase);
// Acknowledge the purchase if it hasn't already been acknowledged.
if (!purchase.isAcknowledged()) {
AcknowledgePurchaseParams acknowledgePurchaseParams =
AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
mBillingClient.acknowledgePurchase(acknowledgePurchaseParams, MainActivity.this);
}
}
}
@Override
public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
displayError(R.string.inapp_purchase_success, billingResult.getResponseCode());
}
}
我添加的用于在我的应用程序上确认购买的方法:
private void applyPurchase(Purchase purchase) {
switch (purchase.getSku()) {
case Parameters.UNIT_P1:
mPrefs.setNoAdsPurchased(true);
break;
case Parameters.UNIT_P2:
mPrefs.setCustomizationPurchased(true);
break;
case Parameters.UNIT_P3:
mPrefs.setChartsPurchased(true);
break;
}
// I remove the ads right away if purchases
if(mPrefs.getNoAdsPurchased()) {
destroyAds();
}
}
最后一种方法用于在应用程序上应用所有 upgrades/purchases(以移除广告为例):
private void applyUpgrades() {
// No ads
if (mPrefs.getNoAdsPurchased()) {
destroyAds();
} else {
loadAds();
}
if (mPrefs.getCustomizationPurchased()) {
// Allow customization
// ...
}
if (mPrefs.getChartsPurchased()) {
// Allow charts visualization
// ...
}
}
我想这个解决方案还不完美,但它正在工作,如果我发现改进,我会修改代码。
这是 Google Java 中的 Play Billing 版本 2 的示例应用程序: