package com.braintreepayments.api;

import android.net.Uri;

import com.braintreepayments.api.exceptions.ConfigurationException;
import com.braintreepayments.api.interfaces.ConfigurationListener;
import com.braintreepayments.api.interfaces.HttpResponseCallback;
import com.braintreepayments.api.interfaces.PaymentMethodNonceCallback;
import com.braintreepayments.api.models.Configuration;
import com.braintreepayments.api.models.PaymentMethodNonce;
import com.braintreepayments.api.models.UnionPayCapabilities;
import com.braintreepayments.api.models.UnionPayCardBuilder;
import com.braintreepayments.api.models.UnionPayConfiguration;

import org.json.JSONException;
import org.json.JSONObject;

/**
 * Used to check, enroll, and tokenize UnionPay cards. For more information see the
 * documentation
 */
public class UnionPay {

    private static final String UNIONPAY_ENROLLMENT_ID_KEY = "unionPayEnrollmentId";
    private static final String UNIONPAY_SMS_REQUIRED_KEY = "smsCodeRequired";
    private static final String UNIONPAY_CAPABILITIES_PATH = TokenizationClient.versionedPath(
            "payment_methods/credit_cards/capabilities");
    private static final String UNIONPAY_ENROLLMENT_PATH = TokenizationClient.versionedPath("union_pay_enrollments");

    /**
     * Fetches the capabilities of a card. If the card needs to be enrolled use {@link
     * UnionPay#enroll(BraintreeFragment, UnionPayCardBuilder)}.
     *

     * On completion, returns the {@link UnionPayCapabilities} to
     * {@link com.braintreepayments.api.interfaces.UnionPayListener#onCapabilitiesFetched(UnionPayCapabilities)}
     *

     * On error, an exception will be passed back to
     * {@link com.braintreepayments.api.interfaces.BraintreeErrorListener#onError(Exception)}
     *
     * @param fragment {@link BraintreeFragment}
     * @param cardNumber The card number to check for Union Pay capabilities.
     */
    public static void fetchCapabilities(final BraintreeFragment fragment, final String cardNumber) {
        fragment.waitForConfiguration(new ConfigurationListener() {
            @Override
            public void onConfigurationFetched(Configuration configuration) {
                if (!configuration.getUnionPay().isEnabled()) {
                    fragment.postCallback(new ConfigurationException("UnionPay is not enabled"));
                    return;
                }

                String fetchCapabilitiesUrl = Uri.parse(UNIONPAY_CAPABILITIES_PATH)
                        .buildUpon()
                        .appendQueryParameter("creditCard[number]", cardNumber)
                        .build()
                        .toString();

                fragment.getHttpClient().get(fetchCapabilitiesUrl, new HttpResponseCallback() {
                    @Override
                    public void success(String responseBody) {
                        fragment.postCallback(UnionPayCapabilities.fromJson(responseBody));
                        fragment.sendAnalyticsEvent("union-pay.capabilities-received");
                    }

                    @Override
                    public void failure(Exception exception) {
                        fragment.postCallback(exception);
                        fragment.sendAnalyticsEvent("union-pay.capabilities-failed");
                    }
                });
            }
        });
    }

    /**
     * Enrolls a Union Pay card. Only call this method if the card needs to be enrolled. Check {@link
     * UnionPay#fetchCapabilities(BraintreeFragment, String)} if your card needs to be enrolled.
     *

     * On completion, returns a enrollmentId to
     * {@link com.braintreepayments.api.interfaces.UnionPayListener#onSmsCodeSent(String, boolean)}
     * This enrollmentId needs to be applied to {@link UnionPayCardBuilder} along with the SMS code
     * collected from the merchant before invoking {@link UnionPay#tokenize(BraintreeFragment, UnionPayCardBuilder)}
     *

     * On error, an exception will be passed back to
     * {@link com.braintreepayments.api.interfaces.BraintreeErrorListener#onError(Exception)}
     *
     * @param fragment {@link BraintreeFragment}
     * @param unionPayCardBuilder {@link UnionPayCardBuilder}
     */
    public static void enroll(final BraintreeFragment fragment, final UnionPayCardBuilder unionPayCardBuilder) {
        fragment.waitForConfiguration(new ConfigurationListener() {
                @Override
                public void onConfigurationFetched(Configuration configuration) {
                    UnionPayConfiguration unionPayConfiguration = configuration.getUnionPay();
                    if (!unionPayConfiguration.isEnabled()) {
                        fragment.postCallback(new ConfigurationException("UnionPay is not enabled"));
                        return;
                    }

                    try {
                        JSONObject enrollmentPayloadJson =
                                unionPayCardBuilder.buildEnrollment();
                        fragment.getHttpClient().post(UNIONPAY_ENROLLMENT_PATH, enrollmentPayloadJson.toString(),
                                new HttpResponseCallback() {
                                    @Override
                                    public void success(String responseBody) {
                                        try {
                                            JSONObject response = new JSONObject(responseBody);
                                            String enrollmentId = response.getString(UNIONPAY_ENROLLMENT_ID_KEY);
                                            boolean smsCodeRequired = response.getBoolean(UNIONPAY_SMS_REQUIRED_KEY);
                                            fragment.postUnionPayCallback(enrollmentId, smsCodeRequired);
                                            fragment.sendAnalyticsEvent("union-pay.enrollment-succeeded");
                                        } catch (JSONException e) {
                                            failure(e);
                                        }
                                    }

                                    @Override
                                    public void failure(Exception exception) {
                                        fragment.postCallback(exception);
                                        fragment.sendAnalyticsEvent("union-pay.enrollment-failed");
                                    }
                                }
                        );
                    } catch (JSONException exception) {
                        fragment.postCallback(exception);
                    }
                }
            }
        );
    }

    /**
     * Create a {@link com.braintreepayments.api.models.CardNonce}. Note that if the card is a UnionPay card,
     * {@link UnionPayCardBuilder#enrollmentId(String)} and {@link UnionPayCardBuilder#smsCode(String)}
     * need to be set for tokenization to succeed.
     *

     * On completion, returns the {@link com.braintreepayments.api.models.PaymentMethodNonce} to {@link
     * com.braintreepayments.api.interfaces.PaymentMethodNonceCreatedListener}.
     *

     * If creation fails validation, {@link com.braintreepayments.api.interfaces.BraintreeErrorListener#onError(Exception)}
     * will be called with the resulting {@link com.braintreepayments.api.exceptions.ErrorWithResponse}.
     *

     * If an error not due to validation (server error, network issue, etc.) occurs, {@link
     * com.braintreepayments.api.interfaces.BraintreeErrorListener#onError(Exception)} will be called with the {@link
     * Exception} that occurred.
     *
     * @param fragment {@link BraintreeFragment}
     * @param unionPayCardBuilder {@link UnionPayCardBuilder}
     */
    public static void tokenize(final BraintreeFragment fragment, final UnionPayCardBuilder unionPayCardBuilder) {
        TokenizationClient.tokenize(fragment, unionPayCardBuilder, new PaymentMethodNonceCallback() {
            @Override
            public void success(PaymentMethodNonce paymentMethodNonce) {
                fragment.postCallback(paymentMethodNonce);
                fragment.sendAnalyticsEvent("union-pay.nonce-received");
            }

            @Override
            public void failure(Exception exception) {
                fragment.postCallback(exception);
                fragment.sendAnalyticsEvent("union-pay.nonce-failed");
            }
        });
    }
}


package com.braintreepayments.api.models;

import android.os.Parcel;
import android.os.Parcelable;

import com.braintreepayments.api.Json;

import org.json.JSONException;
import org.json.JSONObject;

/**
 * {@link PaymentMethodNonce} representing a local payment.
 *
 * @see {@link PaymentMethodNonce}
 */
public class LocalPaymentResult extends PaymentMethodNonce implements Parcelable {

    protected static final String TYPE = "PayPalAccount";
    protected static final String API_RESOURCE_KEY = "paypalAccounts";

    private static final String DETAILS_KEY = "details";
    private static final String EMAIL_KEY = "email";
    private static final String PAYER_INFO_KEY = "payerInfo";
    private static final String ACCOUNT_ADDRESS_KEY = "accountAddress";
    private static final String SHIPPING_ADDRESS_KEY = "shippingAddress";
    private static final String BILLING_ADDRESS_KEY = "billingAddress";
    private static final String FIRST_NAME_KEY = "firstName";
    private static final String LAST_NAME_KEY = "lastName";
    private static final String PHONE_KEY = "phone";
    private static final String PAYER_ID_KEY = "payerId";
    private static final String CLIENT_METADATA_ID_KEY = "correlationId";
    private static final String TYPE_KEY = "type";

    private String mClientMetadataId;
    private PostalAddress mBillingAddress;
    private PostalAddress mShippingAddress;
    private String mGivenName;
    private String mSurname;
    private String mPhone;
    private String mEmail;
    private String mPayerId;
    private String mType;

    /**
     * Convert an API response to a {@link LocalPaymentResult}.
     *
     * @param json Raw JSON representation of a {@link LocalPaymentResult}.
     * @return {@link LocalPaymentResult} for use in payment method selection UIs.
     */
    public static LocalPaymentResult fromJson(String json) throws JSONException {
        LocalPaymentResult localPaymentResult = new LocalPaymentResult();
        localPaymentResult.fromJson(LocalPaymentResult.getJsonObjectForType(API_RESOURCE_KEY, new JSONObject(json)));

        return localPaymentResult;
    }

