I have an Android project where I want to use com.android.billingclient.api version 4.0.0, which would replace an old billing library that google doesn't allow any more (com.anjlab.android.iab.v3). I've implemented the methods for a one-time purchase, but when querying the SKU Details with billingClient.querySkuDetailsAsync using the SKU string for the product, I get an empty result set. I've been assured that the SKU is correct, so I don't know where the error might be.
Also, the old implementation required to provide a license key, which isn't the case with the new library. Do I need to define it somewhere else in the app?
Here's the code where it fails:
List<String> skuList = new ArrayList<>();
skuList.add(SKU_ID);
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(SkuType.INAPP);
final Activity v = this;
billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() {
#Override
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
Has anyone a suggestion what to do?
This is how I query the SKU details within my app.
You can try to use this example and see if this works for you.
billingClient.startConnection(new BillingClientStateListener() {
#Override
public void onBillingSetupFinished(#NonNull BillingResult billingResult) {
Log.d(TAG, "Connection finished");
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// The BillingClient is ready. You can query purchases here.
List<String> skuList = new ArrayList<> ();
skuList.add(ITEM_SKU_ADREMOVAL);
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);
billingClient.querySkuDetailsAsync(params.build(),
(billingResult1, skuDetailsList) -> {
// Process the result.
if (billingResult1.getResponseCode() == BillingClient.BillingResponseCode.OK && skuDetailsList != null) {
for (Object skuDetailsObject : skuDetailsList) {
skuDetails = (SkuDetails) skuDetailsObject;
String sku = skuDetails.getSku();
String price = skuDetails.getPrice();
if (ITEM_SKU_ADREMOVAL.equals(sku)) {
removeadsPrice = price;
}
else {
Log.d(TAG,"Sku is null");
}
}
Log.d(TAG, "i got response");
Log.d(TAG, String.valueOf(billingResult1.getResponseCode()));
Log.d(TAG, billingResult1.getDebugMessage());
}
else if (billingResult1.getResponseCode() == BillingClient.BillingResponseCode.ERROR) {
Toast.makeText(MainActivity.this, "Error in completing the purchase!", Toast.LENGTH_SHORT).show();
}
});
}
else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.SERVICE_TIMEOUT) {
Toast.makeText(MainActivity.this, "Service timeout!", Toast.LENGTH_SHORT).show();
}
else {
Toast.makeText(MainActivity.this, "Failed to connect to the billing client!", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onBillingServiceDisconnected() {
restartConnection();
}
});
Getting this one working properly depends on several different factors
Have you published your app to Play Console or at least to an
internal track or something?
Do you have active products or subscriptions on your Google Play
Console?
Have you configured your licensed testers?
Please see the documentation for more info.
Related
I have set up in-app billing on an Android app (java). When I call launchBillingFlow on the BillingClient:
BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.build();
BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams);
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
//the system displays the Google Play purchase screen
} else {
Log.e(TAG, "Billing failed: + " + billingResult.getDebugMessage());
}
This is what my onPurchasesUpdated (from PurchasesUpdatedListener) looks like:
#Override
public void onPurchasesUpdated(#NonNull BillingResult billingResult, #Nullable List<Purchase> purchases) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
&& purchases != null) {
for (Purchase purchase : purchases) {
for (String sku : purchase.getSkus()) {
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
if (skuConsumables.contains(sku)) {
handlePurchaseConsumable(purchase);
} else if (skuNonconsumables.contains(sku)) {
handlePurchaseNonconsumable(purchase);
}
}
}
}
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
// Handle an error caused by a user cancelling the purchase flow.
billingServiceListener.receivePurchaseError();
} else {
// Handle any other error codes.
billingServiceListener.receivePurchaseError();
}
}
onPurchasesUpdated is called six times, each time with a responseCode of OK. Twice onPurchasesUpdated is called with zero purchases, that's fine. What I am confused about is how to deal with the four times onPurchasesUpdated is called with one purchase. And it seems as though each of these four purchase objects are indistinguishable - the same packageName, acknowledged, orderId, productId, purchaseState, purchaseToken, etc.
To complicate things, for consumable in-app billing, (these are consumable) I am then calling ConsumeResponseListener and onConsumeResponse is also returning four times, each time with responseCode of OK.
private void handlePurchaseConsumable(Purchase purchase) {
ConsumeParams consumeParams =
ConsumeParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
ConsumeResponseListener listener = new ConsumeResponseListener() {
#Override
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// Handle the success of the consume operation.
}
}
};
billingClient.consumeAsync(consumeParams, listener);
}
Is this behaviour seen by others? I am using static responses while in development, could this be the reason? If people are seeing this, how do you deal with this - do you keep track of what purchase you have attempted, and then when the first response is returned do you register that the purchase was successful and ignore subsequent times that onPurchasesUpdated returns a purchase if you weren't expecting a payment? Though I've seen that Android permits cash purchases with enablePendingPurchases, so that can't be a solution...
This can happen for 2 reasons.
if you are using an outdated version of the google-billing API.
if you forgot to destroy the billing client at the finish of the activity.
I've checked Samples for Google Play In-app Billing and how I can see the main idea of usage the billing client is to use it with activity/fragment lifecycles. So your billing client should implement DefaultLifecycleObserver. We need it to have connect/disconnect billing client use UI lifecycle. In other case we could have few instances of billing client in the App and few listeners that will be triggered with success purchase in same time. So code should looks like:
Activity/Fragment:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycle.addObserver(billingLifecycleManager)
}
BillingLifecycleManager:
class BillingLifecycleManager {
override fun onCreate(owner: LifecycleOwner) {
super.onCreate(owner)
billingClient = BillingClient.newBuilder(app)
.setListener(purchasesUpdatedListener)
.enablePendingPurchases()
.build()
if (billingClient.isReady) {
billingClient.startConnection(...)
}
}
override fun onDestroy(owner: LifecycleOwner) {
super.onDestroy(owner)
if (billingClient.isReady) {
billingClient.endConnection()
}
}
}
I'm about to implement google in app payment into my android app and here's the thing:
I have 3 subscriptions levels, let's say noob, normal and pro. My app have a login/signup, besides of google login. I've already implement all the purchase in my app, but now i'm wondering which is the best practice to attach that premium subs payed with google billing, with my users, so i can check each user in the app even if they're sharing a device with the same google account but different subscription level.
*For example: I'm in 'normal' subscription payed with account asd#gmail.com with username MINE21 in my nexus 5
Then my sister buys a 'pro' subscription payed with account she#gmail.com with
username SHE123 in her Galaxy S6, but then she grab my Nexus 5 and
logins with SHE123 but in playstore i'm logged as asd#gmail.com.
At that moment, i want to show all the pro features to her, but i need
to confirm if SHE123 is premium even if that the playstore account is
asd#gmail.com linked with MINE21.*
If i'm not being clear explaining myself please let me know.
Am i right if i get the userid from the user buying the subscription, and saving it in my database with the google account id, token purchase and premium level?
I wanna know how can i do to check the google payment with each user of my database even if they're using another google account in play store.
--My db is a MySQL database handled with PHP--
Regards,
Inrovero
I'm going to put my process payment here:
private void processPayment(final String SKU){
PurchasesUpdatedListener purchasesUpdatedListener = new PurchasesUpdatedListener() {
#Override
public void onPurchasesUpdated(#NonNull BillingResult billingResult, #Nullable List<Purchase> list) {
// To be implemented
if(billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && list != null){
// do Something you want
Log.i(TAG, "Compra exitosa");
// Preparar categoría de premium
premium = "";
if(list.get(0).getSku().equals(SKU_BS)) premium = "BS";
else if(list.get(0).getSku().equals(SKU_BN)) premium = "BN";
else if(list.get(0).getSku().equals(SKU_BP)) premium = "BP";
Toast.makeText(mContext, premium + " adquirido", Toast.LENGTH_LONG).show();
// Sincronizar premium con la DB y suscripción con el usuario
User currentUser = SharedPrefManager.getInstance(mContext).getUser();
int uid = currentUser.getId();
// Datos a sincronizar
AccountIdentifiers accountIdentifiers = list.get(0).getAccountIdentifiers();
String accID = accountIdentifiers.getObfuscatedAccountId();
String token = list.get(0).getPurchaseToken();
// Pasar a la DB el token, accID y uid vinculados con el premium.
// UID // ACCID // TOKEN // PREMIUM //
}else if(billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED){
// do Something you want
Log.i(TAG, "Compra cancelada");
// Nada? Cancelar all?
}else if(billingResult.getResponseCode() == BillingClient.BillingResponseCode.ITEM_UNAVAILABLE){
Log.i(TAG, "Item inexistente");
}
}
};
final BillingClient billingClient = BillingClient.newBuilder(mContext)
.setListener(purchasesUpdatedListener)
.enablePendingPurchases()
.build();
billingClient.startConnection(new BillingClientStateListener() {
#Override
public void onBillingSetupFinished(#NonNull BillingResult billingResult) {
if(billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK){
// The BillingClient is ready. You can query purchases here.
List<String> skuList = new ArrayList<>();
skuList.add(SKU);
SkuDetailsParams params = SkuDetailsParams.newBuilder()
.setSkusList(skuList).setType(BillingClient.SkuType.SUBS)
.build();
billingClient.querySkuDetailsAsync(params, new SkuDetailsResponseListener() {
#Override
public void onSkuDetailsResponse(#NonNull BillingResult billingResult, #Nullable List<SkuDetails> list) {
if(list == null){
Log.i(TAG, "lista vacía");
return;
}
for(int i = 0; i < list.size(); i++){
Log.i(TAG, String.valueOf(list.get(i)));
}
BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
.setSkuDetails(list.get(0))
.build();
int responseCode = billingClient.launchBillingFlow(requireActivity(), billingFlowParams).getResponseCode();
if(responseCode == BillingClient.BillingResponseCode.OK){
// do Something you want
Log.i(TAG, "responseCode OK");
}else if(responseCode == BillingClient.BillingResponseCode.ERROR){
Log.i(TAG, "responseCode ERROR");
}else if(responseCode == BillingClient.BillingResponseCode.ITEM_UNAVAILABLE){
Log.i(TAG, "responseCode ITEM_UNAVAILABLE");
}
}
});
}
}
#Override
public void onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
});
}
When purchase is sucsses send http request to your php server and put the subscription az int
For example int if noob=1 if pro=2
And save it in your database on your server
Than you can create http request from your php to get that number so you know what subscription user have
You can even set string for more understandable form
So if user x have int = 1 you know that is noob subcription
Than do what you want
I hope it will help you figure out
I am new to android and trying to implement in-app billing first time.
I am using google play in-app library.
https://developer.android.com/google/play/billing/billing_library_overview
I want to implement consumable in-app purchase. I am using 'android.test.purchased' reserved id for testing. I could load skuDetails and make purchase successfully and consume purchase successfully
here is my handlePurchase method with consumeAsync
void handlePurchase(Purchase purchase) {
BillingClient client = BillingClient.newBuilder(NewAdActivity.this)
.enablePendingPurchases()
.setListener(this)
.build();
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
System.out.println("item successfully purchased");
if (!purchase.isAcknowledged()) {
ConsumeParams consumeParams = ConsumeParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.setDeveloperPayload(purchase.getDeveloperPayload())
.build();
ConsumeResponseListener consumeResponseListener = new ConsumeResponseListener() {
#Override
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchaseToken != null) {
System.out.println("SUCCESSFULLY consumed PURCHASE");
providecontent();
}
else {
System.out.println("FAILED TO consume:”);
}
}
};
client.consumeAsync(consumeParams, consumeResponseListener);
}
}
}
Does it also acknowledge purchase when I consume purchase? Do I need to set "acknowledged":true in purchase.originalJson manually?
Is my code correct to consume purchased item? or I need to include a separate acknowledgePurchase before consuming item.
Please reply. Any help is truly appreciated.
Thanks.
For consumable products, you want to use consumeAsync(). For products that aren't consumable, you want to use acknowledgePurchase(). For more about acknowledging purchases in your app, check out the official documentation: https://developer.android.com/google/play/billing/billing_library_overview#acknowledge
Welcome to stackoverflow !
You are incorrectly using consumeAsync(), to acknowledge a purchase you should call acknowledgePurchase().
consumeAsync() removes the item purchased, for example if the purchase allows the user to play 10 times and he uses all of them then you would call consumeAsync() to let the user to buy the item again
An example:
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
// Grant entitlement to the user.
boolean signOk = verifyPurchaseSignature(purchase.getOriginalJson(), purchase.getSignature());
if (!signOk) {
// Alert the user about wrong signature
return;
} else if (!purchase.isAcknowledged()) {
AcknowledgePurchaseParams acknowledgePurchaseParams =
AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
billingClient.acknowledgePurchase(acknowledgePurchaseParams, new AcknowledgePurchaseResponseListener() {
#Override
public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
//Give thanks for the purchase
}
});
}
}
I'm working on an android Studio project, requiring text to speech.
I would like to implement a program which can recognize in which language the text is written and read it with the appropriate pronunciation.
Example : if i have a text in English, i want the application to pronounce it in English with the English pronunciation.
Is it possible ?
Thank you
I have successfully implemented TTS in French and it works fine.
mTTS = new TextToSpeech(this, new TextToSpeech.OnInitListener() {
#Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
int result = mTTS.setLanguage(Locale.FRENCH);
if (result == TextToSpeech.LANG_MISSING_DATA
|| result == TextToSpeech.LANG_NOT_SUPPORTED) {
Log.e("TTS", "Language not supported");
} else {
mButtonSpeak.setEnabled(true);
}
} else {
Log.e("TTS", "Initialization failed");
}
}
});
I would like to have a multilingual text to speech app
Thank you
You can identify the language of a text with Android ML Kit
Add the dependencies for the ML Kit Android libraries to your module (app-level) Gradle file (usually app/build.gradle):
dependencies {
// ...
implementation 'com.google.firebase:firebase-ml-natural-language:20.0.0'
implementation 'com.google.firebase:firebase-ml-natural-language-language-id-model:20.0.0'
}
To identify the language of a string, get an instance of FirebaseLanguageIdentification, and then pass the string to the identifyLanguage()method.
For example:
FirebaseLanguageIdentification languageIdentifier =
FirebaseNaturalLanguage.getInstance().getLanguageIdentification();
languageIdentifier.identifyLanguage(text)
.addOnSuccessListener(
new OnSuccessListener<String>() {
#Override
public void onSuccess(#Nullable String languageCode) {
if (languageCode != "und") {
Log.i(TAG, "Language: " + languageCode);
} else {
Log.i(TAG, "Can't identify language.");
}
}
})
.addOnFailureListener(
new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
// Model couldn’t be loaded or other internal error.
// ...
}
});
I'm having problems with Android Google Places API - auto complete feature.
I'm trying to use Places.GeoDataApi.getAutocompletePredictions() method to get the placeId, however, it doesn't work.(The output doesn't even show "can't get the value")
Could anyone help me?
Here is my Java code:
PendingResult<AutocompletePredictionBuffer> result =
Places.GeoDataApi.getAutocompletePredictions(mGoogleApiClient, query,
mLatLonBounds, null);
result.setResultCallback(new ResultCallback<AutocompletePredictionBuffer>() {
#Override
public void onResult(#NonNull AutocompletePredictionBuffer autocompletePredictions) {
if (autocompletePredictions.getStatus().isSuccess()) { //如果回傳SUCCESS
Iterator<AutocompletePrediction> iterator = autocompletePredictions.iterator();
resultList = new ArrayList<>(autocompletePredictions.getCount());
while (iterator.hasNext()) {
AutocompletePrediction prediction = iterator.next();
resultList.add(prediction.getPlaceId().toString());
}
}
else {
resultList.add("can't get the vaule");
}
autocompletePredictions.release();
}
});
textView.setText(resultList.get(0).toString());
Thanks.
I had the same issue and I got it worked by following the below steps
Enable the Google Places API for Android in developers console and
check on the credentials page that your key is still present.
Add the required library to your module (play-services-places:11.2.0+).
Use the below code to get the predictions
Task<AutocompletePredictionBufferResponse> results = mGeoDataClient.getAutocompletePredictions(yourInputString, yourLatLngBounds, yourTypeFilter);
Add on success listener for the predictions as below
results.addOnSuccessListener(new OnSuccessListener<AutocompletePredictionBufferResponse>() {
#Override
public void onSuccess(AutocompletePredictionBufferResponse autocompletePredictions) {
Iterator<AutocompletePrediction> iterator = autocompletePredictions.iterator();
suggesstions = new ArrayList<String>(autocompletePredictions.getCount());
if (iterator.hasNext()) {
while (iterator.hasNext()) {
AutocompletePrediction prediction = iterator.next();
//do your stuff with prediction here
}
}else {
//Nothing matches...
}
autocompletePredictions.release();
}
});
Add on failure listener for the predictions as below
results.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Toast.makeText(MapsActivity.this,"Error while fetching suggessions : "+e.getLocalizedMessage(),Toast.LENGTH_SHORT).show();
}
});
If all the above code snippet is placed properly, then you probably check your internet connectivity.
Hope the above code will be helpful.
Thanks.