Reputation: 23
Im trying to add Google PLay Billing in my app from last few days , in the previous question i mentioned that the launch flow is opening the real payment method instead of test cards
view the previous question here
now after some modification in the handlePurchase()
& PurchasesUpdatedListener
and finally adding the verify java class
to verify the purchase im getting this message from the PurchasesUpdatedListener 'SERVICE_DISCONNECTED
'
This is the code
HelpActivity.Java
public class HelpActivity extends AppCompatActivity {
AcknowledgePurchaseResponseListener acknowledgePurchaseResponseListener = new AcknowledgePurchaseResponseListener() {
@Override
public void onAcknowledgePurchaseResponse(@NonNull BillingResult billingResult) {
Toast.makeText(HelpActivity.this, "Acknowledge", Toast.LENGTH_SHORT).show();
}
};
private BillingClient billingClient;
private final PurchasesUpdatedListener purchasesUpdatedListener = new PurchasesUpdatedListener() {
@Override
public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null) {
for (Purchase purchase : purchases) {
handlePurchase(purchase);
}
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
// Handle an error caused by a user cancelling the purchase flow.
Toast.makeText(HelpActivity.this, "Purchase Canceled", Toast.LENGTH_SHORT).show();
//Note!!!! only this toast message is being shown in this entre process
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
for (Purchase purchase : purchases) {
handlePurchase(purchase);
}
Toast.makeText(HelpActivity.this, "You already own the item", Toast.LENGTH_SHORT).show();
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.BILLING_UNAVAILABLE) {
Toast.makeText(HelpActivity.this, "BILLING_UNAVAILABLE", Toast.LENGTH_SHORT).show();
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.ERROR) {
Toast.makeText(HelpActivity.this, "ERROR", Toast.LENGTH_SHORT).show();
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.DEVELOPER_ERROR) {
Toast.makeText(HelpActivity.this, "DEVELOPER_ERROR", Toast.LENGTH_SHORT).show();
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.ITEM_NOT_OWNED) {
Toast.makeText(HelpActivity.this, "ITEM_NOT_OWNED", Toast.LENGTH_SHORT).show();
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.ITEM_UNAVAILABLE) {
Toast.makeText(HelpActivity.this, "ITEM_UNAVAILABLE", Toast.LENGTH_SHORT).show();
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED) {
Toast.makeText(HelpActivity.this, "FEATURE_NOT_SUPPORTED", Toast.LENGTH_SHORT).show();
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.NETWORK_ERROR) {
Toast.makeText(HelpActivity.this, "FEATURE_NOT_SUPPORTED", Toast.LENGTH_SHORT).show();
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.SERVICE_DISCONNECTED) {
Toast.makeText(HelpActivity.this, "SERVICE_DISCONNECTED", Toast.LENGTH_SHORT).show();
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE) {
Toast.makeText(HelpActivity.this, "SERVICE_UNAVAILABLE", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(HelpActivity.this, " " + billingResult.getDebugMessage(), Toast.LENGTH_SHORT).show();
}
}
};
private MaterialButton removeAdsBtn;
private MaterialButton restorePurchaseBtn;
private ProductDetails productDetails;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_help_avtivity);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); // Set vertical orientation
Window window = this.getWindow();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.setStatusBarColor(this.getResources().getColor(R.color.black));
}
restorePurchaseBtn = findViewById(R.id.restoreAdsBtn);
removeAdsBtn = findViewById(R.id.removeAdsBtn);
// Initialize BillingClient
setupBillingClient();
restorePurchaseBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
/* restorePurchases();*/
}
});
removeAdsBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Call the method to initiate the billing flow
setupBillingClient();
initiateBillingFlow();
}
});
}
public void setupBillingClient() {
billingClient = BillingClient.newBuilder(this).setListener(purchasesUpdatedListener).enablePendingPurchases().build();
establishConnection();
}
void establishConnection() {
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.
QueryPurchase();
}
}
@Override
public void onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
establishConnection();
}
});
}
public void QueryPurchase() {
QueryProductDetailsParams queryProductDetailsParams = QueryProductDetailsParams.newBuilder().setProductList(ImmutableList.of(QueryProductDetailsParams.Product.newBuilder().setProductId("removeads_rickroll").setProductType(BillingClient.ProductType.INAPP).build())).build();
billingClient.queryProductDetailsAsync(queryProductDetailsParams, new ProductDetailsResponseListener() {
public void onProductDetailsResponse(BillingResult billingResult, List<ProductDetails> productDetailsList) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// Check if the productDetailsList is not empty
if (productDetailsList != null) {
for (ProductDetails productDetails1 : productDetailsList) {
productDetails = productDetails1;
}
}
}
}
});
}
public void initiateBillingFlow() {
if (productDetails != null) {
ImmutableList<BillingFlowParams.ProductDetailsParams> productDetailsParamsList =
ImmutableList.of(
BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(productDetails)
.build());
BillingFlowParams billingFlowParams =
BillingFlowParams.newBuilder()
.setProductDetailsParamsList(productDetailsParamsList)
.build();
billingClient.launchBillingFlow(HelpActivity.this, billingFlowParams);
}
}
private void handlePurchase(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.
}
}
};
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
//Verify Purchase
if (!verifyValidSignature(purchase.getOriginalJson(), purchase.getSignature())) {
Toast.makeText(this, "Error:- Invalid Purchase", Toast.LENGTH_SHORT).show();
return;
}
if (!purchase.isAcknowledged()) {
AcknowledgePurchaseParams acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build();
billingClient.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener);
// Grant the user access to remove ads
// Save the purchase details to SharedPreferences to remember the purchase
Toast.makeText(HelpActivity.this, "Thank you for the purchase", Toast.LENGTH_SHORT).show();
Toast.makeText(HelpActivity.this, "Restarting the app, Please wait ...", Toast.LENGTH_SHORT).show();
SharedPreferences preferences = getSharedPreferences("adremoveSP", MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("ads_removed", true);
editor.apply();
Intent intent = new Intent(HelpActivity.this, MainActivity.class);
startActivity(intent);
} else {
Toast.makeText(this, "Already Purchased", Toast.LENGTH_SHORT).show();
SharedPreferences preferences = getSharedPreferences("adremoveSP", MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("ads_removed", true);
editor.apply();
Intent intent = new Intent(HelpActivity.this, MainActivity.class);
startActivity(intent);
}
} else if (purchase.getPurchaseState() == Purchase.PurchaseState.UNSPECIFIED_STATE) {
Toast.makeText(this, "UNSPECIFIED_STATE", Toast.LENGTH_SHORT).show();
} else if (purchase.getPurchaseState() == Purchase.PurchaseState.PENDING) {
Toast.makeText(this, "PENDING", Toast.LENGTH_SHORT).show();
}
billingClient.consumeAsync(consumeParams, listener);
}
private boolean verifyValidSignature(String originalJson, String signature) {
try {
String base64Key = "<MyBaseKey>";
return Verify.verifyPurchase(base64Key, originalJson, signature);
} catch (IOException e) {
return false;
}
}
protected void onResume() {
super.onResume();
billingClient.queryPurchasesAsync(QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.INAPP).build(), (billingResult, list) -> {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
for (Purchase purchase : list) {
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED && !purchase.isAcknowledged()) {
handlePurchase(purchase);
}
}
}
});
}
}
Verify.Java
public class Verify {
private static final String TAG = "IABUtil/Security";
private static final String KEY_FACTORY_ALGORITHM = "RSA";
private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) throws IOException{
if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey) || TextUtils.isEmpty(signature)){
return false;
}
PublicKey key = generatePublicKey(base64PublicKey);
return verifyM(key,signedData,signature);
}
public static PublicKey generatePublicKey(String encodedPublicKey)throws IOException{
try {
byte[] decodedKey = Base64.decode(encodedPublicKey,Base64.DEFAULT);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
}catch (NoSuchAlgorithmException e){
throw new RuntimeException(e);
}
catch (InvalidKeySpecException e){
String msg = "Invalid Key Specification: "+ e;
throw new IOException(msg);
}
}
public static boolean verifyM(PublicKey publicKey,String signedData, String signature){
byte [] signatureBytes;
try{
signatureBytes = Base64.decode(signature,Base64.DEFAULT);
}catch (IllegalArgumentException e){
return false;
}
try {
Signature signatureAlgorithm = Signature.getInstance(SIGNATURE_ALGORITHM);
signatureAlgorithm.initVerify(publicKey);
signatureAlgorithm.update(signedData.getBytes());
if (!signatureAlgorithm.verify(signatureBytes)){
return false;
}
return true;
}catch (IllegalArgumentException e){
return false;
}catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e){
return false;
}
}
}
Upvotes: 1
Views: 301