    /**
     * Generates a {@link LocalPaymentResult} from the {@link JSONObject}.
     *
     * @param json {@link JSONObject} that holds properties for {@link LocalPaymentResult}.
     * @throws JSONException
     */
    protected void fromJson(JSONObject json) throws JSONException {
        super.fromJson(json);

        JSONObject details = json.getJSONObject(DETAILS_KEY);
        mEmail = Json.optString(details, EMAIL_KEY, null);
        mClientMetadataId = Json.optString(details, CLIENT_METADATA_ID_KEY, null);
        mType = Json.optString(json, TYPE_KEY, "PayPalAccount");

        try {
            JSONObject payerInfo = details.getJSONObject(PAYER_INFO_KEY);

            JSONObject billingAddress;
            if (payerInfo.has(ACCOUNT_ADDRESS_KEY)) {
                billingAddress = payerInfo.optJSONObject(ACCOUNT_ADDRESS_KEY);
            } else {
                billingAddress = payerInfo.optJSONObject(BILLING_ADDRESS_KEY);
            }

            JSONObject shippingAddress = payerInfo.optJSONObject(SHIPPING_ADDRESS_KEY);

            mBillingAddress = PostalAddressParser.fromJson(billingAddress);
            mShippingAddress = PostalAddressParser.fromJson(shippingAddress);

            mGivenName = Json.optString(payerInfo, FIRST_NAME_KEY, "");
            mSurname = Json.optString(payerInfo, LAST_NAME_KEY, "");
            mPhone = Json.optString(payerInfo, PHONE_KEY, "");
            mPayerId = Json.optString(payerInfo, PAYER_ID_KEY, "");

            if(mEmail == null) {
                mEmail = Json.optString(payerInfo, EMAIL_KEY, null);
            }
        } catch (JSONException e) {
            mBillingAddress = new PostalAddress();
            mShippingAddress = new PostalAddress();
        }
    }

    /**
     * @return The email address associated with this local payment
     */
    public String getEmail() {
        return mEmail;
    }

    /**
     * @return The description of this local payment for displaying to a customer
     */
    @Override
    public String getDescription() {
        return super.getDescription();
    }

    /**
     * @return The type of this {@link PaymentMethodNonce}
     */
    @Override
    public String getTypeLabel() {
        return mType;
    }

    /**
     * @return The billing address of the user if requested with additional scopes.
     */
    public PostalAddress getBillingAddress() {
        return mBillingAddress;
    }

    /**
     * @return The shipping address of the user provided by checkout flows.
     */
    public PostalAddress getShippingAddress() {
        return mShippingAddress;
    }

    /**
     * @return The first name associated with the local payment.
     */
    public String getGivenName() {
        return mGivenName;
    }

    /**
     * @return The last name associated with the local payment.
     */
    public String getSurname() {
        return mSurname;
    }

    /**
     * @return The phone number associated with the local payment.
     */
    public String getPhone() {
        return mPhone;
    }

    /**
     * @return The ClientMetadataId associated with this transaction.
     */
    public String getClientMetadataId(){
        return mClientMetadataId;
    }

    /**
     * @return The Payer ID provided in local payment flows.
     */
    public String getPayerId(){
        return mPayerId;
    }

    public LocalPaymentResult() {}

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        super.writeToParcel(dest, flags);
        dest.writeString(mClientMetadataId);
        dest.writeParcelable(mBillingAddress, flags);
        dest.writeParcelable(mShippingAddress, flags);
        dest.writeString(mGivenName);
        dest.writeString(mSurname);
        dest.writeString(mEmail);
        dest.writeString(mPhone);
        dest.writeString(mPayerId);
        dest.writeString(mType);
    }

    private LocalPaymentResult(Parcel in) {
        super(in);
        mClientMetadataId = in.readString();
        mBillingAddress = in.readParcelable(PostalAddress.class.getClassLoader());
        mShippingAddress = in.readParcelable(PostalAddress.class.getClassLoader());
        mGivenName = in.readString();
        mSurname = in.readString();
        mEmail = in.readString();
        mPhone = in.readString();
        mPayerId = in.readString();
        mType = in.readString();
    }

    public static final Creator<LocalPaymentResult> CREATOR = new Creator<LocalPaymentResult>() {
        public LocalPaymentResult createFromParcel(Parcel source) {
            return new LocalPaymentResult(source);
        }

        public LocalPaymentResult[] newArray(int size) {
            return new LocalPaymentResult[size];
        }
    };
}


package com.braintreepayments.api;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.text.TextUtils;

import com.braintreepayments.api.exceptions.AppSwitchNotAvailableException;
import com.braintreepayments.api.interfaces.ConfigurationListener;
import com.braintreepayments.api.interfaces.PaymentMethodNonceCallback;
import com.braintreepayments.api.internal.AppHelper;
import com.braintreepayments.api.internal.BraintreeSharedPreferences;
import com.braintreepayments.api.internal.SignatureVerification;
import com.braintreepayments.api.models.BraintreeRequestCodes;
import com.braintreepayments.api.models.ClientToken;
import com.braintreepayments.api.models.Configuration;
import com.braintreepayments.api.models.MetadataBuilder;
import com.braintreepayments.api.models.PaymentMethodNonce;
import com.braintreepayments.api.models.VenmoAccountBuilder;
import com.braintreepayments.api.models.VenmoAccountNonce;
import com.braintreepayments.api.models.VenmoConfiguration;

import org.json.JSONException;
import org.json.JSONObject;

import androidx.appcompat.app.AppCompatActivity;

/**
 * Used to create and tokenize Venmo accounts. For more information see the documentation
 */
public class Venmo {

    private static final String VAULT_VENMO_KEY = "com.braintreepayments.api.Venmo.VAULT_VENMO_KEY";

    private static final String META_KEY = "_meta";

    static final String EXTRA_MERCHANT_ID = "com.braintreepayments.api.MERCHANT_ID";
    static final String EXTRA_ACCESS_TOKEN = "com.braintreepayments.api.ACCESS_TOKEN";
    static final String EXTRA_ENVIRONMENT = "com.braintreepayments.api.ENVIRONMENT";
    static final String EXTRA_BRAINTREE_DATA = "com.braintreepayments.api.EXTRA_BRAINTREE_DATA";
    static final String EXTRA_PAYMENT_METHOD_NONCE = "com.braintreepayments.api.EXTRA_PAYMENT_METHOD_NONCE";
    static final String EXTRA_USERNAME = "com.braintreepayments.api.EXTRA_USER_NAME";

    static final String PACKAGE_NAME = "com.venmo";
    static final String APP_SWITCH_ACTIVITY = "controller.SetupMerchantActivity";
    static final String CERTIFICATE_SUBJECT = "CN=Andrew Kortina,OU=Engineering,O=Venmo,L=Philadelphia,ST=PA,C=US";
    static final String CERTIFICATE_ISSUER = "CN=Andrew Kortina,OU=Engineering,O=Venmo,L=Philadelphia,ST=PA,C=US";
    static final int PUBLIC_KEY_HASH_CODE = -129711843;

    /**
     * @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);
    }

    /**
     * Launches an {@link Intent} pointing to the Venmo app on the Google Play Store
     *
     * @param fragment used to open the Venmo's Google Play Store
     */
    public static void openVenmoAppPageInGooglePlay(BraintreeFragment fragment) {
        fragment.sendAnalyticsEvent("android.pay-with-venmo.app-store.invoked");
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setData(Uri.parse(
                "https://play.google.com/store/apps/details?id=" + PACKAGE_NAME));
        fragment.startActivity(intent);
    }

    private static Intent getVenmoIntent() {
        return new Intent().setComponent(new ComponentName(PACKAGE_NAME, PACKAGE_NAME + "." + APP_SWITCH_ACTIVITY));
    }

    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;
    }

    /**
     * Start the Pay With Venmo flow. This will app switch to the Venmo app.
     *

     * 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 our
     * docs on client authorization for more info.
     * @param profileId The Venmo profile ID to be used during payment authorization. Customers will see the business
     * name and logo associated with this Venmo profile, and it will show up in the Venmo app as a "Connected Merchant".
     * Venmo profile IDs can be found in the Braintree Control Panel. Passing `null` will use the default Venmo profile.
     */
    public static void authorizeAccount(final BraintreeFragment fragment, final boolean vault, final String profileId) {
        fragment.waitForConfiguration(new ConfigurationListener() {
            @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");
                }
            }
        });
    }

    /**
     * Start the Venmo flow. This will app switch to the Venmo app.
     *

     * 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 our
     * docs on client authorization for more info.
     */
    public static void authorizeAccount(final BraintreeFragment fragment, final boolean vault) {
        authorizeAccount(fragment, vault, null);
    }

    public static void authorizeAccount(final BraintreeFragment fragment) {
        authorizeAccount(fragment, false, null);
    }

    private static void persistVenmoVaultOption(boolean shouldVault, Context context) {
        BraintreeSharedPreferences.getSharedPreferences(context).edit()
                .putBoolean(VAULT_VENMO_KEY, shouldVault)
                .apply();
    }

    private static boolean shouldVault(Context context) {
        return BraintreeSharedPreferences.getSharedPreferences(context)
                .getBoolean(VAULT_VENMO_KEY, false);
    }

    static void onActivityResult(final BraintreeFragment fragment, int resultCode, Intent data) {
        if (resultCode == AppCompatActivity.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 == AppCompatActivity.RESULT_CANCELED) {
            fragment.sendAnalyticsEvent("pay-with-venmo.app-switch.canceled");
        }
    }

    private static void vault(final BraintreeFragment fragment, String nonce) {
        VenmoAccountBuilder vaultBuilder = new VenmoAccountBuilder()
                .nonce(nonce);
        TokenizationClient.tokenize(fragment, vaultBuilder, new PaymentMethodNonceCallback() {
            @Override
            public void success(PaymentMethodNonce paymentMethodNonce) {
                fragment.postCallback(paymentMethodNonce);
                fragment.sendAnalyticsEvent("pay-with-venmo.vault.success");
            }

            @Override
            public void failure(Exception exception) {
                fragment.postCallback(exception);
                fragment.sendAnalyticsEvent("pay-with-venmo.vault.failed");
            }
        });
    }
}


package com.braintreepayments.api.models;

import org.json.JSONArray;
import org.json.JSONObject;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * Contains the remote card configuration for the Braintree SDK.
 */
public class CardConfiguration {

    private static final String SUPPORTED_CARD_TYPES_KEY = "supportedCardTypes";
    private static final String COLLECT_DEVICE_DATA_KEY = "collectDeviceData";

    private final Set<String> mSupportedCardTypes = new HashSet<>();
    private boolean mCollectFraudData = false;

