/** * Start the Venmo flow. This will app switch to the Venmo app. * <p/> * If the Venmo app is not available, {@link AppSwitchNotAvailableException} will be sent to {@link * com.braintreepayments.api.interfaces.BraintreeErrorListener#onError(Exception)}. * * @param fragment {@link BraintreeFragment} * @param vault If true, and you are using Client Token authorization with a customer ID, this payment method will * be added to your customer's vault. @see <a href="https://developers.braintreepayments.com/guides/authorization/overview">our * docs on client authorization</a> for more info. */ public static void authorizeAccount(final BraintreeFragment fragment, final boolean vault) { authorizeAccount(fragment, vault, null); }
/** * Determines if the Venmo flow is available to be used. This can be used to determine * if UI components should be shown or hidden. * * @param context A context to access the {@link PackageManager} * @return boolean if Venmo is enabled, and available to be used */ public boolean isEnabled(Context context) { return isAccessTokenValid() && Venmo.isVenmoInstalled(context); }
/** * @param context A context to access the installed packages. * @return boolean depending on if the Venmo app is installed, and has a valid signature. */ public static boolean isVenmoInstalled(Context context) { return AppHelper.isIntentAvailable(context, getVenmoIntent()) && SignatureVerification.isSignatureValid(context, PACKAGE_NAME, CERTIFICATE_SUBJECT, CERTIFICATE_ISSUER, PUBLIC_KEY_HASH_CODE); }
static void onActivityResult(final BraintreeFragment fragment, int resultCode, Intent data) { if (resultCode == Activity.RESULT_OK) { fragment.sendAnalyticsEvent("pay-with-venmo.app-switch.success"); String nonce = data.getStringExtra(EXTRA_PAYMENT_METHOD_NONCE); if (shouldVault(fragment.getApplicationContext()) && fragment.getAuthorization() instanceof ClientToken) { vault(fragment, nonce); } else { String venmoUsername = data.getStringExtra(EXTRA_USERNAME); VenmoAccountNonce venmoAccountNonce = new VenmoAccountNonce(nonce, venmoUsername, venmoUsername); fragment.postCallback(venmoAccountNonce); } } else if (resultCode == Activity.RESULT_CANCELED) { fragment.sendAnalyticsEvent("pay-with-venmo.app-switch.canceled"); } }
@Test public void onActivityResult_doesNotPerformRequestIfTokenizationKeyUsed() throws NoSuchAlgorithmException, InvalidArgumentException { Configuration configuration = getConfigurationFromFixture(); mockStatic(TokenizationClient.class); BraintreeFragment fragment = new MockFragmentBuilder() .context(VenmoInstalledContextFactory.venmoInstalledContext(true, RuntimeEnvironment.application)) .configuration(configuration) .authorization(Authorization.fromString("sandbox_tk_abcd")) .sessionId("another-session-id") .build(); Venmo.authorizeAccount(fragment, true); Venmo.onActivityResult(fragment, Activity.RESULT_OK, new Intent()); verifyStatic(times(0)); TokenizationClient.tokenize(eq(fragment), any(VenmoAccountBuilder.class), any(PaymentMethodNonceCallback.class)); }
@Override public void onConfigurationFetched(Configuration configuration) { fragment.sendAnalyticsEvent("pay-with-venmo.selected"); String venmoProfileId = profileId; if (TextUtils.isEmpty(venmoProfileId)) { venmoProfileId = configuration.getPayWithVenmo().getMerchantId(); } String exceptionMessage = ""; if (!configuration.getPayWithVenmo().isAccessTokenValid()) { exceptionMessage = "Venmo is not enabled"; } else if (!Venmo.isVenmoInstalled(fragment.getApplicationContext())) { exceptionMessage = "Venmo is not installed"; } if (!TextUtils.isEmpty(exceptionMessage)) { fragment.postCallback(new AppSwitchNotAvailableException(exceptionMessage)); fragment.sendAnalyticsEvent("pay-with-venmo.app-switch.failed"); } else { persistVenmoVaultOption(vault && fragment.getAuthorization() instanceof ClientToken, fragment.getApplicationContext()); fragment.startActivityForResult(getLaunchIntent(configuration.getPayWithVenmo(), venmoProfileId, fragment), BraintreeRequestCodes.VENMO); fragment.sendAnalyticsEvent("pay-with-venmo.app-switch.started"); } } });
@Test public void onActivityResult_sendsAnalyticsEventOnSuccess() { BraintreeFragment fragment = new MockFragmentBuilder() .build(); Intent intent = new Intent() .putExtra(Venmo.EXTRA_PAYMENT_METHOD_NONCE, "123456-12345-12345-a-adfa") .putExtra(Venmo.EXTRA_USERNAME, "username"); Venmo.onActivityResult(fragment, Activity.RESULT_OK, intent); verify(fragment).sendAnalyticsEvent("pay-with-venmo.app-switch.success"); }
@Test public void getLaunchIntent_containsCorrectVenmoExtras() throws JSONException, InvalidArgumentException { Configuration configuration = getConfigurationFromFixture(); BraintreeFragment fragment = new MockFragmentBuilder() .authorization(Authorization.fromString(stringFromFixture("client_token.json"))) .configuration(configuration) .sessionId("session-id") .build(); when(fragment.getIntegrationType()).thenReturn("custom"); Intent intent = Venmo.getLaunchIntent(configuration.getPayWithVenmo(), configuration.getPayWithVenmo().getMerchantId(), fragment); assertEquals(new ComponentName("com.venmo", "com.venmo.controller.SetupMerchantActivity"), intent.getComponent()); assertEquals("merchant-id", intent.getStringExtra(Venmo.EXTRA_MERCHANT_ID)); assertEquals("access-token", intent.getStringExtra(Venmo.EXTRA_ACCESS_TOKEN)); assertEquals("environment", intent.getStringExtra(Venmo.EXTRA_ENVIRONMENT)); JSONObject braintreeData = new JSONObject(intent.getStringExtra(Venmo.EXTRA_BRAINTREE_DATA)); JSONObject meta = braintreeData.getJSONObject("_meta"); assertNotNull(meta); assertEquals("session-id", meta.getString("sessionId")); assertEquals("custom", meta.getString("integration")); assertEquals(BuildConfig.VERSION_NAME, meta.getString("version")); assertEquals("android", meta.getString("platform")); }
@Test public void openVenmoAppPageInGooglePlay_opensVenmoAppStoreURL() throws JSONException { Configuration configuration = new TestConfigurationBuilder().buildConfiguration(); BraintreeFragment fragment = new MockFragmentBuilder() .configuration(configuration) .build(); Venmo.openVenmoAppPageInGooglePlay(fragment); ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); verify(fragment).startActivity(captor.capture()); assertEquals(captor.getValue().getData().toString(),"https://play.google.com/store/apps/details?id=com.venmo"); }
@Test public void onActivityResult_withFailedVaultCall_postsCallbackToErrorListener() throws InvalidArgumentException { Configuration configuration = getConfigurationFromFixture(); Authorization clientToken = Authorization.fromString(stringFromFixture("base_64_client_token.txt")); disableSignatureVerification(); BraintreeFragment fragment = new MockFragmentBuilder() .context(VenmoInstalledContextFactory.venmoInstalledContext(true, RuntimeEnvironment.application)) .configuration(configuration) .authorization(clientToken) .sessionId("session-id") .errorResponse(new AuthorizationException("Bad fingerprint")) .build(); ArgumentCaptor<Exception> responseCaptor = ArgumentCaptor.forClass(Exception.class); Venmo.authorizeAccount(fragment, true); Intent responseIntent = new Intent() .putExtra(Venmo.EXTRA_PAYMENT_METHOD_NONCE, "nonce"); Venmo.onActivityResult(fragment, Activity.RESULT_OK, responseIntent); verify(fragment).postCallback(responseCaptor.capture()); Exception exception = responseCaptor.getValue(); assertTrue(exception instanceof AuthorizationException); assertEquals("Bad fingerprint", exception.getMessage()); }
@Test public void onActivityResult_postsPaymentMethodNonceOnSuccess() { BraintreeFragment fragment = new MockFragmentBuilder() .build(); Intent intent = new Intent() .putExtra(Venmo.EXTRA_PAYMENT_METHOD_NONCE, "123456-12345-12345-a-adfa") .putExtra(Venmo.EXTRA_USERNAME, "username"); Venmo.onActivityResult(fragment, Activity.RESULT_OK, intent); ArgumentCaptor<VenmoAccountNonce> captor = ArgumentCaptor.forClass(VenmoAccountNonce.class); verify(fragment).postCallback(captor.capture()); assertEquals("123456-12345-12345-a-adfa", captor.getValue().getNonce()); assertEquals("username", captor.getValue().getDescription()); assertEquals("username", captor.getValue().getUsername()); }
@Test public void openVenmoAppPageInGooglePlay_sendsAnalyticsEvent() throws JSONException { Configuration configuration = new TestConfigurationBuilder().buildConfiguration(); BraintreeFragment fragment = new MockFragmentBuilder() .configuration(configuration) .build(); Venmo.openVenmoAppPageInGooglePlay(fragment); ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); verify(fragment).startActivity(captor.capture()); verify(fragment).sendAnalyticsEvent("android.pay-with-venmo.app-store.invoked"); }
@Test public void onActivityResult_withSuccessfulVaultCall_returnsVenmoAccountNonce() throws InvalidArgumentException { Configuration configuration = getConfigurationFromFixture(); Authorization clientToken = Authorization.fromString(stringFromFixture("base_64_client_token.txt")); disableSignatureVerification(); BraintreeFragment fragment = new MockFragmentBuilder() .context(VenmoInstalledContextFactory.venmoInstalledContext(true, RuntimeEnvironment.application)) .configuration(configuration) .authorization(clientToken) .sessionId("session-id") .successResponse(stringFromFixture("payment_methods/venmo_account_response.json")) .build(); Venmo.authorizeAccount(fragment, true); ArgumentCaptor<PaymentMethodNonce> responseCaptor = ArgumentCaptor.forClass(PaymentMethodNonce.class); Intent responseIntent = new Intent() .putExtra(Venmo.EXTRA_PAYMENT_METHOD_NONCE, "nonce"); Venmo.onActivityResult(fragment, Activity.RESULT_OK, responseIntent); verify(fragment).postCallback(responseCaptor.capture()); PaymentMethodNonce capturedNonce = responseCaptor.getValue(); assertTrue(capturedNonce instanceof VenmoAccountNonce); VenmoAccountNonce venmoAccountNonce = (VenmoAccountNonce) capturedNonce; assertEquals("Venmo", venmoAccountNonce.getTypeLabel()); assertEquals("fake-venmo-nonce", venmoAccountNonce.getNonce()); assertEquals("venmojoe", venmoAccountNonce.getUsername()); }
public static void authorizeAccount(final BraintreeFragment fragment) { authorizeAccount(fragment, false, null); }
@Test public void onActivityResult_sendsAnalyticsEventOnCancel() { BraintreeFragment fragment = new MockFragmentBuilder() .build(); Venmo.onActivityResult(fragment, Activity.RESULT_CANCELED, new Intent()); verify(fragment).sendAnalyticsEvent("pay-with-venmo.app-switch.canceled"); }
public AnalyticsEvent(Context context, String sessionId, String integration, String event) { this.event = "android." + integration + "." + event; this.timestamp = System.currentTimeMillis() / 1000; metadata = new JSONObject(); try { metadata.put(SESSION_ID_KEY, sessionId) .put(DEVICE_NETWORK_TYPE_KEY, getNetworkType(context)) .put(USER_INTERFACE_ORIENTATION_KEY, getUserOrientation(context)) .put(MERCHANT_APP_VERSION_KEY, getAppVersion(context)) .put(PAYPAL_INSTALLED_KEY, isPayPalInstalled(context)) .put(VENMO_INSTALLED_KEY, Venmo.isVenmoInstalled(context)); } catch (JSONException ignored) {} }
static Intent getLaunchIntent(VenmoConfiguration venmoConfiguration, String profileId, BraintreeFragment fragment) { Intent venmoIntent = getVenmoIntent() .putExtra(EXTRA_MERCHANT_ID, profileId) .putExtra(EXTRA_ACCESS_TOKEN, venmoConfiguration.getAccessToken()) .putExtra(EXTRA_ENVIRONMENT, venmoConfiguration.getEnvironment()); try { JSONObject braintreeData = new JSONObject(); JSONObject meta = new MetadataBuilder() .sessionId(fragment.getSessionId()) .integration(fragment.getIntegrationType()) .version() .build(); braintreeData.put(META_KEY, meta); venmoIntent.putExtra(EXTRA_BRAINTREE_DATA, braintreeData.toString()); } catch (JSONException ignored) {} return venmoIntent; }
@Test public void onActivityResult_performsVaultRequestIfRequestPersisted() throws InvalidArgumentException, NoSuchAlgorithmException { Configuration configuration = getConfigurationFromFixture(); Authorization clientToken = Authorization.fromString(stringFromFixture("base_64_client_token.txt")); disableSignatureVerification(); BraintreeFragment fragment = new MockFragmentBuilder() .context(VenmoInstalledContextFactory.venmoInstalledContext(true, RuntimeEnvironment.application)) .configuration(configuration) .authorization(clientToken) .sessionId("session-id") .build(); Venmo.authorizeAccount(fragment, true); mockStatic(TokenizationClient.class); Venmo.onActivityResult(fragment, Activity.RESULT_OK, new Intent()); verifyStatic(); TokenizationClient.tokenize(eq(fragment), any(VenmoAccountBuilder.class), any(PaymentMethodNonceCallback.class)); }
public void launchVenmo(View v) { setProgressBarIndeterminateVisibility(true); Venmo.authorizeAccount(mBraintreeFragment, Settings.vaultVenmo(this) && !TextUtils.isEmpty(Settings.getCustomerId(this))); }
@Test public void onActivityResult_handlesVenmoResult() throws InvalidArgumentException { BraintreeFragment fragment = BraintreeFragment.newInstance(mActivity, TOKENIZATION_KEY); mockStatic(Venmo.class); Intent intent = new Intent(); fragment.onActivityResult(BraintreeRequestCodes.VENMO, Activity.RESULT_OK, intent); verifyStatic(); Venmo.onActivityResult(fragment, Activity.RESULT_OK, intent); }