Miután már a sokadik Google Billingclient verzióváltás van (évente ugrik a kötelező verzió
) , megint összedobtamösszekókányoltam egy tesztprogit, amiben van egy működő kliens. És ha már megtettem, miért ne tegyem közkinccsé
? Mármint sokat nem tudok a működéséről, de legalább megy; inicializálni kell, van az a pár paraméter, amit össze kell rakni a számlázókliens hívása előtt, és ha megfelelő termékazonosítót kap, akkor a megfelelő hívásokkal lemegy a vásárlás. Sokat nem kell variálni vele. Meg persze kell lennie a Play-en egy regisztrált alkalmazásnak a megfelelő névvel, és ahhoz létrehozva termékeknek, amiknek az azonosítóit kell paraméterlistaként átadni.
Nyilván nekem ez annyiban érdekes, hogy egy $1-1,5 értékű, adománynak felvett termék van a programjaimhoz, mert sajnos a Google nem tűri, hogy Play-ben regisztrált alkamazásban másképpen lehessen a fejlesztőnek adakozni (pl. mezei Paypal link), így kénytelen vagyok karbantartani ezt is
. Amúgy nyilván lehet "előfizetés" meg consumable típusú dolgokat is felvenni, pl. játékokban van értelme. (Update : később láttam, hogy a Google kiadott teszteszközt is a Billingclienthez, a Play Billing Lab alkalmazással elég sokféle esetet szimulálhatunk a sikeres/sikertelen vásárlások között, konkrétan a kliensnek érkező lehetséges válaszokat lehet megadni .)
Szóval remélem, másnak is hasznos lesz - a progi alant, meg pár képernyőkép a működéséről.
Nyilván az app neve azért billing5test, mert annak idején az 5-öshöz ezen a néven volt a teszt app, és a megvehető adomány is ahhoz van létrehozva; csak emiatt elég macera lett volna még egy alkalmazást akár csak tesztfázisig felregisztrálni a Play-re. A lényeges pont a GetSingleInAppDetail(), azt lehet rátenni valamilyen vezérlőelemre, és ha meghívjuk, akkor indul a vásárlás.)
MainActivity.javapackage com.test.billing5test;
Hirdetés
import static android.content.ContentValues.TAG;
import android.os.Bundle;import android.util.Log;import android.view.View;import android.widget.Button;import android.widget.TextView;import android.widget.Toast;
import androidx.annotation.NonNull;import androidx.appcompat.app.AppCompatActivity;
import com.android.billingclient.api.AcknowledgePurchaseParams;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.PendingPurchasesParams;import com.android.billingclient.api.ProductDetails;import com.android.billingclient.api.ProductDetailsResponseListener;import com.android.billingclient.api.Purchase;import com.android.billingclient.api.QueryProductDetailsParams;import com.android.billingclient.api.QueryProductDetailsResult;
import java.util.ArrayList;import java.util.List;
public class MainActivity extends AppCompatActivity {public TextView textview;
//Definiáljuk a BillingClient-etprivate BillingClient billingClient;
//Létre kell hozni a "terméket" is, ugyanazzal az ID-el, mint a Play-enprivate final String DONATION = "1d";
//Terméklista tömbprivate ArrayList<String> purchaseItemIDs = new ArrayList<String>(2) {{add(DONATION);}};
@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);
//UI elemekButton button = findViewById(R.id.button);textview=findViewById(R.id.textView);
//A gomb eseménykezelőjebutton.setOnClickListener( new View.OnClickListener() {
@Overridepublic void onClick(View v) {GetSingleInAppDetail();
}
});
//Billingclient inicializálásbillingClient = BillingClient.newBuilder(this)
.enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build()).setListener((billingResult, list) -> {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && list != null) {for (Purchase purchase : list) {Toast.makeText(getApplicationContext(),"Billingclient response OK",Toast.LENGTH_SHORT).show();Log.d(TAG, "Response is OK" + list.toString());handlePurchase(purchase);}} else {Toast.makeText(getApplicationContext(),"Purchase failed",Toast.LENGTH_SHORT).show();Log.d(TAG, "Purchase failed");}}).build();
establishConnection();}
void establishConnection() {billingClient.startConnection(new BillingClientStateListener() {@Overridepublic void onBillingSetupFinished(@NonNull BillingResult billingResult) {if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
//Ha eddig eljutott, akkor már van kapcsolat a Google BillingService-el
//Elvileg ezekkel lehet vásárlást kezdeményezni, vagy az elérhető termékeket lekérdezni. Sosem próbáltam :D// GetSingleInAppDetail();//GetListsInAppDetail();
Log.d(TAG, "Connection Established");} else {Log.d("TAG", "Billingclient connection FAILED");Toast.makeText(getApplicationContext(),"Error during establishing Google Billing connection",Toast.LENGTH_SHORT).show();}}
//Ha nincs kapcsolat, akkor próbáljon meg kapcsolódni :D@Overridepublic void onBillingServiceDisconnected() {Log.d(TAG, "Connection NOT Established");establishConnection();}});}
void GetSingleInAppDetail() {Log.d(TAG, "GetSingleInappDetail");ArrayList<QueryProductDetailsParams.Product> productList = new ArrayList<>();
//Felépíti a termékek listájátproductList.add(QueryProductDetailsParams.Product.newBuilder().setProductId(DONATION).setProductType(BillingClient.ProductType.INAPP).build());
QueryProductDetailsParams params = QueryProductDetailsParams.newBuilder().setProductList(List.of(QueryProductDetailsParams.Product.newBuilder().setProductId(DONATION).setProductType(BillingClient.ProductType.INAPP).build())).build();
billingClient.queryProductDetailsAsync(params, new ProductDetailsResponseListener() {@Override
public void onProductDetailsResponse(BillingResult billingResult,QueryProductDetailsResult productDetailsResult) {Log.d(TAG, "Purchaseflow");if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {List<ProductDetails> productDetailsList = productDetailsResult.getProductDetailsList();if (productDetailsList != null && !productDetailsList.isEmpty()) {ProductDetails productDetails = productDetailsList.get(0);
//Ez indítja a vásárlástLaunchPurchaseFlow(productDetails);
} else {Log.d(TAG,"ProductDetailsList is null");Toast.makeText(getApplicationContext(),"ProductDetailsList is null",Toast.LENGTH_SHORT).show();}}}
});}
void LaunchPurchaseFlow(ProductDetails productDetails) {Log.d(TAG, "PurchaseFlow started");//Valamiért ez a toast megakasztja//Toast.makeText(getApplicationContext(),"PurchaseFlow started",Toast.LENGTH_SHORT).show();ArrayList<BillingFlowParams.ProductDetailsParams> productList = new ArrayList<>();
//Összerakja a paramétereket a konkrét vásárláshozproductList.add(BillingFlowParams.ProductDetailsParams.newBuilder().setProductDetails(productDetails).build());
BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder().setProductDetailsParamsList(productList).build();Log.d(TAG, "BillingFlow started");Log.d(TAG, String.valueOf(productList));//Meg ez is//Toast.makeText(getApplicationContext(),"BillingFlow started",Toast.LENGTH_SHORT).show();
//És itt indítja a számlázást, amit már a Billing csinálbillingClient.launchBillingFlow(this, billingFlowParams);}
//Elvileg ez kezeli le a vásárlás végeredményétvoid handlePurchase(Purchase purchases) {
if (!purchases.isAcknowledged()) {billingClient.acknowledgePurchase(AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchases.getPurchaseToken()).build(), billingResult -> {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {for (String pur : purchases.getProducts()) {if (pur.equalsIgnoreCase(DONATION)) {Log.d("TAG", "Purchase is successful");Toast.makeText(getApplicationContext(),"Thanks for your donation :) ",Toast.LENGTH_SHORT).show();textview.setText("Yay! Purchased");
//Ha valami elhasználható termékről van szó, akkor ezzel lehet majd felhasználni - sosem próbáltam :D//ConsumePurchase(purchases);}}}else {Log.d("TAG", "Purchase FAILED");Toast.makeText(getApplicationContext(),"Error during Google Billing call",Toast.LENGTH_SHORT).show();}});}}
Activity_main.xml
<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity">
<TextViewandroid:id="@+id/textView"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello World!"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" />
<Buttonandroid:id="@+id/button"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginBottom="@android:dimen/app_icon_size"android:text="Consume"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Build.gradle (app level)plugins {id("com.android.application")}
android {namespace = "com.test.billing5test"compileSdk = 34
defaultConfig {applicationId = "com.test.billing5test"minSdk = 29targetSdk = 34versionCode = 2versionName = "2.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"}
buildTypes {release {isMinifyEnabled = falseproguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")}}compileOptions {sourceCompatibility = JavaVersion.VERSION_1_8targetCompatibility = JavaVersion.VERSION_1_8}
buildFeatures{dataBinding = trueviewBinding = true}}
dependencies {
implementation("androidx.appcompat:appcompat:1.7.1")implementation("com.android.billingclient:billing:8.1.0")implementation("com.google.android.material:material:1.13.0")implementation("androidx.constraintlayout:constraintlayout:2.2.1")testImplementation("junit:junit:4.13.2")androidTestImplementation("androidx.test.ext:junit:1.3.0")androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0")}
AndroidManifest.xml<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools">
<applicationandroid:allowBackup="true"android:dataExtractionRules="@xml/data_extraction_rules"android:fullBackupContent="@xml/backup_rules"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/Theme.Billing6test"tools:targetApi="34"><activityandroid:name=".MainActivity"android:exported="true"><intent-filter><action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application>
</manifest>
A beregisztrált "termék" (az 1d érdekes, így neveztem el a kb. $1 értékű adományt, ugyanezzel kell hivatkozni rá a programban is) :

És akkor lehet a tesztkártyával vásárolni 



...de csak egyszer - a termék tulajdonságainál állítható, hogy pl. többet is lehessen venni.
A végén jön egy számla is :