    /**
     * Parse a {@link CardConfiguration} from json.
     *
     * @param json The {@link JSONObject} to parse.
     * @return a {@link CardConfiguration} instance with the data that was able to be parsed from the {@link JSONObject}.
     */
    public static CardConfiguration fromJson(JSONObject json) {
        if (json == null) {
            json = new JSONObject();
        }

        CardConfiguration cardConfiguration = new CardConfiguration();

        JSONArray jsonArray = json.optJSONArray(SUPPORTED_CARD_TYPES_KEY);
        if (jsonArray != null) {
            for (int i = 0; i < jsonArray.length(); i++) {
                cardConfiguration.mSupportedCardTypes.add(jsonArray.optString(i, ""));
            }
        }
        cardConfiguration.mCollectFraudData = json.optBoolean(COLLECT_DEVICE_DATA_KEY, false);
        return cardConfiguration;
    }

    /**
     * @return a {@link Set<String>} of card types supported by the merchant.
     */
    public Set<String> getSupportedCardTypes() {
        return Collections.unmodifiableSet(mSupportedCardTypes);
    }

    /**
     * @return if fraud data collection should occur.
     */
    public boolean isFraudDataCollectionEnabled() {
        return mCollectFraudData;
    }
}


package com.braintreepayments.api;

import android.net.Uri;

import com.braintreepayments.api.interfaces.ConfigurationListener;
import com.braintreepayments.api.interfaces.HttpResponseCallback;
import com.braintreepayments.api.models.AmericanExpressRewardsBalance;
import com.braintreepayments.api.models.Configuration;

import org.json.JSONException;

/**
 * Used to integrate with Braintree's American Express API
 */
public class AmericanExpress {

    private static final String AMEX_REWARDS_BALANCE_PATH = TokenizationClient.versionedPath(
            "payment_methods/amex_rewards_balance");

    /**
     * Gets the rewards balance associated with a Braintree nonce. Only for American Express cards.
     *
     * @param fragment the {@link BraintreeFragment} This fragment will also be responsible
     * for handling callbacks to it's listeners
     * @param nonce A nonce representing a card that will be used to look up the rewards balance
     * @param currencyIsoCode The currencyIsoCode to use. Example: 'USD'
     */
    public static void getRewardsBalance(final BraintreeFragment fragment, final String nonce,
            final String currencyIsoCode) {
        fragment.waitForConfiguration(new ConfigurationListener() {
            @Override
            public void onConfigurationFetched(Configuration configuration) {
                String getRewardsBalanceUrl = Uri.parse(AMEX_REWARDS_BALANCE_PATH)
                        .buildUpon()
                        .appendQueryParameter("paymentMethodNonce", nonce)
                        .appendQueryParameter("currencyIsoCode", currencyIsoCode)
                        .build()
                        .toString();

                fragment.sendAnalyticsEvent("amex.rewards-balance.start");
                fragment.getHttpClient().get(getRewardsBalanceUrl, new HttpResponseCallback() {
                    @Override
                    public void success(String responseBody) {
                        fragment.sendAnalyticsEvent("amex.rewards-balance.success");
                        try {
                            fragment.postAmericanExpressCallback(AmericanExpressRewardsBalance.fromJson(responseBody));
                        } catch (JSONException e) {
                            fragment.sendAnalyticsEvent("amex.rewards-balance.parse.failed");
                            fragment.postCallback(e);
                        }
                    }

                    @Override
                    public void failure(Exception exception) {
                        fragment.postCallback(exception);
                        fragment.sendAnalyticsEvent("amex.rewards-balance.error");
                    }
                });
            }
        });
    }
}


package com.braintreepayments.api.models;

import com.braintreepayments.api.Json;

import org.json.JSONException;
import org.json.JSONObject;

/**
 * A PayPal payment resource
 */
public class PayPalPaymentResource {

    private static final String PAYMENT_RESOURCE_KEY = "paymentResource";
    private static final String REDIRECT_URL_KEY = "redirectUrl";
    private static final String AGREEMENT_SETUP_KEY = "agreementSetup";
    private static final String APPROVAL_URL_KEY = "approvalUrl";

    private String mRedirectUrl;

    public PayPalPaymentResource redirectUrl(String redirectUrl) {
        mRedirectUrl = redirectUrl;
        return this;
    }

    /**
     * The redirectUrl for the payment used by One Touch Core for authorization
     *
     * @return a redirect URL string containing an EC token
     */
    public String getRedirectUrl() {
        return mRedirectUrl;
    }

    /**
     * Create a PayPalPaymentResource from a jsonString. Checks for keys associated with
     * Single Payment and Billing Agreement flows.
     *
     * @param jsonString a valid JSON string representing the payment resource
     * @return a PayPal payment resource
     * @throws JSONException
     */
    public static PayPalPaymentResource fromJson(String jsonString) throws JSONException {
        JSONObject json = new JSONObject(jsonString);

        PayPalPaymentResource payPalPaymentResource = new PayPalPaymentResource();
        JSONObject redirectJson = json.optJSONObject(PAYMENT_RESOURCE_KEY);
        if(redirectJson != null) {
            payPalPaymentResource.redirectUrl(Json.optString(redirectJson, REDIRECT_URL_KEY, ""));
        } else {
            redirectJson = json.optJSONObject(AGREEMENT_SETUP_KEY);
            payPalPaymentResource.redirectUrl(Json.optString(redirectJson, APPROVAL_URL_KEY, ""));
        }
        return payPalPaymentResource;
    }
}


package com.braintreepayments.api.internal;

import android.net.Uri;

import com.braintreepayments.api.core.BuildConfig;
import com.braintreepayments.api.exceptions.AuthorizationException;
import com.braintreepayments.api.exceptions.ErrorWithResponse;
import com.braintreepayments.api.exceptions.UnprocessableEntityException;
import com.braintreepayments.api.interfaces.HttpResponseCallback;
import com.braintreepayments.api.models.Authorization;
import com.braintreepayments.api.models.ClientToken;
import com.braintreepayments.api.models.TokenizationKey;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.net.HttpURLConnection;

import javax.net.ssl.SSLException;

/**
 * Network request class that handles Braintree request specifics and threading.
 */
public class BraintreeHttpClient extends HttpClient {

    private static final String AUTHORIZATION_FINGERPRINT_KEY = "authorizationFingerprint";
    private static final String TOKENIZATION_KEY_HEADER_KEY = "Client-Key";

    private final Authorization mAuthorization;

    public BraintreeHttpClient(Authorization authorization) {
        mAuthorization = authorization;

        setUserAgent(getUserAgent());

        try {
            setSSLSocketFactory(new TLSSocketFactory(BraintreeGatewayCertificate.getCertInputStream()));
        } catch (SSLException e) {
            setSSLSocketFactory(null);
        }
    }

    /**
     * @return User Agent {@link String} for the current SDK version.
     */
    public static String getUserAgent() {
        return "braintree/android/" + BuildConfig.VERSION_NAME;
    }

    /**
     * Make a HTTP GET request to Braintree using the base url, path and authorization provided.
     * If the path is a full url, it will be used instead of the previously provided url.
     *
     * @param path The path or url to request from the server via GET
     * @param callback The {@link HttpResponseCallback} to receive the response or error.
     */
    @Override
    public void get(String path, HttpResponseCallback callback) {
        if (path == null) {
            postCallbackOnMainThread(callback, new IllegalArgumentException("Path cannot be null"));
            return;
        }

        Uri uri;
        if (path.startsWith("http")) {
            uri = Uri.parse(path);
        } else {
            uri = Uri.parse(mBaseUrl + path);
        }

        if (mAuthorization instanceof ClientToken) {
            uri = uri.buildUpon()
                    .appendQueryParameter(AUTHORIZATION_FINGERPRINT_KEY,
                            ((ClientToken) mAuthorization).getAuthorizationFingerprint())
                    .build();
        }

        super.get(uri.toString(), callback);
    }

    /**
     * Make a HTTP POST request to Braintree using the base url, path and authorization provided.
     * If the path is a full url, it will be used instead of the previously provided url.
     *
     * @param path The path or url to request from the server via HTTP POST
     * @param data The body of the POST request
     * @param callback The {@link HttpResponseCallback} to receive the response or error.
     */
    @Override
    public void post(String path, String data, HttpResponseCallback callback) {
        try {
            if (mAuthorization instanceof ClientToken) {
                data = new JSONObject(data)
                        .put(AUTHORIZATION_FINGERPRINT_KEY,
                                ((ClientToken) mAuthorization).getAuthorizationFingerprint())
                        .toString();
            }

            super.post(path, data, callback);
        } catch (JSONException e) {
            postCallbackOnMainThread(callback, e);
        }
    }

    /**
     * Makes a synchronous HTTP POST request to Braintree using the base url, path, and authorization provided.
     * @see BraintreeHttpClient#post(String, String, HttpResponseCallback)
     *
     * @param path the path or url to request from the server via HTTP POST
     * @param data the body of the post request
     * @return the HTTP response body
     */
    public String post(String path, String data) throws Exception {
        if (mAuthorization instanceof ClientToken) {
            data = new JSONObject(data)
                    .put(AUTHORIZATION_FINGERPRINT_KEY, ((ClientToken) mAuthorization).getAuthorizationFingerprint())
                    .toString();
        }
        return super.post(path, data);
    }

    @Override
    protected HttpURLConnection init(String url) throws IOException {
        HttpURLConnection connection = super.init(url);

        if (mAuthorization instanceof TokenizationKey) {
            connection.setRequestProperty(TOKENIZATION_KEY_HEADER_KEY, mAuthorization.getBearer());
        }

        return connection;
    }

    @Override
    protected String parseResponse(HttpURLConnection connection) throws Exception {
        try {
            return super.parseResponse(connection);
        } catch (AuthorizationException | UnprocessableEntityException e) {
            if (e instanceof AuthorizationException) {
                String errorMessage = new ErrorWithResponse(403, e.getMessage()).getMessage();
                throw new AuthorizationException(errorMessage);
            } else {
                throw new ErrorWithResponse(422, e.getMessage());
            }
        }
    }
}


package com.braintreepayments.api.models;

import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
import android.text.TextUtils;

import com.braintreepayments.api.Json;
import com.braintreepayments.api.Venmo;

import org.json.JSONObject;

/**
 * Contains the remote Venmo configuration for the Braintree SDK.
 */
public class VenmoConfiguration {

    private static final String ACCESS_TOKEN_KEY = "accessToken";
    private static final String ENVIRONMENT_KEY = "environment";
    private static final String MERCHANT_ID_KEY = "merchantId";

    private String mAccessToken;
    private String mEnvironment;
    private String mMerchantId;

