From d965fd622220a63f71715009ef1416d2aa4771e8 Mon Sep 17 00:00:00 2001 From: Michael Shafrir Date: Mon, 25 Feb 2019 08:40:08 -0500 Subject: [PATCH] Implement equals and hashCode in models **Summary** Implement `equals()` and `hashCode()` for all models, specifically the subclasses of `StripeJsonModel` **Motivation** The implementation of `equals()`, `hashCode()`, and `toString()` in `StripeJsonModel`, which is the base class of almost all model classes, is extremely inefficient. It implements these methods by first transforming the model object to a JSON string, which is an expensive operation. **Testing** - Updated unit tests - Tested on device with example app --- .../activity/PaymentIntentActivity.java | 2 +- .../activity/PaymentSessionActivity.java | 2 +- .../stripe/android/AbstractEphemeralKey.java | 65 +++-- .../stripe/android/CustomerEphemeralKey.java | 35 +-- .../stripe/android/model/AccountParams.java | 47 ++- .../com/stripe/android/model/Address.java | 15 +- .../com/stripe/android/model/BankAccount.java | 25 ++ .../java/com/stripe/android/model/Card.java | 47 ++- .../com/stripe/android/model/Customer.java | 112 +++++--- .../stripe/android/model/CustomerSource.java | 38 ++- .../com/stripe/android/model/ModelUtils.java | 1 - .../stripe/android/model/PaymentIntent.java | 38 ++- .../android/model/PaymentIntentParams.java | 1 - .../stripe/android/model/PaymentMethod.java | 23 ++ .../android/model/ShippingInformation.java | 72 +++-- .../stripe/android/model/ShippingMethod.java | 68 +++-- .../java/com/stripe/android/model/Source.java | 204 +++++++------ .../stripe/android/model/SourceCardData.java | 269 +++++++++++------- .../android/model/SourceCodeVerification.java | 45 +-- .../com/stripe/android/model/SourceOwner.java | 138 +++++---- .../stripe/android/model/SourceParams.java | 3 - .../stripe/android/model/SourceReceiver.java | 54 ++-- .../stripe/android/model/SourceRedirect.java | 66 +++-- .../android/model/SourceSepaDebitData.java | 62 ++-- .../stripe/android/model/StripeJsonModel.java | 20 -- .../android/model/StripeSourceTypeModel.java | 61 +++- .../java/com/stripe/android/model/Token.java | 75 +++-- .../android/view/AddSourceActivity.java | 2 +- .../android/view/PaymentMethodsActivity.java | 4 +- .../android/EphemeralKeyManagerTest.java | 104 ++++--- .../com/stripe/android/model/AddressTest.java | 27 +- .../stripe/android/model/BankAccountTest.java | 17 +- .../com/stripe/android/model/CardTest.java | 27 +- .../stripe/android/model/CustomerTest.java | 8 +- .../model/ShippingInformationTest.java | 24 ++ .../android/model/ShippingMethodTest.java | 24 +- .../android/model/SourceCardDataTest.java | 50 ++-- .../stripe/android/model/SourceOwnerTest.java | 6 +- .../model/SourceSepaDebitDataTest.java | 18 +- .../com/stripe/android/model/SourceTest.java | 1 + .../com/stripe/android/model/TokenTest.java | 32 +-- .../view/PaymentMethodsActivityTest.java | 7 +- 42 files changed, 1224 insertions(+), 715 deletions(-) create mode 100644 stripe/src/test/java/com/stripe/android/model/ShippingInformationTest.java diff --git a/example/src/main/java/com/stripe/example/activity/PaymentIntentActivity.java b/example/src/main/java/com/stripe/example/activity/PaymentIntentActivity.java index aa7360c156f..ba9c5bcb7ec 100644 --- a/example/src/main/java/com/stripe/example/activity/PaymentIntentActivity.java +++ b/example/src/main/java/com/stripe/example/activity/PaymentIntentActivity.java @@ -238,7 +238,7 @@ public void call() { @Override public void call(PaymentIntent paymentIntent) { if (paymentIntent != null) { - mPaymentIntentValue.setText(paymentIntent.toString()); + mPaymentIntentValue.setText(paymentIntent.toJson().toString()); Uri authUrl = paymentIntent.getAuthorizationUrl(); if ("requires_source_action".equals(paymentIntent.getStatus()) && authUrl != null) { diff --git a/example/src/main/java/com/stripe/example/activity/PaymentSessionActivity.java b/example/src/main/java/com/stripe/example/activity/PaymentSessionActivity.java index d06bbac0a3f..0badf662870 100644 --- a/example/src/main/java/com/stripe/example/activity/PaymentSessionActivity.java +++ b/example/src/main/java/com/stripe/example/activity/PaymentSessionActivity.java @@ -210,7 +210,7 @@ private String formatStringResults(PaymentSessionData data) { .append(" ending in ") .append(scd.getLast4()); } else { - stringBuilder.append('\n').append(source.toString()).append('\n'); + stringBuilder.append('\n').append(source.toJson().toString()).append('\n'); } String isOrNot = data.isPaymentReadyToCharge() ? " IS " : " IS NOT "; stringBuilder.append(isOrNot).append("ready to charge.\n\n"); diff --git a/stripe/src/main/java/com/stripe/android/AbstractEphemeralKey.java b/stripe/src/main/java/com/stripe/android/AbstractEphemeralKey.java index fee5b1e1ac3..234c19cc2c3 100644 --- a/stripe/src/main/java/com/stripe/android/AbstractEphemeralKey.java +++ b/stripe/src/main/java/com/stripe/android/AbstractEphemeralKey.java @@ -5,15 +5,16 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; import com.stripe.android.model.StripeJsonModel; +import com.stripe.android.utils.ObjectUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.lang.reflect.InvocationTargetException; +import java.util.AbstractMap; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -33,14 +34,15 @@ abstract class AbstractEphemeralKey extends StripeJsonModel implements Parcelabl static final String FIELD_ASSOCIATED_OBJECTS = "associated_objects"; static final String FIELD_TYPE = "type"; static final String NULL = "null"; - protected @NonNull String mObjectId; - private long mCreated; - private long mExpires; - private @NonNull String mId; - private boolean mLiveMode; - private @NonNull String mObject; - private @NonNull String mSecret; - private @NonNull String mType; + + @NonNull final String mObjectId; + private final long mCreated; + private final long mExpires; + @NonNull private final String mId; + private final boolean mLiveMode; + @NonNull private final String mObject; + @NonNull private final String mSecret; + @NonNull private final String mType; /** * Parcel constructor for this {@link Parcelable} class. @@ -50,7 +52,7 @@ abstract class AbstractEphemeralKey extends StripeJsonModel implements Parcelabl * * @param in the {@link Parcel} in which this Ephemeral Key has been stored. */ - protected AbstractEphemeralKey(Parcel in) { + AbstractEphemeralKey(@NonNull Parcel in) { mCreated = in.readLong(); mObjectId = in.readString(); mExpires = in.readLong(); @@ -61,7 +63,7 @@ protected AbstractEphemeralKey(Parcel in) { mType = in.readString(); } - protected AbstractEphemeralKey( + AbstractEphemeralKey( long created, @NonNull String objectId, long expires, @@ -81,9 +83,7 @@ protected AbstractEphemeralKey( mType = type; } - protected AbstractEphemeralKey( - @Nullable JSONObject jsonObject - ) throws JSONException { + AbstractEphemeralKey(@Nullable JSONObject jsonObject) throws JSONException { mCreated = jsonObject.getLong(FIELD_CREATED); mExpires = jsonObject.getLong(FIELD_EXPIRES); mId = jsonObject.getString(FIELD_ID); @@ -129,9 +129,8 @@ public JSONObject toJson() { @NonNull @Override - @SuppressWarnings("unchecked") public Map toMap() { - Map map = new HashMap<>(); + final AbstractMap map = new HashMap<>(); map.put(FIELD_CREATED, mCreated); map.put(FIELD_EXPIRES, mExpires); map.put(FIELD_OBJECT, mObject); @@ -139,8 +138,8 @@ public Map toMap() { map.put(FIELD_SECRET, mSecret); map.put(FIELD_LIVEMODE, mLiveMode); - List associatedObjectsList = new ArrayList<>(); - Map associatedObjectMap = new HashMap<>(); + final List associatedObjectsList = new ArrayList<>(); + final Map associatedObjectMap = new HashMap<>(); associatedObjectMap.put(FIELD_ID, mObjectId); associatedObjectMap.put(FIELD_TYPE, mType); associatedObjectsList.add(associatedObjectMap); @@ -183,11 +182,6 @@ long getExpires() { return mExpires; } - @VisibleForTesting - void setExpires(long value) { - mExpires = value; - } - @NonNull String getId() { return mId; @@ -223,7 +217,7 @@ String getType() { return fromJson(object, ephemeralKeyClass); } - @Nullable + @NonNull protected static TEphemeralKey fromJson(@Nullable JSONObject jsonObject, Class ephemeralKeyClass) { if (jsonObject == null) { @@ -256,4 +250,27 @@ String getType() { " does not have an accessible (JSONObject) constructor", e); } } + + @Override + public boolean equals(@Nullable Object obj) { + return this == obj + || (obj instanceof AbstractEphemeralKey && typedEquals((AbstractEphemeralKey) obj)); + } + + private boolean typedEquals(@NonNull AbstractEphemeralKey ephemeralKey) { + return ObjectUtils.equals(mObjectId, ephemeralKey.mObjectId) + && mCreated == ephemeralKey.mCreated + && mExpires == ephemeralKey.mExpires + && ObjectUtils.equals(mId, ephemeralKey.mId) + && mLiveMode == ephemeralKey.mLiveMode + && ObjectUtils.equals(mObject, ephemeralKey.mObject) + && ObjectUtils.equals(mSecret, ephemeralKey.mSecret) + && ObjectUtils.equals(mType, ephemeralKey.mType); + } + + @Override + public int hashCode() { + return ObjectUtils.hash(mObjectId, mCreated, mExpires, mId, mLiveMode, mObject, mSecret, + mType); + } } diff --git a/stripe/src/main/java/com/stripe/android/CustomerEphemeralKey.java b/stripe/src/main/java/com/stripe/android/CustomerEphemeralKey.java index 436f2ed15e0..4e548d52aff 100644 --- a/stripe/src/main/java/com/stripe/android/CustomerEphemeralKey.java +++ b/stripe/src/main/java/com/stripe/android/CustomerEphemeralKey.java @@ -10,8 +10,21 @@ import org.json.JSONObject; class CustomerEphemeralKey extends AbstractEphemeralKey { + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + + @Override + public CustomerEphemeralKey createFromParcel(Parcel in) { + return new CustomerEphemeralKey(in); + } - protected CustomerEphemeralKey(Parcel in) { + @Override + public CustomerEphemeralKey[] newArray(int size) { + return new CustomerEphemeralKey[size]; + } + }; + + private CustomerEphemeralKey(@NonNull Parcel in) { super(in); } @@ -37,33 +50,15 @@ protected CustomerEphemeralKey( } @SuppressWarnings("checkstyle:RedundantModifier") // Not actually redundant :| - public CustomerEphemeralKey( - @Nullable JSONObject jsonObject - ) throws JSONException { + public CustomerEphemeralKey(@Nullable JSONObject jsonObject) throws JSONException { super(jsonObject); - } - @NonNull String getCustomerId() { return mObjectId; } - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - - @Override - public CustomerEphemeralKey createFromParcel(Parcel in) { - return new CustomerEphemeralKey(in); - } - - @Override - public CustomerEphemeralKey[] newArray(int size) { - return new CustomerEphemeralKey[size]; - } - }; - @Nullable static CustomerEphemeralKey fromString(@Nullable String rawJson) throws JSONException { return (CustomerEphemeralKey) AbstractEphemeralKey diff --git a/stripe/src/main/java/com/stripe/android/model/AccountParams.java b/stripe/src/main/java/com/stripe/android/model/AccountParams.java index 2b73d0c0645..b562ff102b5 100644 --- a/stripe/src/main/java/com/stripe/android/model/AccountParams.java +++ b/stripe/src/main/java/com/stripe/android/model/AccountParams.java @@ -1,7 +1,11 @@ package com.stripe.android.model; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.stripe.android.utils.ObjectUtils; + +import java.util.AbstractMap; import java.util.HashMap; import java.util.Map; @@ -12,10 +16,11 @@ */ public class AccountParams { - static final String API_PARAM_LEGAL_ENTITY = "legal_entity"; - static final String API_TOS_SHOWN_AND_ACCEPTED = "tos_shown_and_accepted"; - private Boolean mTosShownAndAccepted; - private Map mLegalEntity; + private static final String API_PARAM_LEGAL_ENTITY = "legal_entity"; + private static final String API_TOS_SHOWN_AND_ACCEPTED = "tos_shown_and_accepted"; + + @Nullable private Boolean mTosShownAndAccepted; + @Nullable private Map mLegalEntity; /** * @param tosShownAndAccepted indicates that the platform showed the user the appropriate text @@ -25,20 +30,20 @@ public class AccountParams { * created. Can contain any of the fields specified by legal_entity in the * API docs. * - * See {@linktourl https://stripe.com/docs/api#account_object-legal_entity} + * See https://stripe.com/docs/api/accounts/object#account_object-legal_entity * - * The object in the map is expected to be a string or a list or map of - * strings. All {@link StripeJsonModel} types have a toMap() function that - * can be used to convert the {@link StripeJsonModel} to map representation - * that can be passed in here. + * The object in the map is expected to be a string or a list or map of + * strings. All {@link StripeJsonModel} types have a toMap() function that + * can be used to convert the {@link StripeJsonModel} to map representation + * that can be passed in here. */ + @NonNull public static AccountParams createAccountParams( boolean tosShownAndAccepted, Map legalEntity) { - AccountParams accountParams = new AccountParams() + return new AccountParams() .setTosShownAndAccepted(tosShownAndAccepted) .setLegalEntity(legalEntity); - return accountParams; } /** @@ -47,6 +52,7 @@ public static AccountParams createAccountParams( * when this is true. * @return {@code this}, for chaining purposes */ + @NonNull public AccountParams setTosShownAndAccepted(boolean tosShownAndAccepted) { mTosShownAndAccepted = tosShownAndAccepted; return this; @@ -65,6 +71,7 @@ public AccountParams setTosShownAndAccepted(boolean tosShownAndAccepted) { * that can be passed in here. * @return {@code this}, for chaining purposes */ + @NonNull public AccountParams setLegalEntity(Map legalEntity) { mLegalEntity = legalEntity; return this; @@ -78,8 +85,8 @@ public AccountParams setLegalEntity(Map legalEntity) { */ @NonNull public Map toParamMap() { - Map networkReadyMap = new HashMap<>(); - Map tokenMap = new HashMap<>(); + final Map networkReadyMap = new HashMap<>(); + final AbstractMap tokenMap = new HashMap<>(); tokenMap.put(API_TOS_SHOWN_AND_ACCEPTED, mTosShownAndAccepted); tokenMap.put(API_PARAM_LEGAL_ENTITY, mLegalEntity); networkReadyMap.put("account", tokenMap); @@ -87,4 +94,18 @@ public Map toParamMap() { return networkReadyMap; } + @Override + public int hashCode() { + return ObjectUtils.hash(mTosShownAndAccepted, mLegalEntity); + } + + @Override + public boolean equals(@Nullable Object obj) { + return this == obj || (obj instanceof AccountParams && typedEquals((AccountParams) obj)); + } + + private boolean typedEquals(@NonNull AccountParams accountParams) { + return ObjectUtils.equals(mTosShownAndAccepted, accountParams.mTosShownAndAccepted) + && ObjectUtils.equals(mLegalEntity, accountParams.mLegalEntity); + } } diff --git a/stripe/src/main/java/com/stripe/android/model/Address.java b/stripe/src/main/java/com/stripe/android/model/Address.java index b4c0dba2780..2178c6c30c0 100644 --- a/stripe/src/main/java/com/stripe/android/model/Address.java +++ b/stripe/src/main/java/com/stripe/android/model/Address.java @@ -199,13 +199,13 @@ public boolean equals(Object obj) { return this == obj || (obj instanceof Address && typedEquals((Address) obj)); } - private boolean typedEquals(@NonNull Address obj) { - return ObjectUtils.equals(mCity, obj.mCity) - && ObjectUtils.equals(mCountry, obj.mCountry) - && ObjectUtils.equals(mLine1, obj.mLine1) - && ObjectUtils.equals(mLine2, obj.mLine2) - && ObjectUtils.equals(mPostalCode, obj.mPostalCode) - && ObjectUtils.equals(mState, obj.mState); + private boolean typedEquals(@NonNull Address address) { + return ObjectUtils.equals(mCity, address.mCity) + && ObjectUtils.equals(mCountry, address.mCountry) + && ObjectUtils.equals(mLine1, address.mLine1) + && ObjectUtils.equals(mLine2, address.mLine2) + && ObjectUtils.equals(mPostalCode, address.mPostalCode) + && ObjectUtils.equals(mState, address.mState); } @Override @@ -291,6 +291,5 @@ public Builder setState(@Nullable String state) { public Address build() { return new Address(this); } - } } diff --git a/stripe/src/main/java/com/stripe/android/model/BankAccount.java b/stripe/src/main/java/com/stripe/android/model/BankAccount.java index 34128f32ab4..b39d6e1a668 100644 --- a/stripe/src/main/java/com/stripe/android/model/BankAccount.java +++ b/stripe/src/main/java/com/stripe/android/model/BankAccount.java @@ -5,6 +5,8 @@ import androidx.annotation.Size; import androidx.annotation.StringDef; +import com.stripe.android.utils.ObjectUtils; + import org.json.JSONException; import org.json.JSONObject; @@ -204,4 +206,27 @@ public static BankAccount fromJson(@Nullable JSONObject jsonObject) { StripeJsonUtils.optString(jsonObject, FIELD_LAST4), StripeJsonUtils.optString(jsonObject, FIELD_ROUTING_NUMBER)); } + + @Override + public int hashCode() { + return ObjectUtils.hash(mAccountHolderName, mAccountHolderType, mAccountNumber, + mBankName, mCountryCode, mCurrency, mFingerprint, mLast4, mRoutingNumber); + } + + @Override + public boolean equals(@Nullable Object obj) { + return super.equals(obj) || (obj instanceof BankAccount && typedEquals((BankAccount) obj)); + } + + private boolean typedEquals(@NonNull BankAccount bankAccount) { + return ObjectUtils.equals(mAccountHolderName, bankAccount.mAccountHolderName) + && ObjectUtils.equals(mAccountHolderType, bankAccount.mAccountHolderType) + && ObjectUtils.equals(mAccountNumber, bankAccount.mAccountNumber) + && ObjectUtils.equals(mBankName, bankAccount.mBankName) + && ObjectUtils.equals(mCountryCode, bankAccount.mCountryCode) + && ObjectUtils.equals(mCurrency, bankAccount.mCurrency) + && ObjectUtils.equals(mFingerprint, bankAccount.mFingerprint) + && ObjectUtils.equals(mLast4, bankAccount.mLast4) + && ObjectUtils.equals(mRoutingNumber, bankAccount.mRoutingNumber); + } } diff --git a/stripe/src/main/java/com/stripe/android/model/Card.java b/stripe/src/main/java/com/stripe/android/model/Card.java index 383380e5ba1..3a97e9f0601 100644 --- a/stripe/src/main/java/com/stripe/android/model/Card.java +++ b/stripe/src/main/java/com/stripe/android/model/Card.java @@ -12,6 +12,7 @@ import com.stripe.android.R; import com.stripe.android.StripeNetworkUtils; import com.stripe.android.StripeTextUtils; +import com.stripe.android.utils.ObjectUtils; import org.json.JSONException; import org.json.JSONObject; @@ -1068,7 +1069,7 @@ boolean validateCard(@NonNull Calendar now) { } } - boolean validateExpiryDate(Calendar now) { + boolean validateExpiryDate(@NonNull Calendar now) { if (!validateExpMonth()) { return false; } @@ -1078,7 +1079,7 @@ boolean validateExpiryDate(Calendar now) { return !ModelUtils.hasMonthPassed(expYear, expMonth, now); } - private Card(Builder builder) { + private Card(@NonNull Builder builder) { this.number = StripeTextUtils.nullIfBlank(normalizeCardNumber(builder.number)); this.expMonth = builder.expMonth; this.expYear = builder.expYear; @@ -1116,4 +1117,46 @@ private String normalizeCardNumber(@Nullable String number) { } return number.trim().replaceAll("\\s+|-", ""); } + + @Override + public boolean equals(@Nullable Object obj) { + return this == obj || (obj instanceof Card && typedEquals((Card) obj)); + } + + private boolean typedEquals(@NonNull Card card) { + return ObjectUtils.equals(number, card.number) + && ObjectUtils.equals(cvc, card.cvc) + && ObjectUtils.equals(expMonth, card.expMonth) + && ObjectUtils.equals(expYear, card.expYear) + && ObjectUtils.equals(name, card.name) + && ObjectUtils.equals(addressLine1, card.addressLine1) + && ObjectUtils.equals(addressLine1Check, card.addressLine1Check) + && ObjectUtils.equals(addressLine2, card.addressLine2) + && ObjectUtils.equals(addressCity, card.addressCity) + && ObjectUtils.equals(addressState, card.addressState) + && ObjectUtils.equals(addressZip, card.addressZip) + && ObjectUtils.equals(addressZipCheck, card.addressZipCheck) + && ObjectUtils.equals(addressCountry, card.addressCountry) + && ObjectUtils.equals(last4, card.last4) + && ObjectUtils.equals(brand, card.brand) + && ObjectUtils.equals(funding, card.funding) + && ObjectUtils.equals(fingerprint, card.fingerprint) + && ObjectUtils.equals(country, card.country) + && ObjectUtils.equals(currency, card.currency) + && ObjectUtils.equals(customerId, card.customerId) + && ObjectUtils.equals(cvcCheck, card.cvcCheck) + && ObjectUtils.equals(id, card.id) + && ObjectUtils.equals(loggingTokens, card.loggingTokens) + && ObjectUtils.equals(tokenizationMethod, card.tokenizationMethod) + && ObjectUtils.equals(metadata, card.metadata); + } + + @Override + public int hashCode() { + return ObjectUtils.hash(number, cvc, expMonth, expYear, name, addressLine1, + addressLine1Check, addressLine2, addressCity, addressState, addressZip, + addressZipCheck, addressCountry, last4, brand, funding, fingerprint, + country, currency, customerId, cvcCheck, id, loggingTokens, tokenizationMethod, + metadata); + } } diff --git a/stripe/src/main/java/com/stripe/android/model/Customer.java b/stripe/src/main/java/com/stripe/android/model/Customer.java index f921aaeb7b7..6b9d861ed49 100644 --- a/stripe/src/main/java/com/stripe/android/model/Customer.java +++ b/stripe/src/main/java/com/stripe/android/model/Customer.java @@ -4,11 +4,13 @@ import androidx.annotation.Nullable; import com.stripe.android.StripeNetworkUtils; +import com.stripe.android.utils.ObjectUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import java.util.AbstractMap; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -43,16 +45,26 @@ public class Customer extends StripeJsonModel { private static final String VALUE_APPLE_PAY = "apple_pay"; - private @Nullable String mId; - private @Nullable String mDefaultSource; - private @Nullable ShippingInformation mShippingInformation; - - private @NonNull List mSources = new ArrayList<>(); - private @Nullable Boolean mHasMore; - private @Nullable Integer mTotalCount; - private @Nullable String mUrl; - - private Customer() { } + @Nullable private final String mId; + @Nullable private final String mDefaultSource; + @Nullable private final ShippingInformation mShippingInformation; + @NonNull private final List mSources; + @Nullable private final Boolean mHasMore; + @Nullable private final Integer mTotalCount; + @Nullable private final String mUrl; + + private Customer(@Nullable String id, @Nullable String defaultSource, + @Nullable ShippingInformation shippingInformation, + @NonNull List sources, @Nullable Boolean hasMore, + @Nullable Integer totalCount, @Nullable String url) { + mId = id; + mDefaultSource = defaultSource; + mShippingInformation = shippingInformation; + mSources = sources; + mHasMore = hasMore; + mTotalCount = totalCount; + mUrl = url; + } public String getId() { return mId; @@ -118,17 +130,17 @@ public JSONObject toJson() { @NonNull @Override public Map toMap() { - Map mapObject = new HashMap<>(); - mapObject.put(FIELD_ID, mId); - mapObject.put(FIELD_OBJECT, VALUE_CUSTOMER); - mapObject.put(FIELD_DEFAULT_SOURCE, mDefaultSource); + final AbstractMap map = new HashMap<>(); + map.put(FIELD_ID, mId); + map.put(FIELD_OBJECT, VALUE_CUSTOMER); + map.put(FIELD_DEFAULT_SOURCE, mDefaultSource); StripeJsonModel.putStripeJsonModelMapIfNotNull( - mapObject, + map, FIELD_SHIPPING, mShippingInformation); - Map sourcesObject = new HashMap<>(); + final AbstractMap sourcesObject = new HashMap<>(); sourcesObject.put(FIELD_HAS_MORE, mHasMore); sourcesObject.put(FIELD_TOTAL_COUNT, mTotalCount); sourcesObject.put(FIELD_OBJECT, VALUE_LIST); @@ -139,14 +151,14 @@ public Map toMap() { mSources); StripeNetworkUtils.removeNullAndEmptyParams(sourcesObject); - mapObject.put(FIELD_SOURCES, sourcesObject); + map.put(FIELD_SOURCES, sourcesObject); - StripeNetworkUtils.removeNullAndEmptyParams(mapObject); - return mapObject; + StripeNetworkUtils.removeNullAndEmptyParams(map); + return map; } @Nullable - public static Customer fromString(String jsonString) { + public static Customer fromString(@Nullable String jsonString) { if (jsonString == null) { return null; } @@ -159,23 +171,27 @@ public static Customer fromString(String jsonString) { } @Nullable - public static Customer fromJson(JSONObject jsonObject) { + public static Customer fromJson(@NonNull JSONObject jsonObject) { String objectType = optString(jsonObject, FIELD_OBJECT); if (!VALUE_CUSTOMER.equals(objectType)) { return null; } - Customer customer = new Customer(); - customer.mId = optString(jsonObject, FIELD_ID); - customer.mDefaultSource = optString(jsonObject, FIELD_DEFAULT_SOURCE); - customer.mShippingInformation = + final String id = optString(jsonObject, FIELD_ID); + final String defaultSource = optString(jsonObject, FIELD_DEFAULT_SOURCE); + final ShippingInformation shippingInformation = ShippingInformation.fromJson(jsonObject.optJSONObject(FIELD_SHIPPING)); - JSONObject sources = jsonObject.optJSONObject(FIELD_SOURCES); - if (sources != null && VALUE_LIST.equals(optString(sources, FIELD_OBJECT))) { - customer.mHasMore = optBoolean(sources, FIELD_HAS_MORE); - customer.mTotalCount = optInteger(sources, FIELD_TOTAL_COUNT); - customer.mUrl = optString(sources, FIELD_URL); - List sourceDataList = new ArrayList<>(); - JSONArray dataArray = sources.optJSONArray(FIELD_DATA); + final JSONObject sourcesJson = jsonObject.optJSONObject(FIELD_SOURCES); + + final Boolean hasMore; + final Integer totalCount; + final String url; + final List sources = new ArrayList<>(); + if (sourcesJson != null && VALUE_LIST.equals(optString(sourcesJson, FIELD_OBJECT))) { + hasMore = optBoolean(sourcesJson, FIELD_HAS_MORE); + totalCount = optInteger(sourcesJson, FIELD_TOTAL_COUNT); + url = optString(sourcesJson, FIELD_URL); + + final JSONArray dataArray = sourcesJson.optJSONArray(FIELD_DATA); for (int i = 0; i < dataArray.length(); i++) { try { JSONObject customerSourceObject = dataArray.getJSONObject(i); @@ -184,11 +200,37 @@ public static Customer fromJson(JSONObject jsonObject) { VALUE_APPLE_PAY.equals(sourceData.getTokenizationMethod())) { continue; } - sourceDataList.add(sourceData); + sources.add(sourceData); } catch (JSONException ignored) { } } - customer.mSources = sourceDataList; + } else { + hasMore = null; + totalCount = null; + url = null; } - return customer; + + return new Customer(id, defaultSource, shippingInformation, sources, hasMore, totalCount, + url); + } + + @Override + public boolean equals(@Nullable Object obj) { + return this == obj || (obj instanceof Customer && typedEquals((Customer) obj)); + } + + private boolean typedEquals(@NonNull Customer customer) { + return ObjectUtils.equals(mId, customer.mId) + && ObjectUtils.equals(mDefaultSource, customer.mDefaultSource) + && ObjectUtils.equals(mShippingInformation, customer.mShippingInformation) + && ObjectUtils.equals(mSources, customer.mSources) + && ObjectUtils.equals(mHasMore, customer.mHasMore) + && ObjectUtils.equals(mTotalCount, customer.mTotalCount) + && ObjectUtils.equals(mUrl, customer.mUrl); + } + + @Override + public int hashCode() { + return ObjectUtils.hash(mId, mDefaultSource, mShippingInformation, mSources, mHasMore, + mTotalCount, mUrl); } } diff --git a/stripe/src/main/java/com/stripe/android/model/CustomerSource.java b/stripe/src/main/java/com/stripe/android/model/CustomerSource.java index 4008215a0aa..e934825c46d 100644 --- a/stripe/src/main/java/com/stripe/android/model/CustomerSource.java +++ b/stripe/src/main/java/com/stripe/android/model/CustomerSource.java @@ -3,6 +3,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.stripe.android.utils.ObjectUtils; + import org.json.JSONException; import org.json.JSONObject; @@ -16,13 +18,13 @@ */ public class CustomerSource extends StripeJsonModel implements StripePaymentSource { - private StripePaymentSource mStripePaymentSource; + @NonNull private final StripePaymentSource mStripePaymentSource; - private CustomerSource(StripePaymentSource paymentSource) { + private CustomerSource(@NonNull StripePaymentSource paymentSource) { mStripePaymentSource = paymentSource; } - @Nullable + @NonNull public StripePaymentSource getStripePaymentSource() { return mStripePaymentSource; } @@ -30,7 +32,7 @@ public StripePaymentSource getStripePaymentSource() { @Override @Nullable public String getId() { - return mStripePaymentSource == null ? null : mStripePaymentSource.getId(); + return mStripePaymentSource.getId(); } @Nullable @@ -43,10 +45,10 @@ public Source asSource() { @Nullable public String getTokenizationMethod() { - Source paymentAsSource = asSource(); - Card paymentAsCard = asCard(); - if (paymentAsSource != null && paymentAsSource.getType().equals(Source.CARD)) { - SourceCardData cardData = (SourceCardData) paymentAsSource.getSourceTypeModel(); + final Source paymentAsSource = asSource(); + final Card paymentAsCard = asCard(); + if (paymentAsSource != null && Source.CARD.equals(paymentAsSource.getType())) { + final SourceCardData cardData = (SourceCardData) paymentAsSource.getSourceTypeModel(); if (cardData != null) { return cardData.getTokenizationMethod(); } @@ -91,12 +93,14 @@ public static CustomerSource fromJson(@Nullable JSONObject jsonObject) { return null; } - String objectString = optString(jsonObject, "object"); - StripePaymentSource sourceObject = null; + final String objectString = optString(jsonObject, "object"); + final StripePaymentSource sourceObject; if (Card.VALUE_CARD.equals(objectString)) { sourceObject = Card.fromJson(jsonObject); } else if (Source.VALUE_SOURCE.equals(objectString)) { sourceObject = Source.fromJson(jsonObject); + } else { + sourceObject = null; } if (sourceObject == null) { @@ -127,4 +131,18 @@ public JSONObject toJson() { } return new JSONObject(); } + + @Override + public boolean equals(@Nullable Object obj) { + return this == obj || (obj instanceof CustomerSource && typedEquals((CustomerSource) obj)); + } + + private boolean typedEquals(@NonNull CustomerSource customerSource) { + return ObjectUtils.equals(mStripePaymentSource, customerSource.mStripePaymentSource); + } + + @Override + public int hashCode() { + return ObjectUtils.hash(mStripePaymentSource); + } } diff --git a/stripe/src/main/java/com/stripe/android/model/ModelUtils.java b/stripe/src/main/java/com/stripe/android/model/ModelUtils.java index e52e4208e6c..4d79fa2af54 100644 --- a/stripe/src/main/java/com/stripe/android/model/ModelUtils.java +++ b/stripe/src/main/java/com/stripe/android/model/ModelUtils.java @@ -31,7 +31,6 @@ static boolean isWholePositiveNumber(@Nullable String value) { * @return {@code true} if the input time has passed the specified current time, * {@code false} otherwise. */ - @SuppressWarnings("WrongConstant") static boolean hasMonthPassed(int year, int month, Calendar now) { if (hasYearPassed(year, now)) { return true; diff --git a/stripe/src/main/java/com/stripe/android/model/PaymentIntent.java b/stripe/src/main/java/com/stripe/android/model/PaymentIntent.java index 87b1d6ff45c..e4f5cb52d39 100644 --- a/stripe/src/main/java/com/stripe/android/model/PaymentIntent.java +++ b/stripe/src/main/java/com/stripe/android/model/PaymentIntent.java @@ -6,6 +6,7 @@ import androidx.annotation.Nullable; import com.stripe.android.StripeNetworkUtils; +import com.stripe.android.utils.ObjectUtils; import org.json.JSONArray; import org.json.JSONException; @@ -126,14 +127,17 @@ public Map getNextSourceAction() { return mNextSourceAction; } + @SuppressWarnings("unchecked") @Nullable public Uri getAuthorizationUrl() { if ("requires_source_action".equals(mStatus) && + mNextSourceAction != null && mNextSourceAction.containsKey("authorize_with_url") && mNextSourceAction.get("authorize_with_url") instanceof Map) { final Map authorizeWithUrlMap = (Map) mNextSourceAction.get("authorize_with_url"); - if (authorizeWithUrlMap.containsKey("url") && + if (authorizeWithUrlMap != null && + authorizeWithUrlMap.containsKey("url") && authorizeWithUrlMap.get("url") instanceof String) { return Uri.parse((String) authorizeWithUrlMap.get("url")); } @@ -262,7 +266,7 @@ public static PaymentIntent fromJson(@Nullable JSONObject jsonObject) { @NonNull @Override public JSONObject toJson() { - JSONObject jsonObject = new JSONObject(); + final JSONObject jsonObject = new JSONObject(); putStringIfNotNull(jsonObject, FIELD_ID, mId); putStringIfNotNull(jsonObject, FIELD_OBJECT, mObjectType); putArrayIfNotNull(jsonObject, FIELD_ALLOWED_SOURCE_TYPES, @@ -307,4 +311,34 @@ public Map toMap() { return map; } + @Override + public boolean equals(@Nullable Object obj) { + return this == obj || (obj instanceof PaymentIntent && typedEquals((PaymentIntent) obj)); + } + + private boolean typedEquals(@NonNull PaymentIntent paymentIntent) { + return ObjectUtils.equals(mId, paymentIntent.mId) + && ObjectUtils.equals(mObjectType, paymentIntent.mObjectType) + && ObjectUtils.equals(mAllowedSourceTypes, paymentIntent.mAllowedSourceTypes) + && ObjectUtils.equals(mAmount, paymentIntent.mAmount) + && ObjectUtils.equals(mCanceledAt, paymentIntent.mCanceledAt) + && ObjectUtils.equals(mCaptureMethod, paymentIntent.mCaptureMethod) + && ObjectUtils.equals(mClientSecret, paymentIntent.mClientSecret) + && ObjectUtils.equals(mConfirmationMethod, paymentIntent.mConfirmationMethod) + && ObjectUtils.equals(mCreated, paymentIntent.mCreated) + && ObjectUtils.equals(mCurrency, paymentIntent.mCurrency) + && ObjectUtils.equals(mDescription, paymentIntent.mDescription) + && ObjectUtils.equals(mLiveMode, paymentIntent.mLiveMode) + && ObjectUtils.equals(mNextSourceAction, paymentIntent.mNextSourceAction) + && ObjectUtils.equals(mReceiptEmail, paymentIntent.mReceiptEmail) + && ObjectUtils.equals(mSource, paymentIntent.mSource) + && ObjectUtils.equals(mStatus, paymentIntent.mStatus); + } + + @Override + public int hashCode() { + return ObjectUtils.hash(mId, mObjectType, mAllowedSourceTypes, mAmount, mCanceledAt, + mCaptureMethod, mClientSecret, mConfirmationMethod, mCreated, mCurrency, + mDescription, mLiveMode, mNextSourceAction, mReceiptEmail, mSource, mStatus); + } } diff --git a/stripe/src/main/java/com/stripe/android/model/PaymentIntentParams.java b/stripe/src/main/java/com/stripe/android/model/PaymentIntentParams.java index 87e5bc124ab..949e962b411 100644 --- a/stripe/src/main/java/com/stripe/android/model/PaymentIntentParams.java +++ b/stripe/src/main/java/com/stripe/android/model/PaymentIntentParams.java @@ -315,5 +315,4 @@ public String getPaymentMethodId() { public String getReturnUrl() { return mReturnUrl; } - } diff --git a/stripe/src/main/java/com/stripe/android/model/PaymentMethod.java b/stripe/src/main/java/com/stripe/android/model/PaymentMethod.java index 727353fc8a4..884f9e93381 100644 --- a/stripe/src/main/java/com/stripe/android/model/PaymentMethod.java +++ b/stripe/src/main/java/com/stripe/android/model/PaymentMethod.java @@ -145,6 +145,29 @@ public static PaymentMethod fromJson(@Nullable JSONObject paymentMethod) { return builder.build(); } + @Override + public boolean equals(@Nullable Object obj) { + return this == obj || (obj instanceof PaymentMethod && typedEquals((PaymentMethod) obj)); + } + + private boolean typedEquals(@NonNull PaymentMethod paymentMethod) { + return ObjectUtils.equals(id, paymentMethod.id) + && ObjectUtils.equals(created, paymentMethod.created) + && liveMode == paymentMethod.liveMode + && ObjectUtils.equals(type, paymentMethod.type) + && ObjectUtils.equals(billingDetails, paymentMethod.billingDetails) + && ObjectUtils.equals(card, paymentMethod.card) + && ObjectUtils.equals(cardPresent, paymentMethod.cardPresent) + && ObjectUtils.equals(ideal, paymentMethod.ideal) + && ObjectUtils.equals(customerId, paymentMethod.customerId); + } + + @Override + public int hashCode() { + return ObjectUtils.hash(id, created, liveMode, type, billingDetails, card, cardPresent, + ideal, customerId); + } + public static final class Builder { private String mId; private Long mCreated; diff --git a/stripe/src/main/java/com/stripe/android/model/ShippingInformation.java b/stripe/src/main/java/com/stripe/android/model/ShippingInformation.java index c61b0720ad3..14d0a7a30eb 100644 --- a/stripe/src/main/java/com/stripe/android/model/ShippingInformation.java +++ b/stripe/src/main/java/com/stripe/android/model/ShippingInformation.java @@ -7,9 +7,11 @@ import androidx.annotation.Nullable; import com.stripe.android.StripeNetworkUtils; +import com.stripe.android.utils.ObjectUtils; import org.json.JSONObject; +import java.util.AbstractMap; import java.util.HashMap; import java.util.Map; @@ -20,16 +22,29 @@ * Model representing a shipping address object */ public class ShippingInformation extends StripeJsonModel implements Parcelable { + public static final Creator CREATOR = new Creator() { + @Override + public ShippingInformation createFromParcel(Parcel source) { + return new ShippingInformation(source); + } + + @Override + public ShippingInformation[] newArray(int size) { + return new ShippingInformation[size]; + } + }; private static final String FIELD_ADDRESS = "address"; private static final String FIELD_NAME = "name"; private static final String FIELD_PHONE = "phone"; - private @Nullable Address mAddress; - private @Nullable String mName; - private @Nullable String mPhone; + @Nullable private final Address mAddress; + @Nullable private final String mName; + @Nullable private final String mPhone; - public ShippingInformation() {} + public ShippingInformation() { + this(null, null, null); + } public ShippingInformation(@Nullable Address address, @Nullable String name, @Nullable String phone) { @@ -38,6 +53,12 @@ public ShippingInformation(@Nullable Address address, @Nullable String name, mPhone = phone; } + protected ShippingInformation(@NonNull Parcel in) { + mAddress = in.readParcelable(Address.class.getClassLoader()); + mName = in.readString(); + mPhone = in.readString(); + } + @Nullable public Address getAddress() { return mAddress; @@ -59,18 +80,16 @@ public static ShippingInformation fromJson(@Nullable JSONObject jsonObject) { return null; } - ShippingInformation shippingInformation = new ShippingInformation(); - shippingInformation.mName = optString(jsonObject, FIELD_NAME); - shippingInformation.mPhone = optString(jsonObject, FIELD_PHONE); - shippingInformation.mAddress = - Address.fromJson(jsonObject.optJSONObject(FIELD_ADDRESS)); - return shippingInformation; + return new ShippingInformation( + Address.fromJson(jsonObject.optJSONObject(FIELD_ADDRESS)), + optString(jsonObject, FIELD_NAME), + optString(jsonObject, FIELD_PHONE)); } @NonNull @Override public JSONObject toJson() { - JSONObject jsonObject = new JSONObject(); + final JSONObject jsonObject = new JSONObject(); putStringIfNotNull(jsonObject, FIELD_NAME, mName); putStringIfNotNull(jsonObject, FIELD_PHONE, mPhone); putStripeJsonModelIfNotNull(jsonObject, FIELD_ADDRESS, mAddress); @@ -80,7 +99,7 @@ public JSONObject toJson() { @NonNull @Override public Map toMap() { - Map map = new HashMap<>(); + final AbstractMap map = new HashMap<>(); map.put(FIELD_NAME, mName); map.put(FIELD_PHONE, mPhone); putStripeJsonModelMapIfNotNull(map, FIELD_ADDRESS, mAddress); @@ -95,27 +114,26 @@ public int describeContents() { } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeParcelable(mAddress, flags); dest.writeString(mName); dest.writeString(mPhone); } - protected ShippingInformation(Parcel in) { - mAddress = in.readParcelable(Address.class.getClassLoader()); - mName = in.readString(); - mPhone = in.readString(); + @Override + public boolean equals(Object obj) { + return this == obj + || (obj instanceof ShippingInformation && typedEquals((ShippingInformation) obj)); } - public static final Creator CREATOR = new Creator() { - @Override - public ShippingInformation createFromParcel(Parcel source) { - return new ShippingInformation(source); - } + private boolean typedEquals(@NonNull ShippingInformation shippingInformation) { + return ObjectUtils.equals(mAddress, shippingInformation.mAddress) + && ObjectUtils.equals(mName, shippingInformation.mName) + && ObjectUtils.equals(mPhone, shippingInformation.mPhone); + } - @Override - public ShippingInformation[] newArray(int size) { - return new ShippingInformation[size]; - } - }; + @Override + public int hashCode() { + return ObjectUtils.hash(mAddress, mName, mPhone); + } } diff --git a/stripe/src/main/java/com/stripe/android/model/ShippingMethod.java b/stripe/src/main/java/com/stripe/android/model/ShippingMethod.java index a985b1071c4..31cbd802df4 100644 --- a/stripe/src/main/java/com/stripe/android/model/ShippingMethod.java +++ b/stripe/src/main/java/com/stripe/android/model/ShippingMethod.java @@ -7,8 +7,11 @@ import androidx.annotation.Nullable; import androidx.annotation.Size; +import com.stripe.android.utils.ObjectUtils; + import org.json.JSONObject; +import java.util.AbstractMap; import java.util.Currency; import java.util.HashMap; import java.util.Map; @@ -21,6 +24,17 @@ */ public class ShippingMethod extends StripeJsonModel implements Parcelable { + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public ShippingMethod createFromParcel(Parcel in) { + return new ShippingMethod(in); + } + + public ShippingMethod[] newArray(int size) { + return new ShippingMethod[size]; + } + }; + private static final String FIELD_AMOUNT = "amount"; /*ISO Currency Code*/ private static final String FIELD_CURRENCY_CODE = "currency_code"; @@ -28,11 +42,11 @@ public class ShippingMethod extends StripeJsonModel implements Parcelable { private static final String FIELD_IDENTIFIER = "identifier"; private static final String FIELD_LABEL = "label"; - private long mAmount; - private @NonNull @Size(min = 0, max = 3) String mCurrencyCode; - private @Nullable String mDetail; - private @NonNull String mIdentifier; - private @NonNull String mLabel; + private final long mAmount; + @NonNull @Size(min = 0, max = 3) private final String mCurrencyCode; + @Nullable private final String mDetail; + @NonNull private final String mIdentifier; + @NonNull private final String mLabel; public ShippingMethod(@NonNull String label, @NonNull String identifier, @@ -46,8 +60,7 @@ public ShippingMethod(@NonNull String label, @NonNull String identifier, @Nullable String detail, long amount, - @NonNull @Size(min = 0, max = 3) String - currencyCode) { + @NonNull @Size(min = 0, max = 3) String currencyCode) { mLabel = label; mIdentifier = identifier; mDetail = detail; @@ -55,6 +68,14 @@ public ShippingMethod(@NonNull String label, mCurrencyCode = currencyCode; } + private ShippingMethod(@NonNull Parcel in) { + mAmount = in.readLong(); + mCurrencyCode = in.readString(); + mDetail = in.readString(); + mIdentifier = in.readString(); + mLabel = in.readString(); + } + /** * @return the currency that the specified amount will be rendered in. */ @@ -100,7 +121,7 @@ public String getIdentifier() { @NonNull @Override public JSONObject toJson() { - JSONObject jsonObject = new JSONObject(); + final JSONObject jsonObject = new JSONObject(); putLongIfNotNull(jsonObject, FIELD_AMOUNT, mAmount); putStringIfNotNull(jsonObject, FIELD_CURRENCY_CODE, mCurrencyCode); putStringIfNotNull(jsonObject, FIELD_DETAIL, mDetail); @@ -112,7 +133,7 @@ public JSONObject toJson() { @NonNull @Override public Map toMap() { - Map map = new HashMap<>(); + final AbstractMap map = new HashMap<>(); map.put(FIELD_AMOUNT, mAmount); map.put(FIELD_CURRENCY_CODE, mCurrencyCode); map.put(FIELD_DETAIL, mDetail); @@ -136,22 +157,21 @@ public void writeToParcel(Parcel parcel, int i) { parcel.writeString(mLabel); } - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - public ShippingMethod createFromParcel(Parcel in) { - return new ShippingMethod(in); - } + @Override + public boolean equals(@Nullable Object obj) { + return this == obj || (obj instanceof ShippingMethod && typedEquals((ShippingMethod) obj)); + } - public ShippingMethod[] newArray(int size) { - return new ShippingMethod[size]; - } - }; + private boolean typedEquals(@NonNull ShippingMethod shippingMethod) { + return mAmount == shippingMethod.mAmount + && ObjectUtils.equals(mCurrencyCode, shippingMethod.mCurrencyCode) + && ObjectUtils.equals(mDetail, shippingMethod.mDetail) + && ObjectUtils.equals(mIdentifier, shippingMethod.mIdentifier) + && ObjectUtils.equals(mLabel, shippingMethod.mLabel); + } - private ShippingMethod(Parcel in) { - mAmount = in.readLong(); - mCurrencyCode = in.readString(); - mDetail = in.readString(); - mIdentifier = in.readString(); - mLabel = in.readString(); + @Override + public int hashCode() { + return ObjectUtils.hash(mAmount, mCurrencyCode, mDetail, mIdentifier, mLabel); } } diff --git a/stripe/src/main/java/com/stripe/android/model/Source.java b/stripe/src/main/java/com/stripe/android/model/Source.java index d3a75ce297e..78bb075e49b 100644 --- a/stripe/src/main/java/com/stripe/android/model/Source.java +++ b/stripe/src/main/java/com/stripe/android/model/Source.java @@ -5,11 +5,14 @@ import androidx.annotation.Size; import androidx.annotation.StringDef; +import com.stripe.android.utils.ObjectUtils; + import org.json.JSONException; import org.json.JSONObject; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.AbstractMap; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -123,44 +126,44 @@ public class Source extends StripeJsonModel implements StripePaymentSource { private static final String FIELD_TYPE = "type"; private static final String FIELD_USAGE = "usage"; - private String mId; - private Long mAmount; - private String mClientSecret; - private SourceCodeVerification mCodeVerification; - private Long mCreated; - private String mCurrency; - private String mTypeRaw; - private @SourceFlow String mFlow; - private Boolean mLiveMode; - private Map mMetaData; - private SourceOwner mOwner; - private SourceReceiver mReceiver; - private SourceRedirect mRedirect; - private @SourceStatus String mStatus; - private Map mSourceTypeData; - private StripeSourceTypeModel mSourceTypeModel; - private @SourceType String mType; - private @Usage String mUsage; - - Source( - String id, - Long amount, - String clientSecret, - SourceCodeVerification codeVerification, - Long created, - String currency, - @SourceFlow String flow, - Boolean liveMode, - Map metaData, - SourceOwner owner, - SourceReceiver receiver, - SourceRedirect redirect, - @SourceStatus String status, - Map sourceTypeData, - StripeSourceTypeModel sourceTypeModel, - @SourceType String type, - String rawType, - @Usage String usage + @Nullable private String mId; + @Nullable private Long mAmount; + @Nullable private String mClientSecret; + @Nullable private SourceCodeVerification mCodeVerification; + @Nullable private Long mCreated; + @Nullable private String mCurrency; + @Nullable private String mTypeRaw; + @Nullable @SourceFlow private String mFlow; + @Nullable private Boolean mLiveMode; + @Nullable private Map mMetaData; + @Nullable private SourceOwner mOwner; + @Nullable private SourceReceiver mReceiver; + @Nullable private SourceRedirect mRedirect; + @Nullable @SourceStatus private String mStatus; + @Nullable private Map mSourceTypeData; + @Nullable private StripeSourceTypeModel mSourceTypeModel; + @Nullable @SourceType private String mType; + @Nullable @Usage private String mUsage; + + private Source( + @Nullable String id, + @Nullable Long amount, + @Nullable String clientSecret, + @Nullable SourceCodeVerification codeVerification, + @Nullable Long created, + @Nullable String currency, + @Nullable @SourceFlow String flow, + @Nullable Boolean liveMode, + @Nullable Map metaData, + @Nullable SourceOwner owner, + @Nullable SourceReceiver receiver, + @Nullable SourceRedirect redirect, + @Nullable @SourceStatus String status, + @Nullable Map sourceTypeData, + @Nullable StripeSourceTypeModel sourceTypeModel, + @NonNull @SourceType String type, + @NonNull String rawType, + @Nullable @Usage String usage ) { mId = id; mAmount = amount; @@ -345,36 +348,36 @@ public void setUsage(@Usage String usage) { @NonNull @Override public Map toMap() { - Map hashMap = new HashMap<>(); - hashMap.put(FIELD_ID, mId); - hashMap.put(FIELD_AMOUNT, mAmount); - hashMap.put(FIELD_CLIENT_SECRET, mClientSecret); + final AbstractMap map = new HashMap<>(); + map.put(FIELD_ID, mId); + map.put(FIELD_AMOUNT, mAmount); + map.put(FIELD_CLIENT_SECRET, mClientSecret); - putStripeJsonModelMapIfNotNull(hashMap, FIELD_CODE_VERIFICATION, mCodeVerification); + putStripeJsonModelMapIfNotNull(map, FIELD_CODE_VERIFICATION, mCodeVerification); - hashMap.put(FIELD_CREATED, mCreated); - hashMap.put(FIELD_CURRENCY, mCurrency); - hashMap.put(FIELD_FLOW, mFlow); - hashMap.put(FIELD_LIVEMODE, mLiveMode); - hashMap.put(FIELD_METADATA, mMetaData); + map.put(FIELD_CREATED, mCreated); + map.put(FIELD_CURRENCY, mCurrency); + map.put(FIELD_FLOW, mFlow); + map.put(FIELD_LIVEMODE, mLiveMode); + map.put(FIELD_METADATA, mMetaData); - putStripeJsonModelMapIfNotNull(hashMap, FIELD_OWNER, mOwner); - putStripeJsonModelMapIfNotNull(hashMap, FIELD_RECEIVER, mReceiver); - putStripeJsonModelMapIfNotNull(hashMap, FIELD_REDIRECT, mRedirect); + putStripeJsonModelMapIfNotNull(map, FIELD_OWNER, mOwner); + putStripeJsonModelMapIfNotNull(map, FIELD_RECEIVER, mReceiver); + putStripeJsonModelMapIfNotNull(map, FIELD_REDIRECT, mRedirect); - hashMap.put(mTypeRaw, mSourceTypeData); + map.put(mTypeRaw, mSourceTypeData); - hashMap.put(FIELD_STATUS, mStatus); - hashMap.put(FIELD_TYPE, mTypeRaw); - hashMap.put(FIELD_USAGE, mUsage); - removeNullAndEmptyParams(hashMap); - return hashMap; + map.put(FIELD_STATUS, mStatus); + map.put(FIELD_TYPE, mTypeRaw); + map.put(FIELD_USAGE, mUsage); + removeNullAndEmptyParams(map); + return map; } @NonNull @Override public JSONObject toJson() { - JSONObject jsonObject = new JSONObject(); + final JSONObject jsonObject = new JSONObject(); try { putStringIfNotNull(jsonObject, FIELD_ID, mId); jsonObject.put(FIELD_OBJECT, VALUE_SOURCE); @@ -423,53 +426,45 @@ public static Source fromJson(@Nullable JSONObject jsonObject) { return null; } - String id = optString(jsonObject, FIELD_ID); - Long amount = optLong(jsonObject, FIELD_AMOUNT); - String clientSecret = optString(jsonObject, FIELD_CLIENT_SECRET); - SourceCodeVerification codeVerification = optStripeJsonModel( + final String id = optString(jsonObject, FIELD_ID); + final Long amount = optLong(jsonObject, FIELD_AMOUNT); + final String clientSecret = optString(jsonObject, FIELD_CLIENT_SECRET); + final SourceCodeVerification codeVerification = optStripeJsonModel( jsonObject, FIELD_CODE_VERIFICATION, SourceCodeVerification.class); - Long created = optLong(jsonObject, FIELD_CREATED); - String currency = optString(jsonObject, FIELD_CURRENCY); - @SourceFlow String flow = asSourceFlow(optString(jsonObject, FIELD_FLOW)); - Boolean liveMode = jsonObject.optBoolean(FIELD_LIVEMODE); - Map metadata = + final Long created = optLong(jsonObject, FIELD_CREATED); + final String currency = optString(jsonObject, FIELD_CURRENCY); + @SourceFlow final String flow = asSourceFlow(optString(jsonObject, FIELD_FLOW)); + final Boolean liveMode = jsonObject.optBoolean(FIELD_LIVEMODE); + final Map metadata = StripeJsonUtils.jsonObjectToStringMap(jsonObject.optJSONObject(FIELD_METADATA)); - SourceOwner owner = optStripeJsonModel(jsonObject, FIELD_OWNER, SourceOwner.class); - SourceReceiver receiver = optStripeJsonModel( + final SourceOwner owner = optStripeJsonModel(jsonObject, FIELD_OWNER, SourceOwner.class); + final SourceReceiver receiver = optStripeJsonModel( jsonObject, FIELD_RECEIVER, SourceReceiver.class); - SourceRedirect redirect = optStripeJsonModel( + final SourceRedirect redirect = optStripeJsonModel( jsonObject, FIELD_REDIRECT, SourceRedirect.class); - @SourceStatus String status = asSourceStatus(optString(jsonObject, FIELD_STATUS)); - - String typeRaw = optString(jsonObject, FIELD_TYPE); - if (typeRaw == null) { - // We can't allow this type to be null, as we are using it for a key - // on the JSON object later. - typeRaw = UNKNOWN; - } + @SourceStatus final String status = asSourceStatus(optString(jsonObject, FIELD_STATUS)); - @SourceType String type = asSourceType(typeRaw); - if (type == null) { - type = UNKNOWN; - } + final String typeRawOpt = optString(jsonObject, FIELD_TYPE); + @SourceType final String typeRaw = typeRawOpt != null ? typeRawOpt : UNKNOWN; + @SourceType final String type = asSourceType(typeRaw); // Until we have models for all types, keep the original hash and the // model object. The customType variable can be any field, and is not altered by // trying to force it to be a type that we know of. - Map sourceTypeData = + final Map sourceTypeData = StripeJsonUtils.jsonObjectToMap(jsonObject.optJSONObject(typeRaw)); - StripeSourceTypeModel sourceTypeModel = + final StripeSourceTypeModel sourceTypeModel = MODELED_TYPES.contains(typeRaw) ? optStripeJsonModel(jsonObject, typeRaw, StripeSourceTypeModel.class) : null; - @Usage String usage = asUsage(optString(jsonObject, FIELD_USAGE)); + @Usage final String usage = asUsage(optString(jsonObject, FIELD_USAGE)); return new Source( id, @@ -543,7 +538,7 @@ private static String asSourceStatus(@Nullable String sourceStatus) { return null; } - @Nullable + @NonNull @SourceType static String asSourceType(@Nullable String sourceType) { if (CARD.equals(sourceType)) { @@ -566,9 +561,9 @@ static String asSourceType(@Nullable String sourceType) { return P24; } else if (UNKNOWN.equals(sourceType)) { return UNKNOWN; + } else { + return UNKNOWN; } - - return null; } @Nullable @@ -596,4 +591,37 @@ private static String asSourceFlow(@Nullable String sourceFlow) { } return null; } + + @Override + public boolean equals(@Nullable Object obj) { + return this == obj || (obj instanceof Source && typedEquals((Source) obj)); + } + + private boolean typedEquals(@NonNull Source source) { + return ObjectUtils.equals(mId, source.mId) + && ObjectUtils.equals(mAmount, source.mAmount) + && ObjectUtils.equals(mClientSecret, source.mClientSecret) + && ObjectUtils.equals(mCodeVerification, source.mCodeVerification) + && ObjectUtils.equals(mCreated, source.mCreated) + && ObjectUtils.equals(mCurrency, source.mCurrency) + && ObjectUtils.equals(mTypeRaw, source.mTypeRaw) + && ObjectUtils.equals(mFlow, source.mFlow) + && ObjectUtils.equals(mLiveMode, source.mLiveMode) + && ObjectUtils.equals(mMetaData, source.mMetaData) + && ObjectUtils.equals(mOwner, source.mOwner) + && ObjectUtils.equals(mReceiver, source.mReceiver) + && ObjectUtils.equals(mRedirect, source.mRedirect) + && ObjectUtils.equals(mStatus, source.mStatus) + && ObjectUtils.equals(mSourceTypeData, source.mSourceTypeData) + && ObjectUtils.equals(mSourceTypeModel, source.mSourceTypeModel) + && ObjectUtils.equals(mType, source.mType) + && ObjectUtils.equals(mUsage, source.mUsage); + } + + @Override + public int hashCode() { + return ObjectUtils.hash(mId, mAmount, mClientSecret, mCodeVerification, mCreated, mCurrency, + mTypeRaw, mFlow, mLiveMode, mMetaData, mOwner, mReceiver, mRedirect, mStatus, + mSourceTypeData, mSourceTypeModel, mType, mUsage); + } } diff --git a/stripe/src/main/java/com/stripe/android/model/SourceCardData.java b/stripe/src/main/java/com/stripe/android/model/SourceCardData.java index 740e12490ea..64c6632eec4 100644 --- a/stripe/src/main/java/com/stripe/android/model/SourceCardData.java +++ b/stripe/src/main/java/com/stripe/android/model/SourceCardData.java @@ -6,12 +6,14 @@ import androidx.annotation.VisibleForTesting; import com.stripe.android.StripeNetworkUtils; +import com.stripe.android.utils.ObjectUtils; import org.json.JSONException; import org.json.JSONObject; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.AbstractMap; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; @@ -68,21 +70,34 @@ public class SourceCardData extends StripeSourceTypeModel { FIELD_THREE_D_SECURE, FIELD_TOKENIZATION_METHOD)); - private String mAddressLine1Check; - private String mAddressZipCheck; - private @Card.CardBrand String mBrand; - private String mCountry; - private String mCvcCheck; - private String mDynamicLast4; - private Integer mExpiryMonth; - private Integer mExpiryYear; - private @Card.FundingType String mFunding; - private String mLast4; - private @ThreeDSecureStatus String mThreeDSecureStatus; - private String mTokenizationMethod; - - private SourceCardData() { - super(STANDARD_FIELDS); + @Nullable private final String mAddressLine1Check; + @Nullable private final String mAddressZipCheck; + @Nullable @Card.CardBrand private final String mBrand; + @Nullable private final String mCountry; + @Nullable private final String mCvcCheck; + @Nullable private final String mDynamicLast4; + @Nullable private final Integer mExpiryMonth; + @Nullable private final Integer mExpiryYear; + @Nullable @Card.FundingType private final String mFunding; + @Nullable private final String mLast4; + @Nullable @ThreeDSecureStatus private final String mThreeDSecureStatus; + @Nullable private final String mTokenizationMethod; + + private SourceCardData(@NonNull Builder builder) { + super(builder); + + mAddressLine1Check = builder.mAddressLine1Check; + mAddressZipCheck = builder.mAddressZipCheck; + mBrand = builder.mBrand; + mCountry = builder.mCountry; + mCvcCheck = builder.mCvcCheck; + mDynamicLast4 = builder.mDynamicLast4; + mExpiryMonth = builder.mExpiryMonth; + mExpiryYear = builder.mExpiryYear; + mFunding = builder.mFunding; + mLast4 = builder.mLast4; + mThreeDSecureStatus = builder.mThreeDSecureStatus; + mTokenizationMethod = builder.mTokenizationMethod; } @Nullable @@ -151,7 +166,7 @@ public String getTokenizationMethod() { @NonNull @Override public JSONObject toJson() { - JSONObject jsonObject = new JSONObject(); + final JSONObject jsonObject = super.toJson(); putStringIfNotNull(jsonObject, FIELD_ADDRESS_LINE1_CHECK, mAddressLine1Check); putStringIfNotNull(jsonObject, FIELD_ADDRESS_ZIP_CHECK, mAddressZipCheck); putStringIfNotNull(jsonObject, FIELD_BRAND, mBrand); @@ -163,90 +178,26 @@ public JSONObject toJson() { putStringIfNotNull(jsonObject, FIELD_LAST4, mLast4); putStringIfNotNull(jsonObject, FIELD_THREE_D_SECURE, mThreeDSecureStatus); putStringIfNotNull(jsonObject, FIELD_TOKENIZATION_METHOD, mTokenizationMethod); - - putAdditionalFieldsIntoJsonObject(jsonObject, mAdditionalFields); return jsonObject; } @NonNull @Override public Map toMap() { - Map objectMap = new HashMap<>(); - objectMap.put(FIELD_ADDRESS_LINE1_CHECK, mAddressLine1Check); - objectMap.put(FIELD_ADDRESS_ZIP_CHECK, mAddressZipCheck); - objectMap.put(FIELD_BRAND, mBrand); - objectMap.put(FIELD_COUNTRY, mCountry); - objectMap.put(FIELD_DYNAMIC_LAST4, mDynamicLast4); - objectMap.put(FIELD_EXP_MONTH, mExpiryMonth); - objectMap.put(FIELD_EXP_YEAR, mExpiryYear); - objectMap.put(FIELD_FUNDING, mFunding); - objectMap.put(FIELD_LAST4, mLast4); - objectMap.put(FIELD_THREE_D_SECURE, mThreeDSecureStatus); - objectMap.put(FIELD_TOKENIZATION_METHOD, mTokenizationMethod); - - putAdditionalFieldsIntoMap(objectMap, mAdditionalFields); - StripeNetworkUtils.removeNullAndEmptyParams(objectMap); - return objectMap; - } - - private SourceCardData setAddressLine1Check(String addressLine1Check) { - mAddressLine1Check = addressLine1Check; - return this; - } - - private SourceCardData setAddressZipCheck(String addressZipCheck) { - mAddressZipCheck = addressZipCheck; - return this; - } - - private SourceCardData setBrand(String brand) { - mBrand = brand; - return this; - } - - private SourceCardData setCountry(String country) { - mCountry = country; - return this; - } - - private SourceCardData setCvcCheck(String cvcCheck) { - mCvcCheck = cvcCheck; - return this; - } - - private SourceCardData setDynamicLast4(String dynamicLast4) { - mDynamicLast4 = dynamicLast4; - return this; - } - - private SourceCardData setExpiryMonth(Integer expiryMonth) { - mExpiryMonth = expiryMonth; - return this; - } - - private SourceCardData setExpiryYear(Integer expiryYear) { - mExpiryYear = expiryYear; - return this; - } - - private SourceCardData setFunding(String funding) { - mFunding = funding; - return this; - } - - private SourceCardData setLast4(String last4) { - mLast4 = last4; - return this; - } - - private SourceCardData setThreeDSecureStatus(String threeDSecureStatus) { - mThreeDSecureStatus = threeDSecureStatus; - return this; - } - - private SourceCardData setTokenizationMethod(String tokenizationMethod) { - mTokenizationMethod = tokenizationMethod; - return this; + final AbstractMap map = new HashMap<>(super.toMap()); + map.put(FIELD_ADDRESS_LINE1_CHECK, mAddressLine1Check); + map.put(FIELD_ADDRESS_ZIP_CHECK, mAddressZipCheck); + map.put(FIELD_BRAND, mBrand); + map.put(FIELD_COUNTRY, mCountry); + map.put(FIELD_DYNAMIC_LAST4, mDynamicLast4); + map.put(FIELD_EXP_MONTH, mExpiryMonth); + map.put(FIELD_EXP_YEAR, mExpiryYear); + map.put(FIELD_FUNDING, mFunding); + map.put(FIELD_LAST4, mLast4); + map.put(FIELD_THREE_D_SECURE, mThreeDSecureStatus); + map.put(FIELD_TOKENIZATION_METHOD, mTokenizationMethod); + StripeNetworkUtils.removeNullAndEmptyParams(map); + return map; } @Nullable @@ -255,8 +206,8 @@ static SourceCardData fromJson(@Nullable JSONObject jsonObject) { return null; } - SourceCardData cardData = new SourceCardData(); - cardData.setAddressLine1Check(optString(jsonObject, FIELD_ADDRESS_LINE1_CHECK)) + final Builder cardData = new Builder() + .setAddressLine1Check(optString(jsonObject, FIELD_ADDRESS_LINE1_CHECK)) .setAddressZipCheck(optString(jsonObject, FIELD_ADDRESS_ZIP_CHECK)) .setBrand(Card.asCardBrand(optString(jsonObject, FIELD_BRAND))) .setCountry(optString(jsonObject, FIELD_COUNTRY)) @@ -270,13 +221,13 @@ static SourceCardData fromJson(@Nullable JSONObject jsonObject) { FIELD_THREE_D_SECURE))) .setTokenizationMethod(optString(jsonObject, FIELD_TOKENIZATION_METHOD)); - Map nonStandardFields = - jsonObjectToMapWithoutKeys(jsonObject, cardData.mStandardFields); + final Map nonStandardFields = + jsonObjectToMapWithoutKeys(jsonObject, STANDARD_FIELDS); if (nonStandardFields != null) { cardData.setAdditionalFields(nonStandardFields); } - return cardData; + return cardData.build(); } @VisibleForTesting @@ -306,4 +257,124 @@ private static String asThreeDSecureStatus(@Nullable String threeDSecureStatus) return UNKNOWN; } } + + @Override + public boolean equals(@Nullable Object obj) { + return this == obj || (obj instanceof SourceCardData && typedEquals((SourceCardData) obj)); + } + + boolean typedEquals(@NonNull SourceCardData sourceCardData) { + return super.typedEquals(sourceCardData) + && ObjectUtils.equals(mAddressLine1Check, sourceCardData.mAddressLine1Check) + && ObjectUtils.equals(mAddressZipCheck, sourceCardData.mAddressZipCheck) + && ObjectUtils.equals(mBrand, sourceCardData.mBrand) + && ObjectUtils.equals(mCountry, sourceCardData.mCountry) + && ObjectUtils.equals(mCvcCheck, sourceCardData.mCvcCheck) + && ObjectUtils.equals(mDynamicLast4, sourceCardData.mDynamicLast4) + && ObjectUtils.equals(mExpiryMonth, sourceCardData.mExpiryMonth) + && ObjectUtils.equals(mExpiryYear, sourceCardData.mExpiryYear) + && ObjectUtils.equals(mFunding, sourceCardData.mFunding) + && ObjectUtils.equals(mLast4, sourceCardData.mLast4) + && ObjectUtils.equals(mThreeDSecureStatus, sourceCardData.mThreeDSecureStatus) + && ObjectUtils.equals(mTokenizationMethod, sourceCardData.mTokenizationMethod); + } + + @Override + public int hashCode() { + return ObjectUtils.hash(mAddressLine1Check, mAddressZipCheck, mBrand, mCountry, mCvcCheck, + mDynamicLast4, mExpiryMonth, mExpiryYear, mFunding, mLast4, mThreeDSecureStatus, + mTokenizationMethod); + } + + private static final class Builder extends BaseBuilder { + private String mAddressLine1Check; + private String mAddressZipCheck; + @Card.CardBrand private String mBrand; + private String mCountry; + private String mCvcCheck; + private String mDynamicLast4; + private Integer mExpiryMonth; + private Integer mExpiryYear; + @Card.FundingType private String mFunding; + private String mLast4; + @ThreeDSecureStatus private String mThreeDSecureStatus; + private String mTokenizationMethod; + + @NonNull + private Builder setAddressLine1Check(String addressLine1Check) { + mAddressLine1Check = addressLine1Check; + return this; + } + + @NonNull + private Builder setAddressZipCheck(String addressZipCheck) { + mAddressZipCheck = addressZipCheck; + return this; + } + + @NonNull + private Builder setBrand(String brand) { + mBrand = brand; + return this; + } + + @NonNull + private Builder setCountry(String country) { + mCountry = country; + return this; + } + + @NonNull + private Builder setCvcCheck(String cvcCheck) { + mCvcCheck = cvcCheck; + return this; + } + + @NonNull + private Builder setDynamicLast4(String dynamicLast4) { + mDynamicLast4 = dynamicLast4; + return this; + } + + @NonNull + private Builder setExpiryMonth(Integer expiryMonth) { + mExpiryMonth = expiryMonth; + return this; + } + + @NonNull + private Builder setExpiryYear(Integer expiryYear) { + mExpiryYear = expiryYear; + return this; + } + + @NonNull + private Builder setFunding(String funding) { + mFunding = funding; + return this; + } + + @NonNull + private Builder setLast4(String last4) { + mLast4 = last4; + return this; + } + + @NonNull + private Builder setThreeDSecureStatus(String threeDSecureStatus) { + mThreeDSecureStatus = threeDSecureStatus; + return this; + } + + @NonNull + private Builder setTokenizationMethod(String tokenizationMethod) { + mTokenizationMethod = tokenizationMethod; + return this; + } + + @NonNull + public SourceCardData build() { + return new SourceCardData(this); + } + } } diff --git a/stripe/src/main/java/com/stripe/android/model/SourceCodeVerification.java b/stripe/src/main/java/com/stripe/android/model/SourceCodeVerification.java index 8c385c427b6..422523d6180 100644 --- a/stripe/src/main/java/com/stripe/android/model/SourceCodeVerification.java +++ b/stripe/src/main/java/com/stripe/android/model/SourceCodeVerification.java @@ -4,6 +4,8 @@ import androidx.annotation.Nullable; import androidx.annotation.StringDef; +import com.stripe.android.utils.ObjectUtils; + import org.json.JSONException; import org.json.JSONObject; @@ -15,7 +17,7 @@ import static com.stripe.android.model.StripeJsonUtils.optString; /** * Model for a - * {@url https://stripe.com/docs/api#source_object-code_verification code_verification} + * https://stripe.com/docs/api/sources/object#source_object-code_verification * object in the source api, not source code verification */ public class SourceCodeVerification extends StripeJsonModel { @@ -37,10 +39,10 @@ public class SourceCodeVerification extends StripeJsonModel { private static final String FIELD_STATUS = "status"; private static final int INVALID_ATTEMPTS_REMAINING = -1; - private int mAttemptsRemaining; - private @Status String mStatus; + private final int mAttemptsRemaining; + @Nullable @Status private final String mStatus; - SourceCodeVerification(int attemptsRemaining, @Status String status) { + private SourceCodeVerification(int attemptsRemaining, @Nullable @Status String status) { mAttemptsRemaining = attemptsRemaining; mStatus = status; } @@ -49,34 +51,27 @@ public int getAttemptsRemaining() { return mAttemptsRemaining; } - void setAttemptsRemaining(int attemptsRemaining) { - mAttemptsRemaining = attemptsRemaining; - } - + @Nullable @Status public String getStatus() { return mStatus; } - void setStatus(@Status String status) { - mStatus = status; - } - @NonNull @Override public Map toMap() { - Map hashMap = new HashMap<>(); - hashMap.put(FIELD_ATTEMPTS_REMAINING, mAttemptsRemaining); + final Map map = new HashMap<>(); + map.put(FIELD_ATTEMPTS_REMAINING, mAttemptsRemaining); if (mStatus != null) { - hashMap.put(FIELD_STATUS, mStatus); + map.put(FIELD_STATUS, mStatus); } - return hashMap; + return map; } @NonNull @Override public JSONObject toJson() { - JSONObject jsonObject = new JSONObject(); + final JSONObject jsonObject = new JSONObject(); try { jsonObject.put(FIELD_ATTEMPTS_REMAINING, mAttemptsRemaining); @@ -119,4 +114,20 @@ private static String asStatus(@Nullable String stringStatus) { return null; } + + @Override + public boolean equals(@Nullable Object obj) { + return this == obj || (obj instanceof SourceCodeVerification + && typedEquals((SourceCodeVerification) obj)); + } + + private boolean typedEquals(@NonNull SourceCodeVerification sourceCodeVerification) { + return mAttemptsRemaining == sourceCodeVerification.mAttemptsRemaining + && ObjectUtils.equals(mStatus, sourceCodeVerification.mStatus); + } + + @Override + public int hashCode() { + return ObjectUtils.hash(mAttemptsRemaining, mStatus); + } } diff --git a/stripe/src/main/java/com/stripe/android/model/SourceOwner.java b/stripe/src/main/java/com/stripe/android/model/SourceOwner.java index 47e6f4534df..95319fc189e 100644 --- a/stripe/src/main/java/com/stripe/android/model/SourceOwner.java +++ b/stripe/src/main/java/com/stripe/android/model/SourceOwner.java @@ -3,9 +3,12 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.stripe.android.utils.ObjectUtils; + import org.json.JSONException; import org.json.JSONObject; +import java.util.AbstractMap; import java.util.HashMap; import java.util.Map; @@ -29,24 +32,24 @@ public class SourceOwner extends StripeJsonModel { private static final String FIELD_VERIFIED_NAME = VERIFIED + FIELD_NAME; private static final String FIELD_VERIFIED_PHONE = VERIFIED + FIELD_PHONE; - private Address mAddress; - private String mEmail; - private String mName; - private String mPhone; - private Address mVerifiedAddress; - private String mVerifiedEmail; - private String mVerifiedName; - private String mVerifiedPhone; - - SourceOwner( - Address address, - String email, - String name, - String phone, - Address verifiedAddress, - String verifiedEmail, - String verifiedName, - String verifiedPhone) { + @Nullable private Address mAddress; + @Nullable private String mEmail; + @Nullable private String mName; + @Nullable private String mPhone; + @Nullable private Address mVerifiedAddress; + @Nullable private String mVerifiedEmail; + @Nullable private String mVerifiedName; + @Nullable private String mVerifiedPhone; + + private SourceOwner( + @Nullable Address address, + @Nullable String email, + @Nullable String name, + @Nullable String phone, + @Nullable Address verifiedAddress, + @Nullable String verifiedEmail, + @Nullable String verifiedName, + @Nullable String verifiedPhone) { mAddress = address; mEmail = email; mName = name; @@ -57,34 +60,42 @@ public class SourceOwner extends StripeJsonModel { mVerifiedPhone = verifiedPhone; } + @Nullable public Address getAddress() { return mAddress; } + @Nullable public String getEmail() { return mEmail; } + @Nullable public String getName() { return mName; } + @Nullable public String getPhone() { return mPhone; } + @Nullable public Address getVerifiedAddress() { return mVerifiedAddress; } + @Nullable public String getVerifiedEmail() { return mVerifiedEmail; } + @Nullable public String getVerifiedName() { return mVerifiedName; } + @Nullable public String getVerifiedPhone() { return mVerifiedPhone; } @@ -124,29 +135,29 @@ void setVerifiedPhone(String verifiedPhone) { @NonNull @Override public Map toMap() { - Map hashMap = new HashMap<>(); + final AbstractMap map = new HashMap<>(); if (mAddress != null) { - hashMap.put(FIELD_ADDRESS, mAddress.toMap()); + map.put(FIELD_ADDRESS, mAddress.toMap()); } - hashMap.put(FIELD_EMAIL, mEmail); - hashMap.put(FIELD_NAME, mName); - hashMap.put(FIELD_PHONE, mPhone); + map.put(FIELD_EMAIL, mEmail); + map.put(FIELD_NAME, mName); + map.put(FIELD_PHONE, mPhone); if (mVerifiedAddress != null) { - hashMap.put(FIELD_VERIFIED_ADDRESS, mVerifiedAddress.toMap()); + map.put(FIELD_VERIFIED_ADDRESS, mVerifiedAddress.toMap()); } - hashMap.put(FIELD_VERIFIED_EMAIL, mVerifiedEmail); - hashMap.put(FIELD_VERIFIED_NAME, mVerifiedName); - hashMap.put(FIELD_VERIFIED_PHONE, mVerifiedPhone); - removeNullAndEmptyParams(hashMap); - return hashMap; + map.put(FIELD_VERIFIED_EMAIL, mVerifiedEmail); + map.put(FIELD_VERIFIED_NAME, mVerifiedName); + map.put(FIELD_VERIFIED_PHONE, mVerifiedPhone); + removeNullAndEmptyParams(map); + return map; } @NonNull @Override public JSONObject toJson() { - JSONObject jsonObject = new JSONObject(); - JSONObject jsonAddressObject = mAddress == null ? null : mAddress.toJson(); - JSONObject jsonVerifiedAddressObject = mVerifiedAddress == null + final JSONObject jsonObject = new JSONObject(); + final JSONObject jsonAddressObject = mAddress == null ? null : mAddress.toJson(); + final JSONObject jsonVerifiedAddressObject = mVerifiedAddress == null ? null : mVerifiedAddress.toJson(); try { @@ -182,30 +193,29 @@ public static SourceOwner fromJson(@Nullable JSONObject jsonObject) { return null; } - Address address = null; - String email; - String name; - String phone; - Address verifiedAddress = null; - String verifiedEmail; - String verifiedName; - String verifiedPhone; - - JSONObject addressObject = jsonObject.optJSONObject(FIELD_ADDRESS); - if (addressObject != null) { - address = Address.fromJson(addressObject); + final Address address; + final JSONObject addressJsonOpt = jsonObject.optJSONObject(FIELD_ADDRESS); + if (addressJsonOpt != null) { + address = Address.fromJson(addressJsonOpt); + } else { + address = null; } - email = optString(jsonObject, FIELD_EMAIL); - name = optString(jsonObject, FIELD_NAME); - phone = optString(jsonObject, FIELD_PHONE); - JSONObject vAddressObject = jsonObject.optJSONObject(FIELD_VERIFIED_ADDRESS); - if (vAddressObject != null) { - verifiedAddress = Address.fromJson(vAddressObject); + final String email = optString(jsonObject, FIELD_EMAIL); + final String name = optString(jsonObject, FIELD_NAME); + final String phone = optString(jsonObject, FIELD_PHONE); + + final Address verifiedAddress; + final JSONObject verifiedAddressJsonOpt = jsonObject.optJSONObject(FIELD_VERIFIED_ADDRESS); + if (verifiedAddressJsonOpt != null) { + verifiedAddress = Address.fromJson(verifiedAddressJsonOpt); + } else { + verifiedAddress = null; } - verifiedEmail = optString(jsonObject, FIELD_VERIFIED_EMAIL); - verifiedName = optString(jsonObject, FIELD_VERIFIED_NAME); - verifiedPhone = optString(jsonObject, FIELD_VERIFIED_PHONE); + + final String verifiedEmail = optString(jsonObject, FIELD_VERIFIED_EMAIL); + final String verifiedName = optString(jsonObject, FIELD_VERIFIED_NAME); + final String verifiedPhone = optString(jsonObject, FIELD_VERIFIED_PHONE); return new SourceOwner( address, @@ -217,4 +227,26 @@ public static SourceOwner fromJson(@Nullable JSONObject jsonObject) { verifiedName, verifiedPhone); } + + @Override + public boolean equals(Object obj) { + return this == obj || (obj instanceof SourceOwner && typedEquals((SourceOwner) obj)); + } + + private boolean typedEquals(@NonNull SourceOwner sourceOwner) { + return ObjectUtils.equals(mAddress, sourceOwner.mAddress) + && ObjectUtils.equals(mEmail, sourceOwner.mEmail) + && ObjectUtils.equals(mName, sourceOwner.mName) + && ObjectUtils.equals(mPhone, sourceOwner.mPhone) + && ObjectUtils.equals(mVerifiedAddress, sourceOwner.mVerifiedAddress) + && ObjectUtils.equals(mVerifiedEmail, sourceOwner.mVerifiedEmail) + && ObjectUtils.equals(mVerifiedName, sourceOwner.mVerifiedName) + && ObjectUtils.equals(mVerifiedPhone, sourceOwner.mVerifiedPhone); + } + + @Override + public int hashCode() { + return ObjectUtils.hash(mAddress, mEmail, mName, mPhone, mVerifiedAddress, mVerifiedEmail, + mVerifiedName, mVerifiedPhone); + } } diff --git a/stripe/src/main/java/com/stripe/android/model/SourceParams.java b/stripe/src/main/java/com/stripe/android/model/SourceParams.java index 3806a54dd48..0b87e6d303f 100644 --- a/stripe/src/main/java/com/stripe/android/model/SourceParams.java +++ b/stripe/src/main/java/com/stripe/android/model/SourceParams.java @@ -785,9 +785,6 @@ public SourceParams setType(@Source.SourceType String type) { */ public SourceParams setTypeRaw(@NonNull String typeRaw) { mType = Source.asSourceType(typeRaw); - if (mType == null) { - mType = Source.UNKNOWN; - } mTypeRaw = typeRaw; return this; } diff --git a/stripe/src/main/java/com/stripe/android/model/SourceReceiver.java b/stripe/src/main/java/com/stripe/android/model/SourceReceiver.java index 03c6fd3c30f..e9372176e6f 100644 --- a/stripe/src/main/java/com/stripe/android/model/SourceReceiver.java +++ b/stripe/src/main/java/com/stripe/android/model/SourceReceiver.java @@ -4,6 +4,7 @@ import androidx.annotation.Nullable; import com.stripe.android.StripeTextUtils; +import com.stripe.android.utils.ObjectUtils; import org.json.JSONException; import org.json.JSONObject; @@ -23,26 +24,27 @@ public class SourceReceiver extends StripeJsonModel { private static final String FIELD_AMOUNT_RETURNED = "amount_returned"; // This is not to be confused with the Address object - private String mAddress; + @Nullable private String mAddress; private long mAmountCharged; private long mAmountReceived; private long mAmountReturned; - SourceReceiver(String address, - long amountCharged, - long amountReceived, - long amountReturned) { + private SourceReceiver(@Nullable String address, + long amountCharged, + long amountReceived, + long amountReturned) { mAddress = address; mAmountCharged = amountCharged; mAmountReceived = amountReceived; mAmountReturned = amountReturned; } + @Nullable public String getAddress() { return mAddress; } - public void setAddress(String address) { + public void setAddress(@Nullable String address) { mAddress = address; } @@ -73,21 +75,21 @@ public void setAmountReturned(long amountReturned) { @NonNull @Override public Map toMap() { - Map hashMap = new HashMap<>(); + final Map map = new HashMap<>(); if (!StripeTextUtils.isBlank(mAddress)) { - hashMap.put(FIELD_ADDRESS, mAddress); + map.put(FIELD_ADDRESS, mAddress); } - hashMap.put(FIELD_ADDRESS, mAddress); - hashMap.put(FIELD_AMOUNT_CHARGED, mAmountCharged); - hashMap.put(FIELD_AMOUNT_RECEIVED, mAmountReceived); - hashMap.put(FIELD_AMOUNT_RETURNED, mAmountReturned); - return hashMap; + map.put(FIELD_ADDRESS, mAddress); + map.put(FIELD_AMOUNT_CHARGED, mAmountCharged); + map.put(FIELD_AMOUNT_RECEIVED, mAmountReceived); + map.put(FIELD_AMOUNT_RETURNED, mAmountReturned); + return map; } @NonNull @Override public JSONObject toJson() { - JSONObject jsonObject = new JSONObject(); + final JSONObject jsonObject = new JSONObject(); StripeJsonUtils.putStringIfNotNull(jsonObject, FIELD_ADDRESS, mAddress); try { jsonObject.put(FIELD_AMOUNT_CHARGED, mAmountCharged); @@ -102,8 +104,7 @@ public JSONObject toJson() { @Nullable public static SourceReceiver fromString(@Nullable String jsonString) { try { - JSONObject jsonObject = new JSONObject(jsonString); - return fromJson(jsonObject); + return fromJson(new JSONObject(jsonString)); } catch (JSONException ignored) { return null; } @@ -115,10 +116,27 @@ public static SourceReceiver fromJson(@Nullable JSONObject jsonObject) { return null; } - String address = StripeJsonUtils.optString(jsonObject, FIELD_ADDRESS); - return new SourceReceiver(address, + return new SourceReceiver( + StripeJsonUtils.optString(jsonObject, FIELD_ADDRESS), jsonObject.optLong(FIELD_AMOUNT_CHARGED), jsonObject.optLong(FIELD_AMOUNT_RECEIVED), jsonObject.optLong(FIELD_AMOUNT_RETURNED)); } + + @Override + public boolean equals(Object obj) { + return this == obj || (obj instanceof SourceReceiver && typedEquals((SourceReceiver) obj)); + } + + private boolean typedEquals(@NonNull SourceReceiver sourceReceiver) { + return ObjectUtils.equals(mAddress, sourceReceiver.mAddress) + && mAmountCharged == sourceReceiver.mAmountCharged + && mAmountReceived == sourceReceiver.mAmountReceived + && mAmountReturned == sourceReceiver.mAmountReturned; + } + + @Override + public int hashCode() { + return ObjectUtils.hash(mAddress, mAmountCharged, mAmountReceived, mAmountReturned); + } } diff --git a/stripe/src/main/java/com/stripe/android/model/SourceRedirect.java b/stripe/src/main/java/com/stripe/android/model/SourceRedirect.java index a55a1cde730..3170ffd209a 100644 --- a/stripe/src/main/java/com/stripe/android/model/SourceRedirect.java +++ b/stripe/src/main/java/com/stripe/android/model/SourceRedirect.java @@ -4,11 +4,14 @@ import androidx.annotation.Nullable; import androidx.annotation.StringDef; +import com.stripe.android.utils.ObjectUtils; + import org.json.JSONException; import org.json.JSONObject; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.AbstractMap; import java.util.HashMap; import java.util.Map; @@ -33,60 +36,64 @@ public class SourceRedirect extends StripeJsonModel { public static final String SUCCEEDED = "succeeded"; public static final String FAILED = "failed"; - static final String FIELD_RETURN_URL = "return_url"; - static final String FIELD_STATUS = "status"; - static final String FIELD_URL = "url"; + private static final String FIELD_RETURN_URL = "return_url"; + private static final String FIELD_STATUS = "status"; + private static final String FIELD_URL = "url"; - private String mReturnUrl; - private @Status String mStatus; - private String mUrl; + @Nullable private String mReturnUrl; + @Nullable @Status private String mStatus; + @Nullable private String mUrl; - SourceRedirect(String returnUrl, @Status String status, String url) { + private SourceRedirect(@Nullable String returnUrl, @Status @Nullable String status, + @Nullable String url) { mReturnUrl = returnUrl; mStatus = status; mUrl = url; } + @Nullable public String getReturnUrl() { return mReturnUrl; } - public void setReturnUrl(String returnUrl) { + public void setReturnUrl(@Nullable String returnUrl) { mReturnUrl = returnUrl; } + @Nullable @Status public String getStatus() { return mStatus; } - public void setStatus(@Status String status) { + public void setStatus(@Nullable @Status String status) { mStatus = status; } + @Nullable public String getUrl() { return mUrl; } - public void setUrl(String url) { + public void setUrl(@Nullable String url) { mUrl = url; } @NonNull @Override public Map toMap() { - Map hashMap = new HashMap<>(); - hashMap.put(FIELD_RETURN_URL, mReturnUrl); - hashMap.put(FIELD_STATUS, mStatus); - hashMap.put(FIELD_URL, mUrl); - removeNullAndEmptyParams(hashMap); - return hashMap; + final AbstractMap map = new HashMap<>(); + map.put(FIELD_RETURN_URL, mReturnUrl); + map.put(FIELD_STATUS, mStatus); + map.put(FIELD_URL, mUrl); + removeNullAndEmptyParams(map); + return map; } @NonNull @Override public JSONObject toJson() { - JSONObject jsonObject = new JSONObject(); + final JSONObject jsonObject = new JSONObject(); putStringIfNotNull(jsonObject, FIELD_RETURN_URL, mReturnUrl); putStringIfNotNull(jsonObject, FIELD_STATUS, mStatus); putStringIfNotNull(jsonObject, FIELD_URL, mUrl); @@ -96,8 +103,7 @@ public JSONObject toJson() { @Nullable public static SourceRedirect fromString(@Nullable String jsonString) { try { - JSONObject jsonObject = new JSONObject(jsonString); - return fromJson(jsonObject); + return fromJson(new JSONObject(jsonString)); } catch (JSONException ignored) { return null; } @@ -109,9 +115,9 @@ public static SourceRedirect fromJson(@Nullable JSONObject jsonObject) { return null; } - String returnUrl = optString(jsonObject, FIELD_RETURN_URL); - @Status String status = asStatus(optString(jsonObject, FIELD_STATUS)); - String url = optString(jsonObject, FIELD_URL); + final String returnUrl = optString(jsonObject, FIELD_RETURN_URL); + @Status final String status = asStatus(optString(jsonObject, FIELD_STATUS)); + final String url = optString(jsonObject, FIELD_URL); return new SourceRedirect(returnUrl, status, url); } @@ -128,4 +134,20 @@ private static String asStatus(@Nullable String stringStatus) { return null; } + + @Override + public boolean equals(@Nullable Object obj) { + return this == obj || (obj instanceof SourceRedirect && typedEquals((SourceRedirect) obj)); + } + + private boolean typedEquals(@NonNull SourceRedirect sourceRedirect) { + return ObjectUtils.equals(mReturnUrl, sourceRedirect.mReturnUrl) + && ObjectUtils.equals(mStatus, sourceRedirect.mStatus) + && ObjectUtils.equals(mUrl, sourceRedirect.mUrl); + } + + @Override + public int hashCode() { + return ObjectUtils.hash(mReturnUrl, mStatus, mUrl); + } } diff --git a/stripe/src/main/java/com/stripe/android/model/SourceSepaDebitData.java b/stripe/src/main/java/com/stripe/android/model/SourceSepaDebitData.java index 10d2913bea2..3ef72284c0e 100644 --- a/stripe/src/main/java/com/stripe/android/model/SourceSepaDebitData.java +++ b/stripe/src/main/java/com/stripe/android/model/SourceSepaDebitData.java @@ -10,6 +10,7 @@ import org.json.JSONException; import org.json.JSONObject; +import java.util.AbstractMap; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; @@ -50,7 +51,7 @@ public class SourceSepaDebitData extends StripeSourceTypeModel { @Nullable private final String mMandateUrl; private SourceSepaDebitData(@NonNull Builder builder) { - super(STANDARD_FIELDS); + super(builder); mBankCode = builder.mBankCode; mBranchCode = builder.mBranchCode; mCountry = builder.mCountry; @@ -66,22 +67,21 @@ public static SourceSepaDebitData fromJson(JSONObject jsonObject) { return null; } - final SourceSepaDebitData sepaData = new SourceSepaDebitData.Builder() + final Builder sepaData = new Builder() .setBankCode(optString(jsonObject, FIELD_BANK_CODE)) .setBranchCode(optString(jsonObject, FIELD_BRANCH_CODE)) .setCountry(optString(jsonObject, FIELD_COUNTRY)) .setFingerPrint(optString(jsonObject, FIELD_FINGERPRINT)) .setLast4(optString(jsonObject, FIELD_LAST4)) .setMandateReference(optString(jsonObject, FIELD_MANDATE_REFERENCE)) - .setMandateUrl(optString(jsonObject, FIELD_MANDATE_URL)) - .build(); + .setMandateUrl(optString(jsonObject, FIELD_MANDATE_URL)); final Map nonStandardFields = - jsonObjectToMapWithoutKeys(jsonObject, sepaData.mStandardFields); + jsonObjectToMapWithoutKeys(jsonObject, STANDARD_FIELDS); if (nonStandardFields != null) { sepaData.setAdditionalFields(nonStandardFields); } - return sepaData; + return sepaData.build(); } @Nullable @@ -122,7 +122,7 @@ public String getMandateUrl() { @NonNull @Override public JSONObject toJson() { - final JSONObject jsonObject = new JSONObject(); + final JSONObject jsonObject = super.toJson(); putStringIfNotNull(jsonObject, FIELD_BANK_CODE, mBankCode); putStringIfNotNull(jsonObject, FIELD_BRANCH_CODE, mBranchCode); putStringIfNotNull(jsonObject, FIELD_COUNTRY, mCountry); @@ -130,26 +130,22 @@ public JSONObject toJson() { putStringIfNotNull(jsonObject, FIELD_LAST4, mLast4); putStringIfNotNull(jsonObject, FIELD_MANDATE_REFERENCE, mMandateReference); putStringIfNotNull(jsonObject, FIELD_MANDATE_URL, mMandateUrl); - - putAdditionalFieldsIntoJsonObject(jsonObject, mAdditionalFields); return jsonObject; } @NonNull @Override public Map toMap() { - final Map objectMap = new HashMap<>(); - objectMap.put(FIELD_BANK_CODE, mBankCode); - objectMap.put(FIELD_BRANCH_CODE, mBranchCode); - objectMap.put(FIELD_COUNTRY, mCountry); - objectMap.put(FIELD_FINGERPRINT, mFingerPrint); - objectMap.put(FIELD_LAST4, mLast4); - objectMap.put(FIELD_MANDATE_REFERENCE, mMandateReference); - objectMap.put(FIELD_MANDATE_URL, mMandateUrl); - - putAdditionalFieldsIntoMap(objectMap, mAdditionalFields); - StripeNetworkUtils.removeNullAndEmptyParams(objectMap); - return objectMap; + final AbstractMap map = new HashMap<>(super.toMap()); + map.put(FIELD_BANK_CODE, mBankCode); + map.put(FIELD_BRANCH_CODE, mBranchCode); + map.put(FIELD_COUNTRY, mCountry); + map.put(FIELD_FINGERPRINT, mFingerPrint); + map.put(FIELD_LAST4, mLast4); + map.put(FIELD_MANDATE_REFERENCE, mMandateReference); + map.put(FIELD_MANDATE_URL, mMandateUrl); + StripeNetworkUtils.removeNullAndEmptyParams(map); + return map; } @Nullable @@ -169,7 +165,8 @@ public boolean equals(Object obj) { } private boolean typedEquals(@NonNull SourceSepaDebitData obj) { - return ObjectUtils.equals(mBankCode, obj.mBankCode) + return super.typedEquals(obj) + && ObjectUtils.equals(mBankCode, obj.mBankCode) && ObjectUtils.equals(mBranchCode, obj.mBranchCode) && ObjectUtils.equals(mCountry, obj.mCountry) && ObjectUtils.equals(mFingerPrint, obj.mFingerPrint) @@ -180,11 +177,11 @@ private boolean typedEquals(@NonNull SourceSepaDebitData obj) { @Override public int hashCode() { - return ObjectUtils.hash(mBankCode, mBranchCode, mCountry, mFingerPrint, mLast4, - mMandateReference, mMandateUrl); + return ObjectUtils.hash(super.hashCode(), mBankCode, mBranchCode, mCountry, mFingerPrint, + mLast4, mMandateReference, mMandateUrl); } - public static final class Builder { + public static final class Builder extends BaseBuilder { private String mBankCode; private String mBranchCode; private String mCountry; @@ -194,43 +191,43 @@ public static final class Builder { private String mMandateUrl; @NonNull - public Builder setBankCode(String bankCode) { + Builder setBankCode(String bankCode) { mBankCode = bankCode; return this; } @NonNull - public Builder setBranchCode(String branchCode) { + Builder setBranchCode(String branchCode) { mBranchCode = branchCode; return this; } @NonNull - public Builder setCountry(String country) { + Builder setCountry(String country) { mCountry = country; return this; } @NonNull - public Builder setFingerPrint(String fingerPrint) { + Builder setFingerPrint(String fingerPrint) { mFingerPrint = fingerPrint; return this; } @NonNull - public Builder setLast4(String last4) { + Builder setLast4(String last4) { mLast4 = last4; return this; } @NonNull - public Builder setMandateReference(String mandateReference) { + Builder setMandateReference(String mandateReference) { mMandateReference = mandateReference; return this; } @NonNull - public Builder setMandateUrl(String mandateUrl) { + Builder setMandateUrl(String mandateUrl) { mMandateUrl = mandateUrl; return this; } @@ -240,5 +237,4 @@ public SourceSepaDebitData build() { return new SourceSepaDebitData(this); } } - } diff --git a/stripe/src/main/java/com/stripe/android/model/StripeJsonModel.java b/stripe/src/main/java/com/stripe/android/model/StripeJsonModel.java index 99f8d10c6cf..ff75325c093 100644 --- a/stripe/src/main/java/com/stripe/android/model/StripeJsonModel.java +++ b/stripe/src/main/java/com/stripe/android/model/StripeJsonModel.java @@ -23,26 +23,6 @@ public abstract class StripeJsonModel { @NonNull public abstract JSONObject toJson(); - @Override - public String toString() { - return this.toJson().toString(); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof StripeJsonModel)) { - return false; - } - - StripeJsonModel otherModel = (StripeJsonModel) obj; - return this.toString().equals(otherModel.toString()); - } - - @Override - public int hashCode() { - return this.toString().hashCode(); - } - static void putStripeJsonModelMapIfNotNull( @NonNull Map upperLevelMap, @NonNull @Size(min = 1) String key, diff --git a/stripe/src/main/java/com/stripe/android/model/StripeSourceTypeModel.java b/stripe/src/main/java/com/stripe/android/model/StripeSourceTypeModel.java index f239e492866..1e8d921dbee 100644 --- a/stripe/src/main/java/com/stripe/android/model/StripeSourceTypeModel.java +++ b/stripe/src/main/java/com/stripe/android/model/StripeSourceTypeModel.java @@ -1,8 +1,11 @@ package com.stripe.android.model; +import androidx.annotation.CallSuper; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.stripe.android.utils.ObjectUtils; + import org.json.JSONException; import org.json.JSONObject; @@ -13,14 +16,12 @@ import java.util.Set; public abstract class StripeSourceTypeModel extends StripeJsonModel { + @NonNull private final Map mAdditionalFields; private static final String NULL = "null"; - Map mAdditionalFields; - @NonNull final Set mStandardFields; - - StripeSourceTypeModel(@NonNull Set standardFields) { - mStandardFields = standardFields; - mAdditionalFields = new HashMap<>(); + StripeSourceTypeModel(@NonNull BaseBuilder builder) { + mAdditionalFields = builder.mAdditionalFields != null ? + builder.mAdditionalFields : new HashMap(); } @NonNull @@ -28,8 +29,18 @@ public Map getAdditionalFields() { return mAdditionalFields; } - void setAdditionalFields(@NonNull Map additionalFields) { - mAdditionalFields = additionalFields; + @NonNull + @Override + public Map toMap() { + return new HashMap<>(mAdditionalFields); + } + + @NonNull + @Override + public JSONObject toJson() { + final JSONObject jsonObject = new JSONObject(); + putAdditionalFieldsIntoJsonObject(jsonObject, mAdditionalFields); + return jsonObject; } /** @@ -48,9 +59,9 @@ static Map jsonObjectToMapWithoutKeys( return null; } - Set keysToOmit = omitKeys == null ? new HashSet() : omitKeys; - Map map = new HashMap<>(); - Iterator keyIterator = jsonObject.keys(); + final Set keysToOmit = omitKeys == null ? new HashSet() : omitKeys; + final Map map = new HashMap<>(); + final Iterator keyIterator = jsonObject.keys(); while (keyIterator.hasNext()) { String key = keyIterator.next(); Object value = jsonObject.opt(key); @@ -107,8 +118,32 @@ static void putAdditionalFieldsIntoMap( return; } - for (String key : additionalFields.keySet()) { - map.put(key, additionalFields.get(key)); + map.putAll(additionalFields); + } + + @Override + public boolean equals(@Nullable Object obj) { + return this == obj || (obj instanceof StripeSourceTypeModel + && typedEquals((StripeSourceTypeModel) obj)); + } + + @CallSuper + boolean typedEquals(@NonNull StripeSourceTypeModel model) { + return ObjectUtils.equals(mAdditionalFields, model.mAdditionalFields); + } + + @Override + public int hashCode() { + return ObjectUtils.hash(mAdditionalFields); + } + + abstract static class BaseBuilder { + @Nullable private Map mAdditionalFields; + + @NonNull + BaseBuilder setAdditionalFields(@NonNull Map additionalFields) { + mAdditionalFields = additionalFields; + return this; } } } diff --git a/stripe/src/main/java/com/stripe/android/model/Token.java b/stripe/src/main/java/com/stripe/android/model/Token.java index 228df2937cd..6671dc9c5e3 100644 --- a/stripe/src/main/java/com/stripe/android/model/Token.java +++ b/stripe/src/main/java/com/stripe/android/model/Token.java @@ -2,9 +2,12 @@ import android.text.TextUtils; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringDef; +import com.stripe.android.utils.ObjectUtils; + import org.json.JSONException; import org.json.JSONObject; @@ -38,24 +41,25 @@ public class Token implements StripePaymentSource { private static final String FIELD_TYPE = "type"; private static final String FIELD_USED = "used"; - private final String mId; - private final String mType; - private final Date mCreated; + + @NonNull private final String mId; + @NonNull private final String mType; + @NonNull private final Date mCreated; private final boolean mLivemode; private final boolean mUsed; - private final BankAccount mBankAccount; - private final Card mCard; + @Nullable private final BankAccount mBankAccount; + @Nullable private final Card mCard; /** * Constructor that should not be invoked in your code. This is used by Stripe to * create tokens using a Stripe API response. */ public Token( - String id, + @NonNull String id, boolean livemode, - Date created, - Boolean used, - Card card) { + @NonNull Date created, + boolean used, + @Nullable Card card) { mId = id; mType = TYPE_CARD; mCreated = created; @@ -70,11 +74,11 @@ public Token( * create tokens using a Stripe API response. */ public Token( - String id, + @NonNull String id, boolean livemode, - Date created, - Boolean used, - BankAccount bankAccount) { + @NonNull Date created, + boolean used, + @NonNull BankAccount bankAccount) { mId = id; mType = TYPE_BANK_ACCOUNT; mCreated = created; @@ -89,11 +93,11 @@ public Token( * create tokens using a Stripe API response. */ public Token( - String id, - String type, + @NonNull String id, + @NonNull String type, boolean livemode, - Date created, - Boolean used + @NonNull Date created, + boolean used ) { mId = id; mType = type; @@ -107,6 +111,7 @@ public Token( /*** * @return the {@link Date} this token was created */ + @NonNull public Date getCreated() { return mCreated; } @@ -114,6 +119,7 @@ public Date getCreated() { /** * @return the {@link #mId} of this token */ + @NonNull @Override public String getId() { return mId; @@ -137,6 +143,7 @@ public boolean getUsed() { /** * @return Get the {@link TokenType} of this token. */ + @NonNull @TokenType public String getType() { return mType; @@ -145,6 +152,7 @@ public String getType() { /** * @return the {@link Card} for this token */ + @Nullable public Card getCard() { return mCard; } @@ -152,10 +160,31 @@ public Card getCard() { /** * @return the {@link BankAccount} for this token */ + @Nullable public BankAccount getBankAccount() { return mBankAccount; } + @Override + public int hashCode() { + return ObjectUtils.hash(mId, mType, mCreated, mLivemode, mUsed, mBankAccount, mCard); + } + + @Override + public boolean equals(@Nullable Object obj) { + return super.equals(obj) || (obj instanceof Token && typedEquals((Token) obj)); + } + + private boolean typedEquals(@NonNull Token token) { + return ObjectUtils.equals(mId, token.mId) + && ObjectUtils.equals(mType, token.mType) + && ObjectUtils.equals(mCreated, token.mCreated) + && mLivemode == token.mLivemode + && mUsed == token.mUsed + && ObjectUtils.equals(mBankAccount, token.mBankAccount) + && ObjectUtils.equals(mCard, token.mCard); + } + @Nullable public static Token fromString(@Nullable String jsonString) { if (jsonString == null) { @@ -176,14 +205,16 @@ public static Token fromJson(@Nullable JSONObject jsonObject) { } final String tokenId = StripeJsonUtils.optString(jsonObject, FIELD_ID); final Long createdTimeStamp = StripeJsonUtils.optLong(jsonObject, FIELD_CREATED); - final Boolean liveMode = StripeJsonUtils.optBoolean(jsonObject, FIELD_LIVEMODE); + final Boolean liveModeOpt = StripeJsonUtils.optBoolean(jsonObject, FIELD_LIVEMODE); @TokenType final String tokenType = asTokenType(StripeJsonUtils.optString(jsonObject, FIELD_TYPE)); - final Boolean used = StripeJsonUtils.optBoolean(jsonObject, FIELD_USED); - - if (tokenId == null || createdTimeStamp == null || liveMode == null) { + final Boolean usedOpt = StripeJsonUtils.optBoolean(jsonObject, FIELD_USED); + if (tokenId == null || createdTimeStamp == null || liveModeOpt == null) { return null; } + + final boolean used = Boolean.TRUE.equals(usedOpt); + final boolean liveMode = Boolean.TRUE.equals(liveModeOpt); final Date date = new Date(createdTimeStamp * 1000); final Token token; @@ -219,7 +250,7 @@ public static Token fromJson(@Nullable JSONObject jsonObject) { */ @Nullable @TokenType - static String asTokenType(@Nullable String possibleTokenType) { + private static String asTokenType(@Nullable String possibleTokenType) { if (possibleTokenType == null || TextUtils.isEmpty(possibleTokenType.trim())) { return null; } diff --git a/stripe/src/main/java/com/stripe/android/view/AddSourceActivity.java b/stripe/src/main/java/com/stripe/android/view/AddSourceActivity.java index eae5ea37069..b6b40c921a9 100644 --- a/stripe/src/main/java/com/stripe/android/view/AddSourceActivity.java +++ b/stripe/src/main/java/com/stripe/android/view/AddSourceActivity.java @@ -218,7 +218,7 @@ private void logToProxyIf(@NonNull String logToken, boolean condition) { private void finishWithSource(@NonNull Source source) { setCommunicatingProgress(false); Intent intent = new Intent(); - intent.putExtra(EXTRA_NEW_SOURCE, source.toString()); + intent.putExtra(EXTRA_NEW_SOURCE, source.toJson().toString()); setResult(RESULT_OK, intent); finish(); } diff --git a/stripe/src/main/java/com/stripe/android/view/PaymentMethodsActivity.java b/stripe/src/main/java/com/stripe/android/view/PaymentMethodsActivity.java index 020cfce3230..0e42d0e372c 100644 --- a/stripe/src/main/java/com/stripe/android/view/PaymentMethodsActivity.java +++ b/stripe/src/main/java/com/stripe/android/view/PaymentMethodsActivity.java @@ -288,10 +288,10 @@ private void cancelAndFinish() { } private void finishWithSelection(String selectedSourceId) { - CustomerSource customerSource = mCustomer.getSourceById(selectedSourceId); + final CustomerSource customerSource = mCustomer.getSourceById(selectedSourceId); if (customerSource != null) { Intent intent = new Intent(); - intent.putExtra(EXTRA_SELECTED_PAYMENT, customerSource.toString()); + intent.putExtra(EXTRA_SELECTED_PAYMENT, customerSource.toJson().toString()); setResult(RESULT_OK, intent); } else { setResult(RESULT_CANCELED); diff --git a/stripe/src/test/java/com/stripe/android/EphemeralKeyManagerTest.java b/stripe/src/test/java/com/stripe/android/EphemeralKeyManagerTest.java index 934756c04b2..d06b8665588 100644 --- a/stripe/src/test/java/com/stripe/android/EphemeralKeyManagerTest.java +++ b/stripe/src/test/java/com/stripe/android/EphemeralKeyManagerTest.java @@ -1,6 +1,7 @@ package com.stripe.android; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.stripe.android.testharness.TestEphemeralKeyProvider; @@ -9,6 +10,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; @@ -28,7 +31,6 @@ import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -52,12 +54,14 @@ public class EphemeralKeyManagerTest { "}"; private static final long TEST_SECONDS_BUFFER = 10L; + private static final long DEFAULT_EXPIRES = 1501199335L; - @Mock EphemeralKeyManager.KeyManagerListener mKeyManagerListener; + @Mock private EphemeralKeyManager.KeyManagerListener mKeyManagerListener; + @Captor private ArgumentCaptor> mArgumentCaptor; private TestEphemeralKeyProvider mTestEphemeralKeyProvider; - @NonNull + @Nullable private CustomerEphemeralKey getCustomerEphemeralKey(@NonNull String key) { try { return CustomerEphemeralKey.fromString(key); @@ -96,18 +100,13 @@ public void shouldRefreshKey_whenKeyIsNullAndTimeIsInPast_returnsTrue() { @Test public void shouldRefreshKey_whenKeyExpiryIsAfterBufferFromPresent_returnsFalse() { - Calendar fixedCalendar = Calendar.getInstance(); - CustomerEphemeralKey key = getCustomerEphemeralKey(FIRST_SAMPLE_KEY_RAW); - assertNotNull(key); - - long expiryTimeInSeconds = key.getExpires(); - long sufficientBufferTime = 2*TEST_SECONDS_BUFFER; - key.setExpires(expiryTimeInSeconds + sufficientBufferTime); + final Calendar fixedCalendar = Calendar.getInstance(); + final long expires = TimeUnit.SECONDS.toMillis(DEFAULT_EXPIRES + 2 * TEST_SECONDS_BUFFER); + final CustomerEphemeralKey key = createEphemeralKey(expires); + fixedCalendar.setTimeInMillis(expires); - long expiryTimeInMilliseconds = TimeUnit.SECONDS.toMillis(expiryTimeInSeconds); - fixedCalendar.setTimeInMillis(expiryTimeInMilliseconds); // If you don't call getTime or getTimeInMillis on a Calendar, none of the updates happen. - assertEquals(expiryTimeInMilliseconds, fixedCalendar.getTimeInMillis()); + assertEquals(expires, fixedCalendar.getTimeInMillis()); assertFalse(EphemeralKeyManager.shouldRefreshKey(key, TEST_SECONDS_BUFFER, fixedCalendar)); @@ -115,14 +114,10 @@ public void shouldRefreshKey_whenKeyExpiryIsAfterBufferFromPresent_returnsFalse( @Test public void shouldRefreshKey_whenKeyExpiryIsInThePast_returnsTrue() { - Calendar fixedCalendar = Calendar.getInstance(); - CustomerEphemeralKey key = getCustomerEphemeralKey(FIRST_SAMPLE_KEY_RAW); - assertNotNull(key); - - long currentTimeInMillis = fixedCalendar.getTimeInMillis(); - long timeAgoInMillis = currentTimeInMillis - 100L; - - key.setExpires(TimeUnit.MILLISECONDS.toSeconds(timeAgoInMillis)); + final Calendar fixedCalendar = Calendar.getInstance(); + final long timeAgoInMillis = fixedCalendar.getTimeInMillis() - 100L; + final CustomerEphemeralKey key = createEphemeralKey( + TimeUnit.MILLISECONDS.toSeconds(timeAgoInMillis)); assertTrue(EphemeralKeyManager.shouldRefreshKey(key, TEST_SECONDS_BUFFER, fixedCalendar)); @@ -147,40 +142,32 @@ public void shouldRefreshKey_whenKeyExpiryIsInFutureButWithinBuffer_returnsTrue( } @Test - @SuppressWarnings("unchecked") public void createKeyManager_updatesEphemeralKey_notifiesListener() { CustomerEphemeralKey testKey = getCustomerEphemeralKey(FIRST_SAMPLE_KEY_RAW); assertNotNull(testKey); mTestEphemeralKeyProvider.setNextRawEphemeralKey(FIRST_SAMPLE_KEY_RAW); - EphemeralKeyManager keyManager = new EphemeralKeyManager( + EphemeralKeyManager keyManager = new EphemeralKeyManager<>( mTestEphemeralKeyProvider, mKeyManagerListener, TEST_SECONDS_BUFFER, null, CustomerEphemeralKey.class); - verify(mKeyManagerListener, times(1)).onKeyUpdate( - any(CustomerEphemeralKey.class), (String) isNull(), (Map) isNull()); + verify(mKeyManagerListener).onKeyUpdate( + ArgumentMatchers.any(), + ArgumentMatchers.isNull(), + ArgumentMatchers.>isNull()); assertNotNull(keyManager.getEphemeralKey()); assertEquals(testKey.getId(), keyManager.getEphemeralKey().getId()); } @Test - @SuppressWarnings("unchecked") public void retrieveEphemeralKey_whenUpdateNecessary_returnsUpdateAndArguments() { - CustomerEphemeralKey testKey = getCustomerEphemeralKey(FIRST_SAMPLE_KEY_RAW); - assertNotNull(testKey); - - Calendar fixedCalendar = Calendar.getInstance(); - - long currentTimeInMillis = fixedCalendar.getTimeInMillis(); - long timeAgoInMillis = currentTimeInMillis - 100L; - - testKey.setExpires(TimeUnit.MILLISECONDS.toSeconds(timeAgoInMillis)); + final Calendar fixedCalendar = Calendar.getInstance(); mTestEphemeralKeyProvider.setNextRawEphemeralKey(FIRST_SAMPLE_KEY_RAW); - EphemeralKeyManager keyManager = new EphemeralKeyManager( + EphemeralKeyManager keyManager = new EphemeralKeyManager<>( mTestEphemeralKeyProvider, mKeyManagerListener, TEST_SECONDS_BUFFER, @@ -199,15 +186,13 @@ public void retrieveEphemeralKey_whenUpdateNecessary_returnsUpdateAndArguments() ArgumentCaptor.forClass(CustomerEphemeralKey.class); ArgumentCaptor stringArgumentCaptor = ArgumentCaptor.forClass(String.class); - ArgumentCaptor> mapArgumentCaptor = - ArgumentCaptor.forClass(Map.class); - verify(mKeyManagerListener, times(1)).onKeyUpdate( + verify(mKeyManagerListener).onKeyUpdate( keyArgumentCaptor.capture(), stringArgumentCaptor.capture(), - mapArgumentCaptor.capture()); + mArgumentCaptor.capture()); - Map capturedMap = mapArgumentCaptor.getValue(); + final Map capturedMap = mArgumentCaptor.getValue(); assertNotNull(capturedMap); assertNotNull(keyArgumentCaptor.getValue()); assertEquals(1, capturedMap.size()); @@ -216,7 +201,6 @@ public void retrieveEphemeralKey_whenUpdateNecessary_returnsUpdateAndArguments() } @Test - @SuppressWarnings("unchecked") public void updateKeyIfNecessary_whenReturnsError_setsExistingKeyToNull() { CustomerEphemeralKey testKey = getCustomerEphemeralKey(FIRST_SAMPLE_KEY_RAW); assertNotNull(testKey); @@ -229,7 +213,7 @@ public void updateKeyIfNecessary_whenReturnsError_setsExistingKeyToNull() { assertEquals(expiryTimeInMillis + 1L, proxyCalendar.getTimeInMillis()); mTestEphemeralKeyProvider.setNextRawEphemeralKey(FIRST_SAMPLE_KEY_RAW); - EphemeralKeyManager keyManager = new EphemeralKeyManager( + EphemeralKeyManager keyManager = new EphemeralKeyManager<>( mTestEphemeralKeyProvider, mKeyManagerListener, TEST_SECONDS_BUFFER, @@ -237,8 +221,10 @@ public void updateKeyIfNecessary_whenReturnsError_setsExistingKeyToNull() { CustomerEphemeralKey.class); // Make sure we're in a good state - verify(mKeyManagerListener, times(1)).onKeyUpdate( - any(CustomerEphemeralKey.class), (String) isNull(), (Map) isNull()); + verify(mKeyManagerListener).onKeyUpdate( + ArgumentMatchers.any(), + ArgumentMatchers.isNull(), + ArgumentMatchers.>isNull()); assertNotNull(keyManager.getEphemeralKey()); // Set up the error @@ -248,7 +234,7 @@ public void updateKeyIfNecessary_whenReturnsError_setsExistingKeyToNull() { // It should be necessary to update because the key is expired. keyManager.retrieveEphemeralKey(null, null); - verify(mKeyManagerListener, times(1)).onKeyError(404, errorMessage); + verify(mKeyManagerListener).onKeyError(404, errorMessage); verifyNoMoreInteractions(mKeyManagerListener); assertNull(keyManager.getEphemeralKey()); } @@ -265,8 +251,10 @@ public void triggerCorrectErrorOnInvalidRawKey() { CustomerEphemeralKey.class); verify(mKeyManagerListener, never()).onKeyUpdate( - (CustomerEphemeralKey) isNull(), (String) isNull(), (Map) isNull()); - verify(mKeyManagerListener, times(1)).onKeyError( + ArgumentMatchers.isNull(), + ArgumentMatchers.isNull(), + ArgumentMatchers.>isNull()); + verify(mKeyManagerListener).onKeyError( HttpURLConnection.HTTP_INTERNAL_ERROR, "EphemeralKeyUpdateListener.onKeyUpdate was passed a value that " + "could not be JSON parsed: [Value Not_a_JSON of type java.lang.String " + @@ -277,7 +265,6 @@ public void triggerCorrectErrorOnInvalidRawKey() { @Test public void triggerCorrectErrorOnInvalidJsonKey() { - mTestEphemeralKeyProvider.setNextRawEphemeralKey("{}"); EphemeralKeyManager keyManager = new EphemeralKeyManager<>( mTestEphemeralKeyProvider, @@ -287,8 +274,10 @@ public void triggerCorrectErrorOnInvalidJsonKey() { CustomerEphemeralKey.class); verify(mKeyManagerListener, never()).onKeyUpdate( - (CustomerEphemeralKey) isNull(), (String) isNull(), (Map) isNull()); - verify(mKeyManagerListener, times(1)).onKeyError( + ArgumentMatchers.isNull(), + ArgumentMatchers.isNull(), + ArgumentMatchers.>isNull()); + verify(mKeyManagerListener).onKeyError( HttpURLConnection.HTTP_INTERNAL_ERROR, "EphemeralKeyUpdateListener.onKeyUpdate was passed a JSON String " + "that was invalid: [Improperly formatted JSON for ephemeral " + @@ -299,7 +288,6 @@ public void triggerCorrectErrorOnInvalidJsonKey() { @Test public void triggerCorrectErrorOnNullKey() { - mTestEphemeralKeyProvider.setNextRawEphemeralKey(null); EphemeralKeyManager keyManager = new EphemeralKeyManager<>( mTestEphemeralKeyProvider, @@ -309,10 +297,18 @@ public void triggerCorrectErrorOnNullKey() { CustomerEphemeralKey.class); verify(mKeyManagerListener, never()).onKeyUpdate( - (CustomerEphemeralKey) isNull(), (String) isNull(), (Map) isNull()); - verify(mKeyManagerListener, times(1)).onKeyError( + ArgumentMatchers.isNull(), + ArgumentMatchers.isNull(), + ArgumentMatchers.>isNull()); + verify(mKeyManagerListener).onKeyError( HttpURLConnection.HTTP_INTERNAL_ERROR, "EphemeralKeyUpdateListener.onKeyUpdate was called with a null value"); assertNull(keyManager.getEphemeralKey()); } + + @NonNull + private CustomerEphemeralKey createEphemeralKey(long expires) { + return new CustomerEphemeralKey(1501199335L, "cus_AQsHpvKfKwJDrF", + expires, "ephkey_123", false, "customer", "", ""); + } } diff --git a/stripe/src/test/java/com/stripe/android/model/AddressTest.java b/stripe/src/test/java/com/stripe/android/model/AddressTest.java index 88e55ad5203..d6b72e3ff84 100644 --- a/stripe/src/test/java/com/stripe/android/model/AddressTest.java +++ b/stripe/src/test/java/com/stripe/android/model/AddressTest.java @@ -2,7 +2,6 @@ import org.json.JSONException; import org.json.JSONObject; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -12,6 +11,7 @@ import static com.stripe.android.testharness.JsonTestUtils.assertJsonEquals; import static com.stripe.android.testharness.JsonTestUtils.assertMapEquals; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; @@ -21,7 +21,7 @@ @RunWith(RobolectricTestRunner.class) public class AddressTest { - static final String EXAMPLE_JSON_ADDRESS = "{" + + static final String JSON_ADDRESS = "{" + "\"city\": \"San Francisco\"," + "\"country\": \"US\",\n" + "\"line1\": \"123 Market St\"," + @@ -30,7 +30,7 @@ public class AddressTest { "\"state\": \"CA\"" + "}"; - private static final Map EXAMPLE_MAP_ADDRESS = new HashMap() {{ + private static final Map MAP_ADDRESS = new HashMap() {{ put("city", "San Francisco"); put("country", "US"); put("line1", "123 Market St"); @@ -39,19 +39,14 @@ public class AddressTest { put("state", "CA"); }}; - private Address mAddress; - - @Before - public void setup() { - mAddress = Address.fromString(EXAMPLE_JSON_ADDRESS); - assertNotNull(mAddress); - } + private static final Address ADDRESS = Address.fromString(JSON_ADDRESS); @Test public void fromJsonString_backToJson_createsIdenticalElement() { + assertNotNull(ADDRESS); try { - JSONObject rawConversion = new JSONObject(EXAMPLE_JSON_ADDRESS); - assertJsonEquals(rawConversion, mAddress.toJson()); + JSONObject rawConversion = new JSONObject(JSON_ADDRESS); + assertJsonEquals(rawConversion, ADDRESS.toJson()); } catch (JSONException jsonException) { fail("Test Data failure: " + jsonException.getLocalizedMessage()); } @@ -59,12 +54,13 @@ public void fromJsonString_backToJson_createsIdenticalElement() { @Test public void fromJsonString_toMap_createsExpectedMap() { - assertMapEquals(EXAMPLE_MAP_ADDRESS, mAddress.toMap()); + assertNotNull(ADDRESS); + assertMapEquals(MAP_ADDRESS, ADDRESS.toMap()); } @Test public void builderConstructor_whenCalled_createsExpectedAddress() { - Address address = new Address.Builder() + final Address address = new Address.Builder() .setCity("San Francisco") .setCountry("US") .setLine1("123 Market St") @@ -72,7 +68,6 @@ public void builderConstructor_whenCalled_createsExpectedAddress() { .setPostalCode("94107") .setState("CA") .build(); - assertMapEquals(address.toMap(), mAddress.toMap()); + assertEquals(address, ADDRESS); } - } diff --git a/stripe/src/test/java/com/stripe/android/model/BankAccountTest.java b/stripe/src/test/java/com/stripe/android/model/BankAccountTest.java index c19050cbecb..6011a085fba 100644 --- a/stripe/src/test/java/com/stripe/android/model/BankAccountTest.java +++ b/stripe/src/test/java/com/stripe/android/model/BankAccountTest.java @@ -29,7 +29,7 @@ public class BankAccountTest { @Test public void parseSampleAccount_returnsExpectedValue() { - BankAccount expectedAccount = new BankAccount( + final BankAccount expectedAccount = new BankAccount( "Jane Austen", BankAccount.TYPE_INDIVIDUAL, "STRIPE TEST BANK", @@ -38,18 +38,7 @@ public void parseSampleAccount_returnsExpectedValue() { "1JWtPxqbdX5Gamtc", "6789", "110000000"); - - BankAccount actualAccount = BankAccount.fromString(RAW_BANK_ACCOUNT); - assertNotNull(actualAccount); - assertEquals(expectedAccount.getAccountHolderName(), - actualAccount.getAccountHolderName()); - assertEquals(expectedAccount.getAccountHolderType(), - actualAccount.getAccountHolderType()); - assertEquals(expectedAccount.getBankName(), actualAccount.getBankName()); - assertEquals(expectedAccount.getCountryCode(), actualAccount.getCountryCode()); - assertEquals(expectedAccount.getCurrency(), actualAccount.getCurrency()); - assertEquals(expectedAccount.getFingerprint(), actualAccount.getFingerprint()); - assertEquals(expectedAccount.getLast4(), actualAccount.getLast4()); - assertEquals(expectedAccount.getRoutingNumber(), actualAccount.getRoutingNumber()); + final BankAccount actualAccount = BankAccount.fromString(RAW_BANK_ACCOUNT); + assertEquals(expectedAccount, actualAccount); } } diff --git a/stripe/src/test/java/com/stripe/android/model/CardTest.java b/stripe/src/test/java/com/stripe/android/model/CardTest.java index 0af79fd8ed1..53c98821829 100644 --- a/stripe/src/test/java/com/stripe/android/model/CardTest.java +++ b/stripe/src/test/java/com/stripe/android/model/CardTest.java @@ -688,31 +688,8 @@ public void getBrand_whenNumberIsNullButBrandIsSet_returnsCorrectValue() { @Test public void fromString_whenStringIsValidJson_returnsExpectedCard() { final Card expectedCard = buildEquivalentJsonCard(); - - final Card cardFromJson = Card.fromString(JSON_CARD); - - assertNotNull(cardFromJson); - assertEquals(expectedCard.getBrand(), cardFromJson.getBrand()); - assertEquals(expectedCard.getFunding(), cardFromJson.getFunding()); - assertEquals(expectedCard.getCountry(), cardFromJson.getCountry()); - assertEquals(expectedCard.getLast4(), cardFromJson.getLast4()); - assertEquals(expectedCard.getExpMonth(), cardFromJson.getExpMonth()); - assertEquals(expectedCard.getExpYear(), cardFromJson.getExpYear()); - assertEquals(expectedCard.getCurrency(), cardFromJson.getCurrency()); - assertEquals(expectedCard.getAddressCity(), cardFromJson.getAddressCity()); - assertEquals(expectedCard.getAddressCountry(), cardFromJson.getAddressCountry()); - assertEquals(expectedCard.getAddressLine1(), cardFromJson.getAddressLine1()); - assertEquals(expectedCard.getAddressLine1Check(), cardFromJson.getAddressLine1Check()); - assertEquals(expectedCard.getAddressLine2(), cardFromJson.getAddressLine2()); - assertEquals(expectedCard.getAddressState(), cardFromJson.getAddressState()); - assertEquals(expectedCard.getAddressZip(), cardFromJson.getAddressZip()); - assertEquals(expectedCard.getAddressZipCheck(), cardFromJson.getAddressZipCheck()); - assertEquals(expectedCard.getCvcCheck(), cardFromJson.getCvcCheck()); - assertEquals(expectedCard.getName(), cardFromJson.getName()); - assertEquals(expectedCard.getCustomerId(), cardFromJson.getCustomerId()); - assertEquals(expectedCard.getFingerprint(), cardFromJson.getFingerprint()); - assertEquals(expectedCard.getId(), cardFromJson.getId()); - assertEquals(expectedCard.getMetadata(), cardFromJson.getMetadata()); + final Card actualCard = Card.fromString(JSON_CARD); + assertEquals(expectedCard, actualCard); } @Test diff --git a/stripe/src/test/java/com/stripe/android/model/CustomerTest.java b/stripe/src/test/java/com/stripe/android/model/CustomerTest.java index 131a7a9ee12..30e62b14493 100644 --- a/stripe/src/test/java/com/stripe/android/model/CustomerTest.java +++ b/stripe/src/test/java/com/stripe/android/model/CustomerTest.java @@ -1,5 +1,8 @@ package com.stripe.android.model; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.stripe.android.testharness.JsonTestUtils; import org.json.JSONArray; @@ -90,11 +93,14 @@ public void fromJson_toJson_createsSameObject() { Customer customer = Customer.fromString(TEST_CUSTOMER_OBJECT); assertNotNull(customer); JsonTestUtils.assertJsonEquals(rawJsonCustomer, customer.toJson()); + assertEquals(Customer.fromString(TEST_CUSTOMER_OBJECT), + Customer.fromString(TEST_CUSTOMER_OBJECT)); } catch (JSONException testDataException) { fail("Test data failure: " + testDataException.getMessage()); } } + @NonNull private String createTestCustomerObjectWithApplePaySource() { try { JSONObject rawJsonCustomer = new JSONObject(TEST_CUSTOMER_OBJECT); @@ -137,7 +143,7 @@ private String createTestCustomerObjectWithApplePaySource() { return rawJsonCustomer.toString(); } catch (JSONException testDataException) { fail("Test data failure: " + testDataException.getMessage()); + return null; } - return null; } } diff --git a/stripe/src/test/java/com/stripe/android/model/ShippingInformationTest.java b/stripe/src/test/java/com/stripe/android/model/ShippingInformationTest.java new file mode 100644 index 00000000000..533da1bbd06 --- /dev/null +++ b/stripe/src/test/java/com/stripe/android/model/ShippingInformationTest.java @@ -0,0 +1,24 @@ +package com.stripe.android.model; + +import androidx.annotation.NonNull; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +import static org.junit.Assert.assertEquals; + +@RunWith(RobolectricTestRunner.class) +public class ShippingInformationTest { + + @Test + public void testEquals() { + assertEquals(createShippingInformation(), createShippingInformation()); + } + + @NonNull + private ShippingInformation createShippingInformation() { + return new ShippingInformation(Address.fromString(AddressTest.JSON_ADDRESS), + "home", "555-123-4567"); + } +} \ No newline at end of file diff --git a/stripe/src/test/java/com/stripe/android/model/ShippingMethodTest.java b/stripe/src/test/java/com/stripe/android/model/ShippingMethodTest.java index 4448f9f2fb6..6aa96286964 100644 --- a/stripe/src/test/java/com/stripe/android/model/ShippingMethodTest.java +++ b/stripe/src/test/java/com/stripe/android/model/ShippingMethodTest.java @@ -1,5 +1,7 @@ package com.stripe.android.model; +import androidx.annotation.NonNull; + import org.json.JSONException; import org.json.JSONObject; import org.junit.Test; @@ -11,6 +13,7 @@ import static com.stripe.android.testharness.JsonTestUtils.assertJsonEquals; import static com.stripe.android.testharness.JsonTestUtils.assertMapEquals; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; /** @@ -35,13 +38,13 @@ public class ShippingMethodTest { put("label", "FedEx"); }}; - private ShippingMethod mShippingMethod = new ShippingMethod("FedEx", "fedex", "Arrives tomorrow", 599, "USD"); + private final static ShippingMethod SHIPPING_METHOD = createShippingMethod(); @Test public void testJSONConversion() { try { JSONObject rawConversion = new JSONObject(EXAMPLE_JSON_SHIPPING_ADDRESS); - assertJsonEquals(mShippingMethod.toJson(), rawConversion); + assertJsonEquals(SHIPPING_METHOD.toJson(), rawConversion); } catch (JSONException jsonException) { fail("Test Data failure: " + jsonException.getLocalizedMessage()); } @@ -49,6 +52,21 @@ public void testJSONConversion() { @Test public void testMapCreation() { - assertMapEquals(mShippingMethod.toMap(), EXAMPLE_MAP_SHIPPING_ADDRESS); + assertMapEquals(SHIPPING_METHOD.toMap(), EXAMPLE_MAP_SHIPPING_ADDRESS); + } + + @Test + public void testEquals() { + assertEquals(SHIPPING_METHOD, createShippingMethod()); + } + + @Test + public void testHashcode() { + assertEquals(SHIPPING_METHOD.hashCode(), createShippingMethod().hashCode()); + } + + @NonNull + private static ShippingMethod createShippingMethod() { + return new ShippingMethod("FedEx", "fedex", "Arrives tomorrow", 599, "USD"); } } diff --git a/stripe/src/test/java/com/stripe/android/model/SourceCardDataTest.java b/stripe/src/test/java/com/stripe/android/model/SourceCardDataTest.java index 247b70b8efd..3081cae82fb 100644 --- a/stripe/src/test/java/com/stripe/android/model/SourceCardDataTest.java +++ b/stripe/src/test/java/com/stripe/android/model/SourceCardDataTest.java @@ -22,31 +22,30 @@ public class SourceCardDataTest { ":\"unchecked\",\"funding\":\"credit\",\"last4\":\"4242\",\"three_d_secure\"" + ":\"optional\",\"tokenization_method\":\"apple_pay\",\"dynamic_last4\":\"4242\"}"; + + private static final SourceCardData CARD_DATA = + SourceCardData.fromString(EXAMPLE_JSON_SOURCE_CARD_DATA_WITH_APPLE_PAY); + @Test public void fromExampleJsonCard_createsExpectedObject() { - final SourceCardData cardData = SourceCardData - .fromString(EXAMPLE_JSON_SOURCE_CARD_DATA_WITH_APPLE_PAY); - assertNotNull(cardData); - assertEquals(Card.VISA, cardData.getBrand()); - assertEquals(0, cardData.getAdditionalFields().size()); - assertEquals(Card.FUNDING_CREDIT, cardData.getFunding()); - assertEquals("4242", cardData.getLast4()); - assertNotNull(cardData.getExpiryMonth()); - assertNotNull(cardData.getExpiryYear()); - assertEquals(12, cardData.getExpiryMonth().intValue()); - assertEquals(2050, cardData.getExpiryYear().intValue()); - assertEquals("US", cardData.getCountry()); - assertEquals("optional", cardData.getThreeDSecureStatus()); - assertEquals("apple_pay", cardData.getTokenizationMethod()); + assertNotNull(CARD_DATA); + assertEquals(Card.VISA, CARD_DATA.getBrand()); + assertEquals(0, CARD_DATA.getAdditionalFields().size()); + assertEquals(Card.FUNDING_CREDIT, CARD_DATA.getFunding()); + assertEquals("4242", CARD_DATA.getLast4()); + assertNotNull(CARD_DATA.getExpiryMonth()); + assertNotNull(CARD_DATA.getExpiryYear()); + assertEquals(12, CARD_DATA.getExpiryMonth().intValue()); + assertEquals(2050, CARD_DATA.getExpiryYear().intValue()); + assertEquals("US", CARD_DATA.getCountry()); + assertEquals("optional", CARD_DATA.getThreeDSecureStatus()); + assertEquals("apple_pay", CARD_DATA.getTokenizationMethod()); } @Test public void fromExampleJsonCard_toMap_createsExpectedMapping() { - final SourceCardData cardData = SourceCardData - .fromString(EXAMPLE_JSON_SOURCE_CARD_DATA_WITH_APPLE_PAY); - assertNotNull(cardData); - - final Map cardDataMap = cardData.toMap(); + assertNotNull(CARD_DATA); + final Map cardDataMap = CARD_DATA.toMap(); assertNotNull(cardDataMap); assertEquals("US", cardDataMap.get("country")); assertEquals("4242", cardDataMap.get("last4")); @@ -58,4 +57,17 @@ public void fromExampleJsonCard_toMap_createsExpectedMapping() { assertEquals("apple_pay", cardDataMap.get("tokenization_method")); assertEquals("4242", cardDataMap.get("dynamic_last4")); } + + @Test + public void testEquals() { + assertEquals(CARD_DATA, + SourceCardData.fromString(EXAMPLE_JSON_SOURCE_CARD_DATA_WITH_APPLE_PAY)); + } + + @Test + public void testHashCode() { + assertNotNull(CARD_DATA); + assertEquals(CARD_DATA.hashCode(), + SourceCardData.fromString(EXAMPLE_JSON_SOURCE_CARD_DATA_WITH_APPLE_PAY).hashCode()); + } } diff --git a/stripe/src/test/java/com/stripe/android/model/SourceOwnerTest.java b/stripe/src/test/java/com/stripe/android/model/SourceOwnerTest.java index 651c297ea63..6aca8b35830 100644 --- a/stripe/src/test/java/com/stripe/android/model/SourceOwnerTest.java +++ b/stripe/src/test/java/com/stripe/android/model/SourceOwnerTest.java @@ -10,7 +10,7 @@ import java.util.HashMap; import java.util.Map; -import static com.stripe.android.model.AddressTest.EXAMPLE_JSON_ADDRESS; +import static com.stripe.android.model.AddressTest.JSON_ADDRESS; import static com.stripe.android.testharness.JsonTestUtils.assertJsonEquals; import static com.stripe.android.testharness.JsonTestUtils.assertMapEquals; import static org.junit.Assert.assertNotNull; @@ -34,11 +34,11 @@ public class SourceOwnerTest { "}"; static final String EXAMPLE_JSON_OWNER_WITHOUT_NULLS = "{" + - "\"address\":"+ EXAMPLE_JSON_ADDRESS + "," + + "\"address\":"+ JSON_ADDRESS + "," + "\"email\": \"jenny.rosen@example.com\"," + "\"name\": \"Jenny Rosen\"," + "\"phone\": \"4158675309\"," + - "\"verified_address\":"+ EXAMPLE_JSON_ADDRESS + "," + + "\"verified_address\":"+ JSON_ADDRESS + "," + "\"verified_email\": \"jenny.rosen@example.com\"," + "\"verified_name\": \"Jenny Rosen\"," + "\"verified_phone\": \"4158675309\"" + diff --git a/stripe/src/test/java/com/stripe/android/model/SourceSepaDebitDataTest.java b/stripe/src/test/java/com/stripe/android/model/SourceSepaDebitDataTest.java index b534b29d78c..004b997c5e9 100644 --- a/stripe/src/test/java/com/stripe/android/model/SourceSepaDebitDataTest.java +++ b/stripe/src/test/java/com/stripe/android/model/SourceSepaDebitDataTest.java @@ -27,7 +27,7 @@ public class SourceSepaDebitDataTest { @Test public void fromJson_withExampleData_returnsExpectedObject() { - SourceSepaDebitData sepaData = SourceSepaDebitData.fromString(EXAMPLE_SEPA_JSON_DATA); + final SourceSepaDebitData sepaData = SourceSepaDebitData.fromString(EXAMPLE_SEPA_JSON_DATA); assertNotNull(sepaData); assertEquals("37040044", sepaData.getBankCode()); assertEquals("R8MJxzkSUv1Kv07L", sepaData.getFingerPrint()); @@ -37,4 +37,20 @@ public void fromJson_withExampleData_returnsExpectedObject() { assertEquals(MANDATE_URL, sepaData.getMandateUrl()); assertNull(sepaData.getBranchCode()); } + + @Test + public void testEquals() { + assertEquals( + SourceSepaDebitData.fromString(EXAMPLE_SEPA_JSON_DATA), + SourceSepaDebitData.fromString(EXAMPLE_SEPA_JSON_DATA) + ); + } + + @Test + public void testHashCode() { + assertEquals( + SourceSepaDebitData.fromString(EXAMPLE_SEPA_JSON_DATA).hashCode(), + SourceSepaDebitData.fromString(EXAMPLE_SEPA_JSON_DATA).hashCode() + ); + } } diff --git a/stripe/src/test/java/com/stripe/android/model/SourceTest.java b/stripe/src/test/java/com/stripe/android/model/SourceTest.java index 6d0661bff24..3fd6b46b638 100644 --- a/stripe/src/test/java/com/stripe/android/model/SourceTest.java +++ b/stripe/src/test/java/com/stripe/android/model/SourceTest.java @@ -187,6 +187,7 @@ public void fromJsonString_backToJson_createsIdenticalElement() { JSONObject rawConversion = new JSONObject(EXAMPLE_JSON_SOURCE_WITHOUT_NULLS); JSONObject actualObject = mSource.toJson(); assertJsonEquals(rawConversion, actualObject); + assertEquals(mSource, Source.fromString(EXAMPLE_JSON_SOURCE_WITHOUT_NULLS)); } catch (JSONException jsonException) { fail("Test Data failure: " + jsonException.getLocalizedMessage()); } diff --git a/stripe/src/test/java/com/stripe/android/model/TokenTest.java b/stripe/src/test/java/com/stripe/android/model/TokenTest.java index 77518de2222..c993bae6db9 100644 --- a/stripe/src/test/java/com/stripe/android/model/TokenTest.java +++ b/stripe/src/test/java/com/stripe/android/model/TokenTest.java @@ -5,6 +5,7 @@ import org.robolectric.RobolectricTestRunner; import java.util.Date; +import java.util.HashMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -15,6 +16,15 @@ */ @RunWith(RobolectricTestRunner.class) public class TokenTest { + private static final Card CARD = new Card.Builder(null, 8, 2017, null) + .id("card_189fi32eZvKYlo2CHK8NPRME") + .brand(Card.VISA) + .country("US") + .last4("4242") + .funding(Card.FUNDING_CREDIT) + .metadata(new HashMap()) + .build(); + private static final String RAW_TOKEN = "{\n" + " \"id\": \"tok_189fi32eZvKYlo2Ct0KZvU5Y\",\n" + " \"object\": \"token\",\n" + @@ -128,25 +138,15 @@ public class TokenTest { "}"; @Test - public void parseToken_readsObject() { - Date createdDate = new Date(1462905355L * 1000L); - Token partialExpectedToken = new Token( + public void parseToken_whenCardToken_readsObjectCorrectly() { + final Token expectedToken = new Token( "tok_189fi32eZvKYlo2Ct0KZvU5Y", false, - createdDate, + new Date(1462905355L * 1000L), false, - (Card) null); - Token answerToken = Token.fromString(RAW_TOKEN); - assertNotNull(answerToken); - assertEquals(partialExpectedToken.getId(), answerToken.getId()); - assertEquals(partialExpectedToken.getLivemode(), answerToken.getLivemode()); - assertEquals(partialExpectedToken.getCreated(), answerToken.getCreated()); - assertEquals(partialExpectedToken.getUsed(), answerToken.getUsed()); - assertEquals(Token.TYPE_CARD, answerToken.getType()); - - // Note: we test the validity of the card object in CardTest - assertNotNull(answerToken.getCard()); - assertNull(answerToken.getBankAccount()); + CARD); + final Token actualToken = Token.fromString(RAW_TOKEN); + assertEquals(expectedToken, actualToken); } diff --git a/stripe/src/test/java/com/stripe/android/view/PaymentMethodsActivityTest.java b/stripe/src/test/java/com/stripe/android/view/PaymentMethodsActivityTest.java index b7d1a389a3b..fc8b907cee7 100644 --- a/stripe/src/test/java/com/stripe/android/view/PaymentMethodsActivityTest.java +++ b/stripe/src/test/java/com/stripe/android/view/PaymentMethodsActivityTest.java @@ -196,7 +196,7 @@ public void onActivityResult_withValidSource_refreshesCustomer() { assertNotNull(source); Intent resultIntent = new Intent(); - resultIntent.putExtra(AddSourceActivity.EXTRA_NEW_SOURCE, source.toString()); + resultIntent.putExtra(AddSourceActivity.EXTRA_NEW_SOURCE, source.toJson().toString()); ArgumentCaptor listenerArgumentCaptor = ArgumentCaptor.forClass(CustomerSession.CustomerRetrievalListener.class); @@ -228,7 +228,7 @@ public void onActivityResult_whenOneSourceButNoSelection_updatesSelectedItem() { assertNotNull(source); Intent resultIntent = new Intent(); - resultIntent.putExtra(AddSourceActivity.EXTRA_NEW_SOURCE, source.toString()); + resultIntent.putExtra(AddSourceActivity.EXTRA_NEW_SOURCE, source.toJson().toString()); ArgumentCaptor listenerArgumentCaptor = ArgumentCaptor.forClass(CustomerSession.CustomerRetrievalListener.class); @@ -317,7 +317,8 @@ public void onSaveMenuItem_sendsSelectionToApi_finishedWithExpectedResult() { CustomerSource customerSource = customer.getSourceById(customer.getDefaultSource()); assertNotNull(customerSource); - assertEquals(customerSource.toString(), intent.getStringExtra(EXTRA_SELECTED_PAYMENT)); + assertEquals(customerSource.toJson().toString(), + intent.getStringExtra(EXTRA_SELECTED_PAYMENT)); } @NonNull