Wie implementiere ich In-App Billing in einer Android-Anwendung?


73

Es scheint ziemlich kompliziert zu sein, In-App Billing in einer Android-App zu implementieren. Wie könnte ich das machen? Die Beispiel-App aus dem SDK verfügt nur über eine Aktivität, wodurch sie für eine Anwendung wie meine mit mehreren Aktivitäten zu stark vereinfacht wird.


14
+1 Das Android-Beispiel ist wirklich kompliziert, hat viele Ebenen und deckt alle Funktionen ab, ob Sie mit etwas Kleinerem und Einfacherem beginnen sollten, aber es gibt überhaupt keine Dokumentation. Ich habe eine abgespeckte Version dieses Beispiels. Bitte erläutern Sie, wo Sie stecken bleiben, da Sie die Aktivität grundsätzlich an einen Abrechnungsdienst binden und Anrufe tätigen sollten. Den Rest erledigt der Rundfunkempfänger.
sfratini

@sfratini Kannst du bitte dein Beispiel posten? Vielen Dank!
SZH

Es ist ein Schmerz, sich an die Arbeit zu machen. Wenn nichts anderes, dauert es Stunden, um zu debuggen und richtig zu machen. Ich habe ein Projekt-Setup, wie eine funktionierende Hallo-Welt. Probieren Sie es aus mcondev.wordpress.com/2011/06/26/…
Siddharth

1
Ich denke, dieses Tutorial ( youtu.be/El7q_1a_WVc ) eignet sich am besten für die In-App-Abrechnung. Es zeigt, wie die In-App-Abrechnungsfunktion innerhalb von 5 Minuten implementiert wird!
Zohab Ali

Dies ist veraltet.
Keith Loughnane

Antworten:


42

Nun, ich werde versuchen zu erklären, was ich erlebt habe. Ich betrachte mich nicht als Experte, aber ich habe mir mehrere Tage den Kopf gebrochen.

Für den Anfang hatte ich eine sehr schlechte Zeit, den Workflow des Beispiels und der Anwendung zu verstehen. Ich dachte, es sollte besser sein, mit einem einfachen Beispiel zu beginnen, aber es ist sehr schwierig, den Code in kleine Teile zu trennen und nicht zu wissen, ob Sie etwas kaputt machen. Ich werde Ihnen sagen, was ich habe und was ich gegenüber dem Beispiel geändert habe, damit es funktioniert.

Ich habe eine einzige Aktivität, aus der alle meine Einkäufe stammen. Es heißt Pro.

Zuerst sollten Sie die Variable base64EncodedPublicKey in Ihrer Sicherheitsklasse mit Ihrem öffentlichen Marktentwicklerschlüssel aktualisieren, sonst wird eine nette Ausnahme angezeigt.

Nun, ich binde meine Aktivität wie folgt an meinen BillingService:

      public class Pro extends TrackedActivity implements OnItemClickListener {

            private BillingService mBillingService;
            private BillingPurchaseObserver mBillingPurchaseObserver;
            private Handler mHandler;

            @Override
            protected void onCreate(Bundle savedInstanceState) {    
                super.onCreate(savedInstanceState);     
                setContentView(R.layout.pro);


                //Do my stuff

                mBillingService = new BillingService();
                mBillingService.setContext(getApplicationContext());

                mHandler = new Handler();
                mBillingPurchaseObserver = new BillingPurchaseObserver(mHandler);

            }

        }



    @Override
    protected void onStart() {
       //Register the observer to the service
        super.onStart();
        ResponseHandler.register(mBillingPurchaseObserver);   
    }


    @Override
    protected void onStop() {
        //Unregister the observer since you dont need anymore
        super.onStop();
        ResponseHandler.unregister(mBillingPurchaseObserver);
    }

    @Override
    protected void onDestroy() {
       //Unbind the service
        super.onDestroy();
        mBillingService.unbind();
    }

Auf diese Weise sprechen alle Käufe mit diesem Service, der dann die JSON-Anforderungen an den Markt sendet. Sie könnten denken, dass die Einkäufe im selben Moment getätigt werden, aber nein. Sie senden die Anfrage und der Kauf kann Minuten oder Stunden später erfolgen. Ich denke, dies ist hauptsächlich auf die Serverüberlastung und die Genehmigung der Kreditkarten zurückzuführen.

Dann habe ich eine ListView mit meinen Artikeln und öffne für jeden einen AlertDialog, in dem ich sie zum Kauf des Artikels einlade. Wenn sie auf einen Gegenstand klicken, mache ich das:

  private class BuyButton implements DialogInterface.OnClickListener {

       private BillingItem item = null;
       private String developerPayload;

       public BuyButton(BillingItem item, String developerPayload) {
        this.item = item;
        this.developerPayload = developerPayload;
        }

            @Override
            public void onClick(DialogInterface dialog, int which) {

                if (GeneralHelper.isOnline(getApplicationContext())){
                    //I track the buy here with GA SDK. 

        mBillingService.requestPurchase(this.item.getSku(), this.developerPayload);             
                } else {                
                    Toast.makeText(getApplicationContext(), R.string.msg_not_online, Toast.LENGTH_SHORT).show();
                }

            }

        }