    /**
     * Parses the Venmo configuration from json.
     *
     * @param json The json to parse.
     * @return A {@link VenmoConfiguration} instance with data that was able to be parsed from the
     * {@link JSONObject}.
     */
    static VenmoConfiguration fromJson(JSONObject json) {
        if (json == null) {
            json = new JSONObject();
        }

        VenmoConfiguration venmoConfiguration = new VenmoConfiguration();
        venmoConfiguration.mAccessToken = Json.optString(json, ACCESS_TOKEN_KEY, "");
        venmoConfiguration.mEnvironment = Json.optString(json, ENVIRONMENT_KEY, "");
        venmoConfiguration.mMerchantId = Json.optString(json, MERCHANT_ID_KEY, "");

        return venmoConfiguration;
    }

    /**
     * @return The access token to use with Venmo.
     */
    public String getAccessToken() {
        return mAccessToken;
    }

    /**
     * @return The merchant Id associated with this merchant's Venmo integration.
     */
    public String getMerchantId() {
        return mMerchantId;
    }

    /**
     * @return The Venmo environment the merchant is running in.
     */
    public String getEnvironment() {
        return mEnvironment;
    }

    /**
     * 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);
    }

    public boolean isAccessTokenValid() {
        return !TextUtils.isEmpty(mAccessToken);
    }
}


package com.braintreepayments.api.models;

import android.os.Parcel;
import android.os.Parcelable;

import org.json.JSONException;
import org.json.JSONObject;

/**
 * Class to parse and contain 3D Secure authentication responses
 */
public class ThreeDSecureAuthenticationResponse implements Parcelable {

    private static final String PAYMENT_METHOD_KEY = "paymentMethod";
    private static final String SUCCESS_KEY = "success";

    private CardNonce mCardNonce;
    private boolean mSuccess;
    private String mErrors;
    private String mException;

    /**
     * Used to parse a response from the Braintree Gateway to be used for 3D Secure.
     *
     * @param jsonString The json response from the Braintree Gateway 3D Secure authentication route.
     * @return The {@link ThreeDSecureAuthenticationResponse} to use when performing 3D Secure
     *         authentication.
     */
    public static ThreeDSecureAuthenticationResponse fromJson(String jsonString) {
        ThreeDSecureAuthenticationResponse authenticationResponse = new ThreeDSecureAuthenticationResponse();

        try {
            JSONObject json = new JSONObject(jsonString);

            JSONObject cardJson = json.optJSONObject(PAYMENT_METHOD_KEY);
            if (cardJson != null) {
                CardNonce cardNonce = new CardNonce();
                cardNonce.fromJson(cardJson);
                authenticationResponse.mCardNonce = cardNonce;
            }

            authenticationResponse.mSuccess = json.getBoolean(SUCCESS_KEY);

            if (!authenticationResponse.mSuccess) {
                authenticationResponse.mErrors = jsonString;
            }
        } catch (JSONException e) {
            authenticationResponse.mSuccess = false;
        }

        return authenticationResponse;
    }

    /**
     * @deprecated ThreeDSecure WebView flow is deprecated. Use the browser-switch flow.
     * {@see https://developers.braintreepayments.com/guides/3d-secure/client-side/android/v2}
     */
    @Deprecated
    public static ThreeDSecureAuthenticationResponse fromException(String exception) {
        ThreeDSecureAuthenticationResponse authenticationResponse = new ThreeDSecureAuthenticationResponse();

        authenticationResponse.mSuccess = false;
        authenticationResponse.mException = exception;

        return authenticationResponse;
    }

    /**
     * @return If the authentication was completed
     */
    public boolean isSuccess() {
        return mSuccess;
    }

    /**
     * @return The {@link CardNonce} associated with the 3D Secure
     *         authentication
     */
    public CardNonce getCardNonce() {
        return mCardNonce;
    }

    /**
     * @return Possible errors that occurred during the authentication
     */
    public String getErrors() {
        return mErrors;
    }

    /**
     * @return Possible exception that occurred during the authentication
     */
    public String getException() {
        return mException;
    }

    public ThreeDSecureAuthenticationResponse() {}

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeByte(mSuccess ? (byte) 1 : (byte) 0);
        dest.writeParcelable(mCardNonce, flags);
        dest.writeString(mErrors);
        dest.writeString(mException);
    }

    private ThreeDSecureAuthenticationResponse(Parcel in) {
        mSuccess = in.readByte() != 0;
        mCardNonce = in.readParcelable(CardNonce.class.getClassLoader());
        mErrors = in.readString();
        mException = in.readString();
    }

    public static final Creator<ThreeDSecureAuthenticationResponse> CREATOR =
            new Creator<ThreeDSecureAuthenticationResponse>() {
                public ThreeDSecureAuthenticationResponse createFromParcel(Parcel source) {
                    return new ThreeDSecureAuthenticationResponse(source);
                }

                public ThreeDSecureAuthenticationResponse[] newArray(int size) {
                    return new ThreeDSecureAuthenticationResponse[size];
                }
            };
}


package com.braintreepayments.api;

import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import android.text.TextUtils;

import com.braintreepayments.api.interfaces.BraintreeResponseListener;
import com.braintreepayments.api.interfaces.ConfigurationListener;
import com.braintreepayments.api.internal.UUIDHelper;
import com.braintreepayments.api.models.ClientToken;
import com.braintreepayments.api.models.Configuration;
import com.braintreepayments.api.models.PaymentMethodNonce;
import com.paypal.android.sdk.data.collector.InstallationIdentifier;
import com.paypal.android.sdk.data.collector.PayPalDataCollector;
import com.paypal.android.sdk.data.collector.PayPalDataCollectorRequest;
import com.paypal.android.sdk.onetouch.core.PayPalOneTouchCore;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.HashMap;

/**
 * DataCollector is used to collect device information to aid in fraud detection and prevention.
 */
public class DataCollector {

    private static final String DEVICE_SESSION_ID_KEY = "device_session_id";
    private static final String FRAUD_MERCHANT_ID_KEY = "fraud_merchant_id";
    private static final String CORRELATION_ID_KEY = "correlation_id";

    /**
     * Collect device information for fraud identification purposes.
     *
     * @param fragment {@link BraintreeFragment}
     * @param listener to be called with the device data String to send to Braintree.
     */
    public static void collectDeviceData(BraintreeFragment fragment, BraintreeResponseListener<String> listener) {
        collectDeviceData(fragment, null, listener);
    }

    /**
     * Collect device information for fraud identification purposes. This should be used in conjunction
     * with a non-aggregate fraud id.
     *
     * @param fragment {@link BraintreeFragment}
     * @param merchantId The fraud merchant id from Braintree.
     * @param listener listener to be called with the device data String to send to Braintree.
     */
    public static void collectDeviceData(final BraintreeFragment fragment, final String merchantId,
            final BraintreeResponseListener<String> listener) {
        fragment.waitForConfiguration(new ConfigurationListener() {
            @Override
            public void onConfigurationFetched(Configuration configuration) {
                final JSONObject deviceData = new JSONObject();

                try {
                    String clientMetadataId = getPayPalClientMetadataId(fragment.getApplicationContext());
                    if (!TextUtils.isEmpty(clientMetadataId)) {
                        deviceData.put(CORRELATION_ID_KEY, clientMetadataId);
                    }
                } catch (JSONException ignored) {}

                if (configuration.getKount().isEnabled()) {
                    final String id;
                    if (merchantId != null) {
                        id = merchantId;
                    } else {
                        id = configuration.getKount().getKountMerchantId();
                    }

                    try {
                        final String deviceSessionId = UUIDHelper.getFormattedUUID();
                        startDeviceCollector(fragment, id, deviceSessionId, new BraintreeResponseListener<String>() {
                            @Override
                            public void onResponse(String sessionId) {
                                try {
                                    deviceData.put(DEVICE_SESSION_ID_KEY, deviceSessionId);
                                    deviceData.put(FRAUD_MERCHANT_ID_KEY, id);
                                } catch (JSONException ignored) {}

                                listener.onResponse(deviceData.toString());
                            }
                        });
                    } catch (ClassNotFoundException | NoClassDefFoundError | NumberFormatException ignored) {
                        listener.onResponse(deviceData.toString());
                    }
                } else {
                    listener.onResponse(deviceData.toString());
                }
            }
        });
    }

    /**
     * Collect PayPal device information for fraud identification purposes.
     *
     * @param fragment {@link BraintreeFragment}
     * @param listener listener to be called with the device data String to send to Braintree.
     */
    public static void collectPayPalDeviceData(final BraintreeFragment fragment, final BraintreeResponseListener<String> listener) {
        final JSONObject deviceData = new JSONObject();

        try {
            String clientMetadataId = getPayPalClientMetadataId(fragment.getApplicationContext());
            if (!TextUtils.isEmpty(clientMetadataId)) {
                deviceData.put(CORRELATION_ID_KEY, clientMetadataId);
            }
        } catch (JSONException ignored) {}

        listener.onResponse(deviceData.toString());
    }

    /**
     * Collect device information for fraud identification purposes from PayPal only.
     *
     * @param context A valid {@link Context}
     * @return The client metadata id associated with the collected data.
     */
    public static String getPayPalClientMetadataId(Context context) {
        try {
            return PayPalOneTouchCore.getClientMetadataId(context);
        } catch (NoClassDefFoundError ignored) {}

        try {
            return PayPalDataCollector.getClientMetadataId(context);
        } catch (NoClassDefFoundError ignored) {}

        return "";
    }

