Skip to content
This repository has been archived by the owner on Jan 3, 2024. It is now read-only.

Google Play Update #785

Closed
ecarrera opened this issue Jun 12, 2021 · 41 comments · Fixed by #790 or #800
Closed

Google Play Update #785

ecarrera opened this issue Jun 12, 2021 · 41 comments · Fixed by #790 or #800

Comments

@ecarrera
Copy link

The com.tinkyapp app version 5 code includes the com.stripe SDK: stripe-android, 10.4.6, which facilitates the transmission or collection of personal and sensitive information. These actions must comply with the Consent and Clear Disclosure Requirements and applicable terms of the Google Play Privacy Policy, as described in the User Data policy.

As of midnight on July 12, 2021 (UTC), new versions of apps containing this version of the SDK that do not comply with the Consent and Clear Disclosure Requirements set forth in the User Data policy will be rejected. Consider upgrading to version 16.9.0, recommended by the SDK vendor. Request more information from the SDK vendor.

@karanmehta4797
Copy link

We are also facing the same issue in google play console while uploading app to play store.

@ImPDP
Copy link

ImPDP commented Jun 15, 2021

It seems Tipsi will not update the stripe module, as there is an official library from Stripe Itself.

I'm using stripe for Google pay and Apple Pay only. Even though I haven't used the Card payment, It is mandatory to update to 16.9.0/16.10.0 in order to prevent rejection.

One Good Way: Open Android Studio -> Go to Build.gradle of tipsi-stripe -> Update Stripe SDK to 16.9.0 (it will show you the tooltip dialog box to update to 16.9.0, as you hover over implementation 'com.stripe:stripe-android:10.4.6' line).

Click Sync now (in the header part of build.gradle)

Go to tipsi-stripe -> java ->com.gettipsi.stripe

The following Files will get affected after the update:

  • converters.java(under util folder)
  • GoogleApiPayFlowImpl.java
  • StripeModule.java

Open the respected files and change the old imports to new imports. You can fix most of the errors by hover over the error and do recommended fix.

Replace old code with below:

Converters.java

package com.gettipsi.stripe.util;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.text.TextUtils;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.bridge.WritableNativeMap;
import com.google.android.gms.identity.intents.model.CountrySpecification;
import com.google.android.gms.identity.intents.model.UserAddress;
import com.google.android.gms.wallet.PaymentData;
import com.stripe.android.PaymentIntentResult;
import com.stripe.android.SetupIntentResult;
import com.stripe.android.model.Address;
import com.stripe.android.model.BankAccount;
import com.stripe.android.model.BankAccountTokenParams;
import com.stripe.android.model.Card;
import com.stripe.android.model.PaymentIntent;
import com.stripe.android.model.PaymentMethod;
import com.stripe.android.model.SetupIntent;
import com.stripe.android.model.Source;
import com.stripe.android.model.Source.CodeVerification;
import com.stripe.android.model.Source.Owner;
import com.stripe.android.model.Source.Receiver;
import com.stripe.android.model.Source.Redirect;
import com.stripe.android.model.Token;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;

/**
 * Created by ngoriachev on 13/03/2018.
 */

public class Converters {

  public static WritableMap convertTokenToWritableMap(Token token) {
    WritableMap newToken = Arguments.createMap();

    if (token == null) return newToken;

    newToken.putString("tokenId", token.getId());
    newToken.putBoolean("livemode", token.getLivemode());
    newToken.putBoolean("used", token.getUsed());
    newToken.putDouble("created", token.getCreated().getTime());

    if (token.getCard() != null) {
      newToken.putMap("card", convertCardToWritableMap(token.getCard()));
    }
    if (token.getBankAccount() != null) {
      newToken.putMap("bankAccount", convertBankAccountToWritableMap(token.getBankAccount()));
    }

    return newToken;
  }

  public static WritableMap putExtraToTokenMap(final WritableMap tokenMap, UserAddress billingAddress, UserAddress shippingAddress, String emailAddress) {
    ArgCheck.nonNull(tokenMap);

    WritableMap extra = Arguments.createMap();

    //add email address to billing and shipping contact as per apple
    WritableMap billingContactMap = convertAddressToWritableMap(billingAddress);
    WritableMap shippingContactMap = convertAddressToWritableMap(shippingAddress);

    billingContactMap.putString("emailAddress", emailAddress);
    shippingContactMap.putString("emailAddress", emailAddress);


    extra.putMap("billingContact", billingContactMap);
    extra.putMap("shippingContact", shippingContactMap);

    tokenMap.putMap("extra", extra);

    return tokenMap;
  }

  private static WritableMap convertCardToWritableMap(final Card card) {
    WritableMap result = Arguments.createMap();

    if (card == null) return result;

    result.putString("cardId", card.getId());
    result.putString("number", card.getNumber());
    result.putString("cvc", card.getCvc() );
    result.putInt("expMonth", card.getExpMonth() );
    result.putInt("expYear", card.getExpYear() );
    result.putString("name", card.getName() );
    result.putString("addressLine1", card.getAddressLine1() );
    result.putString("addressLine2", card.getAddressLine2() );
    result.putString("addressCity", card.getAddressCity() );
    result.putString("addressState", card.getAddressState() );
    result.putString("addressZip", card.getAddressZip() );
    result.putString("addressCountry", card.getAddressCountry() );
    result.putString("last4", card.getLast4() );
    result.putString("fingerprint", card.getFingerprint() );
    result.putString("country", card.getCountry() );
    result.putString("currency", card.getCurrency() );

    return result;
  }

  public static WritableMap convertBankAccountToWritableMap(BankAccount account) {
    WritableMap result = Arguments.createMap();

    if (account == null) return result;

    result.putString("routingNumber", account.getRoutingNumber());
    result.putString("countryCode", account.getCountryCode());
    result.putString("currency", account.getCurrency());
    result.putString("accountHolderName", account.getAccountHolderName());
    result.putString("fingerprint", account.getFingerprint());
    result.putString("bankName", account.getBankName());
    result.putString("last4", account.getLast4());

    return result;
  }

  public static String getValue(final ReadableMap map, final String key, final String def) {
    if (map.hasKey(key)) {
      return map.getString(key);
    } else {
      // If map don't have some key - we must pass to constructor default value.
      return def;
    }
  }

  public static Boolean getValue(final ReadableMap map, final String key, final Boolean def) {
    if (map.hasKey(key)) {
      return map.getBoolean(key);
    } else {
      // If map don't have some key - we must pass to constructor default value.
      return def;
    }
  }

  public static ReadableArray getValue(final ReadableMap map, final String key, final ReadableArray def) {
    if (map.hasKey(key)) {
      return map.getArray(key);
    } else {
      // If map don't have some key - we must pass to constructor default value.
      return def;
    }
  }

  public static String getValue(final ReadableMap map, final String key) {
    return getValue(map, key, (String) null);
  }

  public static Collection<String> getAllowedShippingCountryCodes(final ReadableMap map) {
    ArrayList<String> allowedCountryCodesForShipping = new ArrayList<>();
    ReadableArray countries = getValue(map, "shipping_countries", (ReadableArray) null);

    if (countries != null){
      for (int i = 0; i < countries.size(); i++) {
        String code = countries.getString(i);
        allowedCountryCodesForShipping.add(code);
      }
    }

    return allowedCountryCodesForShipping;
  }

  public static ArrayList<CountrySpecification> getAllowedShippingCountries(final ReadableMap map) {
    ArrayList<CountrySpecification> allowedCountriesForShipping = new ArrayList<>();
    ReadableArray countries = getValue(map, "shipping_countries", (ReadableArray) null);

    if (countries != null){
      for (int i = 0; i < countries.size(); i++) {
        String code = countries.getString(i);
        allowedCountriesForShipping.add(new CountrySpecification(code));
      }
    }

    return allowedCountriesForShipping;
  }

  public static Card createCard(final ReadableMap cardData) {
    return new Card.Builder(
        cardData.getString("number"),
        cardData.getInt("expMonth"),
        cardData.getInt("expYear"),
        getValue(cardData, "cvc"))
      .name(getValue(cardData, "name"))
      .addressLine1(getValue(cardData, "addressLine1"))
      .addressLine2(getValue(cardData, "addressLine2"))
      .addressCity(getValue(cardData, "addressCity"))
      .addressState(getValue(cardData, "addressState"))
      .addressZip(getValue(cardData, "addressZip"))
      .addressCountry(getValue(cardData, "addressCountry"))
      .last4(getValue(cardData, "last4"))
      .fingerprint(getValue(cardData, "fingerprint"))
      .country(getValue(cardData, "country"))
      .currency(getValue(cardData, "currency"))
      .id(getValue(cardData, "id"))
      .build();
  }



  @NonNull
  public static WritableMap convertSourceToWritableMap(@Nullable Source source) {
    WritableMap newSource = Arguments.createMap();

    if (source == null) {
      return newSource;
    }

    newSource.putString("sourceId", source.getId());
    newSource.putInt("amount", source.getAmount().intValue());
    newSource.putInt("created", source.getCreated().intValue());
    newSource.putString("currency", source.getCurrency());
    newSource.putString("flow", String.valueOf(source.getFlow()));
    newSource.putBoolean("livemode", source.isLiveMode());
    newSource.putMap("metadata", stringMapToWritableMap(source.getMetaData()));
    newSource.putMap("owner", convertOwnerToWritableMap(source.getOwner()));
    newSource.putMap("receiver", convertReceiverToWritableMap(source.getReceiver()));
    newSource.putMap("redirect", convertRedirectToWritableMap(source.getRedirect()));
    newSource.putMap("sourceTypeData", mapToWritableMap(source.getSourceTypeData()));
    newSource.putString("status", String.valueOf(source.getStatus()));
    newSource.putString("type", source.getType());
    newSource.putString("typeRaw", source.getTypeRaw());
    newSource.putString("usage", String.valueOf(source.getUsage()));

    return newSource;
  }