In Ordnung, Sie sollten sehen, dass sich der Markt öffnet und der Benutzer den Kauf entweder beendet oder abbricht.

Was dann wichtig ist, ist mein PurChaseObserver, der alle Ereignisse verarbeitet, die der Markt sendet. Dies ist eine abgespeckte Version davon, aber Sie sollten den Punkt verstehen (siehe meine Kommentare durch den Code):

private class BillingPurchaseObserver extends PurchaseObserver {
        public BillingPurchaseObserver(Handler handler) {
            super(Pro.this, handler);
        }

        @Override
        public void onBillingSupported(boolean supported) {

            if (supported) {
                //Enable buy functions. Not required, but you can do stuff here. The market first checks if billing is supported. Maybe your country is not supported, for example. 
            } else {
                Toast.makeText(getApplicationContext(), R.string.billing_not_supported, Toast.LENGTH_LONG).show();
            }
        }

        @Override
        public void onPurchaseStateChange(PurchaseState purchaseState, String itemId,
                int quantity, long purchaseTime, String developerPayload) {

//This is the method that is called when the buy is completed or refunded I believe. 
// Here you can do something with the developerPayload. Its basically a Tag you can use to follow your transactions. i dont use it. 

        BillingItem item = BillingItem.getBySku(getApplicationContext(), itemId);

        if (purchaseState == PurchaseState.PURCHASED) {
            if (item != null){
//This is my own implementation that sets the item purchased in my database. BillingHelper is a class with methods I use to check if the user bought an option and update the UI. You should also check for refunded. You can see the Consts class to find what you need to check for. 

                    boolean resu = item.makePurchased(getApplicationContext());
                    if (resu){                      
                        Toast.makeText(getApplicationContext(), R.string.billing_item_purchased, Toast.LENGTH_LONG).show();
                    }
                }
            }
        }

        private void trackPurchase(BillingItem item, long purchaseTime) {           
            //My code to track the purchase in GA
        }

        @Override
        public void onRequestPurchaseResponse(RequestPurchase request,
                ResponseCode responseCode) {

               //This is the callback that happens when you sent the request. It doesnt mean you bought something. Just that the Market received it. 

            if (responseCode == ResponseCode.RESULT_OK) {               

                Toast.makeText(getApplicationContext(), R.string.billing_item_request_sent, Toast.LENGTH_SHORT).show();

            } else if (responseCode == ResponseCode.RESULT_USER_CANCELED) {
                //The user canceled the item. 
            } else {
            //If it got here, the Market had an unexpected problem. 
            }
        }

        @Override
        public void onRestoreTransactionsResponse(RestoreTransactions request,
                ResponseCode responseCode) {
            if (responseCode == ResponseCode.RESULT_OK) {
//Restore transactions should only be run once in the lifecycle of your application unless you reinstalled the app or wipe the data. 

                SharedPreferences.Editor edit = PreferencesHelper.getInstance().getDefaultSettings(getApplicationContext()).edit();
                edit.putBoolean(Consts.DB_INITIALIZED, true);
                edit.commit();

            } else {
    //Something went wrong
            }
        }
    }

Und ich glaube, Sie sollten nichts anderes bearbeiten müssen. Der Rest des Codes "funktioniert". Sie können versuchen, die Beispiel-SKU zunächst in Ihren eigenen Artikeln "android.test.purchased" zu verwenden. Bisher habe ich dies getestet und es funktioniert, aber ich muss noch alles wie den erstatteten Zustand abdecken. In diesem Fall lasse ich den Benutzer die Funktionen behalten, möchte aber sicherstellen, dass sie perfekt funktionieren, bevor ich sie modifiziere.

Ich hoffe es hilft dir und anderen.


1
Ein großes Lob für die vereinfachte Stichprobe und für die genaue Beobachtung, was ich ebenfalls beobachtet habe. Aber ... gehen Sie kein Risiko ein, indem Sie die "offizielle" Probe nicht genau befolgen? Schließlich gibt es diese Fehlerprüfungen und unzähligen "möglichen Szenarien" aus einem bestimmten Grund. Nein?
Bill The Ape

@ BillTheApe Nun, ich habe es einfach gemacht. Wenn Sie versuchen, das Beispiel zu verstehen, haben all diese zusätzlichen Zeilen, nur um etwas zu protokollieren, den Code härter und länger erscheinen lassen. Das Hinzufügen einer Protokollzeile ist schließlich einfach genug.
Sfratini

