@Nonnull PurchaseFlow createPurchaseFlow(@Nonnull IntentStarter intentStarter, int requestCode, @Nonnull RequestListener<Purchase> listener) { if (mCache.hasCache()) { listener = new RequestListenerWrapper<Purchase>(listener) { @Override public void onSuccess(@Nonnull Purchase result) { mCache.removeAll(RequestType.GET_PURCHASES.getCacheKeyType()); super.onSuccess(result); } }; } return new PurchaseFlow(intentStarter, requestCode, listener, mConfiguration.getPurchaseVerifier()); }
/** * Destroys previously created purchase flow. Nothing happens if flow has already been * destroyed. * * @param requestCode purchase request code */ public void destroyPurchaseFlow(int requestCode) { final PurchaseFlow flow = mFlows.get(requestCode); if (flow == null) { return; } mFlows.delete(requestCode); // instead of cancelling purchase request in `Billing` class (which we can't do as we don't // have `requestId`) let's cancel it here flow.cancel(); }
/** * This method must be called from {@link Activity#onActivityResult(int, int, Intent)} (or * {@link android.app.Fragment#onActivityResult(int, int, Intent)}) in order to finish a started * purchase flow. * * @return true if activity result was handled (there existed a purchase flow for the given * <var>requestCode</var>) * @see Activity#onActivityResult(int, int, Intent) * @see android.app.Fragment#onActivityResult(int, int, Intent) */ public boolean onActivityResult(int requestCode, int resultCode, Intent data) { final PurchaseFlow flow = mFlows.get(requestCode); if (flow == null) { Billing.warning("Purchase flow doesn't exist for requestCode=" + requestCode + ". Have you forgotten to create it?"); return false; } flow.onActivityResult(requestCode, resultCode, data); return true; }
@Test public void testShouldNotCallListenerIfCancelled() throws Exception { Tests.mockVerifier(mVerifier, true); mFlow.cancel(); mFlow.onActivityResult(1, RESULT_OK, newOkIntent()); mFlow.onActivityResult(1, RESULT_OK, newIntent(ACCOUNT_ERROR, "{productId:'test', purchaseTime:1000}", "signature")); verify(mListener, never()).onError(anyInt(), any(Exception.class)); verify(mListener, never()).onSuccess(any(Purchase.class)); } }
@Override public void onSuccess(@Nonnull PendingIntent purchaseIntent) { if (mListener == null) { // request was cancelled => stop here return; } try { mIntentStarter.startForResult(purchaseIntent.getIntentSender(), mRequestCode, new Intent()); } catch (RuntimeException | IntentSender.SendIntentException e) { handleError(e); } }
private void handleError(@Nonnull Exception e) { Billing.error("Exception in Purchase/ChangePurchase request: ", e); onError(ResponseCodes.EXCEPTION, e); }
void onActivityResult(int requestCode, int resultCode, Intent intent) { try { Check.equals(mRequestCode, requestCode); if (intent == null) { // sometimes intent is null (it's not obvious when it happens but it happens from time to time) handleError(NULL_INTENT); return; } final int responseCode = intent.getIntExtra(EXTRA_RESPONSE, OK); if (resultCode != RESULT_OK || responseCode != OK) { handleError(responseCode); return; } final String data = intent.getStringExtra(EXTRA_PURCHASE_DATA); final String signature = intent.getStringExtra(EXTRA_PURCHASE_SIGNATURE); Check.isNotNull(data); Check.isNotNull(signature); final Purchase purchase = Purchase.fromJson(data, signature); mVerifier.verify(singletonList(purchase), new VerificationListener()); } catch (RuntimeException | JSONException e) { handleError(e); } }
private void handleError(int response) { Billing.error("Error response: " + response + " in Purchase/ChangePurchase request"); onError(response, new BillingException(response)); }
@Test public void testShouldErrorIfRequestCodeIsDifferent() throws Exception { mFlow.onActivityResult(2, 1, new Intent()); verifyError(ResponseCodes.EXCEPTION, RuntimeException.class); }
@Before public void setUp() throws Exception { mListener = mock(RequestListener.class); mVerifier = mock(PurchaseVerifier.class); Tests.mockVerifier(mVerifier, false); mFlow = new PurchaseFlow(Mockito.mock(IntentStarter.class), 1, mListener, mVerifier); }
@Test public void testShouldErrorIfResultCodeItNotOk() throws Exception { mFlow.onActivityResult(1, Activity.RESULT_CANCELED, new Intent()); verifyError(OK, BillingException.class); }
@Test public void testShouldFinishSuccessfully() throws Exception { Tests.mockVerifier(mVerifier, true); mFlow.onActivityResult(1, RESULT_OK, newOkIntent()); verify(mListener, never()).onError(anyInt(), any(Exception.class)); verify(mListener).onSuccess(any(Purchase.class)); }
@Test public void testShouldErrorIfIntentIsNull() throws Exception { mFlow.onActivityResult(1, 1, null); verifyError(ResponseCodes.NULL_INTENT, BillingException.class); }
@Test public void testShouldErrorWithEmptySignature() throws Exception { mFlow.onActivityResult(1, RESULT_OK, newIntent(OK, "{productId:'test', purchaseTime:1000}", "")); verifyError(ResponseCodes.WRONG_SIGNATURE, RuntimeException.class); }
@Test public void testShouldErrorWithNoData() throws Exception { mFlow.onActivityResult(1, RESULT_OK, newIntent(OK, null, "signature")); verifyError(ResponseCodes.EXCEPTION, RuntimeException.class); }
@Test public void testShouldErrorIfResponseCodeItNotOk() throws Exception { mFlow.onActivityResult(1, RESULT_OK, newIntent(ResponseCodes.ACCOUNT_ERROR, null, null)); verifyError(ResponseCodes.ACCOUNT_ERROR, BillingException.class); }
@Test public void testShouldErrorWithNoSignature() throws Exception { mFlow.onActivityResult(1, RESULT_OK, newIntent(OK, "data", null)); verifyError(ResponseCodes.EXCEPTION, RuntimeException.class); }
@Test public void testShouldErrorIfVerificationFailed() throws Exception { Tests.mockVerifier(mVerifier, false); mFlow.onActivityResult(1, RESULT_OK, newIntent(OK, "{productId:'test', purchaseTime:1000}", "signature")); verifyError(ResponseCodes.WRONG_SIGNATURE, RuntimeException.class); }