  @NonNull
  public static WritableMap convertPaymentIntentResultToWritableMap(@Nullable PaymentIntentResult paymentIntentResult) {
    WritableMap wm = Arguments.createMap();

    if (paymentIntentResult == null) {
      wm.putString("status", "unknown");
      return wm;
    }

    PaymentIntent intent = paymentIntentResult.getIntent();
    wm.putString("status", intent.getStatus().toString());
    wm.putString("paymentIntentId", intent.getId());

//    String paymentMethodId = intent.getPaymentMethodId();
//    if (paymentMethodId != null) {
//      wm.putString("paymentMethodId", paymentMethodId);
//    }
    return wm;
  }


  @NonNull
  public static WritableMap convertSetupIntentResultToWritableMap(@Nullable SetupIntentResult setupIntentResult) {
    WritableMap wm = Arguments.createMap();

    if (setupIntentResult == null) {
      wm.putString("status", "unknown");
      return wm;
    }

    SetupIntent intent = setupIntentResult.getIntent();
    wm.putString("status", intent.getStatus().toString());
    wm.putString("setupIntentId", intent.getId());

    String paymentMethodId = intent.getPaymentMethodId();
    if (paymentMethodId != null) {
      wm.putString("paymentMethodId", paymentMethodId);
    }
    return wm;
  }

  @NonNull
  public static WritableMap convertPaymentMethodToWritableMap(@Nullable PaymentMethod paymentMethod) {
    WritableMap wm = Arguments.createMap();

    if (paymentMethod == null) {
      return wm;
    }

    wm.putString("id", paymentMethod.id);
    wm.putInt("created", paymentMethod.created.intValue());
    wm.putBoolean("livemode", paymentMethod.liveMode);
    wm.putString("type", String.valueOf(paymentMethod.type));
    wm.putMap("billingDetails", convertBillingDetailsToWritableMap(paymentMethod.billingDetails));
    wm.putMap("card", convertPaymentMethodCardToWritableMap(paymentMethod.card));
    wm.putString("customerId", paymentMethod.customerId);

    // TODO support metadata
    return wm;
  }

  @NonNull
  public static WritableMap convertPaymentMethodCardToWritableMap(@Nullable final PaymentMethod.Card card) {
    WritableMap wm = Arguments.createMap();

    if (card == null) {
      return wm;
    }

    // Omitted (can be introduced later): card.checks, card.threeDSecureUsage, card.wallet

    wm.putString("brand", String.valueOf(card.brand));
    wm.putString("country", card.country);
    wm.putInt("expMonth", card.expiryMonth);
    wm.putInt("expYear", card.expiryYear);
    wm.putString("funding", card.funding);
    wm.putString("last4", card.last4);
    return wm;
  }

  @NonNull
  public static WritableMap convertBillingDetailsToWritableMap(@Nullable final PaymentMethod.BillingDetails billingDetails) {
    WritableMap wm = Arguments.createMap();

    if (billingDetails == null) {
      return wm;
    }

    wm.putMap("address", convertAddressToWritableMap(billingDetails.address));
    wm.putString("email", billingDetails.email);
    wm.putString("name", billingDetails.name);
    wm.putString("phone", billingDetails.phone);
    return wm;
  }


  @NonNull
  public static WritableMap stringMapToWritableMap(@Nullable Map<String, String> map) {
    WritableMap writableMap = Arguments.createMap();

    if (map == null) {
      return writableMap;
    }

    for (Map.Entry<String, String> entry : map.entrySet()) {
      writableMap.putString(entry.getKey(), entry.getValue());
    }

    return writableMap;
  }

  @NonNull
  public static WritableMap convertOwnerToWritableMap(@Nullable final Owner owner) {
    WritableMap map = Arguments.createMap();

    if (owner == null) {
      return map;
    }

    map.putMap("address", convertAddressToWritableMap(owner.getAddress()));
    map.putString("email", owner.getEmail());
    map.putString("name", owner.getName());
    map.putString("phone", owner.getPhone());
    map.putString("verifiedEmail", owner.getVerifiedEmail());
    map.putString("verifiedPhone", owner.getVerifiedPhone());
    map.putString("verifiedName", owner.getVerifiedName());
    map.putMap("verifiedAddress", convertAddressToWritableMap(owner.getVerifiedAddress()));

    return map;
  }

  @NonNull
  public static WritableMap convertAddressToWritableMap(@Nullable final Address address) {
    WritableMap map = Arguments.createMap();

    if (address == null) {
      return map;
    }

    map.putString("city", address.getCity());
    map.putString("country", address.getCountry());
    map.putString("line1", address.getLine1());
    map.putString("line2", address.getLine2());
    map.putString("postalCode", address.getPostalCode());
    map.putString("state", address.getState());

    return map;
  }

  @NonNull
  public static WritableMap convertReceiverToWritableMap(@Nullable final Receiver receiver) {
    WritableMap map = Arguments.createMap();

    if (receiver == null) {
      return map;
    }

    map.putInt("amountCharged", (int) receiver.getAmountCharged());
    map.putInt("amountReceived", (int) receiver.getAmountReceived());
    map.putInt("amountReturned", (int) receiver.getAmountReturned());
    map.putString("address", receiver.getAddress());

    return map;
  }

  @NonNull
  public static WritableMap convertRedirectToWritableMap(@Nullable Redirect redirect) {
    WritableMap map = Arguments.createMap();

    if (redirect == null) {
      return map;
    }

    map.putString("returnUrl", redirect.getReturnUrl());
    map.putString("status", String.valueOf(redirect.getStatus()));
    map.putString("url", redirect.getUrl());

    return map;
  }

  @NonNull
  public static WritableMap convertCodeVerificationToWritableMap(@Nullable CodeVerification codeVerification) {
    WritableMap map = Arguments.createMap();

    if (codeVerification == null) {
      return map;
    }

    map.putInt("attemptsRemaining", codeVerification.getAttemptsRemaining());
    map.putString("status", String.valueOf(codeVerification.getStatus()));

    return map;
  }

  @NonNull
  public static WritableMap mapToWritableMap(@Nullable Map<String, Object> map){
    WritableMap writableMap = Arguments.createMap();

    if (map == null) {
      return writableMap;
    }

    for (String key: map.keySet()) {
      pushRightTypeToMap(writableMap, key, map.get(key));
    }

    return writableMap;
  }

  public static void pushRightTypeToMap(@NonNull WritableMap map, @NonNull String key, @NonNull Object object) {
    Class argumentClass = object.getClass();
    if (argumentClass == Boolean.class) {
      map.putBoolean(key, (Boolean) object);
    } else if (argumentClass == Integer.class) {
      map.putDouble(key, ((Integer)object).doubleValue());
    } else if (argumentClass == Double.class) {
      map.putDouble(key, (Double) object);
    } else if (argumentClass == Float.class) {
      map.putDouble(key, ((Float)object).doubleValue());
    } else if (argumentClass == String.class) {
      map.putString(key, object.toString());
    } else if (argumentClass == WritableNativeMap.class) {
      map.putMap(key, (WritableNativeMap)object);
    } else if (argumentClass == WritableNativeArray.class) {
      map.putArray(key, (WritableNativeArray) object);
    } else {

    }
  }

  public static WritableMap convertAddressToWritableMap(final UserAddress address){
    WritableMap result = Arguments.createMap();

    if (address == null) return result;

    putIfNotEmpty(result, "address1", address.getAddress1());
    putIfNotEmpty(result, "address2", address.getAddress2());
    putIfNotEmpty(result, "address3", address.getAddress3());
    putIfNotEmpty(result, "address4", address.getAddress4());
    putIfNotEmpty(result, "address5", address.getAddress5());
    putIfNotEmpty(result, "administrativeArea", address.getAdministrativeArea());
    putIfNotEmpty(result, "companyName", address.getCompanyName());
    putIfNotEmpty(result, "countryCode", address.getCountryCode());
    putIfNotEmpty(result, "locality", address.getLocality());
    putIfNotEmpty(result, "name", address.getName());
    putIfNotEmpty(result, "phoneNumber", address.getPhoneNumber());
    putIfNotEmpty(result, "postalCode", address.getPostalCode());
    putIfNotEmpty(result, "sortingCode", address.getSortingCode());

    return result;
  }


  public static String getStringOrNull(@NonNull ReadableMap map, @NonNull String key) {
    return map.hasKey(key) ? map.getString(key) : null;
  }

  public static ReadableMap getMapOrNull(@NonNull ReadableMap map, @NonNull String key) {
    return map.hasKey(key) ? map.getMap(key) : null;
  }

  public static boolean getBooleanOrNull(@NonNull ReadableMap map, @NonNull String key, boolean defaultVal) {
    return map.hasKey(key) ? map.getBoolean(key) : defaultVal;
  }

  public static void putIfNotEmpty(final WritableMap map, final String key, final String value) {
    if (!TextUtils.isEmpty(value)) {
      map.putString(key, value);
    }
  }

  public static UserAddress getBillingAddress(PaymentData paymentData) {
    if (paymentData != null && paymentData.getCardInfo() != null) {
      return paymentData.getCardInfo().getBillingAddress();
    }

    return null;
  }

}