@ Fratini danke, es ist schön, aber als ich App-Demo als Ihr Schritt gemacht habe, habe ich Fehler bekommen. Ich importiere nichts. Kannst du mir sagen, was mit mir los ist?
Kyogs

4
Es gibt keinen BillingService oder BillingPurchaseObserver. Warum gibt es zu diesem Thema kein gutes Tutorial!
Bisclavret

Wahrscheinlich, weil ich dies in einer der ersten Versionen verwendet habe. Vielleicht haben sie die API geändert. Ich musste es leider nicht noch einmal benutzen.
Sfratini


7

Es gibt ein vollständiges Beispiel für Android In-App Billing v3. Schritt für Schritt finden Sie hier einen Screenshot. Bitte überprüfen Sie das Tutorial: Android In-App Billing v3 mit ServiceConnection Class

Hoffe es wird helfen.

Weitere Informationen finden Sie in diesem Lernprogramm: Implementieren der In-App-Abrechnung in der API der Version 3

Schritte zur Integration der In-App-Abrechnungsbibliothek in unser Projekt

Aktualisieren Sie Ihre AndroidManifest.xml-Datei.

Erstellen Sie eine ServiceConnection und binden Sie sie an IInAppBillingService.

Senden Sie In-App-Abrechnungsanforderungen aus Ihrer Anwendung an IInAppBillingService.

In-App-Abrechnungsantworten von Google Play verarbeiten.

Aktualisieren Sie AndroidManifest.xml

<uses-permission android:name="com.android.vending.BILLING" />

Fügen Sie die Berechtigungen in der Datei Manifest.xml hinzu

Hinzufügen der AIDL-Datei zu Ihrem Projekt

Erstellen Sie Ihre Anwendung. Sie sollten eine generierte Datei mit dem Namen IInAppBillingService.java im Verzeichnis / gen Ihres Projekts sehen.

Aktualisieren Sie die Abhängigkeiten in der Datei build.gradle

