diff --git a/financial-connections/api/financial-connections.api b/financial-connections/api/financial-connections.api index 24c40043057..68a24212ebc 100644 --- a/financial-connections/api/financial-connections.api +++ b/financial-connections/api/financial-connections.api @@ -820,19 +820,6 @@ public final class com/stripe/android/financialconnections/model/FinancialConnec public static fun values ()[Lcom/stripe/android/financialconnections/model/FinancialConnectionsAccount$Category; } -public final class com/stripe/android/financialconnections/model/FinancialConnectionsAccount$Category$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field $stable I - public static final field INSTANCE Lcom/stripe/android/financialconnections/model/FinancialConnectionsAccount$Category$$serializer; - public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/stripe/android/financialconnections/model/FinancialConnectionsAccount$Category; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/stripe/android/financialconnections/model/FinancialConnectionsAccount$Category;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - public final class com/stripe/android/financialconnections/model/FinancialConnectionsAccount$Category$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } @@ -850,6 +837,7 @@ public final class com/stripe/android/financialconnections/model/FinancialConnec } public final class com/stripe/android/financialconnections/model/FinancialConnectionsAccount$Permissions : java/lang/Enum { + public static final field ACCOUNT_NUMBERS Lcom/stripe/android/financialconnections/model/FinancialConnectionsAccount$Permissions; public static final field BALANCES Lcom/stripe/android/financialconnections/model/FinancialConnectionsAccount$Permissions; public static final field Companion Lcom/stripe/android/financialconnections/model/FinancialConnectionsAccount$Permissions$Companion; public static final field OWNERSHIP Lcom/stripe/android/financialconnections/model/FinancialConnectionsAccount$Permissions; @@ -861,19 +849,6 @@ public final class com/stripe/android/financialconnections/model/FinancialConnec public static fun values ()[Lcom/stripe/android/financialconnections/model/FinancialConnectionsAccount$Permissions; } -public final class com/stripe/android/financialconnections/model/FinancialConnectionsAccount$Permissions$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field $stable I - public static final field INSTANCE Lcom/stripe/android/financialconnections/model/FinancialConnectionsAccount$Permissions$$serializer; - public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/stripe/android/financialconnections/model/FinancialConnectionsAccount$Permissions; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/stripe/android/financialconnections/model/FinancialConnectionsAccount$Permissions;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - public final class com/stripe/android/financialconnections/model/FinancialConnectionsAccount$Permissions$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } @@ -889,19 +864,6 @@ public final class com/stripe/android/financialconnections/model/FinancialConnec public static fun values ()[Lcom/stripe/android/financialconnections/model/FinancialConnectionsAccount$Status; } -public final class com/stripe/android/financialconnections/model/FinancialConnectionsAccount$Status$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field $stable I - public static final field INSTANCE Lcom/stripe/android/financialconnections/model/FinancialConnectionsAccount$Status$$serializer; - public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/stripe/android/financialconnections/model/FinancialConnectionsAccount$Status; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/stripe/android/financialconnections/model/FinancialConnectionsAccount$Status;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - public final class com/stripe/android/financialconnections/model/FinancialConnectionsAccount$Status$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } @@ -920,19 +882,6 @@ public final class com/stripe/android/financialconnections/model/FinancialConnec public static fun values ()[Lcom/stripe/android/financialconnections/model/FinancialConnectionsAccount$Subcategory; } -public final class com/stripe/android/financialconnections/model/FinancialConnectionsAccount$Subcategory$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field $stable I - public static final field INSTANCE Lcom/stripe/android/financialconnections/model/FinancialConnectionsAccount$Subcategory$$serializer; - public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/stripe/android/financialconnections/model/FinancialConnectionsAccount$Subcategory; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/stripe/android/financialconnections/model/FinancialConnectionsAccount$Subcategory;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - public final class com/stripe/android/financialconnections/model/FinancialConnectionsAccount$Subcategory$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } @@ -947,19 +896,6 @@ public final class com/stripe/android/financialconnections/model/FinancialConnec public static fun values ()[Lcom/stripe/android/financialconnections/model/FinancialConnectionsAccount$SupportedPaymentMethodTypes; } -public final class com/stripe/android/financialconnections/model/FinancialConnectionsAccount$SupportedPaymentMethodTypes$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field $stable I - public static final field INSTANCE Lcom/stripe/android/financialconnections/model/FinancialConnectionsAccount$SupportedPaymentMethodTypes$$serializer; - public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/stripe/android/financialconnections/model/FinancialConnectionsAccount$SupportedPaymentMethodTypes; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/stripe/android/financialconnections/model/FinancialConnectionsAccount$SupportedPaymentMethodTypes;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - public final class com/stripe/android/financialconnections/model/FinancialConnectionsAccount$SupportedPaymentMethodTypes$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/model/FinancialConnectionsAccount.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/model/FinancialConnectionsAccount.kt index bd1f31eb16d..7a2258703c4 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/model/FinancialConnectionsAccount.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/model/FinancialConnectionsAccount.kt @@ -2,6 +2,7 @@ package com.stripe.android.financialconnections.model import android.os.Parcelable import com.stripe.android.core.model.StripeModel +import com.stripe.android.core.model.serializers.EnumIgnoreUnknownSerializer import kotlinx.parcelize.Parcelize import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -109,7 +110,7 @@ data class FinancialConnectionsAccount( * * Values: cash,credit,investment,other */ - @Serializable + @Serializable(with = Category.Serializer::class) enum class Category(val value: String) { @SerialName("cash") CASH("cash"), @@ -124,6 +125,9 @@ data class FinancialConnectionsAccount( OTHER("other"), UNKNOWN("unknown"); + + internal object Serializer : + EnumIgnoreUnknownSerializer(Category.values(), UNKNOWN) } /** @@ -131,7 +135,7 @@ data class FinancialConnectionsAccount( * * Values: active,disconnected,inactive */ - @Serializable + @Serializable(with = Status.Serializer::class) enum class Status(val value: String) { @SerialName("active") ACTIVE("active"), @@ -143,6 +147,8 @@ data class FinancialConnectionsAccount( INACTIVE("inactive"), UNKNOWN("unknown"); + + internal object Serializer : EnumIgnoreUnknownSerializer(Status.values(), UNKNOWN) } /** @@ -152,7 +158,7 @@ data class FinancialConnectionsAccount( * * Values: checking,creditCard,lineOfCredit,mortgage,other,savings */ - @Serializable + @Serializable(with = Subcategory.Serializer::class) enum class Subcategory(val value: String) { @SerialName("checking") CHECKING("checking"), @@ -173,6 +179,9 @@ data class FinancialConnectionsAccount( SAVINGS("savings"), UNKNOWN("unknown"); + + internal object Serializer : + EnumIgnoreUnknownSerializer(Subcategory.values(), UNKNOWN) } /** @@ -181,7 +190,7 @@ data class FinancialConnectionsAccount( * * Values: link,usBankAccount */ - @Serializable + @Serializable(with = SupportedPaymentMethodTypes.Serializer::class) enum class SupportedPaymentMethodTypes(val value: String) { @SerialName("link") LINK("link"), @@ -190,6 +199,11 @@ data class FinancialConnectionsAccount( US_BANK_ACCOUNT("us_bank_account"), UNKNOWN("unknown"); + + internal object Serializer : + EnumIgnoreUnknownSerializer( + SupportedPaymentMethodTypes.values(), UNKNOWN + ) } /** @@ -197,7 +211,7 @@ data class FinancialConnectionsAccount( * * Values: balances,identity,ownership,paymentMethod,transactions */ - @Serializable + @Serializable(with = Permissions.Serializer::class) enum class Permissions(val value: String) { @SerialName("balances") BALANCES("balances"), @@ -211,7 +225,14 @@ data class FinancialConnectionsAccount( @SerialName("transactions") TRANSACTIONS("transactions"), + @SerialName("account_numbers") + ACCOUNT_NUMBERS("account_numbers"), + UNKNOWN("unknown"); + + internal object Serializer : EnumIgnoreUnknownSerializer( + Permissions.values(), UNKNOWN + ) } companion object { diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/repository/FinancialConnectionsApiRepositoryTest.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/repository/FinancialConnectionsApiRepositoryTest.kt index 344e8cf2ae7..81f81a505ee 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/repository/FinancialConnectionsApiRepositoryTest.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/repository/FinancialConnectionsApiRepositoryTest.kt @@ -38,7 +38,8 @@ class FinancialConnectionsApiRepositoryTest { ) ) - val result = financialConnectionsApiRepository.getFinancialConnectionsSession("client_secret") + val result = + financialConnectionsApiRepository.getFinancialConnectionsSession("client_secret") assertThat(result.accounts.data.size).isEqualTo(1) } @@ -52,7 +53,8 @@ class FinancialConnectionsApiRepositoryTest { ) ) - val result = financialConnectionsApiRepository.getFinancialConnectionsSession("client_secret") + val result = + financialConnectionsApiRepository.getFinancialConnectionsSession("client_secret") assertThat(result.accounts.data.size).isEqualTo(1) } @@ -66,7 +68,8 @@ class FinancialConnectionsApiRepositoryTest { ) ) - val result = financialConnectionsApiRepository.getFinancialConnectionsSession("client_secret") + val result = + financialConnectionsApiRepository.getFinancialConnectionsSession("client_secret") assertThat(result.paymentAccount).isInstanceOf(FinancialConnectionsAccount::class.java) } @@ -80,7 +83,8 @@ class FinancialConnectionsApiRepositoryTest { ) ) - val result = financialConnectionsApiRepository.getFinancialConnectionsSession("client_secret") + val result = + financialConnectionsApiRepository.getFinancialConnectionsSession("client_secret") assertThat(result.paymentAccount).isInstanceOf(FinancialConnectionsAccount::class.java) } @@ -94,11 +98,30 @@ class FinancialConnectionsApiRepositoryTest { ) ) - val result = financialConnectionsApiRepository.getFinancialConnectionsSession("client_secret") + val result = + financialConnectionsApiRepository.getFinancialConnectionsSession("client_secret") assertThat(result.paymentAccount).isInstanceOf(FinancialConnectionsAccount::class.java) } + @Test + fun `getFinancialConnectionsSession - account with unknown permissions ignores unknown values`() = + runTest { + givenGetRequestReturns( + readResourceAsString( + "json/linked_account_session_unknown_permission.json" + ) + ) + + val result = financialConnectionsApiRepository + .getFinancialConnectionsSession("client_secret") + val financialConnectionsAccount = result.paymentAccount as FinancialConnectionsAccount + assertThat(financialConnectionsAccount.permissions).containsExactly( + FinancialConnectionsAccount.Permissions.PAYMENT_METHOD, + FinancialConnectionsAccount.Permissions.UNKNOWN, + ) + } + @Test fun `getFinancialConnectionsSession - paymentAccount is BankAccount`() = runTest { @@ -108,7 +131,8 @@ class FinancialConnectionsApiRepositoryTest { ) ) - val result = financialConnectionsApiRepository.getFinancialConnectionsSession("client_secret") + val result = + financialConnectionsApiRepository.getFinancialConnectionsSession("client_secret") assertThat(result.paymentAccount).isInstanceOf(BankAccount::class.java) } diff --git a/financial-connections/src/test/resources/json/linked_account_session_unknown_permission.json b/financial-connections/src/test/resources/json/linked_account_session_unknown_permission.json new file mode 100644 index 00000000000..8fd430b4390 --- /dev/null +++ b/financial-connections/src/test/resources/json/linked_account_session_unknown_permission.json @@ -0,0 +1,34 @@ +{ + "id": "las_dhgfsklhgfkdsjhgk", + "object": "link_account_session", + "client_secret": "las_client_secret_ldafjlfkjlsfadkjk", + "linked_accounts": { + "object": "list", + "data": [ + ], + "has_more": false, + "total_count": 0, + "url": "/v1/linked_accounts" + }, + "livemode": true, + "payment_account": { + "id": "la_dfsdfasfds", + "object": "financial_connections.account", + "balance": null, + "balance_refresh": null, + "category": "credit", + "created": 1648749414, + "display_name": "CREDIT CARD", + "institution_name": "Chase", + "last4": "5579", + "livemode": true, + "permissions": [ + "payment_method", + "hola" + ], + "status": "active", + "subcategory": "credit_card", + "supported_payment_method_types": [ + ] + } +} \ No newline at end of file diff --git a/stripe-core/src/main/java/com/stripe/android/core/model/serializers/EnumIgnoreUnknownSerializer.kt b/stripe-core/src/main/java/com/stripe/android/core/model/serializers/EnumIgnoreUnknownSerializer.kt new file mode 100644 index 00000000000..89247ed8015 --- /dev/null +++ b/stripe-core/src/main/java/com/stripe/android/core/model/serializers/EnumIgnoreUnknownSerializer.kt @@ -0,0 +1,43 @@ +package com.stripe.android.core.model.serializers + +import androidx.annotation.RestrictTo +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +/** + * Parses an enum using [values], and on unknown values, falls back to [defaultValue]. + */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +abstract class EnumIgnoreUnknownSerializer>( + values: Array, + private val defaultValue: T +) : + KSerializer { + // Alternative to taking values in param, take clazz: Class + // - private val values = clazz.enumConstants + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor(values.first()::class.qualifiedName!!, PrimitiveKind.STRING) + + // Build maps for faster parsing, used @SerialName annotation if present, fall back to name + private val lookup = values.associateBy({ it }, { it.serialName }) + private val revLookup = values.associateBy { it.serialName } + + private val Enum.serialName: String + get() = this::class.java.getField(this.name).getAnnotation(SerialName::class.java)?.value + ?: name + + override fun serialize(encoder: Encoder, value: T) { + encoder.encodeString(lookup.getValue(value)) + } + + override fun deserialize(decoder: Decoder): T { + // only run 'decoder.decodeString()' once + return revLookup[decoder.decodeString()] + ?: defaultValue // map.getOrDefault is not available < API-24 + } +}