private PendingIntent fetchPendingIntentFromGetBuyIntentResponse(Bundle responseData) { int code = responseData.getInt(RESPONSE_CODE); ResponseCode responseCode = ResponseCode.findByCode(code); if (responseCode != ResponseCode.BILLING_RESPONSE_RESULT_OK) { // TODO: unit test this. throw new GdxPayException("Unexpected getBuyIntent() responseCode: " + responseCode + " with response data: " + responseData); } PendingIntent pendingIntent = responseData.getParcelable(BUY_INTENT); if (pendingIntent == null) { throw new GdxPayException("Missing value for key: " + BUY_INTENT + "in getBuyIntent() response: " + responseData); } return pendingIntent; }
@Override public void disconnected(GdxPayException exception) { observer.handleInstallError(new GdxPayException("Failed to bind to service", exception)); } });
private void assertInstalled() { if (!installed()) { throw new GdxPayException("Payment system must be installed to perform this action."); } }
@Override public void onServiceDisconnected(ComponentName name) { unbindBillingServiceConnection(); iInAppBillingService = null; connectionListener.disconnected(new GdxPayException(ERROR_ON_SERVICE_DISCONNECTED_RECEIVED)); } }
private IInAppBillingService billingService() { if (!isConnected()) { throw new GdxPayException(ERROR_NOT_CONNECTED_TO_GOOGLE_IAB); } return iInAppBillingService; }
@Override public void run() { // it might happen that this was already disposed until the service connection was established if (PurchaseManagerGoogleBilling.this.observer == null) return; if (!serviceConnected) PurchaseManagerGoogleBilling.this.observer.handleInstallError( new GdxPayException("Connection to Play Billing not possible")); else if (autoFetchInformation) { fetchOfferDetails(); } else setInstalledAndNotifyObserver(); } });
@Override public void purchaseRestore() { mBillingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.INAPP, new PurchaseHistoryResponseListener() { @Override public void onPurchaseHistoryResponse(int responseCode, List<Purchase> purchases) { if (responseCode == BillingClient.BillingResponse.OK && purchases != null) { handlePurchase(purchases, true); } else { Gdx.app.error(TAG, "onPurchaseHistoryResponse failed with responseCode " + responseCode); observer.handleRestoreError(new GdxPayException("onPurchaseHistoryResponse failed with " + "responseCode " + responseCode)); } } }); }
@Override public void install(PurchaseObserver observer, PurchaseManagerConfig config, final boolean autoFetchInformation) { this.observer = observer; this.config = config; // make sure to call the observer again installationComplete = false; startServiceConnection(new Runnable() { @Override public void run() { // it might happen that this was already disposed until the service connection was established if (PurchaseManagerGoogleBilling.this.observer == null) return; if (!serviceConnected) PurchaseManagerGoogleBilling.this.observer.handleInstallError( new GdxPayException("Connection to Play Billing not possible")); else if (autoFetchInformation) { fetchOfferDetails(); } else setInstalledAndNotifyObserver(); } }); }
@Override public void onPurchaseHistoryResponse(int responseCode, List<Purchase> purchases) { if (responseCode == BillingClient.BillingResponse.OK && purchases != null) { handlePurchase(purchases, true); } else { Gdx.app.error(TAG, "onPurchaseHistoryResponse failed with responseCode " + responseCode); observer.handleRestoreError(new GdxPayException("onPurchaseHistoryResponse failed with " + "responseCode " + responseCode)); } } });
protected void bindBillingServiceConnectionToActivity() { try { if (!applicationProxy.bindService(createBindBillingServiceIntent(), billingServiceConnection, Context.BIND_AUTO_CREATE)) { this.connectionListener.disconnected(new GdxPayException("bindService() returns false.")); } } catch (GdxPayException e) { throw e; } catch (RuntimeException e) { this.connectionListener.disconnected(new GdxPayException("requestConnect() failed.", e)); } }
protected void handleResultOk(Intent data) { final Transaction transaction; try { transaction = convertPurchaseResponseDataToTransaction(data); } catch (GdxPayException e) { listener.purchaseError(new GdxPayException("Error converting purchase success response: " + data, e)); return; } listener.purchaseSuccess(transaction); } });
private Bundle executeGetSkuDetails(Bundle skusRequest, String type) { try { return billingService().getSkuDetails(BILLING_API_VERSION, installerPackageName, type, skusRequest); } catch (RemoteException e) { // TODO: unit test this. throw new GdxPayException("getProductsDetails failed for bundle:" + skusRequest, e); } }
@Override public Map<String, Information> getProductsDetails(List<String> productIds, String productType) { long startTimeInMs = System.currentTimeMillis(); try { Map<String, Information> result = new HashMap<String, Information>(); // max 20 Items List<List<String>> splitProductList = splitList(productIds, 20); for (List<String> splitProductIds : splitProductList) { result.putAll(fetchSkuDetails(splitProductIds, productType)); } return result; } catch (RuntimeException e) { throw new GdxPayException("getProductsDetails(" + productIds + " failed) after " + deltaInSeconds(startTimeInMs) + " seconds", e); } }
public Transaction convertToTransaction(Intent responseData) { String purchaseDataString = responseData.getStringExtra(GoogleBillingConstants.INAPP_PURCHASE_DATA); try { Transaction transaction = convertJSONPurchaseToTransaction(purchaseDataString); transaction.setTransactionDataSignature(responseData.getStringExtra(GoogleBillingConstants.INAPP_DATA_SIGNATURE)); String productId = transaction.getIdentifier(); setInformationFields(transaction, productId); return transaction; } catch (JSONException e) { throw new GdxPayException("JSON Exception while parsing: " + purchaseDataString, e); } }
@Override public void purchaseSuccess(Transaction transaction) { if (observer != null) { switch (offerType) { case CONSUMABLE: // Warning: observer.handlePurchase is called in googleInAppBillingService.consumePurchase. // That is not clean, I would prefer to keep it on one place. // Should be refactored later. googleInAppBillingService.consumePurchase(transaction, observer); break; case ENTITLEMENT: case SUBSCRIPTION: observer.handlePurchase(transaction); break; default: String error = "Unsupported OfferType=" + getOfferType(identifier) + " for identifier=" + identifier; throw new GdxPayException(error); } } }
@Override public List<Transaction> getPurchases() { try { Bundle inAppPurchases = billingService().getPurchases(BILLING_API_VERSION, installerPackageName, PURCHASE_TYPE_IN_APP, null); Bundle subscriptions = billingService().getPurchases(BILLING_API_VERSION, installerPackageName, PURCHASE_TYPE_SUBSCRIPTION, null); List<Transaction> transactions = new ArrayList<>(); transactions.addAll(convertPurchasesResponseToTransactions(inAppPurchases)); transactions.addAll(convertPurchasesResponseToTransactions(subscriptions)); return transactions; } catch (RemoteException | RuntimeException e) { // TODO: unit test RuntimeException scenario, e.g. : java.lang.IllegalArgumentException: Unexpected response code: ResponseCode{code=3, message='Billing API version is not supported for the type requested'}, response: Bundle[{RESPONSE_CODE=3}] throw new GdxPayException("Unexpected exception in getPurchases()", e); } }
@Override public void onEvent(int resultCode, Intent data) { if (resultCode == Activity.RESULT_OK) { handleResultOk(data); return; } if (resultCode == Activity.RESULT_CANCELED) { listener.purchaseCanceled(); return; } listener.purchaseError(new GdxPayException("Unexpected resultCode:" + resultCode + "with data:" + data)); }
transaction = convertPurchaseResponseDataToTransaction(data); } catch (GdxPayException e) { listener.purchaseError(new GdxPayException("Error converting purchase success response: " + data, e)); return; listener.purchaseError(new GdxPayException("startIntentSenderForResult failed for product: " + productId, e));
@Override public void install(final PurchaseObserver observer, final PurchaseManagerConfig purchaseManagerConfig, final boolean autoFetchInformation) { this.observer = observer; this.purchaseManagerConfig = purchaseManagerConfig; if (googleInAppBillingService.isListeningForConnections()) { // Supports calling me multiple times. // TODO: scenario not unit tested, test this! googleInAppBillingService.disconnect(); } googleInAppBillingService.requestConnect(new ConnectionListener() { @Override public void connected() { onServiceConnected(observer, autoFetchInformation); } @Override public void disconnected(GdxPayException exception) { observer.handleInstallError(new GdxPayException("Failed to bind to service", exception)); } }); }
@Override public void onPurchasesUpdated(int responseCode, @Nullable List<Purchase> purchases) { // check the edge case that the callback comes with a delay right after dispose() was called if (observer == null) return; if (responseCode == BillingClient.BillingResponse.OK && purchases != null) { handlePurchase(purchases, false); } else if (responseCode == BillingClient.BillingResponse.USER_CANCELED) { observer.handlePurchaseCanceled(); } else if (responseCode == BillingClient.BillingResponse.ITEM_ALREADY_OWNED) { observer.handlePurchaseError(new ItemAlreadyOwnedException()); } else { Gdx.app.error(TAG, "onPurchasesUpdated failed with responseCode " + responseCode); observer.handlePurchaseError(new GdxPayException("onPurchasesUpdated failed with responseCode " + responseCode)); } }