    private static void startDeviceCollector(final BraintreeFragment fragment, final String merchantId,
            final String deviceSessionId, @Nullable final BraintreeResponseListener<String> listener)
            throws ClassNotFoundException, NumberFormatException {
        fragment.sendAnalyticsEvent("data-collector.kount.started");

        Class.forName(com.kount.api.DataCollector.class.getName());

        fragment.waitForConfiguration(new ConfigurationListener() {
            @Override
            public void onConfigurationFetched(Configuration configuration) {
                final com.kount.api.DataCollector dataCollector = com.kount.api.DataCollector.getInstance();
                dataCollector.setContext(fragment.getApplicationContext());
                dataCollector.setMerchantID(Integer.parseInt(merchantId));
                dataCollector.setLocationCollectorConfig(com.kount.api.DataCollector.LocationConfig.COLLECT);
                dataCollector.setEnvironment(getDeviceCollectorEnvironment(configuration.getEnvironment()));

                dataCollector.collectForSession(deviceSessionId, new com.kount.api.DataCollector.CompletionHandler() {
                    @Override
                    public void completed(String sessionID) {
                        fragment.sendAnalyticsEvent("data-collector.kount.succeeded");

                        if (listener != null) {
                            listener.onResponse(sessionID);
                        }
                    }

                    @Override
                    public void failed(String sessionID, final com.kount.api.DataCollector.Error error) {
                        fragment.sendAnalyticsEvent("data-collector.kount.failed");

                        if (listener != null) {
                            listener.onResponse(sessionID);
                        }
                    }
                });
            }
        });
    }

    static void collectRiskData(final BraintreeFragment fragment,
                                @NonNull final PaymentMethodNonce paymentMethodNonce) {
        fragment.waitForConfiguration(new ConfigurationListener() {
            @Override
            public void onConfigurationFetched(Configuration configuration) {
                if (configuration.getCardConfiguration().isFraudDataCollectionEnabled()) {
                    HashMap<String,String> additionalProperties = new HashMap<>();
                    additionalProperties.put("rda_tenant", "bt_card");
                    additionalProperties.put("mid", configuration.getMerchantId());

                    if (fragment.getAuthorization() instanceof ClientToken) {
                        String customerId = ((ClientToken)fragment.getAuthorization()).getCustomerId();
                        if (customerId != null) {
                            additionalProperties.put("cid", customerId);
                        }
                    }

                    PayPalDataCollectorRequest request = new PayPalDataCollectorRequest()
                            .setApplicationGuid(InstallationIdentifier.getInstallationGUID(fragment.getApplicationContext()))
                            .setClientMetadataId(paymentMethodNonce.getNonce())
                            .setDisableBeacon(true)
                            .setAdditionalData(additionalProperties);

                    PayPalDataCollector.getClientMetadataId(fragment.getApplicationContext(), request);
                }
            }
        });
    }

    @VisibleForTesting
    static int getDeviceCollectorEnvironment(String environment) {
        if ("production".equalsIgnoreCase(environment)) {
            return com.kount.api.DataCollector.ENVIRONMENT_PRODUCTION;
        }
        return com.kount.api.DataCollector.ENVIRONMENT_TEST;
    }
}


package com.braintreepayments.api.internal;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

public class SignatureVerification {

    /**
     * Used to disable signature verification for development and test.
     */
    static boolean sEnableSignatureVerification = true;

    /**
     * Check if an app has the correct, matching, signature. Used to prevent malicious apps from
     * impersonating other apps.
     *
     * @param context
     * @param packageName the package name of the app to verify.
     * @param certificateSubject the expected certificate subject of the app.
     * @param certificateIssuer the expected certificate issuer of the app.
     * @param publicKeyHashCode the hash code of the app's public key.
     * @return true is signature is valid or signature verification has been disabled.
     */
    @SuppressLint("PackageManagerGetSignatures")
    public static boolean isSignatureValid(Context context, String packageName,
            String certificateSubject, String certificateIssuer, int publicKeyHashCode) {
        if (!sEnableSignatureVerification) {
            return true;
        }

        PackageManager packageManager = context.getPackageManager();
        Signature[] signatures;
        try {
            signatures = packageManager
                    .getPackageInfo(packageName, PackageManager.GET_SIGNATURES).signatures;
        } catch (NameNotFoundException e) {
            return false;
        }

        InputStream certStream = null;
        boolean validated = (signatures.length != 0);
        for (Signature signature : signatures) {
            try {
                certStream = new ByteArrayInputStream(signature.toByteArray());

                X509Certificate x509Cert =
                        (X509Certificate) CertificateFactory.getInstance("X509")
                                .generateCertificate(certStream);

                String subject = x509Cert.getSubjectX500Principal().getName();
                String issuer = x509Cert.getIssuerX500Principal().getName();
                int actualPublicKeyHashCode = x509Cert.getPublicKey().hashCode();

                validated &= (certificateSubject.equals(subject) &&
                        certificateIssuer.equals(issuer) &&
                        publicKeyHashCode == actualPublicKeyHashCode);

                if (!validated) {
                    return false;
                }
            } catch (CertificateException e) {
                return false;
            } finally {
                try {
                    if (certStream != null) {
                        certStream.close();
                    }
                } catch(IOException ignored) {}
            }
        }

        return validated;
    }
}


package com.braintreepayments.api;

import android.content.res.Resources;
import android.net.Uri;
import android.os.Build;

import com.braintreepayments.api.exceptions.BraintreeException;
import com.braintreepayments.api.exceptions.PaymentMethodDeleteException;
import com.braintreepayments.api.interfaces.ConfigurationListener;
import com.braintreepayments.api.interfaces.HttpResponseCallback;
import com.braintreepayments.api.interfaces.PaymentMethodNoncesUpdatedListener;
import com.braintreepayments.api.internal.GraphQLConstants;
import com.braintreepayments.api.internal.GraphQLQueryHelper;
import com.braintreepayments.api.models.ClientToken;
import com.braintreepayments.api.models.Configuration;
import com.braintreepayments.api.models.MetadataBuilder;
import com.braintreepayments.api.models.PaymentMethodNonce;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.util.List;

/**
 * Class used to retrieve a customer's payment methods.
 */
public class PaymentMethod {

    protected static final String SINGLE_USE_TOKEN_ID = "singleUseTokenId";
    protected static final String VARIABLES = "variables";
    protected static final String INPUT = "input";
    protected static final String CLIENT_SDK_META_DATA = "clientSdkMetadata";

    /**
     * Retrieves the current list of {@link PaymentMethodNonce}s for the current customer.
     *

     * When finished, the {@link java.util.List} of {@link PaymentMethodNonce}s will be sent to {@link
     * PaymentMethodNoncesUpdatedListener#onPaymentMethodNoncesUpdated(List)}.
     *
     * @param fragment {@link BraintreeFragment}
     * @param defaultFirst when {@code true} the customer's default payment method will be first in the list, otherwise
     *        payment methods will be ordered my most recently used.
     */
    public static void getPaymentMethodNonces(final BraintreeFragment fragment, boolean defaultFirst) {
        final Uri uri = Uri.parse(TokenizationClient.versionedPath(TokenizationClient.PAYMENT_METHOD_ENDPOINT))
                .buildUpon()
                .appendQueryParameter("default_first", String.valueOf(defaultFirst))
                .appendQueryParameter("session_id", fragment.getSessionId())
                .build();

        fragment.waitForConfiguration(new ConfigurationListener() {
            @Override
            public void onConfigurationFetched(Configuration configuration) {
                fragment.getHttpClient().get(uri.toString(), new HttpResponseCallback() {
                    @Override
                    public void success(String responseBody) {
                        try {
                            fragment.postCallback(PaymentMethodNonce.parsePaymentMethodNonces(responseBody));
                            fragment.sendAnalyticsEvent("get-payment-methods.succeeded");
                        } catch (JSONException e) {
                            fragment.postCallback(e);
                            fragment.sendAnalyticsEvent("get-payment-methods.failed");
                        }
                    }

                    @Override
                    public void failure(Exception exception) {
                        fragment.postCallback(exception);
                        fragment.sendAnalyticsEvent("get-payment-methods.failed");
                    }
                });
            }
        });
    }

    /**
     * Retrieves the current list of {@link PaymentMethodNonce}s for the current customer.
     *

     * When finished, the {@link java.util.List} of {@link PaymentMethodNonce}s will be sent to {@link
     * PaymentMethodNoncesUpdatedListener#onPaymentMethodNoncesUpdated(List)}.
     *
     * @param fragment {@link BraintreeFragment}
     */
    public static void getPaymentMethodNonces(BraintreeFragment fragment) {
        getPaymentMethodNonces(fragment, false);
    }

    /**
     * Deletes a payment method owned by the customer whose id was used to generate the {@link ClientToken}
     * used to create the {@link BraintreeFragment}.
     *

     * Note: This method only works with Android Lollipop (>= 21) and above.
     * This will invoke {@link com.braintreepayments.api.interfaces.BraintreeErrorListener#onError(Exception)} when
     *

     *
A {@link com.braintreepayments.api.models.TokenizationKey} is used.

     *
The device is below Lollipop.

     *
If the request fails.

     *

     *
     * @param fragment {@link BraintreeFragment}
     * @param paymentMethodNonce The payment method nonce that references a vaulted payment method.
     */
    public static void deletePaymentMethod(final BraintreeFragment fragment,
                                           final PaymentMethodNonce paymentMethodNonce)  {

        boolean usesClientToken = fragment.getAuthorization() instanceof ClientToken;

        if (!usesClientToken) {
            fragment.postCallback(new BraintreeException("A client token with a customer id must be used to delete a payment method nonce."));
            return;
        }

        JSONObject base = new JSONObject();
        JSONObject variables = new JSONObject();
        JSONObject input = new JSONObject();

        try {
            base.put(CLIENT_SDK_META_DATA, new MetadataBuilder()
                    .sessionId(fragment.getSessionId())
                    .source("client")
                    .integration(fragment.getIntegrationType())
                    .build());

            base.put(GraphQLConstants.Keys.QUERY, GraphQLQueryHelper.getQuery(
                    fragment.getApplicationContext(), R.raw.delete_payment_method_mutation));
            input.put(SINGLE_USE_TOKEN_ID, paymentMethodNonce.getNonce());
            variables.put(INPUT, input);
            base.put(VARIABLES, variables);
            base.put(GraphQLConstants.Keys.OPERATION_NAME,
                    "DeletePaymentMethodFromSingleUseToken");
        } catch (Resources.NotFoundException | IOException | JSONException e) {
            fragment.postCallback(new BraintreeException("Unable to read GraphQL query"));
        }

        fragment.getGraphQLHttpClient().post(base.toString(), new HttpResponseCallback() {
            @Override
            public void success(String responseBody) {
                fragment.postPaymentMethodDeletedCallback(paymentMethodNonce);
                fragment.sendAnalyticsEvent("delete-payment-methods.succeeded");
            }

            @Override
            public void failure(Exception exception) {
                fragment.postCallback(new PaymentMethodDeleteException(paymentMethodNonce, exception));
                fragment.sendAnalyticsEvent("delete-payment-methods.failed");
            }
        });
    }
}