apply plugin: 'com.android.application'
android {
    compileSdkVersion 24
    buildToolsVersion "24.0.0"
    defaultConfig {
        applicationId "com.inducesmile.androidinapppurchase"
        minSdkVersion 14
        targetSdkVersion 24
        versionCode 2
        versionName "1.1"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:24.1.1'
    compile 'com.intuit.sdp:sdp-android:1.0.3'
    compile 'com.android.support:support-annotations:24.1.1'
    compile 'org.jetbrains:annotations-java5:15.0'
}

InAppPurchaseActivity.java und activity_in_app_purchase.xml

Hier bieten wir unseren App-Benutzern die Möglichkeit, In-App-Käufe zu tätigen. In der Layoutdatei geben wir dem Benutzer die Möglichkeit, in verschiedenen Stückelungen einzukaufen.

InAppPurchaseActivity.java

Hinweis: Die Methoden getAllUserPurchase () und itemPurchaseAvailability () sollten in Nicht-UI-Threads aufgerufen werden, um einen Absturz der App zu vermeiden.

public class InAppPurchaseActivity extends AppCompatActivity {
    private static final String TAG = InAppPurchaseActivity.class.getSimpleName();
    private IInAppBillingService mService;
    private CustomSharedPreference customSharedPreference;
    String[] productIds = new String[]{Helper.ITEM_ONE_ID, Helper.ITEM_TWO_ID, Helper.ITEM_THREE_ID};
    private ImageView buyOneButton, buyTwoButton, buyThreeButton;
    private static final char[] symbols = new char[36];
    static {
        for (int idx = 0; idx < 10; ++idx)
            symbols[idx] = (char) ('0' + idx);
        for (int idx = 10; idx < 36; ++idx)
            symbols[idx] = (char) ('a' + idx - 10);
    }
    private String appPackageName;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_in_app_purchase);
        appPackageName = this.getPackageName();
        Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
        serviceIntent.setPackage("com.android.vending");
        bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
        customSharedPreference = new CustomSharedPreference(InAppPurchaseActivity.this);
        buyOneButton = (ImageView)findViewById(R.id.buy_one);
        buyOneButton.setVisibility(View.GONE);
        assert buyOneButton != null;
        buyOneButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(!isBillingSupported()){
                    Helper.displayMessage(InAppPurchaseActivity.this, getString(R.string.in_app_support));
                    return;
                }
                purchaseItem(Helper.ITEM_ONE_ID);
            }
        });
        buyTwoButton = (ImageView)findViewById(R.id.buy_two);
        buyTwoButton.setVisibility(View.GONE);
        assert buyTwoButton != null;
        buyTwoButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(!isBillingSupported()){
                    Helper.displayMessage(InAppPurchaseActivity.this, getString(R.string.in_app_support));
                    return;
                }
                purchaseItem(Helper.ITEM_TWO_ID);
            }
        });
        buyThreeButton = (ImageView)findViewById(R.id.buy_three);
        buyThreeButton.setVisibility(View.GONE);
        assert buyThreeButton != null;
        buyThreeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(!isBillingSupported()){
                    Helper.displayMessage(InAppPurchaseActivity.this, getString(R.string.in_app_support));
                    return;
                }
                purchaseItem(Helper.ITEM_THREE_ID);
            }
        });
    }
    ServiceConnection mServiceConn = new ServiceConnection() {
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mService = null;
        }
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = IInAppBillingService.Stub.asInterface(service);
            AvailablePurchaseAsyncTask mAsyncTask = new AvailablePurchaseAsyncTask(appPackageName);
            mAsyncTask.execute();
        }
    };
    private void purchaseItem(String sku){
        String generatedPayload = getPayLoad();
        customSharedPreference.setDeveloperPayLoad(generatedPayload);
        try {
            Bundle buyIntentBundle = mService.getBuyIntent(3, getPackageName(), sku, "inapp", generatedPayload);
            PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT");
            try {
                startIntentSenderForResult(pendingIntent.getIntentSender(), Helper.RESPONSE_CODE, new Intent(), Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0));
            } catch (IntentSender.SendIntentException e) {
                e.printStackTrace();
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == Helper.RESPONSE_CODE) {
            int responseCode = data.getIntExtra("RESPONSE_CODE", 0);
            String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");
            String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");
            if (resultCode == RESULT_OK) {
                try {
                    JSONObject purchaseJsonObject = new JSONObject(purchaseData);
                    String sku = purchaseJsonObject.getString("productId");
                    String developerPayload = purchaseJsonObject.getString("developerPayload");
                    String purchaseToken = purchaseJsonObject.getString("purchaseToken");
                    //the developerPayload value is better stored in remote database but in this tutorial
                    //we will use a shared preference
                    for(int i = 0; i < productIds.length; i++){
                        if(productIds[i].equals(sku) && developerPayload.equals(customSharedPreference.getDeveloperPayload())){
                            customSharedPreference.setPurchaseToken(purchaseToken);
                            //access to private content
                            Intent contentIntent = new Intent(InAppPurchaseActivity.this, PrivateContentActivity.class);
                            startActivity(contentIntent);
                        }
                    }
                }
                catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    private String getPayLoad(){
        RandomString randomString = new RandomString(36);
        String payload = randomString.nextString();
        return payload;
    }
    public class RandomString {
        private final Random random = new Random();
        private final char[] buf;
        public RandomString(int length) {
            if (length < 1)
                throw new IllegalArgumentException("length < 1: " + length);
            buf = new char[length];
        }
        public String nextString() {
            for (int idx = 0; idx < buf.length; ++idx)
                buf[idx] = symbols[random.nextInt(symbols.length)];
            return new String(buf);
        }
    }
    public final class SessionIdentifierGenerator {
        private SecureRandom random = new SecureRandom();
        public String nextSessionId() {
            return new BigInteger(130, random).toString(32);
        }
    }
    private class AvailablePurchaseAsyncTask extends AsyncTask<Void, Void, Bundle> {
        String packageName;
        public AvailablePurchaseAsyncTask(String packageName){
            this.packageName = packageName;
        }
        @Override
        protected Bundle doInBackground(Void... voids) {
            ArrayList<String> skuList = new ArrayList<String>();
            skuList.add(Helper.ITEM_ONE_ID);
            skuList.add(Helper.ITEM_TWO_ID);
            skuList.add(Helper.ITEM_THREE_ID);
            Bundle query = new Bundle();
            query.putStringArrayList(Helper.ITEM_ID_LIST, skuList);
            Bundle skuDetails = null;
            try {
                skuDetails = mService.getSkuDetails(3, packageName, "inapp", query);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            return skuDetails;
        }
        @Override
        protected void onPostExecute(Bundle skuDetails) {
            List<AvailablePurchase> canPurchase = new ArrayList<AvailablePurchase>();
            int response = skuDetails.getInt("RESPONSE_CODE");
            if (response == 0) {
                ArrayList<String> responseList = skuDetails.getStringArrayList("DETAILS_LIST");
                if(responseList != null){
                    for (String thisResponse : responseList) {
                        JSONObject object = null;
                        try {
                            object = new JSONObject(thisResponse);
                            String sku = object.getString("productId");
                            String price = object.getString("price");
                            canPurchase.add(new AvailablePurchase(sku, price));
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            if(checkIfPurchaseIsAvailable(canPurchase, productIds[0])){
                buyOneButton.setVisibility(View.VISIBLE);
            }else{
                buyOneButton.setVisibility(View.GONE);
            }
            if(checkIfPurchaseIsAvailable(canPurchase, productIds[1])){
                buyTwoButton.setVisibility(View.VISIBLE);
            }else{
                buyTwoButton.setVisibility(View.GONE);
            }
            if(checkIfPurchaseIsAvailable(canPurchase, productIds[2])){
                buyThreeButton.setVisibility(View.VISIBLE);
            }else{
                buyThreeButton.setVisibility(View.GONE);
            }
        }
    }
    @org.jetbrains.annotations.Contract("null, _ -> false")
    private boolean checkIfPurchaseIsAvailable(List<AvailablePurchase> all, String productId){
        if(all == null){ return false;}
        for(int i = 0; i < all.size(); i++){
            if(all.get(i).getSku().equals(productId)){
                return true;
            }
        }
        return false;
    }
    public boolean isBillingSupported(){
        int response = 1;
        try {
            response = mService.isBillingSupported(3, getPackageName(), "inapp");
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        if(response > 0){
            return false;
        }
        return true;
    }
    public void consumePurchaseItem(String purchaseToken){
        try {
            int response = mService.consumePurchase(3, getPackageName(), purchaseToken);
            if(response != 0){
                return;
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
    public Bundle getAllUserPurchase(){
        Bundle ownedItems = null;
        try {
            ownedItems = mService.getPurchases(3, getPackageName(), "inapp", null);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return ownedItems;
    }
    public List<UserPurchaseItems> extractAllUserPurchase(Bundle ownedItems){
        List<UserPurchaseItems> mUserItems = new ArrayList<UserPurchaseItems>();
        int response = ownedItems.getInt("RESPONSE_CODE");
        if (response == 0) {
            ArrayList<String> ownedSkus = ownedItems.getStringArrayList("INAPP_PURCHASE_ITEM_LIST");
            ArrayList<String>  purchaseDataList = ownedItems.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
            ArrayList<String>  signatureList = ownedItems.getStringArrayList("INAPP_DATA_SIGNATURE_LIST");
            String continuationToken = ownedItems.getString("INAPP_CONTINUATION_TOKEN");
            if(purchaseDataList != null){
                for (int i = 0; i < purchaseDataList.size(); ++i) {
                    String purchaseData = purchaseDataList.get(i);
                    assert signatureList != null;
                    String signature = signatureList.get(i);
                    assert ownedSkus != null;
                    String sku = ownedSkus.get(i);
                    UserPurchaseItems allItems = new UserPurchaseItems(sku, purchaseData, signature);
                    mUserItems.add(allItems);
                }
            }
        }
        return mUserItems;
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mService != null) {
            unbindService(mServiceConn);
        }
    }
}

Erstellen Sie ein Hilfspaketverzeichnis

Erstellen Sie einen neuen Paketordner und nennen Sie ihn Helfer. Erstellen Sie im Paket eine neue Java-Datei Helper.java.

Helper.java

public class Helper {
    public static final String ITEM_ID_LIST = "ITEM_ID_LIST";
    public static final String ITEM_ONE_ID = "productone";
    public static final String ITEM_TWO_ID = "producttwo";
    public static final String ITEM_THREE_ID = "productthree";
    public static final int RESPONSE_CODE = 1001;
    public static final String SHARED_PREF = "shared_pref";
    public static final String DEVELOPER_PAYLOAD = "developer_payload";
    public static final String PURCHASE_TOKEN = "purchase_token";
    public static void displayMessage(Context context, String message){
        Toast.makeText(context.getApplicationContext(), message, Toast.LENGTH_LONG).show();
    }
}

Testen des In-App-Abrechnungskaufs

  1. Erstellen Sie ein Google+ Konto (verwenden Sie kein Hauptkonto)
  2. Fügen Sie die Benutzer hinzu, die die App in Ihrer Gruppe oder Community testen sollen.

Fehler, die beim Testen des In-App-Kaufs auftreten können

Der von Ihnen angeforderte Artikel kann nicht gekauft werden

Lösung - Laut AndreiBogdan in Stackoverflow ,

Alle Verdienste gehen an Inducesmile für sein Tutorial

Android Developer Blog empfiehlt außerdem eine Schulungsklasse zum Verkauf von In-App-Produkten. In diesem Tutorial finden Sie eine vollständige Implementierung und Informationen zum Testen der Anwendung: Verkauf von In-App-Produkten


Der erste Link, sollten Sie den größten Teil des Codes zur Antwort hinzufügen, falls der Link tot sein sollte
Zoe

@LunarWatcher Ich habe die Antwort aktualisiert. Alle Codes werden hinzugefügt. Bitte prüfen.
SkyWalker

2
@ SkyWalker Woher bekomme ich die customSharedPreferenceKlasse?
TheQ

5

Wenn Sie eine einfache Bibliothek zum Veröffentlichen in Google Play und im Amazon Appstore verwenden möchten , können Sie sich für RoboBillingLibrary entscheiden . Es abstrahiert die Details von beiden in einer benutzerfreundlichen Bibliothek. Detaillierte Anweisungen finden Sie auf der Github-Seite.


5

Okay, dies ist eines der Dinge, für die online nicht viel Dokumentation verfügbar ist. Deshalb werde ich mein Bestes geben, um alles Schritt für Schritt zu erklären. Entnommen aus meinem Blog-Beitrag, der eine detailliertere Version davon (mit Screenshots) ist, hier auf The Millibit . Ohne weiteres,

Erster Schritt: Berechtigungen Dies ist der einfachste Schritt. Navigieren Sie zu Ihrer Datei manifest.xml und fügen Sie die folgende Zeile unter Ihrem Tag hinzu:

<uses-permission android:name="com.android.vending.BILLING" />

Dadurch erhält Ihre App die Berechtigung, auf die In-App-Abrechnung zuzugreifen. Wenn Sie auf Versionen über API 22 abzielen, müssen Sie sicherstellen, dass diese Berechtigung zur Laufzeit erteilt wird.

Schritt 2: Play Console Jetzt müssen Sie Ihre App auf die Google Play Console hochladen. Wir veröffentlichen unsere App noch nicht für die Öffentlichkeit (keine Sorge). Wir laden sie lediglich in den Bereich BETA RELEASE hoch, in dem wir In-App-Käufe testen können. Der Grund dafür ist, dass Google eine Version Ihrer APK hochladen muss, damit die Abrechnungsprozesse tatsächlich funktionieren.

  1. Gehen Sie zu https://play.google.com/apps/publish/

  2. Erstellen Sie die Anwendung

  3. Befolgen Sie die Schritte, um Ihre App einzurichten

  4. Gehen Sie zu App-Versionen

  5. Navigieren Sie zu Beta

  6. Erstellen Sie eine APK Ihrer App in Android Studio und laden Sie sie in die Beta-Produktion in der Play Console hoch

(Stellen Sie vor der Veröffentlichung sicher, dass Sie die Store-Liste, die Inhaltsbewertung sowie die Preisgestaltung und Verteilung bereits ausgefüllt haben.)

  1. Drücke den magischen Knopf (veröffentlichen!)

Schritt 3: Projekt einrichten Okay, dies ist der Teil, in dem Sie eine Reihe von Dateien kopieren und einfügen müssen.

Nehmen Sie zuerst diese Datei, laden Sie sie herunter und platzieren Sie sie unter. src/mainSie sollte sich in einen Ordner einfügen. Nehmen Sie als Nächstes den gesamten util-Ordner und fügen Sie ihn ein. Erstellen Sie src/java folder.dann Ihr Projekt neu, um Fehler zu beheben. Der Util-Ordner enthält die folgenden Klassen:

  • IabBroadcastReceiver
  • IabException
  • IabHelper
  • IabResult
  • Inventar
  • Kauf
  • Sicherheit
  • SkuDetails

Schritt vier: Produkte erstellen

  1. Verwaltetes Produkt erstellen

  2. Klicken Sie auf Speichern und erstellen Sie eine "Preisvorlage".

Hier wählen Sie den Preis für dieses Produkt aus. Sie können den Preis für verschiedene Länder auswählen oder ihn automatisch anpassen lassen, wenn Sie nur alle Länder unter Ihrem Preis auswählen:

  1. Stellen Sie sicher, dass das In-App-Produkt ein letztes Mal aktiviert und mit der richtigen Anwendung in der Konsole verknüpft ist.

Notieren Sie abschließend die ID Ihres Produkts. Wir werden diese ID in den nächsten Schritten verwenden.

  1. Holen Sie sich Ihren Base64EncodedString

Gehen Sie zu "Services & APIs" und holen Sie sich Ihren Base64EncodedString. Kopieren Sie diese und fügen Sie sie irgendwo in einen Notizblock ein, damit Sie darauf zugreifen können. Teilen Sie dies nicht mit anderen, sie werden in der Lage sein, böswillige Dinge damit zu tun.

Fünfter Schritt: Endlich! Wir können mit dem Codieren beginnen: Wir binden zuerst an die In-App-Abrechnungsbibliothek und fragen ab, was der Benutzer gekauft hat / nicht gekauft hat. Dann kaufen wir das Produkt, das wir zuvor eingerichtet haben.

Importieren Sie zunächst alles, was wir zuvor eingerichtet haben:

import util.*;

Jetzt werden wir ein IabHelper-Objekt namens mHelper verwenden und alles damit machen.

base64EncodedPublicKey = ""; //PUT YOUR BASE64KEY HERE

mHelper = new IabHelper(this, base64EncodedPublicKey);
mHelper.enableDebugLogging(false); //set to false in real app


mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
    public void onIabSetupFinished(IabResult result) {
        if (!result.isSuccess()) {
            // Oh no, there was a problem.

            if (result.getResponse() == 3) {
                new AlertDialog.Builder(MainActivity.this)
                        .setTitle("In app billing")
                        .setMessage("This device is not compatible with In App Billing, so" +
                                " you may not be able to buy the premium version on your phone. ")
                        .setPositiveButton("Okay", null)
                        .show();
            }

            Log.v(TAG, "Problem setting up In-app Billing: " + result);
        } else {
            Log.v(TAG, "YAY, in app billing set up! " + result);
            try {
                mHelper.queryInventoryAsync(mGotInventoryListener); //Getting inventory of purchases and assigning listener
            } catch (IabHelper.IabAsyncInProgressException e) {
                e.printStackTrace();
            }
        }
    }
});

Okay, lassen Sie mich zusammenfassen, was hier los ist. Grundsätzlich rufen wir "startSetup" auf, um unseren "IabHelper" zu initialisieren. Wenn das Setup erfolgreich ist, fragen wir ab, welche Käufe der Benutzer bereits hat, und speichern die Antworten in mGotInventoryListener, die wir als Nächstes codieren:

IabHelper.QueryInventoryFinishedListener mGotInventoryListener
        = new IabHelper.QueryInventoryFinishedListener() {
    public void onQueryInventoryFinished(IabResult result,
                                         Inventory inventory) {

        i = inventory;

        if (result.isFailure()) {
            // handle error here

            Log.v(TAG, "failure in checking if user has purchases");
        } else {
            // does the user have the premium upgrade?
            if (inventory.hasPurchase("premium_version")) {

                premiumEditor.putBoolean("hasPremium", true);
                premiumEditor.commit(); 

                Log.v(TAG, "Has purchase, saving in storage");

            } else {

                premiumEditor.putBoolean("hasPremium", false);
                premiumEditor.commit();

                Log.v(TAG, "Doesn't have purchase, saving in storage");

            }
        }
    }
};

Der obige Code ist ziemlich selbsterklärend. Grundsätzlich wird nur geprüft, welche Einkäufe der Benutzer bereits hat. Jetzt, da wir wissen, ob der Benutzer unser Produkt bereits gekauft hat oder nicht, wissen wir, ob wir ihn bitten sollen, unseren Artikel zu kaufen! Wenn sie unser Produkt noch nie gekauft haben, starten wir eine Kaufanfrage:

public void buyPremium() {
    try {

     mHelper.flagEndAsync();//If any async is going, make sure we have it stop eventually
     mHelper.launchPurchaseFlow(this, "premium_version", 9, mPurchaseFinishedListener, "SECURITYSTRING"); //Making purchase request and attaching listener
    } catch (Exception e) {
        e.printStackTrace();

mHelper.flagEndAsync();//If any async is going, make sure we have it stop eventually




        new AlertDialog.Builder(MainActivity.this)
                .setTitle("Error")
                .setMessage("An error occurred in buying the premium version. Please try again.")
                .setPositiveButton("Okay", null)
                .show();
    }
}


    @Override

protected void onActivityResult(int requestCode, int resultCode, Intent data) {

    Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);

    // Pass on the activity result to the helper for handling

    if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {

    }

    else 
        Log.d(TAG, "onActivityResult handled by IABUtil.");
    }

}

IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener
        = new IabHelper.OnIabPurchaseFinishedListener() {
    public void onIabPurchaseFinished(IabResult result, Purchase purchase) {

        Log.v(TAG, "purchase finished");

        if (purchase != null) {

            if (purchase.getSku().equals("premium_version")) {

                Toast.makeText(MainActivity.this, "Purchase successful!", Toast.LENGTH_SHORT).show();

                premiumEditor.putBoolean("hasPremium", true);
                premiumEditor.commit();
            }
        } else {
            return;
        }
        if (result.isFailure()) {
            return;
        }
    }
};

Hier kaufen wir den Artikel (mit der ID, die wir zuvor in der Spielkonsole generiert haben) wie folgt:

 mHelper.launchPurchaseFlow(this, "premium_version", 9, mPurchaseFinishedListener, "SECURITYSTRING"); //Making purchase request and attaching listener

Beachten Sie, dass wir mPurchaseFinishedListenerdie Parameter übergeben haben. Dies bedeutet, dass das Ergebnis des Kaufs an diesen Hörer zurückgegeben wird. Dann prüfen wir einfach, ob der Kauf null ist, und geben dem Benutzer, falls nicht, die von ihm gekaufte Funktion.

Lass die Zuhörer nicht lecken! Wir müssen sie zerstören, wenn die App zerstört.

@Override
public void onDestroy() {
    super.onDestroy();
    if (mHelper != null)
        try {
            mHelper.dispose();
            mHelper = null;

        } catch (IabHelper.IabAsyncInProgressException e) {
            e.printStackTrace();
        }
}

Wenn Sie Ihren Einkauf konsumieren und ihn wieder zum Kauf anbieten möchten, können Sie dies ganz einfach tun. Ein Beispiel hierfür ist, wenn ein Benutzer Benzin für ein virtuelles Auto gekauft hat und es ausgegangen ist. Sie müssen dasselbe Produkt erneut kaufen, und Sie können es für einen zweiten Kauf verfügbar machen, indem Sie es konsumieren:

public void consume(){

    //MAKING A QUERY TO GET AN ACCURATE INVENTORY
    try {
        mHelper.flagEndAsync(); //If any async is going, make sure we have it stop eventually

        mHelper.queryInventoryAsync(mGotInventoryListener); //Getting inventory of purchases and assigning listener

        if(i.getPurchase("gas")==null){
            Toast.makeText(this, "Already consumed!", Toast.LENGTH_SHORT).show();
        }
    } catch (IabHelper.IabAsyncInProgressException e) {
        e.printStackTrace();

        Toast.makeText(this, "Error, try again", Toast.LENGTH_SHORT).show();
        mHelper.flagEndAsync();//If any async is going, make sure we have it stop eventually
    }

    //ACTUALLY CONSUMING
    try {
        mHelper.flagEndAsync();//If any async is going, make sure we have it stop eventually

        this.mHelper.consumeAsync(this.i.getPurchase("gas"), new IabHelper.OnConsumeFinishedListener() {
            public void onConsumeFinished(Purchase paramAnonymousPurchase, IabResult paramAnonymousIabResult) {
//resell the gas to them
            }
        });

        return;
    } catch (IabHelper.IabAsyncInProgressException localIabAsyncInProgressException) {
        localIabAsyncInProgressException.printStackTrace();
        Toast.makeText(this, "ASYNC IN PROGRESS ALREADY!!!!" +localIabAsyncInProgressException, Toast.LENGTH_LONG).show();
        Log.v("myTag", "ASYNC IN PROGRESS ALREADY!!!");

        mHelper.flagEndAsync();
    }
}

Das ist es! Sie können jetzt anfangen, Geld zu verdienen. So einfach ist das wirklich!

Wenn Sie eine detailliertere Version dieses Tutorials mit Screenshots und Bildern wünschen, besuchen Sie den Originalbeitrag hier . Lassen Sie mich in den Kommentaren wissen, wenn Sie weitere Fragen haben.


Danke für deine tolle Arbeit! Der Code enthält viele kleine Fehler, die einfach zu beheben sind. Aber ich bekomme auch folgendes : error: flagEndAsync() is not public in IabHelper; cannot be accessed from outside package.
Dmitry

Nur öffentlich zu machen, scheint nicht richtig zu sein. Es war nicht für die Verwendung außerhalb des Pakets vorgesehen.
Dmitry

Und das zweite schwerwiegende Problem mit dem Code: Unable to destroy activity java.lang.IllegalArgumentException: Service not registered.Es passiert, wenn Sie lange auf die Systemschaltfläche "Switching Applications" im Emulator tippen.
Dmitry

Das Schlimmste ist, dass die catch-Klausel nicht geholfen hat und die Anwendung abgestürzt ist .
Dmitry

Behoben durch: if (serviceBound) {mContext.unbindService (mServiceConn); } (add serviceBound = true in der Nähe von mContext.bindService ()).
Dmitry


1

Ich habe eine Android In App-Abrechnungsbibliothek entwickelt, die "com.android.billingclient: billing: 2.1.0" verwendet.

Hier sind seine Eigenschaften:

  1. Bibliothek wird für "INAPP" unterstützt
  2. Das Abonnement wird später unterstützt!
  3. Bibliothek verwendet Roomdb für Ihre Produkte. Sie benötigen keine Implementierung, um den Status Ihrer Produkte zu überprüfen
  4. Bibliothek verwendet gemeinsame Abhängigkeit. Ihre App wird kleiner und es wird kein Multidex benötigt
  5. Die Bibliothek überprüft Ihren Produktstatus bei jedem Start der App. Sie können Status erhalten (gekauft oder nicht)!
  6. Jedes vom Kunden gekaufte Produkt muss im SUCCES-Status "anerkannt" sein. Die Bibliothek macht das für Sie!
  7. Bibliotheksunterstützung (sofortiger Kauf, Antwort auf verspäteten Kauf, Antwort auf verspäteten Kauf, Ablehnung des Kaufs durch den Benutzer)

Bibliotheksquelle

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.