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 12ee813029a..178f8ae1d6b 100644 --- a/stripe/src/main/java/com/stripe/android/model/Source.kt +++ b/stripe/src/main/java/com/stripe/android/model/Source.kt @@ -2,6 +2,7 @@ package com.stripe.android.model import androidx.annotation.Size import androidx.annotation.StringDef +import com.stripe.android.model.Source.SourceFlow import com.stripe.android.model.Source.SourceType import com.stripe.android.model.StripeJsonUtils.optLong import com.stripe.android.model.StripeJsonUtils.optString @@ -13,28 +14,89 @@ import org.json.JSONObject * * See [Sources API Reference](https://stripe.com/docs/api/sources/object). */ -data class Source private constructor( +data class Source internal constructor( + /** + * Unique identifier for the object. + */ override val id: String?, - val amount: Long?, - val clientSecret: String?, - val codeVerification: SourceCodeVerification?, - val created: Long?, - val currency: String?, + /** + * A positive integer in the smallest currency unit (that is, 100 cents for $1.00, or 1 for ¥1, + * Japanese Yen being a zero-decimal currency) representing the total amount associated with + * the source. This is the amount for which the source will be chargeable once ready. + * Required for `single_use` sources. + */ + val amount: Long? = null, + + /** + * The client secret of the source. Used for client-side retrieval using a publishable key. + */ + val clientSecret: String? = null, + + /** + * Information related to the code verification flow. Present if the source is authenticated + * by a verification code (`flow` is `code_verification`). + */ + val codeVerification: SourceCodeVerification? = null, + + /** + * Time at which the object was created. Measured in seconds since the Unix epoch. + */ + val created: Long? = null, + + /** + * Three-letter [ISO code for the currency](https://stripe.com/docs/currencies) associated with + * the source. This is the currency for which the source will be chargeable once ready. + * Required for `single_use` sources. + */ + val currency: String? = null, + + /** + * The authentication `flow` of the source. + * `flow` is one of `redirect`, `receiver`, `code_verification`, `none`. + */ @param:SourceFlow @field:SourceFlow @get:SourceFlow val flow: String? = null, - val isLiveMode: Boolean?, - val metaData: Map?, - val owner: SourceOwner?, + /** + * Has the value true if the object exists in live mode or the value false if the object + * exists in test mode. + */ + val isLiveMode: Boolean? = null, + + /** + * Set of key-value pairs that you can attach to an object. This can be useful for storing + * additional information about the object in a structured format. + */ + val metaData: Map? = null, + + /** + * Information about the owner of the payment instrument that may be used or required by + * particular source types. + */ + val owner: SourceOwner? = null, + + /** + * Information related to the receiver flow. + * Present if the source is a receiver ([flow] is [SourceFlow.RECEIVER]). + */ val receiver: SourceReceiver? = null, + + /** + * Information related to the redirect flow. Present if the source is authenticated by a + * redirect ([flow] is [SourceFlow.REDIRECT]). + */ val redirect: SourceRedirect? = null, + /** + * The status of the source, one of `canceled`, `chargeable`, `consumed`, `failed`, + * or `pending`. Only `chargeable` sources can be used to create a charge. + */ @param:SourceStatus @field:SourceStatus @get:SourceStatus - val status: String?, + val status: String? = null, - val sourceTypeData: Map?, - val sourceTypeModel: StripeSourceTypeModel?, + val sourceTypeData: Map? = null, + val sourceTypeModel: StripeSourceTypeModel? = null, /** * Gets the [SourceType] of this Source, as one of the enumerated values. @@ -55,10 +117,21 @@ data class Source private constructor( */ val typeRaw: String, + /** + * Either `reusable` or `single_use`. Whether this source should be reusable or not. Some source + * types may or may not be reusable by construction, while others may leave the option at + * creation. If an incompatible value is passed, an error will be returned. + */ @param:Usage @field:Usage @get:Usage - val usage: String?, + val usage: String? = null, + + private val weChatParam: WeChat? = null, - private val weChatParam: WeChat? + /** + * Information about the items and shipping associated with the source. Required for + * transactional credit (for example Klarna) sources before you can charge it. + */ + val sourceOrder: SourceOrder? = null ) : StripeModel(), StripePaymentSource { val weChat: WeChat @@ -149,6 +222,7 @@ data class Source private constructor( private const val FIELD_OWNER: String = "owner" private const val FIELD_RECEIVER: String = "receiver" private const val FIELD_REDIRECT: String = "redirect" + private const val FIELD_SOURCE_ORDER: String = "source_order" private const val FIELD_STATUS: String = "status" private const val FIELD_TYPE: String = "type" private const val FIELD_USAGE: String = "usage" @@ -181,11 +255,10 @@ data class Source private constructor( val sourceTypeModel = SourceCardData.fromJson(jsonObject) return Source( - id, null, null, null, - null, null, null, null, - null, null, null, null, - null, null, sourceTypeModel, SourceType.CARD, - SourceType.CARD, null, null + id, + sourceTypeModel = sourceTypeModel, + type = SourceType.CARD, + typeRaw = SourceType.CARD ) } @@ -240,6 +313,9 @@ data class Source private constructor( WeChat.fromJson(jsonObject.optJSONObject(FIELD_WECHAT) ?: JSONObject()) } else { null + }, + sourceOrder = jsonObject.optJSONObject(FIELD_SOURCE_ORDER)?.let { + SourceOrder.fromJson(it) } ) } diff --git a/stripe/src/test/java/com/stripe/android/model/SourceFixtures.kt b/stripe/src/test/java/com/stripe/android/model/SourceFixtures.kt index 09becf251de..8450d2c2dbd 100644 --- a/stripe/src/test/java/com/stripe/android/model/SourceFixtures.kt +++ b/stripe/src/test/java/com/stripe/android/model/SourceFixtures.kt @@ -1,10 +1,10 @@ package com.stripe.android.model +import com.stripe.android.model.SourceOrderFixtures.SOURCE_ORDER_JSON import org.json.JSONObject internal object SourceFixtures { - @JvmField val ALIPAY_JSON = JSONObject( """ { @@ -43,8 +43,7 @@ internal object SourceFixtures { """.trimIndent() ) - @JvmField - val WECHAT = Source.fromJson(JSONObject( + val WECHAT = requireNotNull(Source.fromJson(JSONObject( """ { "id": "src_1F4ZSkBNJ02", @@ -84,10 +83,9 @@ internal object SourceFixtures { } } """.trimIndent() - ))!! + ))) - @JvmField - val SOURCE_CARD = Source.fromJson(JSONObject( + val SOURCE_CARD = requireNotNull(Source.fromJson(JSONObject( """ { "id": "src_19t3xKBZqEXluyI4uz2dxAfQ", @@ -132,10 +130,9 @@ internal object SourceFixtures { } } """.trimIndent() - ))!! + ))) - @JvmField - val CARD = Source.fromJson(JSONObject( + val CARD = requireNotNull(Source.fromJson(JSONObject( """ { "id": "card_1ELxrOCRMbs6FrXfdxOGjnaD", @@ -162,9 +159,8 @@ internal object SourceFixtures { "tokenization_method": null } """.trimIndent() - ))!! + ))) - @JvmField val APPLE_PAY = JSONObject( """ { @@ -194,8 +190,7 @@ internal object SourceFixtures { """.trimIndent() ) - @JvmField - val SOURCE_REDIRECT_JSON = JSONObject( + private val SOURCE_REDIRECT_JSON = JSONObject( """ { "return_url": "https://google.com", @@ -205,11 +200,11 @@ internal object SourceFixtures { """.trimIndent() ) - @JvmField - val SOURCE_REDIRECT = SourceRedirect.fromJson(SOURCE_REDIRECT_JSON)!! + val SOURCE_REDIRECT = requireNotNull( + SourceRedirect.fromJson(SOURCE_REDIRECT_JSON) + ) - @JvmField - val SOURCE_CODE_VERIFICATION_JSON = JSONObject( + private val SOURCE_CODE_VERIFICATION_JSON = JSONObject( """ { "attempts_remaining": 3, @@ -218,13 +213,11 @@ internal object SourceFixtures { """.trimIndent() ) - @JvmField - val SOURCE_CODE_VERIFICATION = SourceCodeVerification.fromJson( - SOURCE_CODE_VERIFICATION_JSON - )!! + val SOURCE_CODE_VERIFICATION = requireNotNull( + SourceCodeVerification.fromJson(SOURCE_CODE_VERIFICATION_JSON) + ) - @JvmField - val SOURCE_RECEIVER_JSON = JSONObject( + private val SOURCE_RECEIVER_JSON = JSONObject( """ { "address": "test_1MBhWS3uv4ynCfQXF3xQjJkzFPukr4K56N", @@ -235,10 +228,8 @@ internal object SourceFixtures { """.trimIndent() ) - @JvmField - val SOURCE_RECEIVER = SourceReceiver.fromJson(SOURCE_RECEIVER_JSON)!! + val SOURCE_RECEIVER = requireNotNull(SourceReceiver.fromJson(SOURCE_RECEIVER_JSON)) - @JvmField val SOURCE_OWNER_WITH_NULLS = JSONObject( """ { @@ -254,7 +245,6 @@ internal object SourceFixtures { """.trimIndent() ) - @JvmField val SOURCE_OWNER_WITHOUT_NULLS = JSONObject( """ { @@ -284,7 +274,6 @@ internal object SourceFixtures { """.trimIndent() ) - @JvmField internal val SOURCE_CARD_DATA_WITH_APPLE_PAY_JSON = JSONObject( """ { @@ -303,4 +292,24 @@ internal object SourceFixtures { } """.trimIndent() ) + + internal val SOURCE_WITH_SOURCE_ORDER = requireNotNull(Source.fromJson(JSONObject( + """ + { + "id": "src_1FfB6GKmrohBAXC", + "object": "source", + "amount": 1000, + "created": 1573848540, + "currency": "eur", + "flow": "redirect", + "livemode": false, + "metadata": {}, + "source_order": $SOURCE_ORDER_JSON, + "statement_descriptor": null, + "status": "pending", + "type": "klarna", + "usage": "single_use" + } + """.trimIndent() + ))) } diff --git a/stripe/src/main/java/com/stripe/android/model/SourceOrderFixtures.kt b/stripe/src/test/java/com/stripe/android/model/SourceOrderFixtures.kt similarity index 55% rename from stripe/src/main/java/com/stripe/android/model/SourceOrderFixtures.kt rename to stripe/src/test/java/com/stripe/android/model/SourceOrderFixtures.kt index dee85a8d49b..8443f662afc 100644 --- a/stripe/src/main/java/com/stripe/android/model/SourceOrderFixtures.kt +++ b/stripe/src/test/java/com/stripe/android/model/SourceOrderFixtures.kt @@ -4,7 +4,7 @@ import org.json.JSONObject internal object SourceOrderFixtures { - val SOURCE_ORDER = SourceOrder.fromJson(JSONObject( + val SOURCE_ORDER_JSON = JSONObject( """ { "amount": 1000, @@ -53,5 +53,46 @@ internal object SourceOrderFixtures { } } """.trimIndent() - )) + ) + + val SOURCE_ORDER = SourceOrder( + amount = 1000, + currency = "eur", + email = "jrosen@example.com", + items = listOf( + SourceOrder.Item( + type = SourceOrder.Item.Type.Sku, + amount = 1000, + currency = "eur", + description = "shoes", + quantity = 1 + ), + SourceOrder.Item( + type = SourceOrder.Item.Type.Sku, + amount = 1000, + currency = "eur", + description = "socks", + quantity = 1 + ), + SourceOrder.Item( + type = SourceOrder.Item.Type.Shipping, + amount = 499, + currency = "eur", + description = "ground shipping" + ), + SourceOrder.Item( + type = SourceOrder.Item.Type.Tax, + amount = 299, + currency = "eur", + description = "sales tax" + ) + ), + shipping = SourceOrder.Shipping( + address = AddressFixtures.ADDRESS, + carrier = "UPS", + name = "Jenny Rosen", + phone = "1-800-555-1234", + trackingNumber = "tracking_12345" + ) + ) } diff --git a/stripe/src/test/java/com/stripe/android/model/SourceOrderTest.kt b/stripe/src/test/java/com/stripe/android/model/SourceOrderTest.kt index 57a1a915f27..f916d310e4e 100644 --- a/stripe/src/test/java/com/stripe/android/model/SourceOrderTest.kt +++ b/stripe/src/test/java/com/stripe/android/model/SourceOrderTest.kt @@ -7,46 +7,9 @@ class SourceOrderTest { @Test fun testParse() { - val expected = SourceOrder( - amount = 1000, - currency = "eur", - email = "jrosen@example.com", - items = listOf( - SourceOrder.Item( - type = SourceOrder.Item.Type.Sku, - amount = 1000, - currency = "eur", - description = "shoes", - quantity = 1 - ), - SourceOrder.Item( - type = SourceOrder.Item.Type.Sku, - amount = 1000, - currency = "eur", - description = "socks", - quantity = 1 - ), - SourceOrder.Item( - type = SourceOrder.Item.Type.Shipping, - amount = 499, - currency = "eur", - description = "ground shipping" - ), - SourceOrder.Item( - type = SourceOrder.Item.Type.Tax, - amount = 299, - currency = "eur", - description = "sales tax" - ) - ), - shipping = SourceOrder.Shipping( - address = AddressFixtures.ADDRESS, - carrier = "UPS", - name = "Jenny Rosen", - phone = "1-800-555-1234", - trackingNumber = "tracking_12345" - ) + assertEquals( + SourceOrderFixtures.SOURCE_ORDER, + SourceOrder.fromJson(SourceOrderFixtures.SOURCE_ORDER_JSON) ) - assertEquals(expected, SourceOrderFixtures.SOURCE_ORDER) } } diff --git a/stripe/src/test/java/com/stripe/android/model/SourceTest.kt b/stripe/src/test/java/com/stripe/android/model/SourceTest.kt index 36220b70cd0..5b7ae103f5b 100644 --- a/stripe/src/test/java/com/stripe/android/model/SourceTest.kt +++ b/stripe/src/test/java/com/stripe/android/model/SourceTest.kt @@ -58,6 +58,14 @@ class SourceTest { assertEquals("wxa0df8has9d78ce", weChat.appId) } + @Test + fun fromJson_withSourceOrder() { + assertEquals( + SourceOrderFixtures.SOURCE_ORDER, + SourceFixtures.SOURCE_WITH_SOURCE_ORDER.sourceOrder + ) + } + internal companion object { internal val EXAMPLE_JSON_SOURCE_WITHOUT_NULLS = JSONObject( """