From a0c91b8cf7a3572fe9aa8375b52c1101f61c2334 Mon Sep 17 00:00:00 2001
From: Michael Shafrir <45020849+mshafrir-stripe@users.noreply.github.com>
Date: Fri, 10 Jul 2020 14:40:30 -0400
Subject: [PATCH] Add support for Bank Account as source on Customer object
 (#2641)

Fixes #2601
---
 .../com/stripe/android/model/BankAccount.kt   |  4 +-
 .../java/com/stripe/android/model/Card.kt     |  2 -
 .../java/com/stripe/android/model/Source.kt   |  2 -
 .../model/parsers/CustomerJsonParser.kt       | 12 ++-
 .../model/parsers/CustomerSourceJsonParser.kt | 17 ++--
 .../model/parsers/CustomerJsonParserTest.kt   | 82 +++++++++++++++++++
 6 files changed, 101 insertions(+), 18 deletions(-)
 create mode 100644 stripe/src/test/java/com/stripe/android/model/parsers/CustomerJsonParserTest.kt

diff --git a/stripe/src/main/java/com/stripe/android/model/BankAccount.kt b/stripe/src/main/java/com/stripe/android/model/BankAccount.kt
index ba5f9e8cf34..503b20ed35a 100644
--- a/stripe/src/main/java/com/stripe/android/model/BankAccount.kt
+++ b/stripe/src/main/java/com/stripe/android/model/BankAccount.kt
@@ -13,7 +13,7 @@ data class BankAccount internal constructor(
      *
      * [id](https://stripe.com/docs/api/customer_bank_accounts/object#customer_bank_account_object-id)
      */
-    val id: String? = null,
+    override val id: String? = null,
 
     /**
      * The name of the person or business that owns the bank account.
@@ -91,7 +91,7 @@ data class BankAccount internal constructor(
      * [status](https://stripe.com/docs/api/customer_bank_accounts/object#customer_bank_account_object-status)
      */
     val status: Status? = null
-) : StripeModel {
+) : StripeModel, StripePaymentSource {
 
     enum class Type(internal val code: String) {
         Company("company"),
diff --git a/stripe/src/main/java/com/stripe/android/model/Card.kt b/stripe/src/main/java/com/stripe/android/model/Card.kt
index 514c7f3f44c..38e8f81034d 100644
--- a/stripe/src/main/java/com/stripe/android/model/Card.kt
+++ b/stripe/src/main/java/com/stripe/android/model/Card.kt
@@ -525,8 +525,6 @@ data class Card internal constructor(
     }
 
     companion object {
-        internal const val OBJECT_TYPE = "card"
-
         /**
          * Create a Card object from a raw JSON string.
          *
diff --git a/stripe/src/main/java/com/stripe/android/model/Source.kt b/stripe/src/main/java/com/stripe/android/model/Source.kt
index 62a21fc46c5..abd7e2c381d 100644
--- a/stripe/src/main/java/com/stripe/android/model/Source.kt
+++ b/stripe/src/main/java/com/stripe/android/model/Source.kt
@@ -411,8 +411,6 @@ data class Source internal constructor(
     ) : StripeModel
 
     companion object {
-        internal const val OBJECT_TYPE = "source"
-
         internal const val EURO: String = "eur"
         internal const val USD: String = "usd"
 
diff --git a/stripe/src/main/java/com/stripe/android/model/parsers/CustomerJsonParser.kt b/stripe/src/main/java/com/stripe/android/model/parsers/CustomerJsonParser.kt
index ce6d039ca3f..e97a35ab447 100644
--- a/stripe/src/main/java/com/stripe/android/model/parsers/CustomerJsonParser.kt
+++ b/stripe/src/main/java/com/stripe/android/model/parsers/CustomerJsonParser.kt
@@ -25,7 +25,7 @@ internal class CustomerJsonParser : ModelJsonParser<Customer> {
         val hasMore: Boolean
         val totalCount: Int?
         val url: String?
-        val sources: List<CustomerSource>?
+        val sources: List<CustomerSource>
         if (sourcesJson != null && VALUE_LIST == StripeJsonUtils.optString(sourcesJson, FIELD_OBJECT)) {
             hasMore = StripeJsonUtils.optBoolean(sourcesJson, FIELD_HAS_MORE)
             totalCount = StripeJsonUtils.optInteger(sourcesJson, FIELD_TOTAL_COUNT)
@@ -44,7 +44,15 @@ internal class CustomerJsonParser : ModelJsonParser<Customer> {
             sources = emptyList()
         }
 
-        return Customer(id, defaultSource, shippingInformation, sources, hasMore, totalCount, url)
+        return Customer(
+            id = id,
+            defaultSource = defaultSource,
+            shippingInformation = shippingInformation,
+            sources = sources,
+            hasMore = hasMore,
+            totalCount = totalCount,
+            url = url
+        )
     }
 
     private companion object {
diff --git a/stripe/src/main/java/com/stripe/android/model/parsers/CustomerSourceJsonParser.kt b/stripe/src/main/java/com/stripe/android/model/parsers/CustomerSourceJsonParser.kt
index a7a447c13fc..5ef445d8b53 100644
--- a/stripe/src/main/java/com/stripe/android/model/parsers/CustomerSourceJsonParser.kt
+++ b/stripe/src/main/java/com/stripe/android/model/parsers/CustomerSourceJsonParser.kt
@@ -1,25 +1,22 @@
 package com.stripe.android.model.parsers
 
-import com.stripe.android.model.Card
 import com.stripe.android.model.CustomerSource
-import com.stripe.android.model.Source
-import com.stripe.android.model.StripeJsonUtils
+import com.stripe.android.model.StripeJsonUtils.optString
 import com.stripe.android.model.StripePaymentSource
 import org.json.JSONObject
 
 internal class CustomerSourceJsonParser : ModelJsonParser<CustomerSource> {
     override fun parse(json: JSONObject): CustomerSource? {
         val sourceObject: StripePaymentSource? =
-            when (StripeJsonUtils.optString(json, "object")) {
-                Card.OBJECT_TYPE -> CardJsonParser().parse(json)
-                Source.OBJECT_TYPE -> SourceJsonParser().parse(json)
+            when (optString(json, "object")) {
+                "card" -> CardJsonParser().parse(json)
+                "source" -> SourceJsonParser().parse(json)
+                "bank_account" -> BankAccountJsonParser().parse(json)
                 else -> null
             }
 
-        return if (sourceObject == null) {
-            null
-        } else {
-            CustomerSource(sourceObject)
+        return sourceObject?.let {
+            CustomerSource(it)
         }
     }
 }
diff --git a/stripe/src/test/java/com/stripe/android/model/parsers/CustomerJsonParserTest.kt b/stripe/src/test/java/com/stripe/android/model/parsers/CustomerJsonParserTest.kt
new file mode 100644
index 00000000000..58e53edf09d
--- /dev/null
+++ b/stripe/src/test/java/com/stripe/android/model/parsers/CustomerJsonParserTest.kt
@@ -0,0 +1,82 @@
+package com.stripe.android.model.parsers
+
+import com.google.common.truth.Truth.assertThat
+import com.stripe.android.model.BankAccount
+import com.stripe.android.model.Customer
+import com.stripe.android.model.CustomerSource
+import kotlin.test.Test
+import org.json.JSONObject
+
+class CustomerJsonParserTest {
+
+    @Test
+    fun `should correctly parse Customer with BankAccount source`() {
+        assertThat(
+            CustomerJsonParser()
+                .parse(CUSTOMER_JSON)
+        ).isEqualTo(
+            Customer(
+                id = "cus_HcLIwF3BCi",
+                defaultSource = "ba_1H3NOMCRMbs6FrXfahj",
+                shippingInformation = null,
+                sources = listOf(
+                    CustomerSource(
+                        BankAccount(
+                            id = "ba_1H3NOMCRMbs6FrXfahj",
+                            accountHolderName = "Test Bank Account",
+                            accountHolderType = BankAccount.Type.Individual,
+                            bankName = "STRIPE TEST BANK",
+                            countryCode = "US",
+                            currency = "usd",
+                            fingerprint = "wxXSAD5idPUzgBEz",
+                            last4 = "6789",
+                            routingNumber = "110000000",
+                            status = BankAccount.Status.New
+                        )
+                    )
+                ),
+                hasMore = false,
+                totalCount = 1,
+                url = "/v1/customers/cus_HcLIwF3BCi/sources"
+            )
+        )
+    }
+
+    private companion object {
+        private val CUSTOMER_JSON = JSONObject(
+            """
+            {
+            	"id": "cus_HcLIwF3BCi",
+            	"object": "customer",
+            	"created": 1594327465,
+            	"default_source": "ba_1H3NOMCRMbs6FrXfahj",
+            	"description": "mobile SDK example customer",
+            	"email": null,
+            	"livemode": false,
+            	"shipping": null,
+            	"sources": {
+            		"object": "list",
+            		"data": [{
+            			"id": "ba_1H3NOMCRMbs6FrXfahj",
+            			"object": "bank_account",
+            			"account_holder_name": "Test Bank Account",
+            			"account_holder_type": "individual",
+            			"bank_name": "STRIPE TEST BANK",
+            			"country": "US",
+            			"currency": "usd",
+            			"customer": "cus_HcLIwF3BCiRp0t",
+            			"fingerprint": "wxXSAD5idPUzgBEz",
+            			"last4": "6789",
+            			"metadata": {},
+            			"routing_number": "110000000",
+            			"status": "new"
+            		}],
+            		"has_more": false,
+            		"total_count": 1,
+            		"url": "\/v1\/customers\/cus_HcLIwF3BCi\/sources"
+            	}
+            }
+            """.trimIndent()
+        )
+    }
+}