Open GoogleApiPayFlowImpl.java


package com.gettipsi.stripe;

import android.app.Activity;
import android.content.Intent;
import androidx.annotation.NonNull;

import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReadableMap;
import com.gettipsi.stripe.util.ArgCheck;
import com.gettipsi.stripe.util.Converters;
import com.gettipsi.stripe.util.Fun0;
import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.android.gms.wallet.AutoResolveHelper;
import com.google.android.gms.wallet.CardRequirements;
import com.google.android.gms.wallet.IsReadyToPayRequest;
import com.google.android.gms.wallet.PaymentData;
import com.google.android.gms.wallet.PaymentDataRequest;
import com.google.android.gms.wallet.PaymentMethodTokenizationParameters;
import com.google.android.gms.wallet.PaymentsClient;
import com.google.android.gms.wallet.ShippingAddressRequirements;
import com.google.android.gms.wallet.TransactionInfo;
import com.google.android.gms.wallet.Wallet;
import com.google.android.gms.wallet.WalletConstants;
import com.stripe.android.BuildConfig;
import com.stripe.android.Stripe;
import com.stripe.android.model.GooglePayResult;
import com.stripe.android.model.Token;

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

import java.util.Arrays;
import java.util.Collection;

import static com.gettipsi.stripe.Errors.toErrorCode;
import static com.gettipsi.stripe.util.Converters.convertTokenToWritableMap;
import static com.gettipsi.stripe.util.Converters.getAllowedShippingCountryCodes;
import static com.gettipsi.stripe.util.Converters.getBillingAddress;
import static com.gettipsi.stripe.util.Converters.putExtraToTokenMap;
import static com.gettipsi.stripe.util.PayParams.CURRENCY_CODE;
import static com.gettipsi.stripe.util.PayParams.BILLING_ADDRESS_REQUIRED;
import static com.gettipsi.stripe.util.PayParams.SHIPPING_ADDRESS_REQUIRED;
import static com.gettipsi.stripe.util.PayParams.PHONE_NUMBER_REQUIRED;
import static com.gettipsi.stripe.util.PayParams.EMAIL_REQUIRED;
import static com.gettipsi.stripe.util.PayParams.TOTAL_PRICE;

/**
 * Created by ngoriachev on 13/03/2018.
 * see https://developers.google.com/pay/api/tutorial
 */
public final class GoogleApiPayFlowImpl extends PayFlow {

  private static final String TAG = GoogleApiPayFlowImpl.class.getSimpleName();
  private static final int LOAD_PAYMENT_DATA_REQUEST_CODE = 65534;

  private PaymentsClient mPaymentsClient;
  private Promise payPromise;

  public GoogleApiPayFlowImpl(@NonNull Fun0<Activity> activityProvider) {
    super(activityProvider);
  }

  private PaymentsClient createPaymentsClient(@NonNull Activity activity) {
    return Wallet.getPaymentsClient(
      activity,
      new Wallet.WalletOptions.Builder().setEnvironment(getEnvironment()).build());
  }

  private void isReadyToPay(@NonNull Activity activity, boolean isExistingPaymentMethodRequired, @NonNull final Promise promise) {
    ArgCheck.nonNull(activity);
    ArgCheck.nonNull(promise);

    IsReadyToPayRequest request =
      IsReadyToPayRequest.newBuilder()
        .addAllowedPaymentMethod(WalletConstants.PAYMENT_METHOD_CARD)
        .addAllowedPaymentMethod(WalletConstants.PAYMENT_METHOD_TOKENIZED_CARD)
        .setExistingPaymentMethodRequired(isExistingPaymentMethodRequired)
        .build();
    mPaymentsClient = createPaymentsClient(activity);
    Task<Boolean> task = mPaymentsClient.isReadyToPay(request);
    task.addOnCompleteListener(
      new OnCompleteListener<Boolean>() {
        public void onComplete(Task<Boolean> task) {
          try {
            boolean result = task.getResult(ApiException.class);
            promise.resolve(result);
          } catch (ApiException exception) {
            promise.reject(toErrorCode(exception), exception.getMessage());
          }
        }
      });
  }

  private PaymentMethodTokenizationParameters createPaymentMethodTokenizationParameters() {
    return PaymentMethodTokenizationParameters.newBuilder()
      .setPaymentMethodTokenizationType(WalletConstants.PAYMENT_METHOD_TOKENIZATION_TYPE_PAYMENT_GATEWAY)
      .addParameter("gateway", "stripe")
      .addParameter("stripe:publishableKey", getPublishableKey())
      .addParameter("stripe:version", Stripe.VERSION_NAME)
      .build();
  }

  private PaymentDataRequest createPaymentDataRequest(ReadableMap payParams) {
    final String estimatedTotalPrice = payParams.getString(TOTAL_PRICE);
    final String currencyCode = payParams.getString(CURRENCY_CODE);
    final boolean billingAddressRequired = Converters.getValue(payParams, BILLING_ADDRESS_REQUIRED, false);
    final boolean shippingAddressRequired = Converters.getValue(payParams, SHIPPING_ADDRESS_REQUIRED, false);
    final boolean phoneNumberRequired = Converters.getValue(payParams, PHONE_NUMBER_REQUIRED, false);
    final boolean emailRequired = Converters.getValue(payParams, EMAIL_REQUIRED, false);
    final Collection<String> allowedCountryCodes = getAllowedShippingCountryCodes(payParams);

    return createPaymentDataRequest(
      estimatedTotalPrice,
      currencyCode,
      billingAddressRequired,
      shippingAddressRequired,
      phoneNumberRequired,
      emailRequired,
      allowedCountryCodes
    );
  }

  private PaymentDataRequest createPaymentDataRequest(@NonNull final String totalPrice,
                                                      @NonNull final String currencyCode,
                                                      final boolean billingAddressRequired,
                                                      final boolean shippingAddressRequired,
                                                      final boolean phoneNumberRequired,
                                                      final boolean emailRequired,
                                                      @NonNull final Collection<String> countryCodes
  ) {

    ArgCheck.isDouble(totalPrice);
    ArgCheck.notEmptyString(currencyCode);

    PaymentDataRequest.Builder builder = PaymentDataRequest.newBuilder();
    builder.setTransactionInfo(
      TransactionInfo.newBuilder()
        .setTotalPriceStatus(WalletConstants.TOTAL_PRICE_STATUS_ESTIMATED)
        .setTotalPrice(totalPrice)
        .setCurrencyCode(currencyCode)
        .build());

    builder
      .setCardRequirements(
        CardRequirements.newBuilder()
          .addAllowedCardNetworks(
            Arrays.asList(
              WalletConstants.CARD_NETWORK_AMEX,
              WalletConstants.CARD_NETWORK_DISCOVER,
              WalletConstants.CARD_NETWORK_VISA,
              WalletConstants.CARD_NETWORK_MASTERCARD))
          .setBillingAddressRequired(billingAddressRequired)
          .build())
      .addAllowedPaymentMethod(WalletConstants.PAYMENT_METHOD_CARD)
      .addAllowedPaymentMethod(WalletConstants.PAYMENT_METHOD_TOKENIZED_CARD)
      .setEmailRequired(emailRequired)
      .setShippingAddressRequired(shippingAddressRequired)
      .setPhoneNumberRequired(phoneNumberRequired);

    if (countryCodes.size() > 0) {
      builder.setShippingAddressRequirements(
        ShippingAddressRequirements.newBuilder()
          .addAllowedCountryCodes(countryCodes)
          .build());
    }

    builder.setPaymentMethodTokenizationParameters(createPaymentMethodTokenizationParameters());
    return builder.build();
  }

  private void startPaymentRequest(@NonNull Activity activity, @NonNull PaymentDataRequest request) {
    ArgCheck.nonNull(activity);
    ArgCheck.nonNull(request);

    mPaymentsClient = createPaymentsClient(activity);

    AutoResolveHelper.resolveTask(
      mPaymentsClient.loadPaymentData(request),
      activity,
      LOAD_PAYMENT_DATA_REQUEST_CODE);
  }

  @Override
  public void paymentRequestWithAndroidPay(@NonNull ReadableMap payParams, @NonNull Promise promise) {
    ArgCheck.nonNull(payParams);
    ArgCheck.nonNull(promise);

    Activity activity = activityProvider.call();
    if (activity == null) {
      promise.reject(
        getErrorCode("activityUnavailable"),
        getErrorDescription("activityUnavailable")
      );
      return;
    }

    this.payPromise = promise;
    startPaymentRequest(activity, createPaymentDataRequest(payParams));
  }

  @Override
  public void deviceSupportsAndroidPay(boolean isExistingPaymentMethodRequired, @NonNull Promise promise) {
    Activity activity = activityProvider.call();
    if (activity == null) {
      promise.reject(
        getErrorCode("activityUnavailable"),
        getErrorDescription("activityUnavailable")
      );
      return;
    }

    if (!isPlayServicesAvailable(activity)) {
      promise.reject(
        getErrorCode("playServicesUnavailable"),
        getErrorDescription("playServicesUnavailable")
      );
      return;
    }

    isReadyToPay(activity, isExistingPaymentMethodRequired, promise);
  }

  public boolean onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
    if (payPromise == null) {
      return false;
    }

