Skip to content

Commit

Permalink
Merge pull request #266 from Adyen/feature/hide_cvc
Browse files Browse the repository at this point in the history
Allow CardComponent to not request CVC
  • Loading branch information
caiofaustino authored Nov 4, 2020
2 parents f987673 + 715703c commit cb86790
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ public PaymentComponentState getState() {
* @param inputData {@link InputDataT}
*/
public final void inputDataChanged(@NonNull InputDataT inputData) {
Logger.v(TAG, "inputDataChanged");
final OutputDataT newOutputData = onInputDataChanged(inputData);
if (!newOutputData.equals(mOutputData)) {
mOutputData = newOutputData;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,14 @@ public static List<CardType> estimate(@NonNull String cardNumber) {
}

/**
* Get CardType from txVariant.
* Get CardType from the brand name as it appears in the Checkout API.
* @see <a href="https://docs.adyen.com/api-explorer/#/CheckoutService/v65/post/paymentMethods__resParam_storedPaymentMethods-brand"></a>
*/
@Nullable
public static CardType getCardTypeByTxVariant(@NonNull String txVariant) {
return MAPPED_BY_NAME.get(txVariant);
public static CardType getByBrandName(@NonNull String brand) {
return MAPPED_BY_NAME.get(brand);
}


CardType(@NonNull String txVariant, @NonNull Pattern pattern) {
mTxVariant = txVariant;
mPattern = pattern;
Expand Down
55 changes: 33 additions & 22 deletions card-base/src/main/java/com/adyen/checkout/card/CardComponent.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

import com.adyen.checkout.base.PaymentComponentProvider;
import com.adyen.checkout.base.component.BasePaymentComponent;
import com.adyen.checkout.base.model.paymentmethods.InputDetail;
import com.adyen.checkout.base.model.paymentmethods.PaymentMethod;
import com.adyen.checkout.base.model.paymentmethods.StoredPaymentMethod;
import com.adyen.checkout.base.model.payments.request.CardPaymentMethod;
Expand All @@ -33,7 +32,9 @@

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

public final class CardComponent extends BasePaymentComponent<
CardConfiguration,
Expand All @@ -47,6 +48,14 @@ public final class CardComponent extends BasePaymentComponent<
private static final String[] PAYMENT_METHOD_TYPES = {PaymentMethodTypes.SCHEME};
private static final int BIN_VALUE_LENGTH = 6;

private static final Set<CardType> NO_CVC_BRANDS;

static {
final HashSet<CardType> brandSet = new HashSet<>();
brandSet.add(CardType.BCMC);
NO_CVC_BRANDS = Collections.unmodifiableSet(brandSet);
}

private List<CardType> mFilteredSupportedCards = Collections.emptyList();
private CardInputData mStoredPaymentInputData;

Expand All @@ -73,7 +82,7 @@ public CardComponent(@NonNull StoredPaymentMethod paymentMethod, @NonNull CardCo
mStoredPaymentInputData.setExpiryDate(ExpiryDate.EMPTY_DATE);
}

final CardType cardType = CardType.getCardTypeByTxVariant(paymentMethod.getBrand());
final CardType cardType = CardType.getByBrandName(paymentMethod.getBrand());
if (cardType != null) {
final List<CardType> storedCardType = new ArrayList<>();
storedCardType.add(cardType);
Expand Down Expand Up @@ -123,7 +132,8 @@ protected CardOutputData onInputDataChanged(@NonNull CardInputData inputData) {
validateExpiryDate(inputData.getExpiryDate()),
validateSecurityCode(inputData.getSecurityCode()),
validateHolderName(inputData.getHolderName()),
inputData.isStorePaymentEnable()
inputData.isStorePaymentEnable(),
isCvcHidden()
);
}

Expand Down Expand Up @@ -163,7 +173,9 @@ protected CardComponentState createComponentState() {
card.setNumber(outputData.getCardNumberField().getValue());
}

card.setSecurityCode(outputData.getSecurityCodeField().getValue());
if (!isCvcHidden()) {
card.setSecurityCode(outputData.getSecurityCodeField().getValue());
}

final ExpiryDate expiryDateResult = outputData.getExpiryDateField().getValue();

Expand Down Expand Up @@ -243,16 +255,29 @@ private ValidatedField<ExpiryDate> validateExpiryDate(@NonNull ExpiryDate expiry
}

private ValidatedField<String> validateSecurityCode(@NonNull String securityCode) {
final InputDetail securityCodeInputDetail = getInputDetail("cvc");
final boolean isRequired = securityCodeInputDetail == null || !securityCodeInputDetail.isOptional();
if (isRequired) {
if (isCvcHidden()) {
return new ValidatedField<>(securityCode, ValidatedField.Validation.VALID);
} else {
final CardType firstCardType = !mFilteredSupportedCards.isEmpty() ? mFilteredSupportedCards.get(0) : null;
return CardValidationUtils.validateSecurityCode(securityCode, firstCardType);
}
}

private boolean isCvcHidden() {
if (isStoredPaymentMethod()) {
return getConfiguration().isHideCvcStoredCard() || isBrandWithoutCvc(((StoredPaymentMethod) getPaymentMethod()).getBrand());
} else {
return new ValidatedField<>(securityCode, ValidatedField.Validation.VALID);
return getConfiguration().isHideCvc();
}
}

private boolean isBrandWithoutCvc(@Nullable String brand) {
if (TextUtils.isEmpty(brand)) {
return false;
}
return NO_CVC_BRANDS.contains(CardType.getByBrandName(brand));
}

private ValidatedField<String> validateHolderName(@NonNull String holderName) {
if (isHolderNameRequire() && TextUtils.isEmpty(holderName)) {
return new ValidatedField<>(holderName, ValidatedField.Validation.INVALID);
Expand All @@ -261,20 +286,6 @@ private ValidatedField<String> validateHolderName(@NonNull String holderName) {
}
}

@Nullable
private InputDetail getInputDetail(@NonNull String key) {
final List<InputDetail> details = getPaymentMethod().getDetails();
if (details != null) {
for (InputDetail inputDetail : getPaymentMethod().getDetails()) {
if (key.equals(inputDetail.getKey())) {
return inputDetail;
}
}
}

return null;
}

private String getBinValueFromCardNumber(String cardNumber) {
return cardNumber.length() < BIN_VALUE_LENGTH ? cardNumber : cardNumber.substring(0, BIN_VALUE_LENGTH);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ private CardConfiguration checkSupportedCardTypes(@NonNull PaymentMethod payment
if (brands != null && !brands.isEmpty()) {
supportedCardTypes = new ArrayList<>();
for (String brand : brands) {
final CardType brandType = CardType.getCardTypeByTxVariant(brand);
final CardType brandType = CardType.getByBrandName(brand);
if (brandType != null) {
supportedCardTypes.add(brandType);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ public class CardConfiguration extends Configuration {
private final boolean mHolderNameRequire;
private final List<CardType> mSupportedCardTypes;
private final boolean mShowStorePaymentField;
private final boolean mHideCvc;
private final boolean mHideCvcStoredCard;

public static final Parcelable.Creator<CardConfiguration> CREATOR = new Parcelable.Creator<CardConfiguration>() {
public CardConfiguration createFromParcel(@NonNull Parcel in) {
Expand All @@ -67,6 +69,8 @@ public CardConfiguration[] newArray(int size) {
* @param holderNameRequire If the holder name of the card should be shown as a required field.
* @param showStorePaymentField If the component should show the option to store the card for later use.
* @param supportCardTypes The list of supported card brands to be shown to the user.
* @param hideCvc Hides the CVC field on the payment flow so that it's not required.
* @param hideCvcStoredCard Hides the CVC field on the stored payment flow so that it's not required.
*/
CardConfiguration(
@NonNull Locale shopperLocale,
Expand All @@ -76,14 +80,19 @@ public CardConfiguration[] newArray(int size) {
boolean holderNameRequire,
@NonNull String shopperReference,
boolean showStorePaymentField,
@NonNull List<CardType> supportCardTypes) {
@NonNull List<CardType> supportCardTypes,
boolean hideCvc,
boolean hideCvcStoredCard
) {
super(shopperLocale, environment, clientKey);

mPublicKey = publicKey;
mHolderNameRequire = holderNameRequire;
mSupportedCardTypes = supportCardTypes;
mShopperReference = shopperReference;
mShowStorePaymentField = showStorePaymentField;
mHideCvc = hideCvc;
mHideCvcStoredCard = hideCvcStoredCard;
}

CardConfiguration(@NonNull Parcel in) {
Expand All @@ -93,6 +102,8 @@ public CardConfiguration[] newArray(int size) {
mHolderNameRequire = ParcelUtils.readBoolean(in);
mSupportedCardTypes = in.readArrayList(CardType.class.getClassLoader());
mShowStorePaymentField = ParcelUtils.readBoolean(in);
mHideCvc = ParcelUtils.readBoolean(in);
mHideCvcStoredCard = ParcelUtils.readBoolean(in);
}

@Override
Expand All @@ -103,6 +114,8 @@ public void writeToParcel(@NonNull Parcel dest, int flags) {
ParcelUtils.writeBoolean(dest, mHolderNameRequire);
dest.writeList(mSupportedCardTypes);
ParcelUtils.writeBoolean(dest, mShowStorePaymentField);
ParcelUtils.writeBoolean(dest, mHideCvc);
ParcelUtils.writeBoolean(dest, mHideCvcStoredCard);
}

/**
Expand Down Expand Up @@ -146,6 +159,16 @@ public Builder newBuilder() {
return new Builder(this);
}

@Nullable
public boolean isHideCvc() {
return mHideCvc;
}

@Nullable
public boolean isHideCvcStoredCard() {
return mHideCvcStoredCard;
}

/**
* Builder to create a {@link CardConfiguration}.
*/
Expand All @@ -157,7 +180,8 @@ public static final class Builder extends BaseConfigurationBuilder<CardConfigura
private boolean mBuilderHolderNameRequire;
private boolean mBuilderShowStorePaymentField = true;
private String mShopperReference;

private boolean mBuilderHideCvc;
private boolean mBuilderHideCvcStoredCard;

/**
* Constructor of Card Configuration Builder with instance of CardConfiguration.
Expand All @@ -171,10 +195,12 @@ public Builder(@NonNull CardConfiguration cardConfiguration) {
mBuilderHolderNameRequire = cardConfiguration.isHolderNameRequire();
mBuilderShowStorePaymentField = cardConfiguration.isShowStorePaymentFieldEnable();
mShopperReference = cardConfiguration.getShopperReference();
mBuilderHideCvc = cardConfiguration.isHideCvc();
mBuilderHideCvcStoredCard = cardConfiguration.isHideCvcStoredCard();
}

/**
* Constructor of Card Configuration Builder with default values.
* Constructor of Card Configuration Builder with default values from Context.
*
* @param context A context
*/
Expand All @@ -183,7 +209,7 @@ public Builder(@NonNull Context context) {
}

/**
* Builder with required parameters for a {@link CardConfiguration}.
* Builder with parameters for a {@link CardConfiguration}.
*
* @param shopperLocale The Locale of the shopper.
* @param environment The {@link Environment} to be used for network calls to Adyen.
Expand Down Expand Up @@ -252,7 +278,7 @@ public Builder setPublicKey(@NonNull String publicKey) {
}

/**
* Set supported card types for card-payment.
* Set the supported card types for this payment. Supported types will be shown as user inputs the card number.
*
* @param supportCardTypes array of {@link CardType}
* @return {@link CardConfiguration.Builder}
Expand All @@ -268,7 +294,7 @@ public Builder setSupportedCardTypes(@NonNull CardType... supportCardTypes) {
}

/**
* Set that if holder name require.
* Set if the holder name is required and should be shown as an input field.
*
* @param holderNameRequire {@link Boolean}
* @return {@link CardConfiguration.Builder}
Expand All @@ -280,7 +306,7 @@ public Builder setHolderNameRequire(boolean holderNameRequire) {
}

/**
* Show store payment field.
* Set if the option to store the card for future payments should be shown as an input field.
*
* @param showStorePaymentField {@link Boolean}
* @return {@link CardConfiguration.Builder}
Expand All @@ -291,12 +317,46 @@ public Builder setShowStorePaymentField(boolean showStorePaymentField) {
return this;
}

/**
* Set the unique reference for the shopper doing this transaction.
* This value will simply be passed back to you in the {@link com.adyen.checkout.base.model.payments.request.PaymentComponentData}
* for convenience.
*
* @param shopperReference The unique shopper reference
* @return {@link CardConfiguration.Builder}
*/
@NonNull
public Builder setShopperReference(@NonNull String shopperReference) {
mShopperReference = shopperReference;
return this;
}

/**
* Set if the CVC field should be hidden from the Component and not requested to the shopper on a regular payment.
* Note that this might have implications for the risk of the transaction. Talk to Adyen Support before enabling this.
*
* @param hideCvc If CVC should be hidden or not.
* @return {@link CardConfiguration.Builder}
*/
@NonNull
public Builder setHideCvc(boolean hideCvc) {
mBuilderHideCvc = hideCvc;
return this;
}

/**
* Set if the CVC field should be hidden from the Component and not requested to the shopper on a stored payment flow.
* Note that this has implications for the risk of the transaction. Talk to Adyen Support before enabling this.
*
* @param hideCvcStoredCard If CVC should be hidden or not for stored payments.
* @return {@link CardConfiguration.Builder}
*/
@NonNull
public Builder setHideCvcStoredCard(boolean hideCvcStoredCard) {
mBuilderHideCvcStoredCard = hideCvcStoredCard;
return this;
}

/**
* Build {@link CardConfiguration} object from {@link CardConfiguration.Builder} inputs.
*
Expand All @@ -309,7 +369,7 @@ public CardConfiguration build() {
throw new CheckoutException("Invalid Public Key. Please find the valid public key on the Customer Area.");
}

// This will not be triggered until the public key check above is removed as it takes prioriy.
// This will not be triggered until the public key check above is removed as it takes priority.
if (!CardValidationUtils.isPublicKeyValid(mBuilderPublicKey) && !ValidationUtils.isClientKeyValid(mBuilderClientKey)) {
throw new CheckoutException("You need either a valid Client key or Public key to use the Card Component.");
}
Expand All @@ -322,7 +382,9 @@ public CardConfiguration build() {
mBuilderHolderNameRequire,
mShopperReference,
mBuilderShowStorePaymentField,
mBuilderSupportedCardTypes
mBuilderSupportedCardTypes,
mBuilderHideCvc,
mBuilderHideCvcStoredCard
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ final class CardOutputData implements OutputData {
private final ValidatedField<ExpiryDate> mExpiryDateField;
private final ValidatedField<String> mSecurityCodeField;

private boolean mIsStoredPaymentMethodEnable;
private final boolean mIsStoredPaymentMethodEnable;
private final boolean mIsCvcHidden;

/**
* Constructs a {@link com.adyen.checkout.card.CardComponent} object.
Expand All @@ -31,13 +32,15 @@ final class CardOutputData implements OutputData {
@NonNull ValidatedField<ExpiryDate> expiryDateField,
@NonNull ValidatedField<String> securityCodeField,
@NonNull ValidatedField<String> holderNameField,
boolean isStoredPaymentMethodEnable
boolean isStoredPaymentMethodEnable,
boolean isCvcHidden
) {
mCardNumberField = cardNumberField;
mExpiryDateField = expiryDateField;
mSecurityCodeField = securityCodeField;
mHolderNameField = holderNameField;
mIsStoredPaymentMethodEnable = isStoredPaymentMethodEnable;
mIsCvcHidden = isCvcHidden;
}

@NonNull
Expand Down Expand Up @@ -68,11 +71,11 @@ public boolean isValid() {
&& mHolderNameField.isValid();
}

public void setStoredPaymentMethodStatus(boolean storedPaymentMethodEnable) {
mIsStoredPaymentMethodEnable = storedPaymentMethodEnable;
}

public boolean isStoredPaymentMethodEnable() {
return mIsStoredPaymentMethodEnable;
}

public boolean isCvcHidden() {
return mIsCvcHidden;
}
}
Loading

0 comments on commit cb86790

Please sign in to comment.