package com.paypal.android.sdk.onetouch.core;

import android.content.Context;
import android.content.Intent;
import androidx.annotation.MainThread;

import com.paypal.android.sdk.data.collector.PayPalDataCollector;
import com.paypal.android.sdk.onetouch.core.base.ContextInspector;
import com.paypal.android.sdk.onetouch.core.config.ConfigManager;
import com.paypal.android.sdk.onetouch.core.config.OAuth2Recipe;
import com.paypal.android.sdk.onetouch.core.config.Recipe;
import com.paypal.android.sdk.onetouch.core.enums.RequestTarget;
import com.paypal.android.sdk.onetouch.core.fpti.FptiManager;
import com.paypal.android.sdk.onetouch.core.fpti.TrackingPoint;
import com.paypal.android.sdk.onetouch.core.network.EnvironmentManager;
import com.paypal.android.sdk.onetouch.core.network.PayPalHttpClient;
import com.paypal.android.sdk.onetouch.core.sdk.AppSwitchHelper;
import com.paypal.android.sdk.onetouch.core.sdk.BrowserSwitchHelper;
import com.paypal.android.sdk.onetouch.core.sdk.PendingRequest;

/**
 * Central class for One Touch functionality.
 */
public class PayPalOneTouchCore {

    private static ContextInspector sContextInspector;
    private static ConfigManager sConfigManager;
    private static FptiManager sFptiManager;