    switch (requestCode) {
      case LOAD_PAYMENT_DATA_REQUEST_CODE:
        switch (resultCode) {
          case Activity.RESULT_OK:
            PaymentData paymentData = PaymentData.getFromIntent(data);
            ArgCheck.nonNull(paymentData);
            String tokenJson = paymentData.getPaymentMethodToken().getToken();
            JSONObject obj = null;
            try {
              obj = new JSONObject(tokenJson);
            } catch (JSONException e) {
              e.printStackTrace();
            }
            Token token = Token.fromJson(obj);
            if (token == null) {
              payPromise.reject(
                getErrorCode("parseResponse"),
                getErrorDescription("parseResponse")
              );
            } else {
              payPromise.resolve(putExtraToTokenMap(
                convertTokenToWritableMap(token),
                getBillingAddress(paymentData),
                paymentData.getShippingAddress(),
                paymentData.getEmail()));
            }
            break;
          case Activity.RESULT_CANCELED:
            payPromise.reject(
              getErrorCode("purchaseCancelled"),
              getErrorDescription("purchaseCancelled")
            );
            break;
          case AutoResolveHelper.RESULT_ERROR:
            Status status = AutoResolveHelper.getStatusFromIntent(data);
            // Log the status for debugging.
            // Generally, there is no need to show an error to
            // the user as the Google Pay API will do that.
            payPromise.reject(
              getErrorCode("stripe"),
              status.getStatusMessage()
            );
            break;

          default:
            // Do nothing.
        }
        payPromise = null;
        return true;
    }

    return false;
  }

}

StripeModule.java

package com.gettipsi.stripe;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.text.TextUtils;

import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.BaseActivityEventListener;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.gettipsi.stripe.dialog.AddCardDialogFragment;
import com.gettipsi.stripe.util.ArgCheck;
import com.gettipsi.stripe.util.Converters;
import com.gettipsi.stripe.util.Fun0;
import com.google.android.gms.wallet.WalletConstants;
import com.stripe.android.ApiResultCallback;
import com.stripe.android.AppInfo;
import com.stripe.android.PaymentIntentResult;
import com.stripe.android.SetupIntentResult;
import com.stripe.android.ApiResultCallback;
import com.stripe.android.Stripe;
import com.stripe.android.model.Address;
import com.stripe.android.model.ConfirmPaymentIntentParams;
import com.stripe.android.model.ConfirmSetupIntentParams;
import com.stripe.android.model.PaymentMethod;
import com.stripe.android.model.PaymentMethodCreateParams;
import com.stripe.android.model.Source;
import com.stripe.android.model.Source.Status;
import com.stripe.android.model.SourceParams;
import com.stripe.android.model.StripeIntent;
import com.stripe.android.model.Token;

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

import static com.gettipsi.stripe.Errors.AUTHENTICATION_FAILED;
import static com.gettipsi.stripe.Errors.CANCELLED;
import static com.gettipsi.stripe.Errors.FAILED;
import static com.gettipsi.stripe.Errors.UNEXPECTED;
import static com.gettipsi.stripe.Errors.getDescription;
import static com.gettipsi.stripe.Errors.getErrorCode;
import static com.gettipsi.stripe.Errors.toErrorCode;
import static com.gettipsi.stripe.util.Converters.convertPaymentIntentResultToWritableMap;
import static com.gettipsi.stripe.util.Converters.convertPaymentMethodToWritableMap;
import static com.gettipsi.stripe.util.Converters.convertSetupIntentResultToWritableMap;
import static com.gettipsi.stripe.util.Converters.convertSourceToWritableMap;
import static com.gettipsi.stripe.util.Converters.convertTokenToWritableMap;
import static com.gettipsi.stripe.util.Converters.createCard;
import static com.gettipsi.stripe.util.Converters.getBooleanOrNull;
import static com.gettipsi.stripe.util.Converters.getMapOrNull;
import static com.gettipsi.stripe.util.Converters.getStringOrNull;
import static com.gettipsi.stripe.util.InitializationOptions.ANDROID_PAY_MODE_KEY;
import static com.gettipsi.stripe.util.InitializationOptions.ANDROID_PAY_MODE_PRODUCTION;
import static com.gettipsi.stripe.util.InitializationOptions.ANDROID_PAY_MODE_TEST;
import static com.gettipsi.stripe.util.InitializationOptions.PUBLISHABLE_KEY;
import static com.stripe.android.model.StripeIntent.Status.Canceled;
import static com.stripe.android.model.StripeIntent.Status.RequiresAction;
import static com.stripe.android.model.StripeIntent.Status.RequiresCapture;
import static com.stripe.android.model.StripeIntent.Status.RequiresConfirmation;
import static com.stripe.android.model.StripeIntent.Status.Succeeded;

public class StripeModule extends ReactContextBaseJavaModule {

  private static final String MODULE_NAME = StripeModule.class.getSimpleName();

  // If you change these, make sure to also change:
  //  ios/TPSStripe/TPSStripeManager
  // Relevant Docs:
  // - https://stripe.dev/stripe-ios/docs/Classes/STPAppInfo.html https://stripe.dev/stripe-android/com/stripe/android/AppInfo.html
  // - https://stripe.com/docs/building-plugins#setappinfo
  private static final String APP_INFO_NAME    = "tipsi-stripe";
  private static final String APP_INFO_URL     = "https://github.com/tipsi/tipsi-stripe";
  private static final String APP_INFO_VERSION = "8.x";
  public static final String CLIENT_SECRET = "clientSecret";

  private static StripeModule sInstance = null;

  public static StripeModule getInstance() {
    return sInstance;
  }

  public Stripe getStripe() {
    return mStripe;
  }

  @Nullable
  private Promise mCreateSourcePromise;

  @Nullable
  private Source mCreatedSource;

  private String mPublicKey;
  private Stripe mStripe;
  private PayFlow mPayFlow;
  private ReadableMap mErrorCodes;

