data;
- @Size(4) private String last4;
- @CardBrand private String brand;
+ @Size(4)
+ private String last4;
+ @CardBrand
+ private String brand;
/**
* Converts an unchecked String value to a {@link CardBrand} or {@code null}.
@@ -149,24 +153,213 @@ public static String asCardBrand(@Nullable String possibleCardType) {
}
/**
- * Convenience constructor for a Card object with a minimum number of inputs.
- *
* @param number the card number
* @param expMonth the expiry month
* @param expYear the expiry year
* @param cvc the CVC code
+ * @deprecated public constructor will be removed in next version, use Card.create(number, expMonth, expYear,cvc) instead.
+ *
+ * Convenience constructor for a Card object with a minimum number of inputs.
*/
+ @Deprecated
public Card(
String number,
Integer expMonth,
Integer expYear,
String cvc) {
+
+ this(number, expMonth, expYear, cvc, new HashMap<>());
+ }
+
+ /**
+ * @param number the card number
+ * @param expMonth the expiry month
+ * @param expYear the expiry year
+ * @param cvc the CVC code
+ * @param data additional data for name, zip, address, country, email
+ */
+ private Card(String number,
+ Integer expMonth,
+ Integer expYear,
+ String cvc,
+ Map data) {
this.number = MonriTextUtils.nullIfBlank(normalizeCardNumber(number));
this.expMonth = expMonth;
this.expYear = expYear;
this.cvc = MonriTextUtils.nullIfBlank(cvc);
this.brand = getBrand();
this.last4 = MonriTextUtils.nullIfBlank(last4) == null ? getLast4() : last4;
+ this.data = data;
+ }
+
+ /**
+ * Builder class for Card model
+ */
+ public static class CardBuilder {
+
+ private final Map data;
+ private final Card card;
+
+ /**
+ * @param number the credit card number
+ * @param expMonth the expiry month, as an integer value between 1 and 12
+ * @param expYear expiry year
+ * @param cvc the card CVC number
+ * @param data additional data for name, zip, address, country, email
+ */
+ private CardBuilder(final String number,
+ final Integer expMonth,
+ final Integer expYear,
+ final String cvc,
+ Map data) {
+
+ this.data = new HashMap<>(data);
+ this.card = new Card(number, expMonth, expYear, cvc);
+ }
+
+ public CardBuilder name(final String name) {
+ this.data.put(AdditionalData.Constants.FULL_NAME, name);
+ return this;
+ }
+
+ public String getName() {
+ return (String) data.get(AdditionalData.Constants.FULL_NAME);
+ }
+
+ public CardBuilder address(final String address) {
+ this.data.put(AdditionalData.Constants.ADDRESS, address);
+ return this;
+ }
+
+ public String getAddress() {
+ return (String) data.get(AdditionalData.Constants.ADDRESS);
+ }
+
+ public String getCity() {
+ return (String) data.get(AdditionalData.Constants.CITY);
+ }
+
+ public CardBuilder city(final String city) {
+ this.data.put(AdditionalData.Constants.CITY, city);
+ return this;
+ }
+
+ public CardBuilder zip(final String zip) {
+ this.data.put(AdditionalData.Constants.ZIP, zip);
+ return this;
+ }
+
+ public String getZip() {
+ return (String) data.get(AdditionalData.Constants.ZIP);
+ }
+
+ public CardBuilder country(final String country) {
+ this.data.put(AdditionalData.Constants.COUNTRY, country);
+ return this;
+ }
+
+ public String getCountry() {
+ return (String) data.get(AdditionalData.Constants.COUNTRY);
+ }
+
+ public String getPhone() {
+ return (String) data.get(AdditionalData.Constants.PHONE);
+ }
+
+ public CardBuilder phone(final String phone) {
+ this.data.put(AdditionalData.Constants.PHONE, phone);
+ return this;
+ }
+
+ public CardBuilder email(final String email) {
+ this.data.put(AdditionalData.Constants.EMAIL, email);
+ return this;
+ }
+
+ public String getEmail() {
+ return (String) data.get(AdditionalData.Constants.EMAIL);
+ }
+
+ public CardBuilder data(Map data) {
+ this.data.put(AdditionalData.Constants.META_DATA, data);
+ return this;
+ }
+
+
+ public Map getData() {
+ //noinspection unchecked
+ return Collections.unmodifiableMap((Map) data.get(AdditionalData.Constants.META_DATA));
+ }
+
+ public boolean validate() {
+
+ return card.validateCard(Calendar.getInstance()) && validateData();
+
+ }
+
+ /**
+ * Checks whether or not additional data is valid
+ *
+ * @return 'true' if all additional data is valid, 'false' otherwise
+ */
+ private boolean validateData() {
+
+ for (String dataKey : data.keySet()) {
+ final Object dataValue = data.get(dataKey);
+
+ if (dataValue == null) {
+ return false;
+ }
+
+ final AdditionalData additionalData = AdditionalData.fromValue(dataKey);
+
+ if (!additionalData.isValid(dataValue)) {
+ return false;
+ }
+ }
+
+ return true;
+
+ }
+
+ /**
+ * @return Card instance
+ */
+ public Card build() {
+ card.setData(this.data);
+ return card;
+ }
+
+ }
+
+ /**
+ * @return a CardBuilder populated with the fields of this Card instance
+ */
+ public CardBuilder toBuilder() {
+ return new CardBuilder(number, expMonth, expYear, cvc, data);
+ }
+
+ /**
+ * @param number the card number
+ * @param expMonth the expiry month
+ * @param expYear the expiry year
+ * @param cvc the CVC code
+ * @return Card object with populated fields
+ */
+ public static Card create(final String number,
+ final Integer expMonth,
+ final Integer expYear,
+ final String cvc) {
+ return new CardBuilder(number, expMonth, expYear, cvc, new HashMap<>())
+ .build();
+ }
+
+ private void setData(final Map data) {
+ this.data = data;
+ }
+
+ public Map getData() {
+ return Collections.unmodifiableMap(data);
}
/**
diff --git a/monri/src/main/java/com/monri/android/model/ModelUtils.java b/monri/src/main/java/com/monri/android/model/ModelUtils.java
index e853da7..5763437 100644
--- a/monri/src/main/java/com/monri/android/model/ModelUtils.java
+++ b/monri/src/main/java/com/monri/android/model/ModelUtils.java
@@ -1,9 +1,9 @@
package com.monri.android.model;
-import android.text.TextUtils;
-
import androidx.annotation.Nullable;
+import com.monri.android.MonriTextUtils;
+
import java.util.Calendar;
import java.util.Locale;
@@ -19,7 +19,7 @@ public class ModelUtils {
* @return {@code true} if the input value consists entirely of integers
*/
static boolean isWholePositiveNumber(@Nullable String value) {
- return value != null && TextUtils.isDigitsOnly(value);
+ return value != null && MonriTextUtils.isDigitsOnly(value);
}
/**
diff --git a/monri/src/main/java/com/monri/android/model/ValidationType.java b/monri/src/main/java/com/monri/android/model/ValidationType.java
new file mode 100644
index 0000000..7298614
--- /dev/null
+++ b/monri/src/main/java/com/monri/android/model/ValidationType.java
@@ -0,0 +1,6 @@
+package com.monri.android.model;
+
+public enum ValidationType {
+ REGEX,
+ MAP_SIZE_VALIDATION
+}
diff --git a/monri/src/main/java/com/monri/android/view/CardMultilineWidget.java b/monri/src/main/java/com/monri/android/view/CardMultilineWidget.java
index 787d66b..3f5c9e9 100644
--- a/monri/src/main/java/com/monri/android/view/CardMultilineWidget.java
+++ b/monri/src/main/java/com/monri/android/view/CardMultilineWidget.java
@@ -122,7 +122,8 @@ public Card getCard() {
int[] cardDate = mExpiryDateEditText.getValidDateFields();
String cvcValue = mCvcEditText.getText().toString();
- Card card = new Card(cardNumber, cardDate[0], cardDate[1], cvcValue);
+ Card card = Card.create(cardNumber, cardDate[0], cardDate[1], cvcValue);
+
if (mShouldShowPostalCode) {
// card.setAddressZip(mPostalCodeEditText.getText().toString());
}
diff --git a/monri/src/test/java/com/monri/android/model/CardTest.java b/monri/src/test/java/com/monri/android/model/CardTest.java
new file mode 100644
index 0000000..ac47eae
--- /dev/null
+++ b/monri/src/test/java/com/monri/android/model/CardTest.java
@@ -0,0 +1,190 @@
+package com.monri.android.model;
+
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class CardTest {
+
+ private static Card createExampleCard() {
+ return Card.create("4111 1111 1111 1111", 12, 2024, "123");
+ }
+
+
+ @Test
+ public void testBuilderDeepCopy() {
+ final Card card = createExampleCard();
+ final Card card1 = card.toBuilder().country("BIH").build();
+ final Card card2 = card1.toBuilder().zip("71000").build();
+
+ //card should have null ip and country
+ Assert.assertNull(card.getData().get(AdditionalData.COUNTRY.getFieldName()));
+ Assert.assertNull(card.getData().get(AdditionalData.ZIP.getFieldName()));
+
+ //card1 should have only country set
+ Assert.assertNull(card1.getData().get(AdditionalData.ZIP.getFieldName()));
+ Assert.assertEquals("BIH", card1.getData().get(AdditionalData.COUNTRY.getFieldName()));
+
+ //card2 should have zip and country
+ Assert.assertEquals("BIH", card2.getData().get(AdditionalData.COUNTRY.getFieldName()));
+ Assert.assertEquals("71000", card2.getData().get(AdditionalData.ZIP.getFieldName()));
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testDataImmutable() {
+ final Card exampleCard = createExampleCard();
+ exampleCard.getData().put(AdditionalData.ZIP.getFieldName(), "71000");
+
+ Assert.assertNull(exampleCard.getData().get(AdditionalData.ZIP.getFieldName()));
+ }
+
+ @Test
+ public void validName() {
+ final Card exampleCard = createExampleCard();
+ final String name = "Adnan";
+
+ final Card modifiedCard = exampleCard.toBuilder().name(name).build();
+
+ Assert.assertNull(exampleCard.getData().get(AdditionalData.FULL_NAME.getFieldName()));
+ Assert.assertEquals(name, modifiedCard.getData().get(AdditionalData.FULL_NAME.getFieldName()));
+
+ Assert.assertTrue(modifiedCard.toBuilder().validate());
+ Assert.assertTrue(exampleCard.validateCard());
+ Assert.assertTrue(modifiedCard.validateCard());
+
+ }
+
+ @Test
+ public void validAddress() {
+ final Card exampleCard = createExampleCard();
+ final String address = "Sabita Užičanina br. 17";
+
+ final Card modifiedCard = exampleCard.toBuilder()
+ .address(address)
+ .build();
+
+ Assert.assertEquals(address, modifiedCard.getData().get(AdditionalData.ADDRESS.getFieldName()));
+ Assert.assertNull(exampleCard.getData().get(AdditionalData.ADDRESS.getFieldName()));
+
+ Assert.assertTrue(modifiedCard.toBuilder().validate());
+ Assert.assertTrue(exampleCard.validateCard());
+ Assert.assertTrue(modifiedCard.validateCard());
+ }
+
+ @Test
+ public void validCity() {
+ final Card exampleCard = createExampleCard();
+ final String city = "Skoplje";
+
+ final Card modifiedCard = exampleCard.toBuilder()
+ .city(city)
+ .build();
+
+ Assert.assertEquals(city, modifiedCard.getData().get(AdditionalData.CITY.getFieldName()));
+ Assert.assertNull(exampleCard.getData().get(AdditionalData.CITY.getFieldName()));
+
+ Assert.assertTrue(modifiedCard.toBuilder().validate());
+ Assert.assertTrue(exampleCard.validateCard());
+ Assert.assertTrue(modifiedCard.validateCard());
+
+ }
+
+ @Test
+ public void validZip() {
+ final Card exampleCard = createExampleCard();
+ final String zip = "10040";
+
+ final Card modifiedCard = exampleCard.toBuilder()
+ .zip(zip)
+ .build();
+
+ Assert.assertTrue(modifiedCard.toBuilder().validate());
+ Assert.assertEquals(zip, modifiedCard.getData().get(AdditionalData.ZIP.getFieldName()));
+ Assert.assertNull(exampleCard.getData().get(AdditionalData.ZIP.getFieldName()));
+
+ }
+
+ @Test
+ public void validPhone() {
+ final Card exampleCard = createExampleCard();
+ final String phoneNumber = "+38763 589-521";
+
+ final Card modifiedCard = exampleCard.toBuilder()
+ .phone(phoneNumber)
+ .build();
+
+ Assert.assertEquals(phoneNumber, modifiedCard.getData().get(AdditionalData.PHONE.getFieldName()));
+ Assert.assertNull(exampleCard.getData().get(AdditionalData.PHONE.getFieldName()));
+
+ Assert.assertTrue(modifiedCard.toBuilder().validate());
+ Assert.assertTrue(exampleCard.validateCard());
+ Assert.assertTrue(modifiedCard.validateCard());
+
+ }
+
+ @Test
+ public void validEmail() {
+ final Card exampleCard = createExampleCard();
+ final String email = "monri@monri.com";
+
+ final Card modifiedCard = exampleCard.toBuilder()
+ .email(email)
+ .build();
+
+ Assert.assertEquals(email, modifiedCard.getData().get(AdditionalData.EMAIL.getFieldName()));
+ Assert.assertNull(exampleCard.getData().get(AdditionalData.EMAIL.getFieldName()));
+
+ Assert.assertTrue(modifiedCard.toBuilder().validate());
+ Assert.assertTrue(exampleCard.validateCard());
+ Assert.assertTrue(modifiedCard.validateCard());
+
+ }
+
+ @Test
+ public void validData() {
+ final Card exampleCard = createExampleCard();
+ final Map testData = new HashMap(){{
+ put("vip","true");
+ put("business","yea");
+ }};
+
+
+ final Card modifiedCard = exampleCard.toBuilder()
+ .data(testData)
+ .build();
+
+ Assert.assertTrue(modifiedCard.toBuilder().validate());
+ Assert.assertTrue(exampleCard.validateCard());
+ Assert.assertTrue(modifiedCard.validateCard());
+
+ }
+
+ @Test
+ public void validAllData() {
+ final Card exampleCard = createExampleCard();
+ final boolean isDataValid = exampleCard.toBuilder()
+ .name("Adnan")
+ .address("Zagrebačka br. 19")
+ .city("Sarajevo")
+ .zip("71000")
+ .phone("063722982")
+ .email("zbregov@protein.com")
+ .validate();
+
+ final boolean isCardValidAfterAddingData = exampleCard.validateCard();
+
+ Assert.assertTrue(isCardValidAfterAddingData);
+ Assert.assertTrue(isDataValid);
+
+ Assert.assertTrue(exampleCard.validateCard());
+ }
+
+ @Test
+ public void validateCard() {
+ final Card exampleCard = createExampleCard();
+ Assert.assertTrue(exampleCard.validateCard());
+ }
+}
\ No newline at end of file