    /**
     * @param context
     * @return {@code true} if the modern wallet app is installed (one that has either v1 or v2 touch
     * intents), {@code false} if the wallet app is older than the touch releases, or not present.
     */
    public static boolean isWalletAppInstalled(Context context) {
        initService(context);

        for (OAuth2Recipe recipe : sConfigManager.getConfig().getOauth2Recipes()) {
            if (recipe.getTarget() == RequestTarget.wallet && recipe.isValidAppTarget(context)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Get a {@link PendingRequest} containing an {@link Intent} used to start a PayPal
     * authentication request using the best possible authentication mechanism: wallet or browser.
     *
     * @param context
     * @param request the {@link Request} used to build the {@link Intent}.
     * @return {@link PendingRequest}. {@link PendingRequest#isSuccess()} should be
     *         checked before attempting to use the {@link Intent} it contains. If {@code true} an
     *         {@link Intent} was created and can be used to start a PayPal authentication request.
     *         If {@code false} it is not possible to authenticate given the current environment
     *         and request.
     */
    public static PendingRequest getStartIntent(Context context, Request request) {
        initService(context);

        // calling this method functionally does nothing, but ensures that we send off FPTI data about wallet installs.
        isWalletAppInstalled(context);

        Recipe recipe = request.getRecipeToExecute(context, sConfigManager.getConfig());

        if (recipe == null) {
            return new PendingRequest(false, null, null, null);
        }

        if (RequestTarget.wallet == recipe.getTarget()) {
            request.trackFpti(context, TrackingPoint.SwitchToWallet, recipe.getProtocol());
            return new PendingRequest(true, RequestTarget.wallet, request.getClientMetadataId(),
                    AppSwitchHelper.getAppSwitchIntent(sContextInspector, sConfigManager, request, recipe));
        } else {
            Intent intent = BrowserSwitchHelper.getBrowserSwitchIntent(sContextInspector, sConfigManager, request);
            if (intent != null) {
                return new PendingRequest(true, RequestTarget.browser, request.getClientMetadataId(), intent);
            } else {
                return new PendingRequest(false, RequestTarget.browser, request.getClientMetadataId(), null);
            }
        }
    }

    /**
     * Gets a {@link Result} from an {@link Intent} returned by either the PayPal Wallet app or
     * the browser.
     *
     * @param context
     * @param request the original {@link Request} that was used to get this {@link Result}.
     * @param data the {@link Intent} returned by either the PayPal Wallet app or the browser.
     * @return {@link Result}
     */
    public static Result parseResponse(Context context, Request request, Intent data) {
        initService(context);

        if (data != null && data.getData() != null) {
            return BrowserSwitchHelper.parseBrowserSwitchResponse(sContextInspector, request, data.getData());
        } else if (data != null && data.getExtras() != null && !data.getExtras().isEmpty()) {
            return AppSwitchHelper.parseAppSwitchResponse(sContextInspector, request, data);
        } else {
            request.trackFpti(context, TrackingPoint.Cancel, null);
            return new Result();
        }
    }

    /**
     * Gets a Client Metadata ID at the time of payment activity. Once a user initiates a PayPal payment
     * from their device, PayPal uses the Client Metadata ID to verify that the payment is
     * originating from a valid, user-consented device and application. This helps reduce fraud and
     * decrease declines. This method MUST be called prior to initiating a pre-consented payment (a
     * "future payment") from a mobile device. Pass the result to your server, to include in the
     * payment request sent to PayPal. Do not otherwise cache or store this value.
     *
     * @param context The application context
     * @return clientMetadataId Your server will send this to PayPal
     */
    @MainThread
    public static String getClientMetadataId(Context context) {
        return PayPalDataCollector.getClientMetadataId(context);
    }

    /**
     * Gets a Client Metadata ID at the time of payment activity. Once a user initiates a PayPal payment
     * from their device, PayPal uses the Client Metadata ID to verify that the payment is
     * originating from a valid, user-consented device and application. This helps reduce fraud and
     * decrease declines. This method MUST be called prior to initiating a pre-consented payment (a
     * "future payment") from a mobile device. Pass the result to your server, to include in the
     * payment request sent to PayPal. Do not otherwise cache or store this value.
     *
     * @param context The application context
     * @param pairingId The desired pairing id
     * @return clientMetadataId Your server will send this to PayPal
     */
    @MainThread
    public static String getClientMetadataId(Context context, String pairingId) {
        return PayPalDataCollector.getClientMetadataId(context, pairingId);
    }

    public static void useHardcodedConfig(Context context, boolean useHardcodedConfig) {
        initService(context);
        sConfigManager.useHardcodedConfig(useHardcodedConfig);
    }

    public static FptiManager getFptiManager(Context context) {
        initService(context);
        return sFptiManager;
    }

    private static void initService(Context context) {
        if (sConfigManager == null || sFptiManager == null) {
            PayPalHttpClient httpClient = new PayPalHttpClient()
                    .setBaseUrl(EnvironmentManager.LIVE_API_M_ENDPOINT);
            sConfigManager = new ConfigManager(getContextInspector(context), httpClient);
            sFptiManager = new FptiManager(getContextInspector(context), httpClient);
        }

        // always refresh configuration
        sConfigManager.refreshConfiguration();
    }

    private static ContextInspector getContextInspector(Context context) {
        if (null == sContextInspector) {
            sContextInspector = new ContextInspector(context);
        }
        return sContextInspector;
    }
}


package com.braintreepayments.api;

import android.content.Intent;
import android.net.Uri;

import com.braintreepayments.api.exceptions.BraintreeException;
import com.braintreepayments.api.exceptions.ErrorWithResponse;
import com.braintreepayments.api.exceptions.InvalidArgumentException;
import com.braintreepayments.api.interfaces.ConfigurationListener;
import com.braintreepayments.api.interfaces.HttpResponseCallback;
import com.braintreepayments.api.interfaces.PaymentMethodNonceCallback;
import com.braintreepayments.api.internal.ManifestValidator;
import com.braintreepayments.api.models.BraintreeRequestCodes;
import com.braintreepayments.api.models.CardBuilder;
import com.braintreepayments.api.models.Configuration;
import com.braintreepayments.api.models.PaymentMethodNonce;
import com.braintreepayments.api.models.ThreeDSecureAuthenticationResponse;
import com.braintreepayments.api.models.ThreeDSecureLookup;
import com.braintreepayments.api.models.ThreeDSecureRequest;

import org.json.JSONException;

import androidx.appcompat.app.AppCompatActivity;

/**
 * 3D Secure is a protocol that enables cardholders and issuers to add a layer of security
 * to e-commerce transactions via password entry at checkout.
 *
 * One of the primary reasons to use 3D Secure is to benefit from a shift in liability from the
 * merchant to the issuer, which may result in interchange savings. Please read our online
 * documentation
 * for a full explanation of 3D Secure.
 */
public class ThreeDSecure {

    /**
     * The versioned path of the 3D Secure assets to use. Hosted by Braintree.
     */
    private static final String THREE_D_SECURE_ASSETS_PATH = "/mobile/three-d-secure-redirect/0.1.5";

    /**
     * Verification is associated with a transaction amount and your merchant account. To specify a
     * different merchant account (or, in turn, currency), you will need to specify the merchant
     * account id when
     *     generating a client token
     *
     * During lookup the original payment method nonce is consumed and a new one is returned,
     * which points to the original payment method, as well as the 3D Secure verification.
     * Transactions created with this nonce will be 3D Secure, and benefit from the appropriate
     * liability shift if authentication is successful or fail with a 3D Secure failure.
     *
     * @param fragment the {@link BraintreeFragment} backing the http request. This fragment will
     *                  also be responsible for handling callbacks to it's listeners
     * @param cardBuilder The cardBuilder created from raw details. Will be tokenized before
     *                    the 3D Secure verification if performed.
     * @param amount The amount of the transaction in the current merchant account's currency
     */
    public static void performVerification(final BraintreeFragment fragment, final CardBuilder cardBuilder,
            final String amount) {
        TokenizationClient.tokenize(fragment, cardBuilder, new PaymentMethodNonceCallback() {
            @Override
            public void success(PaymentMethodNonce paymentMethodNonce) {
                performVerification(fragment, paymentMethodNonce.getNonce(), amount);
            }

            @Override
            public void failure(Exception exception) {
                fragment.postCallback(exception);
            }
        });
    }

    /**
     * Verification is associated with a transaction amount and your merchant account. To specify a
     * different merchant account (or, in turn, currency), you will need to specify the merchant
     * account id when
     *     generating a client token
     *
     * During lookup the original payment method nonce is consumed and a new one is returned,
     * which points to the original payment method, as well as the 3D Secure verification.
     * Transactions created with this nonce will be 3D Secure, and benefit from the appropriate
     * liability shift if authentication is successful or fail with a 3D Secure failure.
     *
     * @param fragment the {@link BraintreeFragment} backing the http request. This fragment will
     *                  also be responsible for handling callbacks to it's listeners
     * @param nonce The nonce that represents a card to perform a 3D Secure verification against.
     * @param amount The amount of the transaction in the current merchant account's currency.
     */
    public static void performVerification(final BraintreeFragment fragment, final String nonce, final String amount) {
        ThreeDSecureRequest request = new ThreeDSecureRequest()
            .nonce(nonce)
            .amount(amount);

        performVerification(fragment, request);
    }

    /**
     * Verification is associated with a transaction amount and your merchant account. To specify a
     * different merchant account (or, in turn, currency), you will need to specify the merchant
     * account id when
     *     generating a client token
     *
     * During lookup the original payment method nonce is consumed and a new one is returned,
     * which points to the original payment method, as well as the 3D Secure verification.
     * Transactions created with this nonce will be 3D Secure, and benefit from the appropriate
     * liability shift if authentication is successful or fail with a 3D Secure failure.
     *
     * @param fragment the {@link BraintreeFragment} backing the http request. This fragment will
     *                  also be responsible for handling callbacks to it's listeners
     * @param request the {@link ThreeDSecureRequest} with information used for authentication.
     */
    public static void performVerification(final BraintreeFragment fragment, final ThreeDSecureRequest request) {
        if (request.getAmount() == null || request.getNonce() == null) {
            fragment.postCallback(new InvalidArgumentException("The ThreeDSecureRequest nonce and amount cannot be null"));
            return;
        }

        fragment.waitForConfiguration(new ConfigurationListener() {
            @Override
            public void onConfigurationFetched(Configuration configuration) {
                if (!configuration.isThreeDSecureEnabled()) {
                    fragment.postCallback(new BraintreeException("Three D Secure is not enabled in the control panel"));
                    return;
                }

                final boolean supportsBrowserSwitch = ManifestValidator.isUrlSchemeDeclaredInAndroidManifest(
                        fragment.getApplicationContext(), fragment.getReturnUrlScheme(), BraintreeBrowserSwitchActivity.class);

                if (!supportsBrowserSwitch) {
                    fragment.sendAnalyticsEvent("three-d-secure.invalid-manifest");
                    fragment.postCallback(new BraintreeException("BraintreeBrowserSwitchActivity missing, " +
                            "incorrectly configured in AndroidManifest.xml or another app defines the same browser " +
                            "switch url as this app. See " +
                            "https://developers.braintreepayments.com/guides/client-sdk/android/v2#browser-switch " +
                            "for the correct configuration"));
                    return;
                }

                fragment.getHttpClient().post(TokenizationClient.versionedPath(
                        TokenizationClient.PAYMENT_METHOD_ENDPOINT + "/" + request.getNonce() +
                                "/three_d_secure/lookup"), request.build(), new HttpResponseCallback() {
                    @Override
                    public void success(String responseBody) {
                        try {
                            ThreeDSecureLookup threeDSecureLookup = ThreeDSecureLookup.fromJson(responseBody);
                            if (threeDSecureLookup.getAcsUrl() != null) {
                                launchBrowserSwitch(fragment, threeDSecureLookup);
                            } else {
                                fragment.postCallback(threeDSecureLookup.getCardNonce());
                            }
                        } catch (JSONException e) {
                            fragment.postCallback(e);
                        }
                    }

                    @Override
                    public void failure(Exception exception) {
                        fragment.postCallback(exception);
                    }
                });
            }
        });
    }

    protected static void onActivityResult(BraintreeFragment fragment, int resultCode, Intent data) {
        if (resultCode == AppCompatActivity.RESULT_OK) {
            Uri resultUri = data.getData();
            ThreeDSecureAuthenticationResponse authenticationResponse = ThreeDSecureAuthenticationResponse
                    .fromJson(resultUri.getQueryParameter("auth_response"));

            if (authenticationResponse.isSuccess()) {
                fragment.postCallback(authenticationResponse.getCardNonce());
            } else if (authenticationResponse.getException() != null) {
                fragment.postCallback(new BraintreeException(authenticationResponse.getException()));
            } else {
                fragment.postCallback(new ErrorWithResponse(422, authenticationResponse.getErrors()));
            }
        }
    }

    private static void launchBrowserSwitch(BraintreeFragment fragment, ThreeDSecureLookup threeDSecureLookup) {
        String assetsBaseUrl = fragment.getConfiguration().getAssetsUrl() + THREE_D_SECURE_ASSETS_PATH;
        String returnUrl = String.format("%s/redirect.html?redirect_url=%s://x-callback-url/braintree/threedsecure?",
                assetsBaseUrl,
                fragment.getReturnUrlScheme());
        Uri redirectUri = Uri.parse(assetsBaseUrl + "/index.html")
                .buildUpon()
                .appendQueryParameter("AcsUrl", threeDSecureLookup.getAcsUrl())
                .appendQueryParameter("PaReq", threeDSecureLookup.getPareq())
                .appendQueryParameter("MD", threeDSecureLookup.getMd())
                .appendQueryParameter("TermUrl", threeDSecureLookup.getTermUrl())
                .appendQueryParameter("ReturnUrl", returnUrl)
                .build();

        fragment.browserSwitch(BraintreeRequestCodes.THREE_D_SECURE, redirectUri.toString());
    }
}


package com.braintreepayments.api.models;

import android.text.TextUtils;

import com.braintreepayments.api.Json;

import org.json.JSONObject;

/**
 * Contains the remote Kount configuration for the Braintree SDK.
 */
public class KountConfiguration {

    private final static String KOUNT_MERCHANT_ID_KEY = "kountMerchantId";

    private String mKountMerchantId;

    /**
     * Parse a {@link KountConfiguration} from json.
     *
     * @param json The {@link JSONObject} to parse.
     * @return a {@link KountConfiguration} instance with the data that was able to be parsed from
     * the {@link JSONObject}.
     */
    public static KountConfiguration fromJson(JSONObject json) {
        if (json == null) {
            json = new JSONObject();
        }

        KountConfiguration kountConfiguration = new KountConfiguration();
        kountConfiguration.mKountMerchantId = Json.optString(json, KOUNT_MERCHANT_ID_KEY, "");

        return kountConfiguration;
    }

    /**
     * @return {@code true} if Kount is enabled, {@code false} otherwise.
     */
    public boolean isEnabled() {
        return !TextUtils.isEmpty(mKountMerchantId);
    }

    /**
     * @return the Kount Merchant Id.
     */
    public String getKountMerchantId() {
        return mKountMerchantId;
    }
}


package com.braintreepayments.api;

import com.braintreepayments.api.interfaces.ConfigurationListener;
import com.braintreepayments.api.interfaces.PaymentMethodNonceCallback;
import com.braintreepayments.api.models.CardBuilder;
import com.braintreepayments.api.models.ClientToken;
import com.braintreepayments.api.models.Configuration;
import com.braintreepayments.api.models.PaymentMethodNonce;
import com.paypal.android.sdk.data.collector.InstallationIdentifier;
import com.paypal.android.sdk.data.collector.PayPalDataCollector;
import com.paypal.android.sdk.data.collector.PayPalDataCollectorRequest;

import java.util.HashMap;
import java.util.Map;

/**
 * Used to tokenize credit or debit cards using a {@link CardBuilder}. For more information see the
 * documentation
 */
public class Card {

    /**
     * Create a {@link com.braintreepayments.api.models.CardNonce}.
     *

     * On completion, returns the {@link com.braintreepayments.api.models.PaymentMethodNonce} to
     * {@link com.braintreepayments.api.interfaces.PaymentMethodNonceCreatedListener}.
     *

     * If creation fails validation, {@link com.braintreepayments.api.interfaces.BraintreeErrorListener#onError(Exception)}
     * will be called with the resulting {@link com.braintreepayments.api.exceptions.ErrorWithResponse}.
     *

     * If an error not due to validation (server error, network issue, etc.) occurs, {@link
     * com.braintreepayments.api.interfaces.BraintreeErrorListener#onError(Exception)}
     * will be called with the {@link Exception} that occurred.
     *
     * @param fragment {@link BraintreeFragment}
     * @param cardBuilder {@link CardBuilder}
     */
    public static void tokenize(final BraintreeFragment fragment, final CardBuilder cardBuilder) {
        TokenizationClient.tokenize(fragment, cardBuilder, new PaymentMethodNonceCallback() {
            @Override
            public void success(PaymentMethodNonce paymentMethodNonce) {
                DataCollector.collectRiskData(fragment, paymentMethodNonce);

                fragment.postCallback(paymentMethodNonce);
                fragment.sendAnalyticsEvent("card.nonce-received");
            }

            @Override
            public void failure(Exception exception) {
                fragment.postCallback(exception);
                fragment.sendAnalyticsEvent("card.nonce-failed");
            }
        });
    }
}


package com.braintreepayments.api;

import org.json.JSONObject;

public class Json {

    /**
     * Returns the value mapped by name if it exists, coercing it if necessary, or fallback if no such mapping exists.
     *
     * This is a work around for http://code.google.com/p/android/issues/detail?id=13830 returning "null" if the json
     * value is null.
     *
     * @param json
     * @param name
     * @param fallback
     * @return {@link String}
     */
    public static String optString(JSONObject json, String name, String fallback) {
        if (json.isNull(name)) {
            return fallback;
        } else {
            return json.optString(name, fallback);
        }
    }
}


package com.braintreepayments.api.models;

import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Base class representing a method of payment for a customer. {@link PaymentMethodNonce} represents the
 * common interface of all payment method nonces, and can be handled by a server interchangeably.
 */
public abstract class PaymentMethodNonce implements Parcelable {

    private static final String PAYMENT_METHOD_NONCE_COLLECTION_KEY = "paymentMethods";
    private static final String PAYMENT_METHOD_TYPE_KEY = "type";
    private static final String PAYMENT_METHOD_NONCE_KEY = "nonce";
    private static final String PAYMENT_METHOD_DEFAULT_KEY = "default";
    private static final String DESCRIPTION_KEY = "description";

    protected static final String DATA_KEY = "data";
    protected static final String TOKEN_KEY = "token";

    protected String mNonce;
    protected String mDescription;
    protected boolean mDefault;

    protected static JSONObject getJsonObjectForType(String apiResourceKey, JSONObject json) throws JSONException {
        return json.getJSONArray(apiResourceKey).getJSONObject(0);
    }

    @CallSuper
    protected void fromJson(JSONObject json) throws JSONException {
        mNonce = json.getString(PAYMENT_METHOD_NONCE_KEY);
        mDescription = json.getString(DESCRIPTION_KEY);
        mDefault = json.optBoolean(PAYMENT_METHOD_DEFAULT_KEY, false);
    }

    /**
     * @return The nonce generated for this payment method by the Braintree gateway. The nonce will
     *          represent this PaymentMethod for the purposes of creating transactions and other monetary
     *          actions.
     */
    public String getNonce() {
        return mNonce;
    }

    /**
     * @return The description of this PaymentMethod for displaying to a customer, e.g. 'Visa ending in...'
     */
    public String getDescription() {
        return mDescription;
    }

    /**
     * @return {@code true} if this payment method is the default for the current customer, {@code false} otherwise
     */
    public boolean isDefault() {
        return mDefault;
    }

    /**
     * @return The type of this PaymentMethod for displaying to a customer, e.g. 'Visa'. Can be used
     *          for displaying appropriate logos, etc.
     */
    public abstract String getTypeLabel();

    /**
     * Parses a response from the Braintree gateway for a list of payment method nonces.
     *
     * @param jsonBody Json-formatted String containing a list of {@link PaymentMethodNonce}s
     * @return List of {@link PaymentMethodNonce}s contained in jsonBody
     * @throws JSONException
     */
    public static List<PaymentMethodNonce> parsePaymentMethodNonces(String jsonBody)
            throws JSONException {
        JSONArray paymentMethods = new JSONObject(jsonBody).getJSONArray(
                PAYMENT_METHOD_NONCE_COLLECTION_KEY);

        if (paymentMethods == null) {
            return Collections.emptyList();
        }

        List<PaymentMethodNonce> paymentMethodsNonces = new ArrayList<>();
        JSONObject json;
        PaymentMethodNonce paymentMethodNonce;
        for(int i = 0; i < paymentMethods.length(); i++) {
            json = paymentMethods.getJSONObject(i);
            paymentMethodNonce = parsePaymentMethodNonces(json,
                    json.getString(PAYMENT_METHOD_TYPE_KEY));
            if (paymentMethodNonce != null) {
                paymentMethodsNonces.add(paymentMethodNonce);
            }
        }

        return paymentMethodsNonces;
    }

    /**
     * Parses a {@link PaymentMethodNonce} from json.
     *
     * @param json {@link String} representation of a {@link PaymentMethodNonce}.
     * @param type The {@link String} type of the {@link PaymentMethodNonce}.
     * @return {@link PaymentMethodNonce}
     * @throws JSONException
     */
    @Nullable
    public static PaymentMethodNonce parsePaymentMethodNonces(String json, String type) throws JSONException {
        return parsePaymentMethodNonces(new JSONObject(json), type);
    }

    /**
     * Parses a {@link PaymentMethodNonce} from json.
     *
     * @param json {@link JSONObject} representation of a {@link PaymentMethodNonce}.
     * @param type The {@link String} type of the {@link PaymentMethodNonce}.
     * @return {@link PaymentMethodNonce}
     * @throws JSONException
     */
    @Nullable
    public static PaymentMethodNonce parsePaymentMethodNonces(JSONObject json, String type) throws JSONException {
        switch (type) {
            case CardNonce.TYPE:
                if (json.has(CardNonce.API_RESOURCE_KEY) || json.has(CardNonce.DATA_KEY)) {
                    return CardNonce.fromJson(json.toString());
                } else {
                    CardNonce cardNonce = new CardNonce();
                    cardNonce.fromJson(json);
                    return cardNonce;
                }
            case PayPalAccountNonce.TYPE:
                if (json.has(PayPalAccountNonce.API_RESOURCE_KEY)) {
                    return PayPalAccountNonce.fromJson(json.toString());
                } else {
                    PayPalAccountNonce payPalAccountNonce = new PayPalAccountNonce();
                    payPalAccountNonce.fromJson(json);
                    return payPalAccountNonce;
                }
            case VenmoAccountNonce.TYPE:
                if (json.has(VenmoAccountNonce.API_RESOURCE_KEY)) {
                    return VenmoAccountNonce.fromJson(json.toString());
                } else {
                    VenmoAccountNonce venmoAccountNonce = new VenmoAccountNonce();
                    venmoAccountNonce.fromJson(json);
                    return venmoAccountNonce;
                }
            case VisaCheckoutNonce.TYPE:
                if (json.has(VisaCheckoutNonce.API_RESOURCE_KEY)) {
                    return VisaCheckoutNonce.fromJson(json.toString());
                } else {
                    VisaCheckoutNonce visaCheckoutNonce = new VisaCheckoutNonce();
                    visaCheckoutNonce.fromJson(json);
                    return visaCheckoutNonce;
                }
            default:
                return null;
        }
    }

    public PaymentMethodNonce() {}

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mNonce);
        dest.writeString(mDescription);
        dest.writeByte(mDefault ? (byte) 1 : (byte) 0);
    }

    protected PaymentMethodNonce(Parcel in) {
        mNonce = in.readString();
        mDescription = in.readString();
        mDefault = in.readByte() > 0;
    }
}


package com.paypal.android.sdk.onetouch.core.config;

import com.paypal.android.sdk.onetouch.core.network.EnvironmentManager;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class OAuth2Recipe extends Recipe<OAuth2Recipe> {

    static final String DEVELOP = "develop";
    private final Collection<String> mScope = new HashSet<>();
    private final Map<String, ConfigEndpoint> mEndpoints = new HashMap<>();
    private boolean mIsValidForAllScopes;

    @Override
    public OAuth2Recipe getThis() {
        return this;
    }

    public void validForScope(String singleScopeValue) {
        mScope.add(singleScopeValue);
    }

    public void withEndpoint(String name, ConfigEndpoint endpoint) {
        mEndpoints.put(name, endpoint);
    }

    public boolean isValidForScopes(Set<String> scopes) {
        if (mIsValidForAllScopes) {
            return true;
        } else {
            return scopes.containsAll(scopes);
        }
    }

    public void validForAllScopes() {
        mIsValidForAllScopes = true;
    }

    /**
     * 1. Look for exact match of environment. (Could be mock, live, or a particular host:port.) 2.
     * If environment is anything other than mock or live, then look for develop. 3. Look for live.
     * (There should always be a live endpoint specified in any v3 browser-switch recipe.)
     *
     * @param environment
     * @return
     */
    public ConfigEndpoint getEndpoint(String environment) {
        ConfigEndpoint configEndpoint;
        if (mEndpoints.containsKey(environment)) {
            configEndpoint = mEndpoints.get(environment);
        } else if (mEndpoints.containsKey(DEVELOP)) {
            configEndpoint = mEndpoints.get(DEVELOP);
        } else {
            // default to live as fallback
            configEndpoint = mEndpoints.get(EnvironmentManager.LIVE);
        }

        return configEndpoint;
    }
}


package com.paypal.android.sdk.onetouch.core;

import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;

import com.paypal.android.sdk.onetouch.core.config.OtcConfiguration;
import com.paypal.android.sdk.onetouch.core.config.Recipe;
import com.paypal.android.sdk.onetouch.core.enums.Protocol;
import com.paypal.android.sdk.onetouch.core.exception.InvalidEncryptionDataException;
import com.paypal.android.sdk.onetouch.core.fpti.TrackingPoint;

import org.json.JSONException;

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

public abstract class Request<T extends Request<T>> implements Parcelable {

    private String mEnvironment;
    private String mClientId;
    private String mClientMetadataId;
    private String mCancelUrl;
    private String mSuccessUrl;

    @SuppressWarnings("unchecked")
    public T environment(String environment) {
        mEnvironment = environment;
        return (T) this;
    }

    public String getEnvironment() {
        return mEnvironment;
    }

    @SuppressWarnings("unchecked")
    public T clientMetadataId(String clientMetadataId) {
        mClientMetadataId = clientMetadataId;
        return (T) this;
    }

    public String getClientMetadataId() {
        return mClientMetadataId;
    }

    @SuppressWarnings("unchecked")
    public T clientId(String clientId) {
        mClientId = clientId;
        return (T) this;
    }

    public String getClientId() {
        return mClientId;
    }

    /**
     * Defines the host to be used in the cancellation url for browser switch (the package name will
     * be used as the scheme)
     */
    @SuppressWarnings("unchecked")
    public T cancelUrl(String scheme, String host) {
        mCancelUrl = scheme + "://" + redirectURLHostAndPath() + host;
        return (T) this;
    }

    public String getCancelUrl() {
        return mCancelUrl;
    }

    /**
     * Defines the host to be used in the success url for browser switch (the package name will be
     * used as the scheme)
     */
    @SuppressWarnings("unchecked")
    public T successUrl(String scheme, String host) {
        mSuccessUrl = scheme + "://" + redirectURLHostAndPath() + host;
        return (T) this;
    }

    public String getSuccessUrl() {
        return mSuccessUrl;
    }

    private static String redirectURLHostAndPath() {
        return "onetouch/v1/";
    }

    public abstract String getBrowserSwitchUrl();

    public abstract Recipe getBrowserSwitchRecipe(OtcConfiguration config);

    public abstract Result parseBrowserResponse(Uri uri);

    public abstract boolean validateV1V2Response(Bundle extras);

    public abstract Recipe getRecipeToExecute(Context context, OtcConfiguration config);

    public abstract void trackFpti(Context context, TrackingPoint trackingPoint, Protocol protocol);

    protected Request() {}

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mEnvironment);
        dest.writeString(mClientId);
        dest.writeString(mClientMetadataId);
        dest.writeString(mCancelUrl);
        dest.writeString(mSuccessUrl);
    }

    protected Request(Parcel source) {
        mEnvironment = source.readString();
        mClientId = source.readString();
        mClientMetadataId = source.readString();
        mCancelUrl = source.readString();
        mSuccessUrl = source.readString();
    }
}