  private final ActivityEventListener mActivityEventListener = new BaseActivityEventListener() {

    @Override
    public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
      boolean handled = getPayFlow().onActivityResult(activity, requestCode, resultCode, data);
      if (!handled) {
        super.onActivityResult(activity, requestCode, resultCode, data);
      }
    }
  };


  public StripeModule(ReactApplicationContext reactContext) {
    super(reactContext);

    // Add the listener for `onActivityResult`
    reactContext.addActivityEventListener(mActivityEventListener);

    sInstance = this;
  }

  @Override
  public String getName() {
    return MODULE_NAME;
  }

  @ReactMethod
  public void init(@NonNull ReadableMap options, @NonNull ReadableMap errorCodes) {
    ArgCheck.nonNull(options);

    String newPubKey = Converters.getStringOrNull(options, PUBLISHABLE_KEY);
    String newAndroidPayMode = Converters.getStringOrNull(options, ANDROID_PAY_MODE_KEY);

    if (newPubKey != null && !TextUtils.equals(newPubKey, mPublicKey)) {
      ArgCheck.notEmptyString(newPubKey);

      mPublicKey = newPubKey;
      Stripe.setAppInfo(AppInfo.create(APP_INFO_NAME, APP_INFO_VERSION, APP_INFO_URL));
      mStripe = new Stripe(getReactApplicationContext(), mPublicKey);
      getPayFlow().setPublishableKey(mPublicKey);
    }

    if (newAndroidPayMode != null) {
      ArgCheck.isTrue(ANDROID_PAY_MODE_TEST.equals(newAndroidPayMode) || ANDROID_PAY_MODE_PRODUCTION.equals(newAndroidPayMode));

      getPayFlow().setEnvironment(androidPayModeToEnvironment(newAndroidPayMode));
    }

    if (mErrorCodes == null) {
      mErrorCodes = errorCodes;
      getPayFlow().setErrorCodes(errorCodes);
    }
  }

  private PayFlow getPayFlow() {
    if (mPayFlow == null) {
      mPayFlow = PayFlow.create(
        new Fun0<Activity>() { public Activity call() {
          return getCurrentActivity();
        }}
      );
    }

    return mPayFlow;
  }

  private static int androidPayModeToEnvironment(@NonNull String androidPayMode) {
    ArgCheck.notEmptyString(androidPayMode);
    return ANDROID_PAY_MODE_TEST.equals(androidPayMode.toLowerCase()) ? WalletConstants.ENVIRONMENT_TEST : WalletConstants.ENVIRONMENT_PRODUCTION;
  }

  @ReactMethod
  public void deviceSupportsAndroidPay(final Promise promise) {
    getPayFlow().deviceSupportsAndroidPay(false, promise);
  }

  @ReactMethod
  public void canMakeAndroidPayPayments(final Promise promise) {
    getPayFlow().deviceSupportsAndroidPay(true, promise);
  }

  @ReactMethod
  public void setStripeAccount(final String stripeAccount) {
    ArgCheck.notEmptyString(mPublicKey);
    if (stripeAccount == null) {
      mStripe = new Stripe(getReactApplicationContext(), mPublicKey);
    } else {
      mStripe = new Stripe(getReactApplicationContext(), mPublicKey, stripeAccount);
    }
  }

  @ReactMethod
  public void createTokenWithCard(final ReadableMap cardData, final Promise promise) {
    try {
      ArgCheck.nonNull(mStripe);
      ArgCheck.notEmptyString(mPublicKey);

      mStripe.createCardToken(
        createCard(cardData),
        mPublicKey,
        new ApiResultCallback<Token>() {
          public void onSuccess(Token token) {
            promise.resolve(convertTokenToWritableMap(token));
          }
          public void onError(Exception error) {
            error.printStackTrace();
            promise.reject(toErrorCode(error), error.getMessage());
          }
        });
    } catch (Exception e) {
      promise.reject(toErrorCode(e), e.getMessage());
    }
  }

  @ReactMethod
  public void createTokenWithBankAccount(final ReadableMap accountData, final Promise promise) {
    try {
      ArgCheck.nonNull(mStripe);
      ArgCheck.notEmptyString(mPublicKey);
    } catch (Exception e) {
      promise.reject(toErrorCode(e), e.getMessage());
    }
  }

  @ReactMethod
  public void paymentRequestWithCardForm(ReadableMap params, final Promise promise) {
    Activity currentActivity = getCurrentActivity();
    try {
      ArgCheck.nonNull(currentActivity);
      ArgCheck.notEmptyString(mPublicKey);

      final AddCardDialogFragment cardDialog = AddCardDialogFragment.newInstance(
        getErrorCode(mErrorCodes, "cancelled"),
        getDescription(mErrorCodes, "cancelled")
      );
      cardDialog.setPromise(promise);
      cardDialog.show(currentActivity.getFragmentManager(), "AddNewCard");
    } catch (Exception e) {
      promise.reject(toErrorCode(e), e.getMessage());
    }
  }

  @ReactMethod
  public void paymentRequestWithAndroidPay(final ReadableMap payParams, final Promise promise) {
    getPayFlow().paymentRequestWithAndroidPay(payParams, promise);
  }

  private void attachPaymentResultActivityListener(final Promise promise) {
    ActivityEventListener ael = new BaseActivityEventListener() {

      @Override
      public void onActivityResult(Activity a, int requestCode, int resultCode, Intent data) {
        final ActivityEventListener ael = this;

        mStripe.onPaymentResult(requestCode, data, new ApiResultCallback<PaymentIntentResult>() {
          @Override
          public void onSuccess(@NonNull PaymentIntentResult result) {
            getReactApplicationContext().removeActivityEventListener(ael);

            StripeIntent.Status resultingStatus = result.getIntent().getStatus();

            if (Succeeded.equals(resultingStatus) ||
                RequiresCapture.equals(resultingStatus) ||
                RequiresConfirmation.equals(resultingStatus)) {
              promise.resolve(convertPaymentIntentResultToWritableMap(result));
            } else {
              if (Canceled.equals(resultingStatus) ||
                  RequiresAction.equals(resultingStatus)
              ) {
                promise.reject(CANCELLED, CANCELLED);      // TODO - normalize the message
              } else {
                promise.reject(FAILED, FAILED);
              }
            }
          }

          @Override
          public void onError(@NonNull Exception e) {
            getReactApplicationContext().removeActivityEventListener(ael);
            e.printStackTrace();
            promise.reject(toErrorCode(e), e.getMessage());
          }
        });
      }

      @Override
      public void onActivityResult(int requestCode, int resultCode, Intent data) {
        onActivityResult(null, requestCode, resultCode, data);
      }
    };
    getReactApplicationContext().addActivityEventListener(ael);
  }

  private void attachSetupResultActivityListener(final Promise promise) {
    ActivityEventListener ael = new BaseActivityEventListener() {
      @Override
      public void onActivityResult(Activity a, int requestCode, int resultCode, Intent data) {
        final ActivityEventListener ael = this;

        mStripe.onSetupResult(requestCode, data, new ApiResultCallback<SetupIntentResult>() {
          @Override
          public void onSuccess(@NonNull SetupIntentResult result) {
            getReactApplicationContext().removeActivityEventListener(ael);

            try {
              switch (result.getIntent().getStatus()) {
                case Canceled:
                  // The Setup Intent was canceled, so reject the promise with a predefined code.
                  promise.reject(CANCELLED, "The SetupIntent was canceled by the user.");
                  break;
                case RequiresAction:
                case RequiresPaymentMethod:
                  promise.reject(AUTHENTICATION_FAILED, "The user failed authentication.");
                  break;
                case Succeeded:
                  promise.resolve(convertSetupIntentResultToWritableMap(result));
                  break;
                case RequiresCapture:
                case RequiresConfirmation:
                default:
                  promise.reject(UNEXPECTED, "Unexpected state");
              }
            } catch (Exception e) {
              promise.reject(UNEXPECTED, "Unexpected error");
            }
          }

          @Override
          public void onError(@NonNull Exception e) {
            getReactApplicationContext().removeActivityEventListener(ael);
            e.printStackTrace();
            promise.reject(toErrorCode(e), e.getMessage());
          }
        });
      }

      @Override
      public void onActivityResult(int requestCode, int resultCode, Intent data) {
        onActivityResult(null, requestCode, resultCode, data);
      }
    };
    getReactApplicationContext().addActivityEventListener(ael);
  }

  @ReactMethod
  public void confirmPaymentIntent(final ReadableMap options, final Promise promise) {
    attachPaymentResultActivityListener(promise);

    Activity activity = getCurrentActivity();
    if (activity != null) {
      mStripe.confirmPayment(activity, extractConfirmPaymentIntentParams(options));
    }
  }

  @ReactMethod
  public void authenticatePaymentIntent(final ReadableMap options, final Promise promise) {
    attachPaymentResultActivityListener(promise);

    String clientSecret = options.getString(CLIENT_SECRET);
    Activity activity = getCurrentActivity();
    if (activity != null) {
      mStripe.authenticatePayment(activity, clientSecret);
    }
  }

  @ReactMethod
  public void confirmSetupIntent(final ReadableMap options, final Promise promise) {
    attachSetupResultActivityListener(promise);

    Activity activity = getCurrentActivity();
    if (activity != null) {
      mStripe.confirmSetupIntent(activity, extractConfirmSetupIntentParams(options));
    }
  }

  @ReactMethod
  public void authenticateSetupIntent(final ReadableMap options, final Promise promise) {
    attachSetupResultActivityListener(promise);

    String clientSecret = options.getString(CLIENT_SECRET);
    Activity activity = getCurrentActivity();
    if (activity != null) {
      mStripe.authenticateSetup(activity, clientSecret);
    }
  }


  @ReactMethod
  public void createPaymentMethod(final ReadableMap options, final Promise promise) {

    PaymentMethodCreateParams pmcp = extractPaymentMethodCreateParams(options);

    mStripe.createPaymentMethod(pmcp, new ApiResultCallback<PaymentMethod>() {

      @Override
      public void onError(Exception error) {
        promise.reject(toErrorCode(error), error.getMessage());
      }

      @Override
      public void onSuccess(PaymentMethod paymentMethod) {
        promise.resolve(convertPaymentMethodToWritableMap(paymentMethod));
      }
    });
  }


  @ReactMethod
  public void createSourceWithParams(final ReadableMap options, final Promise promise) {

    SourceParams sourceParams = extractSourceParams(options);

    ArgCheck.nonNull(sourceParams);

    mStripe.createSource(sourceParams, new ApiResultCallback<Source>() {
      @Override
      public void onError(Exception error) {
        promise.reject(toErrorCode(error));
      }

      @Override
      public void onSuccess(Source source) {
        if (Source.Flow.Redirect.equals(source.getFlow())) {
          Activity currentActivity = getCurrentActivity();
          if (currentActivity == null) {
            promise.reject(
              getErrorCode(mErrorCodes, "activityUnavailable"),
              getDescription(mErrorCodes, "activityUnavailable")
            );
          } else {
            mCreateSourcePromise = promise;
            mCreatedSource = source;
            String redirectUrl = source.getRedirect().getUrl();
            Intent browserIntent = new Intent(currentActivity, OpenBrowserActivity.class)
                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP)
                .putExtra(OpenBrowserActivity.EXTRA_URL, redirectUrl);
            currentActivity.startActivity(browserIntent);
          }
        } else {
          promise.resolve(convertSourceToWritableMap(source));
        }
      }
    });
  }

  private ConfirmSetupIntentParams extractConfirmSetupIntentParams(final ReadableMap options) {
    ReadableMap paymentMethod = getMapOrNull(options, "paymentMethod");
    String paymentMethodId = getStringOrNull(options, "paymentMethodId");
    String returnURL = getStringOrNull(options, "returnURL");
    String clientSecret = options.getString("clientSecret");
    ConfirmSetupIntentParams csip = null;
    if (returnURL ==  null) {
      returnURL = "stripejs://use_stripe_sdk/return_url";
    }

    if (paymentMethod != null) {
      csip = ConfirmSetupIntentParams.create(extractPaymentMethodCreateParams(paymentMethod),
        clientSecret, returnURL);
    } else if (paymentMethodId != null) {
      csip = ConfirmSetupIntentParams.create(paymentMethodId, clientSecret, returnURL);
    }

    ArgCheck.nonNull(csip);
    csip.withShouldUseStripeSdk(true);
    return csip;
  }

  private ConfirmPaymentIntentParams extractConfirmPaymentIntentParams(final ReadableMap options) {
    ConfirmPaymentIntentParams cpip = null;
    String clientSecret = options.getString("clientSecret");

    ReadableMap paymentMethod = getMapOrNull(options, "paymentMethod");
    String paymentMethodId = getStringOrNull(options,"paymentMethodId");

    // ReadableMap source = options.getMap("source");
    // String sourceId = getStringOrNull(options,"sourceId");

    String returnURL = getStringOrNull(options, "returnURL");
    if (returnURL ==  null) {
      returnURL = "stripejs://use_stripe_sdk/return_url";
    }
    boolean savePaymentMethod = getBooleanOrNull(options, "savePaymentMethod", false);

    // TODO support extra params in each of the create methods below
    Map<String, Object> extraParams = null;

    // Create with Payment Method
    if (paymentMethod != null) {

      PaymentMethodCreateParams pmcp = extractPaymentMethodCreateParams(paymentMethod);
      cpip = ConfirmPaymentIntentParams.createWithPaymentMethodCreateParams(pmcp, clientSecret, returnURL, savePaymentMethod, extraParams);

    // Create with Payment Method ID
    } else if (paymentMethodId != null) {

      cpip = ConfirmPaymentIntentParams.createWithPaymentMethodId(paymentMethodId, clientSecret, returnURL, savePaymentMethod, extraParams);

    // Create with Source
    /**
    Support for creating a Source while confirming a PaymentIntent is not being included
    at this time, however, for compatibility with existing saved Sources, you can still confirm
    a payment intent using a pre-existing Source by specifying its 'sourceId', as shown in the next
    branch
    */
    /*
    } else if (source != null) {
      SourceParams sourceParams = extractSourceParams(source);
      cpip = ConfirmPaymentIntentParams.createWithSourceParams(sourceParams, clientSecret, returnURL, savePaymentMethod, extraParams);
    */

    // Create with Source ID
    /**
    If you have a sourceId, pass it into the paymentMethodId parameter instead!
    The payment_method parameter of a payment intent is fully compatible with Sources.
    Reference: https://stripe.com/docs/api/payment_intents/confirm#confirm_payment_intent-payment_method
    */
    /*
    } else if (sourceId != null) {
      cpip = ConfirmPaymentIntentParams.createWithSourceId(sourceId, clientSecret, returnURL, savePaymentMethod, extraParams);
    */

    /**
    This branch can be used if the client secret refers to a payment intent that already
    has payment method information and just needs to be confirmed.
    */
    } else {
      cpip = ConfirmPaymentIntentParams.create(clientSecret, returnURL);
    }

    cpip.withShouldUseStripeSdk(true);

    return cpip;
  }

  private PaymentMethodCreateParams extractPaymentMethodCreateParams(final ReadableMap options) {

    ReadableMap cardParams = getMapOrNull(options, "card");
    ReadableMap billingDetailsParams = getMapOrNull(options, "billingDetails");
    ReadableMap metadataParams = getMapOrNull(options, "metadata");

    PaymentMethodCreateParams.Card card = null;
    PaymentMethod.BillingDetails billingDetails = null;
    Address address = null;
    Map<String, String> metadata = new HashMap<>();

    if (metadataParams != null) {
      ReadableMapKeySetIterator iter = metadataParams.keySetIterator();
      while (iter.hasNextKey()) {
        String key = iter.nextKey();
        metadata.put(key, metadataParams.getString(key));
      }
    }

    if (billingDetailsParams != null) {

      ReadableMap addressParams = getMapOrNull(billingDetailsParams, "address");

      if (addressParams != null) {
        address = new Address.Builder().
          setCity(getStringOrNull(addressParams, "city")).
          setCountry(getStringOrNull(addressParams, "country")).
          setLine1(getStringOrNull(addressParams, "line1")).
          setLine2(getStringOrNull(addressParams, "line2")).
          setPostalCode(getStringOrNull(addressParams, "postalCode")).
          setState(getStringOrNull(addressParams, "state")).
          build();
      }

      billingDetails = new PaymentMethod.BillingDetails.Builder().
        setAddress(address).
        setEmail(getStringOrNull(billingDetailsParams, "email")).
        setName(getStringOrNull(billingDetailsParams,"name")).
        setPhone(getStringOrNull(billingDetailsParams,"phone")).
        build();
    }

    if (cardParams != null) {
      String token = getStringOrNull(cardParams, "token");
      if (token != null) {
        card = PaymentMethodCreateParams.Card.create(token);
      } else {
        card = new PaymentMethodCreateParams.Card.Builder().
          setCvc(cardParams.getString("cvc")).
          setExpiryMonth(cardParams.getInt("expMonth")).
          setExpiryYear(cardParams.getInt("expYear")).
          setNumber(cardParams.getString("number")).
          build();
      }
    }

    return PaymentMethodCreateParams.create(
      card,
      billingDetails,
      metadata
    );
  }

  private SourceParams extractSourceParams(final ReadableMap options) {
    String sourceType = options.getString("type");
    SourceParams sourceParams = null;
    switch (sourceType) {
      case "alipay":
        sourceParams = SourceParams.createAlipaySingleUseParams(
          options.getInt("amount"),
          options.getString("currency"),
          getStringOrNull(options, "name"),
          getStringOrNull(options, "email"),
          options.getString("returnURL"));
        break;
      case "bancontact":
        sourceParams = SourceParams.createBancontactParams(
          options.getInt("amount"),
          options.getString("name"),
          options.getString("returnURL"),
          getStringOrNull(options, "statementDescriptor"),
          options.getString("preferredLanguage"));
        break;
      case "giropay":
        sourceParams = SourceParams.createGiropayParams(
          options.getInt("amount"),
          options.getString("name"),
          options.getString("returnURL"),
          getStringOrNull(options, "statementDescriptor"));
        break;
      case "ideal":
        sourceParams = SourceParams.createIdealParams(
          options.getInt("amount"),
          options.getString("name"),
          options.getString("returnURL"),
          getStringOrNull(options, "statementDescriptor"),
          getStringOrNull(options, "bank"));
        break;
      case "sepaDebit":
        sourceParams = SourceParams.createSepaDebitParams(
          options.getString("name"),
          options.getString("iban"),
          getStringOrNull(options, "addressLine1"),
          options.getString("city"),
          options.getString("postalCode"),
          options.getString("country"));
        break;
      case "sofort":
        sourceParams = SourceParams.createSofortParams(
          options.getInt("amount"),
          options.getString("returnURL"),
          options.getString("country"),
          getStringOrNull(options, "statementDescriptor"));
        break;
      case "threeDSecure":
        sourceParams = SourceParams.createThreeDSecureParams(
          options.getInt("amount"),
          options.getString("currency"),
          options.getString("returnURL"),
          options.getString("card"));
        break;
      case "card":
        sourceParams = SourceParams.createCardParams(Converters.createCard(options));
        break;
    }
    return sourceParams;
  }


  @SuppressLint("StaticFieldLeak")
  void processRedirect(@Nullable Uri redirectData) {
    if (mCreatedSource == null || mCreateSourcePromise == null) {

      return;
    }

    if (redirectData == null) {

      mCreateSourcePromise.reject(
        getErrorCode(mErrorCodes, "redirectCancelled"),
        getDescription(mErrorCodes, "redirectCancelled")
      );
      mCreatedSource = null;
      mCreateSourcePromise = null;
      return;
    }

    final String clientSecret = redirectData.getQueryParameter("client_secret");
    if (!mCreatedSource.getClientSecret().equals(clientSecret)) {
      mCreateSourcePromise.reject(
        getErrorCode(mErrorCodes, "redirectNoSource"),
        getDescription(mErrorCodes, "redirectNoSource")
      );
      mCreatedSource = null;
      mCreateSourcePromise = null;
      return;
    }

    final String sourceId = redirectData.getQueryParameter("source");
    if (!mCreatedSource.getId().equals(sourceId)) {
      mCreateSourcePromise.reject(
        getErrorCode(mErrorCodes, "redirectWrongSourceId"),
        getDescription(mErrorCodes, "redirectWrongSourceId")
      );
      mCreatedSource = null;
      mCreateSourcePromise = null;
      return;
    }

    final Promise promise = mCreateSourcePromise;

    // Nulls those properties to avoid processing them twice
    mCreatedSource = null;
    mCreateSourcePromise = null;

    new AsyncTask<Void, Void, Void>() {
      @Override
      protected Void doInBackground(Void... voids) {
        Source source = null;
        try {
          source = mStripe.retrieveSourceSynchronous(sourceId, clientSecret);
        } catch (Exception e) {

          return null;
        }

        switch (source.getStatus()) {
          case Chargeable:
          case Consumed:
            promise.resolve(convertSourceToWritableMap(source));
            break;
          case Canceled:
            promise.reject(
              getErrorCode(mErrorCodes, "redirectCancelled"),
              getDescription(mErrorCodes, "redirectCancelled")
            );
            break;
          case Pending:
          case Failed:
          default:
            promise.reject(
              getErrorCode(mErrorCodes, "redirectFailed"),
              getDescription(mErrorCodes, "redirectFailed")
            );
        }
        return null;
      }
    }.execute();
  }

}

