From 7e2d1b54a8162dd4e8c2d9d35319f3c578e8dbf9 Mon Sep 17 00:00:00 2001 From: adnanomerovic Date: Fri, 6 Mar 2020 15:37:39 +0100 Subject: [PATCH 1/2] Builder support: MON-10, MON-11, MON-17 --- .gitignore | 3 +- .idea/codeStyles/Project.xml | 116 ---------- .idea/compiler.xml | 11 - .idea/encodings.xml | 6 - .idea/gradle.xml | 16 -- .idea/misc.xml | 17 -- .idea/runConfigurations.xml | 12 - .idea/vcs.xml | 6 - .../example/PaymentPickerActivity.java | 4 +- .../android/ExampleInstrumentedTest.java | 27 --- .../com/monri/android/MonriTextUtils.java | 14 ++ .../monri/android/model/AdditionalData.java | 86 +++++++ .../java/com/monri/android/model/Card.java | 215 +++++++++++++++++- .../com/monri/android/model/ModelUtils.java | 6 +- .../monri/android/model/ValidationType.java | 6 + .../android/view/CardMultilineWidget.java | 3 +- .../com/monri/android/model/CardTest.java | 190 ++++++++++++++++ 17 files changed, 508 insertions(+), 230 deletions(-) delete mode 100644 .idea/codeStyles/Project.xml delete mode 100644 .idea/compiler.xml delete mode 100644 .idea/encodings.xml delete mode 100644 .idea/gradle.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/runConfigurations.xml delete mode 100644 .idea/vcs.xml delete mode 100644 monri/src/androidTest/java/com/monri/android/ExampleInstrumentedTest.java create mode 100644 monri/src/main/java/com/monri/android/model/AdditionalData.java create mode 100644 monri/src/main/java/com/monri/android/model/ValidationType.java create mode 100644 monri/src/test/java/com/monri/android/model/CardTest.java diff --git a/.gitignore b/.gitignore index d127418..1e78739 100644 --- a/.gitignore +++ b/.gitignore @@ -80,4 +80,5 @@ lint/intermediates/ lint/generated/ lint/outputs/ lint/tmp/ -# lint/reports/ \ No newline at end of file +# lint/reports/ +.idea \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index 681f41a..0000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - - -
- - - - xmlns:android - - ^$ - - - -
-
- - - - xmlns:.* - - ^$ - - - BY_NAME - -
-
- - - - .*:id - - http://schemas.android.com/apk/res/android - - - -
-
- - - - .*:name - - http://schemas.android.com/apk/res/android - - - -
-
- - - - name - - ^$ - - - -
-
- - - - style - - ^$ - - - -
-
- - - - .* - - ^$ - - - BY_NAME - -
-
- - - - .* - - http://schemas.android.com/apk/res/android - - - ANDROID_ATTRIBUTE_ORDER - -
-
- - - - .* - - .* - - - BY_NAME - -
-
-
-
-
-
\ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index a21a80b..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index 97626ba..0000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index d291b3d..0000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 2761f20..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 7f68460..0000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/java/com/monri/android/example/PaymentPickerActivity.java b/app/src/main/java/com/monri/android/example/PaymentPickerActivity.java index 0c566cb..c309923 100644 --- a/app/src/main/java/com/monri/android/example/PaymentPickerActivity.java +++ b/app/src/main/java/com/monri/android/example/PaymentPickerActivity.java @@ -129,11 +129,11 @@ Consumer handlePaymentSessionResponse(SupplierTesting documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - - assertEquals("com.monri.android.test", appContext.getPackageName()); - } -} diff --git a/monri/src/main/java/com/monri/android/MonriTextUtils.java b/monri/src/main/java/com/monri/android/MonriTextUtils.java index 08aa1ce..2b68f47 100644 --- a/monri/src/main/java/com/monri/android/MonriTextUtils.java +++ b/monri/src/main/java/com/monri/android/MonriTextUtils.java @@ -161,4 +161,18 @@ private static String bytesToHex(byte[] bytes) { } return new String(hexChars); } + + /** + * Returns whether the given CharSequence contains only digits. + */ + public static boolean isDigitsOnly(CharSequence str) { + final int len = str.length(); + for (int cp, i = 0; i < len; i += Character.charCount(cp)) { + cp = Character.codePointAt(str, i); + if (!Character.isDigit(cp)) { + return false; + } + } + return true; + } } diff --git a/monri/src/main/java/com/monri/android/model/AdditionalData.java b/monri/src/main/java/com/monri/android/model/AdditionalData.java new file mode 100644 index 0000000..3366fcf --- /dev/null +++ b/monri/src/main/java/com/monri/android/model/AdditionalData.java @@ -0,0 +1,86 @@ +package com.monri.android.model; + +import java.util.Map; + +public enum AdditionalData { + //todo consider min and max length that can be included in regex.. + //todo meta data is map no validation? + FULL_NAME(ValidationType.REGEX, "ch_full_name", Constants.ALPHA_NUMERIC_REGEX, 3, 30), + ADDRESS(ValidationType.REGEX, "ch_address", Constants.ALPHA_NUMERIC_REGEX, 3, 100), + CITY(ValidationType.REGEX, "ch_city", Constants.ALPHA_NUMERIC_REGEX, 3, 30), + ZIP(ValidationType.REGEX, "ch_zip", Constants.ALPHA_NUMERIC_REGEX, 3, 9), + COUNTRY(ValidationType.REGEX, "ch_country", Constants.ALPHA_NUMERIC_REGEX, 3, 30), + PHONE(ValidationType.REGEX, "ch_phone", Constants.PHONE_NUMBER_REGEX, 3, 30), + EMAIL(ValidationType.REGEX, "ch_email", Constants.EMAIL_REGEX, 3, 100), + META_DATA(ValidationType.META_DATA_VALIDATION, "meta_data", "", 0, 255); + + private final ValidationType validationType; + private final String fieldName; + private final String validationData; + private final Integer minLength; + private final Integer maxLength; + + AdditionalData(final ValidationType validationType, final String fieldName, + final String validationData, + final Integer minLength, + final Integer maxLength) { + this.validationType = validationType; + this.fieldName = fieldName; + this.validationData = validationData; + this.minLength = minLength; + this.maxLength = maxLength; + } + + public static AdditionalData fromValue(final String additionalDataKey) { + switch (additionalDataKey) { + case "ch_full_name": + return FULL_NAME; + case "ch_address": + return ADDRESS; + case "ch_city": + return CITY; + case "ch_zip": + return ZIP; + case "ch_country": + return COUNTRY; + case "ch_phone": + return PHONE; + case "ch_email": + return EMAIL; + case "meta_data": + return META_DATA; + default: + throw new IllegalArgumentException(additionalDataKey + " is not supported "); + } + } + + public String getFieldName() { + return fieldName; + } + + public boolean isValid(final Object fieldValue) { + switch (validationType) { + case REGEX: + if (!(fieldValue instanceof String)) { + return false; + } + final String value = (String) fieldValue; + return value.matches(validationData) && value.length() >= minLength && value.length() <= maxLength; + case META_DATA_VALIDATION: + if (!(fieldValue instanceof Map)) { + return false; + } + final Map stringObjectMap = (Map) fieldValue; + return stringObjectMap.size() >= minLength && stringObjectMap.size() <= maxLength; + default: + throw new IllegalArgumentException("Unknown validation type"); + } + + } + + private static class Constants { + private static final String ALPHA_NUMERIC_REGEX = "^[\\p{L}\\p{Z}\\p{N}\\.]+$"; + private static final String EMAIL_REGEX ="^\\w+@[a-zA-Z_]+?\\.[a-zA-Z]{2,3}$"; + private static final String PHONE_NUMBER_REGEX = "^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\\s\\./0-9]*$"; + } +} diff --git a/monri/src/main/java/com/monri/android/model/Card.java b/monri/src/main/java/com/monri/android/model/Card.java index c194adb..e3e8cbe 100644 --- a/monri/src/main/java/com/monri/android/model/Card.java +++ b/monri/src/main/java/com/monri/android/model/Card.java @@ -15,6 +15,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Calendar; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -46,15 +47,15 @@ public Map data() { @Retention(RetentionPolicy.SOURCE) @StringDef({ - AMERICAN_EXPRESS, - DISCOVER, - JCB, - DINERS_CLUB, - VISA, - MASTERCARD, - UNIONPAY, - UNKNOWN - }) + AMERICAN_EXPRESS, + DISCOVER, + JCB, + DINERS_CLUB, + VISA, + MASTERCARD, + UNIONPAY, + UNKNOWN + }) public @interface CardBrand { } @@ -112,9 +113,12 @@ public Map data() { private Integer expMonth; private Integer expYear; private boolean tokenizePan; + private Map metaData; - @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}. @@ -156,17 +160,204 @@ public static String asCardBrand(@Nullable String possibleCardType) { * @param expYear the expiry year * @param cvc the CVC code */ - public Card( + private 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 metaData additional data for name, zip, address, country, email + */ + private Card(String number, + Integer expMonth, + Integer expYear, + String cvc, + Map metaData) { 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.metaData = metaData; + } + + /** + * Builder class for Card model + */ + public static class CardBuilder { + + private final Map metaData; + 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 metaData additional data for name, zip, address, country, email + */ + private CardBuilder(final String number, + final Integer expMonth, + final Integer expYear, + final String cvc, + Map metaData) { + + this.metaData = new HashMap<>(metaData); + this.card = new Card(number, expMonth, expYear, cvc); + } + + public CardBuilder name(final String name) { + this.metaData.put(AdditionalData.FULL_NAME.getFieldName(), name); + return this; + } + + public Object getName() { + return metaData.get(AdditionalData.FULL_NAME.getFieldName()); + } + + public CardBuilder address(final String address) { + this.metaData.put(AdditionalData.ADDRESS.getFieldName(), address); + return this; + } + + public Object getAddress() { + return metaData.get(AdditionalData.ADDRESS.getFieldName()); + } + + public Object getCity() { + return metaData.get(AdditionalData.CITY.getFieldName()); + } + + public CardBuilder city(final String city) { + this.metaData.put(AdditionalData.CITY.getFieldName(), city); + return this; + } + + public CardBuilder zip(final String zip) { + this.metaData.put(AdditionalData.ZIP.getFieldName(), zip); + return this; + } + + public Object getZip() { + return metaData.get(AdditionalData.ZIP.getFieldName()); + } + + public CardBuilder country(final String country) { + this.metaData.put(AdditionalData.COUNTRY.getFieldName(), country); + return this; + } + + public Object getCountry() { + return metaData.get(AdditionalData.COUNTRY.getFieldName()); + } + + public Object getPhone() { + return metaData.get(AdditionalData.PHONE.getFieldName()); + } + + public CardBuilder phone(final String phone) { + this.metaData.put(AdditionalData.PHONE.getFieldName(), phone); + return this; + } + + public CardBuilder email(final String email) { + this.metaData.put(AdditionalData.EMAIL.getFieldName(), email); + return this; + } + + public Object getEmail() { + return metaData.get(AdditionalData.EMAIL.getFieldName()); + } + + public CardBuilder metaData(Map metaData) { + this.metaData.put(AdditionalData.META_DATA.getFieldName(), metaData); + return this; + } + + + public Map getMetaData() { + //noinspection unchecked + return Collections.unmodifiableMap((Map) metaData.get(AdditionalData.META_DATA.getFieldName())); + } + + public boolean validate() { + + return card.validateCard(Calendar.getInstance()) && validateMetaData(); + + } + + /** + * Checks whether or not additional data is valid + * + * @return 'true' if all additional data is valid, 'false' otherwise + */ + private boolean validateMetaData() { + + for (String metaDataKey : metaData.keySet()) { + final Object metaDataValue = metaData.get(metaDataKey); + + if (metaDataValue == null) { + return false; + } + + final AdditionalData additionalData = AdditionalData.fromValue(metaDataKey); + + if (!additionalData.isValid(metaDataValue)) { + return false; + } + } + + return true; + + } + + /** + * @return Card instance + */ + public Card build() { + card.setMetaData(this.metaData); + return card; + } + + } + + /** + * @return a CardBuilder populated with the fields of this Card instance + */ + public CardBuilder toBuilder() { + return new CardBuilder(number, expMonth, expYear, cvc, metaData); + } + + /** + * @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 setMetaData(final Map metaData) { + this.metaData = metaData; + } + + public Map getMetaData() { + return Collections.unmodifiableMap(metaData); } /** 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..c4d19da --- /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, + META_DATA_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..94a5c0e --- /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.getMetaData().get(AdditionalData.COUNTRY.getFieldName())); + Assert.assertNull(card.getMetaData().get(AdditionalData.ZIP.getFieldName())); + + //card1 should have only country set + Assert.assertNull(card1.getMetaData().get(AdditionalData.ZIP.getFieldName())); + Assert.assertEquals("BIH", card1.getMetaData().get(AdditionalData.COUNTRY.getFieldName())); + + //card2 should have zip and country + Assert.assertEquals("BIH", card2.getMetaData().get(AdditionalData.COUNTRY.getFieldName())); + Assert.assertEquals("71000", card2.getMetaData().get(AdditionalData.ZIP.getFieldName())); + } + + @Test(expected = UnsupportedOperationException.class) + public void testMetaDataImmutable() { + final Card exampleCard = createExampleCard(); + exampleCard.getMetaData().put(AdditionalData.ZIP.getFieldName(), "71000"); + + Assert.assertNull(exampleCard.getMetaData().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.getMetaData().get(AdditionalData.FULL_NAME.getFieldName())); + Assert.assertEquals(name, modifiedCard.getMetaData().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.getMetaData().get(AdditionalData.ADDRESS.getFieldName())); + Assert.assertNull(exampleCard.getMetaData().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.getMetaData().get(AdditionalData.CITY.getFieldName())); + Assert.assertNull(exampleCard.getMetaData().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.getMetaData().get(AdditionalData.ZIP.getFieldName())); + Assert.assertNull(exampleCard.getMetaData().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.getMetaData().get(AdditionalData.PHONE.getFieldName())); + Assert.assertNull(exampleCard.getMetaData().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.getMetaData().get(AdditionalData.EMAIL.getFieldName())); + Assert.assertNull(exampleCard.getMetaData().get(AdditionalData.EMAIL.getFieldName())); + + Assert.assertTrue(modifiedCard.toBuilder().validate()); + Assert.assertTrue(exampleCard.validateCard()); + Assert.assertTrue(modifiedCard.validateCard()); + + } + + @Test + public void validMetaData() { + final Card exampleCard = createExampleCard(); + final Map testMetaData = new HashMap(){{ + put("vip","true"); + put("business","yea"); + }}; + + + final Card modifiedCard = exampleCard.toBuilder() + .metaData(testMetaData) + .build(); + + Assert.assertTrue(modifiedCard.toBuilder().validate()); + Assert.assertTrue(exampleCard.validateCard()); + Assert.assertTrue(modifiedCard.validateCard()); + + } + + @Test + public void validAllMetaData() { + final Card exampleCard = createExampleCard(); + final boolean isMetaDataValid = exampleCard.toBuilder() + .name("Adnan") + .address("Zagrebačka br. 19") + .city("Sarajevo") + .zip("71000") + .phone("063722982") + .email("zbregov@protein.com") + .validate(); + + final boolean isCardValidAfterAddingMetaData = exampleCard.validateCard(); + + Assert.assertTrue(isCardValidAfterAddingMetaData); + Assert.assertTrue(isMetaDataValid); + + Assert.assertTrue(exampleCard.validateCard()); + } + + @Test + public void validateCard() { + final Card exampleCard = createExampleCard(); + Assert.assertTrue(exampleCard.validateCard()); + } +} \ No newline at end of file From 4b1e0f1af17c7598d0e54c9decb72778106be2c0 Mon Sep 17 00:00:00 2001 From: adnanomerovic Date: Mon, 9 Mar 2020 17:10:40 +0100 Subject: [PATCH 2/2] Improving PR #1 --- .../monri/android/model/AdditionalData.java | 33 +++--- .../java/com/monri/android/model/Card.java | 100 +++++++++--------- .../monri/android/model/ValidationType.java | 2 +- .../com/monri/android/model/CardTest.java | 58 +++++----- 4 files changed, 102 insertions(+), 91 deletions(-) diff --git a/monri/src/main/java/com/monri/android/model/AdditionalData.java b/monri/src/main/java/com/monri/android/model/AdditionalData.java index 3366fcf..48b57e9 100644 --- a/monri/src/main/java/com/monri/android/model/AdditionalData.java +++ b/monri/src/main/java/com/monri/android/model/AdditionalData.java @@ -2,17 +2,17 @@ import java.util.Map; -public enum AdditionalData { +enum AdditionalData { //todo consider min and max length that can be included in regex.. //todo meta data is map no validation? - FULL_NAME(ValidationType.REGEX, "ch_full_name", Constants.ALPHA_NUMERIC_REGEX, 3, 30), - ADDRESS(ValidationType.REGEX, "ch_address", Constants.ALPHA_NUMERIC_REGEX, 3, 100), - CITY(ValidationType.REGEX, "ch_city", Constants.ALPHA_NUMERIC_REGEX, 3, 30), - ZIP(ValidationType.REGEX, "ch_zip", Constants.ALPHA_NUMERIC_REGEX, 3, 9), - COUNTRY(ValidationType.REGEX, "ch_country", Constants.ALPHA_NUMERIC_REGEX, 3, 30), - PHONE(ValidationType.REGEX, "ch_phone", Constants.PHONE_NUMBER_REGEX, 3, 30), - EMAIL(ValidationType.REGEX, "ch_email", Constants.EMAIL_REGEX, 3, 100), - META_DATA(ValidationType.META_DATA_VALIDATION, "meta_data", "", 0, 255); + FULL_NAME(ValidationType.REGEX, Constants.FULL_NAME, Constants.ALPHA_NUMERIC_REGEX, 3, 30), + ADDRESS(ValidationType.REGEX, Constants.ADDRESS, Constants.ALPHA_NUMERIC_REGEX, 3, 100), + CITY(ValidationType.REGEX, Constants.CITY, Constants.ALPHA_NUMERIC_REGEX, 3, 30), + ZIP(ValidationType.REGEX, Constants.ZIP, Constants.ALPHA_NUMERIC_REGEX, 3, 9), + COUNTRY(ValidationType.REGEX, Constants.COUNTRY, Constants.ALPHA_NUMERIC_REGEX, 3, 30), + PHONE(ValidationType.REGEX, Constants.PHONE, Constants.PHONE_NUMBER_REGEX, 3, 30), + EMAIL(ValidationType.REGEX, Constants.EMAIL, Constants.EMAIL_REGEX, 3, 100), + META_DATA(ValidationType.MAP_SIZE_VALIDATION, Constants.META_DATA, "", 0, 255); private final ValidationType validationType; private final String fieldName; @@ -66,7 +66,7 @@ public boolean isValid(final Object fieldValue) { } final String value = (String) fieldValue; return value.matches(validationData) && value.length() >= minLength && value.length() <= maxLength; - case META_DATA_VALIDATION: + case MAP_SIZE_VALIDATION: if (!(fieldValue instanceof Map)) { return false; } @@ -78,9 +78,18 @@ public boolean isValid(final Object fieldValue) { } - private static class Constants { + static class Constants { + static final String FULL_NAME = "ch_full_name"; + static final String ADDRESS = "ch_address"; + static final String CITY = "ch_city"; + static final String ZIP = "ch_zip"; + static final String COUNTRY = "ch_country"; + static final String PHONE = "ch_phone"; + static final String EMAIL = "ch_email"; + static final String META_DATA = "meta_data"; + private static final String ALPHA_NUMERIC_REGEX = "^[\\p{L}\\p{Z}\\p{N}\\.]+$"; - private static final String EMAIL_REGEX ="^\\w+@[a-zA-Z_]+?\\.[a-zA-Z]{2,3}$"; + private static final String EMAIL_REGEX = "^\\w+@[a-zA-Z_]+?\\.[a-zA-Z]{2,3}$"; private static final String PHONE_NUMBER_REGEX = "^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\\s\\./0-9]*$"; } } diff --git a/monri/src/main/java/com/monri/android/model/Card.java b/monri/src/main/java/com/monri/android/model/Card.java index e3e8cbe..9e22fae 100644 --- a/monri/src/main/java/com/monri/android/model/Card.java +++ b/monri/src/main/java/com/monri/android/model/Card.java @@ -113,7 +113,7 @@ public Map data() { private Integer expMonth; private Integer expYear; private boolean tokenizePan; - private Map metaData; + private Map data; @Size(4) private String last4; @@ -153,14 +153,16 @@ 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. */ - private Card( + @Deprecated + public Card( String number, Integer expMonth, Integer expYear, @@ -174,20 +176,20 @@ private Card( * @param expMonth the expiry month * @param expYear the expiry year * @param cvc the CVC code - * @param metaData additional data for name, zip, address, country, email + * @param data additional data for name, zip, address, country, email */ private Card(String number, Integer expMonth, Integer expYear, String cvc, - Map metaData) { + 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.metaData = metaData; + this.data = data; } /** @@ -195,7 +197,7 @@ private Card(String number, */ public static class CardBuilder { - private final Map metaData; + private final Map data; private final Card card; /** @@ -203,95 +205,95 @@ public static class CardBuilder { * @param expMonth the expiry month, as an integer value between 1 and 12 * @param expYear expiry year * @param cvc the card CVC number - * @param metaData additional data for name, zip, address, country, email + * @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 metaData) { + Map data) { - this.metaData = new HashMap<>(metaData); + this.data = new HashMap<>(data); this.card = new Card(number, expMonth, expYear, cvc); } public CardBuilder name(final String name) { - this.metaData.put(AdditionalData.FULL_NAME.getFieldName(), name); + this.data.put(AdditionalData.Constants.FULL_NAME, name); return this; } - public Object getName() { - return metaData.get(AdditionalData.FULL_NAME.getFieldName()); + public String getName() { + return (String) data.get(AdditionalData.Constants.FULL_NAME); } public CardBuilder address(final String address) { - this.metaData.put(AdditionalData.ADDRESS.getFieldName(), address); + this.data.put(AdditionalData.Constants.ADDRESS, address); return this; } - public Object getAddress() { - return metaData.get(AdditionalData.ADDRESS.getFieldName()); + public String getAddress() { + return (String) data.get(AdditionalData.Constants.ADDRESS); } - public Object getCity() { - return metaData.get(AdditionalData.CITY.getFieldName()); + public String getCity() { + return (String) data.get(AdditionalData.Constants.CITY); } public CardBuilder city(final String city) { - this.metaData.put(AdditionalData.CITY.getFieldName(), city); + this.data.put(AdditionalData.Constants.CITY, city); return this; } public CardBuilder zip(final String zip) { - this.metaData.put(AdditionalData.ZIP.getFieldName(), zip); + this.data.put(AdditionalData.Constants.ZIP, zip); return this; } - public Object getZip() { - return metaData.get(AdditionalData.ZIP.getFieldName()); + public String getZip() { + return (String) data.get(AdditionalData.Constants.ZIP); } public CardBuilder country(final String country) { - this.metaData.put(AdditionalData.COUNTRY.getFieldName(), country); + this.data.put(AdditionalData.Constants.COUNTRY, country); return this; } - public Object getCountry() { - return metaData.get(AdditionalData.COUNTRY.getFieldName()); + public String getCountry() { + return (String) data.get(AdditionalData.Constants.COUNTRY); } - public Object getPhone() { - return metaData.get(AdditionalData.PHONE.getFieldName()); + public String getPhone() { + return (String) data.get(AdditionalData.Constants.PHONE); } public CardBuilder phone(final String phone) { - this.metaData.put(AdditionalData.PHONE.getFieldName(), phone); + this.data.put(AdditionalData.Constants.PHONE, phone); return this; } public CardBuilder email(final String email) { - this.metaData.put(AdditionalData.EMAIL.getFieldName(), email); + this.data.put(AdditionalData.Constants.EMAIL, email); return this; } - public Object getEmail() { - return metaData.get(AdditionalData.EMAIL.getFieldName()); + public String getEmail() { + return (String) data.get(AdditionalData.Constants.EMAIL); } - public CardBuilder metaData(Map metaData) { - this.metaData.put(AdditionalData.META_DATA.getFieldName(), metaData); + public CardBuilder data(Map data) { + this.data.put(AdditionalData.Constants.META_DATA, data); return this; } - public Map getMetaData() { + public Map getData() { //noinspection unchecked - return Collections.unmodifiableMap((Map) metaData.get(AdditionalData.META_DATA.getFieldName())); + return Collections.unmodifiableMap((Map) data.get(AdditionalData.Constants.META_DATA)); } public boolean validate() { - return card.validateCard(Calendar.getInstance()) && validateMetaData(); + return card.validateCard(Calendar.getInstance()) && validateData(); } @@ -300,18 +302,18 @@ public boolean validate() { * * @return 'true' if all additional data is valid, 'false' otherwise */ - private boolean validateMetaData() { + private boolean validateData() { - for (String metaDataKey : metaData.keySet()) { - final Object metaDataValue = metaData.get(metaDataKey); + for (String dataKey : data.keySet()) { + final Object dataValue = data.get(dataKey); - if (metaDataValue == null) { + if (dataValue == null) { return false; } - final AdditionalData additionalData = AdditionalData.fromValue(metaDataKey); + final AdditionalData additionalData = AdditionalData.fromValue(dataKey); - if (!additionalData.isValid(metaDataValue)) { + if (!additionalData.isValid(dataValue)) { return false; } } @@ -324,7 +326,7 @@ private boolean validateMetaData() { * @return Card instance */ public Card build() { - card.setMetaData(this.metaData); + card.setData(this.data); return card; } @@ -334,7 +336,7 @@ public Card build() { * @return a CardBuilder populated with the fields of this Card instance */ public CardBuilder toBuilder() { - return new CardBuilder(number, expMonth, expYear, cvc, metaData); + return new CardBuilder(number, expMonth, expYear, cvc, data); } /** @@ -352,12 +354,12 @@ public static Card create(final String number, .build(); } - private void setMetaData(final Map metaData) { - this.metaData = metaData; + private void setData(final Map data) { + this.data = data; } - public Map getMetaData() { - return Collections.unmodifiableMap(metaData); + public Map getData() { + return Collections.unmodifiableMap(data); } /** diff --git a/monri/src/main/java/com/monri/android/model/ValidationType.java b/monri/src/main/java/com/monri/android/model/ValidationType.java index c4d19da..7298614 100644 --- a/monri/src/main/java/com/monri/android/model/ValidationType.java +++ b/monri/src/main/java/com/monri/android/model/ValidationType.java @@ -2,5 +2,5 @@ public enum ValidationType { REGEX, - META_DATA_VALIDATION + MAP_SIZE_VALIDATION } diff --git a/monri/src/test/java/com/monri/android/model/CardTest.java b/monri/src/test/java/com/monri/android/model/CardTest.java index 94a5c0e..ac47eae 100644 --- a/monri/src/test/java/com/monri/android/model/CardTest.java +++ b/monri/src/test/java/com/monri/android/model/CardTest.java @@ -21,24 +21,24 @@ public void testBuilderDeepCopy() { final Card card2 = card1.toBuilder().zip("71000").build(); //card should have null ip and country - Assert.assertNull(card.getMetaData().get(AdditionalData.COUNTRY.getFieldName())); - Assert.assertNull(card.getMetaData().get(AdditionalData.ZIP.getFieldName())); + 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.getMetaData().get(AdditionalData.ZIP.getFieldName())); - Assert.assertEquals("BIH", card1.getMetaData().get(AdditionalData.COUNTRY.getFieldName())); + 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.getMetaData().get(AdditionalData.COUNTRY.getFieldName())); - Assert.assertEquals("71000", card2.getMetaData().get(AdditionalData.ZIP.getFieldName())); + Assert.assertEquals("BIH", card2.getData().get(AdditionalData.COUNTRY.getFieldName())); + Assert.assertEquals("71000", card2.getData().get(AdditionalData.ZIP.getFieldName())); } @Test(expected = UnsupportedOperationException.class) - public void testMetaDataImmutable() { + public void testDataImmutable() { final Card exampleCard = createExampleCard(); - exampleCard.getMetaData().put(AdditionalData.ZIP.getFieldName(), "71000"); + exampleCard.getData().put(AdditionalData.ZIP.getFieldName(), "71000"); - Assert.assertNull(exampleCard.getMetaData().get(AdditionalData.ZIP.getFieldName())); + Assert.assertNull(exampleCard.getData().get(AdditionalData.ZIP.getFieldName())); } @Test @@ -48,8 +48,8 @@ public void validName() { final Card modifiedCard = exampleCard.toBuilder().name(name).build(); - Assert.assertNull(exampleCard.getMetaData().get(AdditionalData.FULL_NAME.getFieldName())); - Assert.assertEquals(name, modifiedCard.getMetaData().get(AdditionalData.FULL_NAME.getFieldName())); + 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()); @@ -66,8 +66,8 @@ public void validAddress() { .address(address) .build(); - Assert.assertEquals(address, modifiedCard.getMetaData().get(AdditionalData.ADDRESS.getFieldName())); - Assert.assertNull(exampleCard.getMetaData().get(AdditionalData.ADDRESS.getFieldName())); + 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()); @@ -83,8 +83,8 @@ public void validCity() { .city(city) .build(); - Assert.assertEquals(city, modifiedCard.getMetaData().get(AdditionalData.CITY.getFieldName())); - Assert.assertNull(exampleCard.getMetaData().get(AdditionalData.CITY.getFieldName())); + 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()); @@ -102,8 +102,8 @@ public void validZip() { .build(); Assert.assertTrue(modifiedCard.toBuilder().validate()); - Assert.assertEquals(zip, modifiedCard.getMetaData().get(AdditionalData.ZIP.getFieldName())); - Assert.assertNull(exampleCard.getMetaData().get(AdditionalData.ZIP.getFieldName())); + Assert.assertEquals(zip, modifiedCard.getData().get(AdditionalData.ZIP.getFieldName())); + Assert.assertNull(exampleCard.getData().get(AdditionalData.ZIP.getFieldName())); } @@ -116,8 +116,8 @@ public void validPhone() { .phone(phoneNumber) .build(); - Assert.assertEquals(phoneNumber, modifiedCard.getMetaData().get(AdditionalData.PHONE.getFieldName())); - Assert.assertNull(exampleCard.getMetaData().get(AdditionalData.PHONE.getFieldName())); + 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()); @@ -134,8 +134,8 @@ public void validEmail() { .email(email) .build(); - Assert.assertEquals(email, modifiedCard.getMetaData().get(AdditionalData.EMAIL.getFieldName())); - Assert.assertNull(exampleCard.getMetaData().get(AdditionalData.EMAIL.getFieldName())); + 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()); @@ -144,16 +144,16 @@ public void validEmail() { } @Test - public void validMetaData() { + public void validData() { final Card exampleCard = createExampleCard(); - final Map testMetaData = new HashMap(){{ + final Map testData = new HashMap(){{ put("vip","true"); put("business","yea"); }}; final Card modifiedCard = exampleCard.toBuilder() - .metaData(testMetaData) + .data(testData) .build(); Assert.assertTrue(modifiedCard.toBuilder().validate()); @@ -163,9 +163,9 @@ public void validMetaData() { } @Test - public void validAllMetaData() { + public void validAllData() { final Card exampleCard = createExampleCard(); - final boolean isMetaDataValid = exampleCard.toBuilder() + final boolean isDataValid = exampleCard.toBuilder() .name("Adnan") .address("Zagrebačka br. 19") .city("Sarajevo") @@ -174,10 +174,10 @@ public void validAllMetaData() { .email("zbregov@protein.com") .validate(); - final boolean isCardValidAfterAddingMetaData = exampleCard.validateCard(); + final boolean isCardValidAfterAddingData = exampleCard.validateCard(); - Assert.assertTrue(isCardValidAfterAddingMetaData); - Assert.assertTrue(isMetaDataValid); + Assert.assertTrue(isCardValidAfterAddingData); + Assert.assertTrue(isDataValid); Assert.assertTrue(exampleCard.validateCard()); }