Use the Above code (It is working fine with no build error) if you use Wallets Alone. I have removed some codes related to card payments after the updates because of confusion.

If you have time and know java, you can update the error codes manually.

Thank you.

@cybergrind
Copy link
Member

hey @ImPDP , if you send this as a PR I will merge it and release :D
The only issue with updates, I'm no longer using the library thus have no options to thoroughly test it

@designerofthing
Copy link

If you have a solution that includes card payments, I would greatly appreciate it, my Java sucks. @ImPDP

@ImPDP
Copy link

ImPDP commented Jun 15, 2021

Sure @cybergrind. But I can't right now. I'll do that in a day or two. @designerofthing, I did not remove all of the card payments code. As I'm using only GPay, I didn't care about some error thrown in the card token related to bank account methods/functions. I'll check again and try not to remove the codes if I'm making the PR.

Still, it will be good, If @cybergrind or anyone, test it before merging.

@saadnaveed94
Copy link

saadnaveed94 commented Jun 17, 2021

@cybergrind So when we expecting an update in this library to fix this issue??

@wijskinner
Copy link

wijskinner commented Jun 17, 2021

I've submitted the PR above with the changes suggested above by @ImPDP and a couple of other to get it to build for me. This works for my fairly narrow use cases which only involves calls to confirmPaymentIntent, confirmSetupIntent and createPaymentMethod. Hope it can be of some use to others.

@DanGDroid
Copy link

i will test
thanx @wijskinner

@DanGDroid
Copy link

my build fails:

Task :app:processDebugGoogleServices
Parsing json file: /../../../android/app/google-services.json

Task :app:processDebugManifest FAILED

See http://g.co/androidstudio/manifest-merger for more information about the manifest merger.

Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/6.3/userguide/command_line_interface.html#sec:command_line_warnings
404 actionable tasks: 13 executed, 391 up-to-date
[com.stripe:payments-core:16.10.0] /../../.gradle/caches/transforms-2/files-2.1/5506c85fa90b120aac72cfc15f62b8b0/payments-core-16.10.0/AndroidManifest.xml:13:9-54 Error:
Missing 'package' key attribute on element package at [com.stripe:payments-core:16.10.0] AndroidManifest.xml:13:9-54
[com.stripe:payments-core:16.10.0] /../../.gradle/caches/transforms-2/files-2.1/5506c85fa90b120aac72cfc15f62b8b0/payments-core-16.10.0/AndroidManifest.xml Error:
Validation failed, exiting

FAILURE: Build failed with an exception.

  • What went wrong:
    Execution failed for task ':app:processDebugManifest'.

Manifest merger failed with multiple errors, see logs

  • Try:
    Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

  • Get more help at https://help.gradle.org

BUILD FAILED in 16s

@saadnaveed94
Copy link

saadnaveed94 commented Jun 17, 2021

Same here for android build

@saadnaveed94
Copy link

Element uses-library#org.apache.http.legacy at AndroidManifest.xml:30:7-85 duplicated with element declared at AndroidManifest.xml:20:7-85

See http://g.co/androidstudio/manifest-merger for more information about the manifest merger.

Task :tipsi-stripe:compileDebugJavaWithJavac
w: Detected multiple Kotlin daemon sessions at build/kotlin/sessions

Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/6.3/userguide/command_line_interface.html#sec:command_line_warnings
365 actionable tasks: 355 executed, 10 up-to-date
warning: [options] source value 7 is obsolete and will be removed in a future release
warning: [options] target value 7 is obsolete and will be removed in a future release
warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
3 warnings
warning: [options] source value 7 is obsolete and will be removed in a future release
warning: [options] target value 7 is obsolete and will be removed in a future release
warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
3 warnings
warning: [options] source value 7 is obsolete and will be removed in a future release
warning: [options] target value 7 is obsolete and will be removed in a future release
warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
3 warnings
warning: [options] source value 7 is obsolete and will be removed in a future release
warning: [options] target value 7 is obsolete and will be removed in a future release
warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
3 warnings
warning: [options] source value 7 is obsolete and will be removed in a future release
warning: [options] target value 7 is obsolete and will be removed in a future release
warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
3 warnings
warning: [options] source value 7 is obsolete and will be removed in a future release
warning: [options] target value 7 is obsolete and will be removed in a future release
warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
Note: /Users/techverx/Desktop/Saad Naveed/Projects/pavemint/client-react-native/node_modules/react-native-braintree-dropin-ui/android/src/main/java/tech/power/RNBraintreeDropIn/RNBraintreeDropInModule.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
3 warnings
warning: [options] source value 7 is obsolete and will be removed in a future release
warning: [options] target value 7 is obsolete and will be removed in a future release
warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
3 warnings
warning: [options] source value 7 is obsolete and will be removed in a future release
warning: [options] target value 7 is obsolete and will be removed in a future release
warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
Note: Some input files use or override a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
3 warnings
warning: [options] source value 7 is obsolete and will be removed in a future release
warning: [options] target value 7 is obsolete and will be removed in a future release
warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
3 warnings
warning: [options] source value 7 is obsolete and will be removed in a future release
warning: [options] target value 7 is obsolete and will be removed in a future release
warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
Note: Some input files use or override a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
3 warnings
warning: [options] source value 7 is obsolete and will be removed in a future release
warning: [options] target value 7 is obsolete and will be removed in a future release
warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
Note: /Users/techverx/Desktop/Saad Naveed/Projects/pavemint/client-react-native/node_modules/react-native-geolocation-service/android/src/main/java/com/agontuk/RNFusedLocation/LocationUtils.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
3 warnings
warning: [options] source value 7 is obsolete and will be removed in a future release
warning: [options] target value 7 is obsolete and will be removed in a future release
warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
Note: Some input files use or override a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
Note: /Users/techverx/Desktop/Saad Naveed/Projects/pavemint/client-react-native/node_modules/react-native-fbsdk/android/src/main/java/com/facebook/reactnative/androidsdk/Utility.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
3 warnings
warning: [options] source value 7 is obsolete and will be removed in a future release
warning: [options] target value 7 is obsolete and will be removed in a future release
warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
Note: /Users/techverx/Desktop/Saad Naveed/Projects/pavemint/client-react-native/node_modules/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
Note: Some input files use unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
3 warnings
warning: [options] source value 7 is obsolete and will be removed in a future release
warning: [options] target value 7 is obsolete and will be removed in a future release
warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
3 warnings
warning: [options] source value 7 is obsolete and will be removed in a future release
warning: [options] target value 7 is obsolete and will be removed in a future release
warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
Note: /Users/techverx/Desktop/Saad Naveed/Projects/pavemint/client-react-native/node_modules/@react-native-google-signin/google-signin/android/src/main/java/co/apptailor/googlesignin/RNGoogleSigninModule.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
3 warnings
warning: [options] source value 7 is obsolete and will be removed in a future release
warning: [options] target value 7 is obsolete and will be removed in a future release
warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
3 warnings
warning: [options] source value 7 is obsolete and will be removed in a future release
warning: [options] target value 7 is obsolete and will be removed in a future release
warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
3 warnings
Note: /Users/techverx/Desktop/Saad Naveed/Projects/pavemint/client-react-native/node_modules/@invertase/react-native-apple-authentication/android/src/main/java/com/RNAppleAuthentication/AppleAuthenticationAndroidModule.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
warning: [options] source value 7 is obsolete and will be removed in a future release
warning: [options] target value 7 is obsolete and will be removed in a future release
warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
warning: [options] source value 7 is obsolete and will be removed in a future release
warning: [options] target value 7 is obsolete and will be removed in a future release
warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
Note: /Users/techverx/Desktop/Saad Naveed/Projects/pavemint/client-react-native/node_modules/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NodesManager.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
Note: /Users/techverx/Desktop/Saad Naveed/Projects/pavemint/client-react-native/node_modules/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NodesManager.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
3 warnings
warning: [options] source value 7 is obsolete and will be removed in a future release
warning: [options] target value 7 is obsolete and will be removed in a future release
warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
Note: /Users/techverx/Desktop/Saad Naveed/Projects/pavemint/client-react-native/node_modules/react-native-sensitive-info/android/src/main/java/br/com/classapp/RNSensitiveInfo/RNSensitiveInfoModule.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
3 warnings
Note: Some input files use or override a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
Note: Some input files use unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
3 warnings
warning: [options] source value 7 is obsolete and will be removed in a future release
warning: [options] target value 7 is obsolete and will be removed in a future release
warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
Note: Some input files use or override a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
Note: Some input files use unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
3 warnings
warning: [options] source value 7 is obsolete and will be removed in a future release
warning: [options] target value 7 is obsolete and will be removed in a future release
warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
warning: [options] source value 7 is obsolete and will be removed in a future release
warning: [options] target value 7 is obsolete and will be removed in a future release
warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
3 warnings
Note: /Users/techverx/Desktop/Saad Naveed/Projects/pavemint/client-react-native/node_modules/@sentry/react-native/android/src/main/java/io/sentry/react/RNSentryModule.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
3 warnings
warning: [options] source value 7 is obsolete and will be removed in a future release
warning: [options] target value 7 is obsolete and will be removed in a future release
warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
[com.stripe:payments-core:16.10.0] /Users/techverx/.gradle/caches/transforms-2/files-2.1/14b92244abe2daf85a91337f2f413533/payments-core-16.10.0/AndroidManifest.xml:13:9-54 Error:
Missing 'package' key attribute on element package at [com.stripe:payments-core:16.10.0] AndroidManifest.xml:13:9-54
[com.stripe:payments-core:16.10.0] /Users/techverx/.gradle/caches/transforms-2/files-2.1/14b92244abe2daf85a91337f2f413533/payments-core-16.10.0/AndroidManifest.xml Error:
Validation failed, exiting
Note: Some input files use or override a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
3 warnings

FAILURE: Build failed with an exception.

  • What went wrong:
    Execution failed for task ':app:processDebugManifest'.

Manifest merger failed with multiple errors, see logs

@ImPDP
Copy link

ImPDP commented Jun 17, 2021

make sure the Google Play services are with version 17 or above.

@saadnaveed94
Copy link

Using version 17.0.0

@wijskinner
Copy link

wijskinner commented Jun 17, 2021

To get it to work for me I had to bump my apps build:gradle dependency to 3.5.4 in android/build.gradle e.g.

classpath("com.android.tools.build:gradle:3.5.4")

@saadnaveed94
Copy link

saadnaveed94 commented Jun 17, 2021

@wijskinner updating classpath("com.android.tools.build:gradle:3.5.2") to classpath("com.android.tools.build:gradle:3.5.4") worked for me

@wijskinner
Copy link

This seemed to be ok for the methods I listed above, however I have just tested this with an auth requiring stripe test card 4000 0027 6000 3184 (as in should get a 3ds popup), and it caused the app to crash with the following error:

FATAL EXCEPTION: main
Process: com.xxx, PID: 6987
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.xxx/com.stripe.android.view.PaymentAuthWebViewActivity}: java.lang.IllegalStateException: PaymentConfiguration was not initialized. Call PaymentConfiguration.init().
	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3449)
	at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
	at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
	at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
	at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
	at android.os.Handler.dispatchMessage(Handler.java:106)
	at android.os.Looper.loop(Looper.java:223)
	at android.app.ActivityThread.main(ActivityThread.java:7656)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
Caused by: java.lang.IllegalStateException: PaymentConfiguration was not initialized. Call PaymentConfiguration.init().
	at com.stripe.android.PaymentConfiguration$Companion.loadInstance(PaymentConfiguration.kt:74)
	at com.stripe.android.PaymentConfiguration$Companion.getInstance(PaymentConfiguration.kt:66)
	at com.stripe.android.view.PaymentAuthWebViewActivityViewModel$Factory.create(PaymentAuthWebViewActivityViewModel.kt:120)
	at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:187)
	at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150)
	at androidx.lifecycle.ViewModelLazy.getValue(ViewModelProvider.kt:54)
	at androidx.lifecycle.ViewModelLazy.getValue(ViewModelProvider.kt:41)
	at com.stripe.android.view.PaymentAuthWebViewActivity.getViewModel(Unknown Source:2)
	at com.stripe.android.view.PaymentAuthWebViewActivity.customizeToolbar(PaymentAuthWebViewActivity.kt:172)
	at com.stripe.android.view.PaymentAuthWebViewActivity.onCreate(PaymentAuthWebViewActivity.kt:65)
	at android.app.Activity.performCreate(Activity.java:8000)
	at android.app.Activity.performCreate(Activity.java:7984)
	at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3422)
	... 11 more

So I would say some more work needed. Will take a look if I have time tomorrow.

@wijskinner
Copy link

Also thanks to @cybergrind for moving so quickly. But could we make this a beta at best. With a nice warning about probably breaking everything if possible.

@cybergrind
Copy link
Member

@wijskinner :D it is released as a beta, so users must explicitly install it

@wijskinner
Copy link

Phew. 😄

@wijskinner
Copy link

OK - super easy fix for that 3ds dialog issue. Just needed to add a:

PaymentConfiguration.init(getReactApplicationContext(), mPublicKey);

In StripeModule.java init function. Will do a PR when I have a sec.

@wijskinner
Copy link

Fix is there ☝️ for anyone who needs it. I'm having to use a fork anyway as we can't move our app to target iOS11 so I'm branching off tipsi-stripe 8.0.2 with the above fixes added. In any normal scenario we would have moved our app to the official stripe-react-native, but we're actually in the process of moving away from stripe so having to use a sticky plaster to keep google happy just for the next month or so.

@alexpchin
Copy link

Hi all, will this fix be released in a version > 9.1.0 soon. I can't see a beta release at the moment and I'm also stuck with the error:

Google Api Error: Invalid request - Your artifact (version code 515) contains an SDK (com.stripe:stripe-android) with version 10.4.6 that doesn't comply with Google Play policies. To publish a new release, remove this SDK from your app or ask the SDK developer for a different version. For more information see https://support.google.com/googleplay/android-developer/answer/10358880.

I can go through and make the suggested changes but if a new version is being imminently released, I'll hold off.

@cybergrind
Copy link
Member

ok, now we have beta 9.1.1
Install with npm install -s tipsi-stripe@9.1.1

@DanGDroid @saadnaveed94 your turn

@manohar-octifi
Copy link

Hi all, Need help After updating to the latest version I'm getting this error on running Archive

ld: library not found for -lPods-***
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Tried excluding arm64
XCODE 12.4
Stripe 21.3.1
tipsi-stripe 9.1.1

@ImPDP
Copy link

ImPDP commented Jun 21, 2021

recent beta version has change in Java code only. ios directory is untouched. try 9.1.0. if you face same error, raise new issue.

@hbole
Copy link

hbole commented Jun 21, 2021

#790 (comment)

Can someone help on this please @wijskinner @ImPDP @saadnaveed94 @cybergrind

@wijskinner
Copy link

I haven't tried that method @hbole. Or any method that isn't the ones I listed above I'm afraid. That error seems to be implying you are mixing test/live stripe. How did you generate the token - are you sure it was with your live key? Will have a quick look in the stripe-android code now for anything obvious.

@hbole
Copy link

hbole commented Jun 22, 2021

@wijskinner I was using this beta version of tipsi-stripe 8.0.0-beta.10, My code is working absolutely fine with this beta version. Can you please help me with the latest beta version so that I can publish my app on play store.

@wijskinner
Copy link

The error suggests you are reusing an idempotency_key in your params (that has been used previously) with different other params. See this answer on stack overflow: https://stackoverflow.com/a/51281228/5222819 . Are you trying to redo a previously failed transaction - not sure this would work if the first was made on an earlier SDK?

I don't see anything in the code to indicate this needs to be dealt with differently with latest SDK.

@hbole
Copy link

hbole commented Jun 25, 2021

I am not trying to redo any previous transaction @wijskinner

@suseendiran
Copy link

suseendiran commented Jun 25, 2021

The error suggests you are reusing an idempotency_key in your params (that has been used previously) with different other params. See this answer on stack overflow: https://stackoverflow.com/a/51281228/5222819 . Are you trying to redo a previously failed transaction - not sure this would work if the first was made on an earlier SDK?

I don't see anything in the code to indicate this needs to be dealt with differently with latest SDK.

I'm also getting the same error when creating token second time with different card.
I did not pass any idempotency_key

My code:
tipsiStripe.createTokenWithCard(cardInfo).then(onTokenCreated).catch(onTokenCreateError);

The same code works well in "tipsi-stripe": "^8.0.2"

@hbole
Copy link

hbole commented Jun 26, 2021

@cybergrind
Copy link
Member

@hbole probably you need to read about idempotency https://stripe.com/docs/api/idempotent_requests

also, I'm giving you a timeout for excessive pinging people

@cybergrind
Copy link
Member

Another fix arrived in beta npm install tipsi-stripe@9.1.3 to get fix from #800

@edgarbonillag
Copy link

In my case, my app with tipsi-stripe@9.1.3 or any version that includes the new Android Stripe SDK to 16.9.0 is building successfully but crashing at start up :/

@cybergrind
Copy link
Member

hi @edgarbonillag , it would be great if you create an issue and link it here, probably someone will figure out how to fix it

This was linked to pull requests Jul 5, 2021
@edgarbonillag
Copy link

edgarbonillag commented Jul 6, 2021

Hi @cybergrind , thanks for your comment. I have a project running perfectly with tipsi-stripe@9.0.0, but when I update to any of the 9.1.x versions, the project builds successfully but the app crashes at start up. Is there other modification on the native or JS code I need to make besides just installing the new version? I wanted first to rule out issues relates maybe to my specific project before opening an issue. Thanks!
Edit: The project's RN version is 0.61.5

@edgarbonillag
Copy link

Hi @cybergrind and everyone experiencing issues. Just FYI I finally managed to make my project work! 🎉
The issue was related to AndroidX on my project. Thanks to Crashlytics, I could know exactly what was happening, because the JS debugger and even the Android debugger wasn't giving me any hint.

Basically

Fatal Exception: java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/core/view/OnReceiveContentViewBehavior;
       at androidx.appcompat.app.AppCompatDelegateImpl.createView(AppCompatDelegateImpl.java:1530)
       at ...

on
android.view.LayoutInflater.tryCreateView (LayoutInflater.java:1061)

Caused by java.lang.ClassNotFoundException: Didn't find class "androidx.core.view.OnReceiveContentViewBehavior" on path: DexPathList[[zip file "/data/app/com.ujama.staging-XD9Ycd0aXRT2nU9Xgc4NQQ==/base.apk"],nativeLibraryDirectories=[/data/app/com.ujama.staging-XD9Ycd0aXRT2nU9Xgc4NQQ==/lib/x86, /data/app/com.ujama.staging-XD9Ycd0aXRT2nU9Xgc4NQQ==/base.apk!/lib/x86, /system/lib, /system/product/lib]]
       at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:196)
       at ...

The cause was that I was using a very old version of androidx core library and the androidx.core:core-ktx library was also missing. So everything was solved after configuring my project like this: (and removing other project specific configs that were preventing the project to use those latest versions of AndroidX libraries)

implementation 'androidx.core:core:1.6.0'
implementation 'androidx.core:core-ktx:1.6.0'

@cybergrind
Copy link
Member

Rolled it as public release 9.1.4

@RajeshRam
Copy link

New version 9.1.4 has missing function.
Can we expect an update for this soon.

@ReactMethod
public void createTokenWithBankAccount(final ReadableMap accountData, final Promise promise) {
try {
ArgCheck.nonNull(mStripe);
ArgCheck.notEmptyString(mPublicKey);
} catch (Exception e) {
promise.reject(toErrorCode(e), e.getMessage());
}
}

@fbartho
Copy link
Collaborator

fbartho commented Jun 20, 2023

Closing this ticket, so that new users don't think this project is still active.

Stripe does not want you using this, and you will find pain if you do; Please migrate to the official @stripe/stripe-react-native package, for your user's safety, and your developer's sanity!

See more: #842

@fbartho fbartho closed this as completed Jun 20, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.