diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/FinancialConnectionsSheetViewModel.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/FinancialConnectionsSheetViewModel.kt index 1fde5080edd..f9201c22bf4 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/FinancialConnectionsSheetViewModel.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/FinancialConnectionsSheetViewModel.kt @@ -461,7 +461,9 @@ internal class FinancialConnectionsSheetViewModel @Inject constructor( instantDebits = InstantDebitsResult( encodedPaymentMethod = paymentMethod, last4 = url.getQueryParameter(QUERY_PARAM_LAST4), - bankName = url.getQueryParameter(QUERY_BANK_NAME) + bankName = url.getQueryParameter(QUERY_BANK_NAME), + // TODO(tillh-stripe): Pull this from the URL + eligibleForIncentive = false, ), financialConnectionsSession = null, token = null diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/domain/CreateInstantDebitsResult.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/domain/CreateInstantDebitsResult.kt index 887ee336bf2..53900750d6d 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/domain/CreateInstantDebitsResult.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/domain/CreateInstantDebitsResult.kt @@ -58,10 +58,26 @@ internal class RealCreateInstantDebitsResult @Inject constructor( ) } + val incentiveEligibilitySessionId = elementsSessionContext?.incentiveEligibilitySession?.id + + val eligibleForIncentive = if (incentiveEligibilitySessionId != null) { + val availableIncentives = runCatching { + consumerRepository.updateAvailableIncentives( + sessionId = incentiveEligibilitySessionId, + consumerSessionClientSecret = clientSecret, + ) + }.getOrNull() + + availableIncentives?.data?.isNotEmpty() == true + } else { + false + } + return InstantDebitsResult( encodedPaymentMethod = paymentMethod, bankName = paymentDetails.bankName, last4 = paymentDetails.last4, + eligibleForIncentive = eligibleForIncentive, ) } } diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/launcher/FinancialConnectionsSheetActivityResult.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/launcher/FinancialConnectionsSheetActivityResult.kt index f604d0c9088..8447f33bf61 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/launcher/FinancialConnectionsSheetActivityResult.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/launcher/FinancialConnectionsSheetActivityResult.kt @@ -56,5 +56,6 @@ internal sealed class FinancialConnectionsSheetActivityResult : Parcelable { internal data class InstantDebitsResult( val encodedPaymentMethod: String, val last4: String?, - val bankName: String? + val bankName: String?, + val eligibleForIncentive: Boolean, ) : Parcelable diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/launcher/FinancialConnectionsSheetForInstantDebitsContract.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/launcher/FinancialConnectionsSheetForInstantDebitsContract.kt index 1c01cd40236..4ba4807f79e 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/launcher/FinancialConnectionsSheetForInstantDebitsContract.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/launcher/FinancialConnectionsSheetForInstantDebitsContract.kt @@ -45,6 +45,7 @@ class FinancialConnectionsSheetForInstantDebitsContract : encodedPaymentMethod = instantDebits.encodedPaymentMethod, last4 = instantDebits.last4, bankName = instantDebits.bankName, + eligibleForIncentive = instantDebits.eligibleForIncentive, ) } } diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/launcher/FinancialConnectionsSheetInstantDebitsResult.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/launcher/FinancialConnectionsSheetInstantDebitsResult.kt index f7ff5b45e16..178ab2266e9 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/launcher/FinancialConnectionsSheetInstantDebitsResult.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/launcher/FinancialConnectionsSheetInstantDebitsResult.kt @@ -11,16 +11,14 @@ import kotlinx.parcelize.Parcelize sealed class FinancialConnectionsSheetInstantDebitsResult : Parcelable { /** * The customer completed the connections session. - * @param paymentMethodId The payment method id, that can be used to confirm the payment. - * @param last4 The last 4 digits of the bank account. - * @param bankName The name of the bank. */ @Parcelize @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) data class Completed( val encodedPaymentMethod: String, val last4: String?, - val bankName: String? + val bankName: String?, + val eligibleForIncentive: Boolean, ) : FinancialConnectionsSheetInstantDebitsResult() /** diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/FinancialConnectionsConsumerSessionRepository.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/FinancialConnectionsConsumerSessionRepository.kt index 80378469a0e..53f2ec7d591 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/FinancialConnectionsConsumerSessionRepository.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/FinancialConnectionsConsumerSessionRepository.kt @@ -17,6 +17,7 @@ import com.stripe.android.model.ConsumerSessionSignup import com.stripe.android.model.ConsumerSignUpConsentAction.EnteredPhoneNumberClickedSaveToLink import com.stripe.android.model.CustomEmailType import com.stripe.android.model.SharePaymentDetails +import com.stripe.android.model.UpdateAvailableIncentives import com.stripe.android.model.VerificationType import com.stripe.android.repository.ConsumersApiService import kotlinx.coroutines.sync.Mutex @@ -69,6 +70,11 @@ internal interface FinancialConnectionsConsumerSessionRepository { billingPhone: String?, ): SharePaymentDetails + suspend fun updateAvailableIncentives( + sessionId: String, + consumerSessionClientSecret: String, + ): UpdateAvailableIncentives + companion object { operator fun invoke( consumersApiService: ConsumersApiService, @@ -238,6 +244,18 @@ private class FinancialConnectionsConsumerSessionRepositoryImpl( ).getOrThrow() } + override suspend fun updateAvailableIncentives( + sessionId: String, + consumerSessionClientSecret: String, + ): UpdateAvailableIncentives { + return consumersApiService.updateAvailableIncentives( + sessionId = sessionId, + consumerSessionClientSecret = consumerSessionClientSecret, + requestSurface = requestSurface, + requestOptions = provideApiRequestOptions(useConsumerPublishableKey = true), + ).getOrThrow() + } + private suspend fun postConsumerSession( email: String, clientSecret: String @@ -266,6 +284,9 @@ private class FinancialConnectionsConsumerSessionRepositoryImpl( signup: ConsumerSessionSignup, ) { logger.debug("SYNC_CACHE: updating local consumer session from signUp") - consumerSessionRepository.storeNewConsumerSession(signup.consumerSession, signup.publishableKey) + consumerSessionRepository.storeNewConsumerSession( + consumerSession = signup.consumerSession, + publishableKey = signup.publishableKey, + ) } } diff --git a/payments-core/src/main/java/com/stripe/android/payments/bankaccount/navigation/CollectBankAccountForInstantDebitsResult.kt b/payments-core/src/main/java/com/stripe/android/payments/bankaccount/navigation/CollectBankAccountForInstantDebitsResult.kt index 2136abd6d46..35624a531ff 100644 --- a/payments-core/src/main/java/com/stripe/android/payments/bankaccount/navigation/CollectBankAccountForInstantDebitsResult.kt +++ b/payments-core/src/main/java/com/stripe/android/payments/bankaccount/navigation/CollectBankAccountForInstantDebitsResult.kt @@ -18,7 +18,8 @@ sealed interface CollectBankAccountForInstantDebitsResult : Parcelable { val intent: StripeIntent?, val paymentMethod: PaymentMethod, val last4: String?, - val bankName: String? + val bankName: String?, + val eligibleForIncentive: Boolean, ) : CollectBankAccountForInstantDebitsResult @Parcelize @@ -51,7 +52,8 @@ internal fun CollectBankAccountResultInternal.toInstantDebitsResult(): CollectBa intent = response.intent, paymentMethod = response.instantDebitsData.paymentMethod, last4 = response.instantDebitsData.last4, - bankName = response.instantDebitsData.bankName + bankName = response.instantDebitsData.bankName, + eligibleForIncentive = response.instantDebitsData.eligibleForIncentive, ) } } diff --git a/payments-core/src/main/java/com/stripe/android/payments/bankaccount/navigation/CollectBankAccountResultInternal.kt b/payments-core/src/main/java/com/stripe/android/payments/bankaccount/navigation/CollectBankAccountResultInternal.kt index 714b5eedb82..74f9645a0a8 100644 --- a/payments-core/src/main/java/com/stripe/android/payments/bankaccount/navigation/CollectBankAccountResultInternal.kt +++ b/payments-core/src/main/java/com/stripe/android/payments/bankaccount/navigation/CollectBankAccountResultInternal.kt @@ -54,6 +54,7 @@ data class CollectBankAccountResponseInternal( data class InstantDebitsData( val paymentMethod: PaymentMethod, val last4: String?, - val bankName: String? + val bankName: String?, + val eligibleForIncentive: Boolean, ) : StripeModel } diff --git a/payments-core/src/main/java/com/stripe/android/payments/bankaccount/ui/CollectBankAccountViewModel.kt b/payments-core/src/main/java/com/stripe/android/payments/bankaccount/ui/CollectBankAccountViewModel.kt index 017c5c6b9a1..ecdafa96f6b 100644 --- a/payments-core/src/main/java/com/stripe/android/payments/bankaccount/ui/CollectBankAccountViewModel.kt +++ b/payments-core/src/main/java/com/stripe/android/payments/bankaccount/ui/CollectBankAccountViewModel.kt @@ -200,6 +200,7 @@ internal class CollectBankAccountViewModel @Inject constructor( paymentMethod = it, last4 = result.last4, bankName = result.bankName, + eligibleForIncentive = result.eligibleForIncentive, ) }, ) diff --git a/payments-model/src/main/java/com/stripe/android/model/LinkConsumerIncentive.kt b/payments-model/src/main/java/com/stripe/android/model/LinkConsumerIncentive.kt index e3364fb92f1..4958027219c 100644 --- a/payments-model/src/main/java/com/stripe/android/model/LinkConsumerIncentive.kt +++ b/payments-model/src/main/java/com/stripe/android/model/LinkConsumerIncentive.kt @@ -3,9 +3,11 @@ package com.stripe.android.model import androidx.annotation.RestrictTo import com.stripe.android.core.model.StripeModel import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Serializable @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Parcelize +@Serializable data class LinkConsumerIncentive( val incentiveParams: IncentiveParams, val incentiveDisplayText: String?, @@ -13,6 +15,7 @@ data class LinkConsumerIncentive( @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Parcelize + @Serializable data class IncentiveParams( val paymentMethod: String, ) : StripeModel diff --git a/payments-model/src/main/java/com/stripe/android/model/UpdateAvailableIncentives.kt b/payments-model/src/main/java/com/stripe/android/model/UpdateAvailableIncentives.kt new file mode 100644 index 00000000000..d3e41f9698d --- /dev/null +++ b/payments-model/src/main/java/com/stripe/android/model/UpdateAvailableIncentives.kt @@ -0,0 +1,14 @@ +package com.stripe.android.model + +import androidx.annotation.RestrictTo +import com.stripe.android.core.model.StripeModel +import kotlinx.parcelize.Parcelize +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +@Parcelize +@Serializable +data class UpdateAvailableIncentives( + @SerialName("data") val data: List, +) : StripeModel diff --git a/payments-model/src/main/java/com/stripe/android/model/parsers/UpdateAvailableIncentivesJsonParser.kt b/payments-model/src/main/java/com/stripe/android/model/parsers/UpdateAvailableIncentivesJsonParser.kt new file mode 100644 index 00000000000..0b060f48013 --- /dev/null +++ b/payments-model/src/main/java/com/stripe/android/model/parsers/UpdateAvailableIncentivesJsonParser.kt @@ -0,0 +1,22 @@ +package com.stripe.android.model.parsers + +import androidx.annotation.RestrictTo +import com.stripe.android.core.model.parsers.ModelJsonParser +import com.stripe.android.model.UpdateAvailableIncentives +import org.json.JSONObject + +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +object UpdateAvailableIncentivesJsonParser : ModelJsonParser { + + override fun parse(json: JSONObject): UpdateAvailableIncentives? { + val incentives = json.optJSONArray("data")?.let { data -> + List(data.length()) { index -> + LinkConsumerIncentiveJsonParser.parse(data.getJSONObject(index)) + } + } + + return incentives?.let { + UpdateAvailableIncentives(data = it.filterNotNull()) + } + } +} diff --git a/payments-model/src/main/java/com/stripe/android/repository/ConsumersApiService.kt b/payments-model/src/main/java/com/stripe/android/repository/ConsumersApiService.kt index 343faa09b4e..1daa58e62cf 100644 --- a/payments-model/src/main/java/com/stripe/android/repository/ConsumersApiService.kt +++ b/payments-model/src/main/java/com/stripe/android/repository/ConsumersApiService.kt @@ -18,6 +18,7 @@ import com.stripe.android.model.ConsumerSignUpConsentAction import com.stripe.android.model.CustomEmailType import com.stripe.android.model.IncentiveEligibilitySession import com.stripe.android.model.SharePaymentDetails +import com.stripe.android.model.UpdateAvailableIncentives import com.stripe.android.model.VerificationType import com.stripe.android.model.parsers.AttachConsumerToLinkAccountSessionJsonParser import com.stripe.android.model.parsers.ConsumerPaymentDetailsJsonParser @@ -25,6 +26,7 @@ import com.stripe.android.model.parsers.ConsumerSessionJsonParser import com.stripe.android.model.parsers.ConsumerSessionLookupJsonParser import com.stripe.android.model.parsers.ConsumerSessionSignupJsonParser import com.stripe.android.model.parsers.SharePaymentDetailsJsonParser +import com.stripe.android.model.parsers.UpdateAvailableIncentivesJsonParser import java.util.Locale @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @@ -91,6 +93,13 @@ interface ConsumersApiService { requestOptions: ApiRequest.Options, extraParams: Map, ): Result + + suspend fun updateAvailableIncentives( + sessionId: String, + consumerSessionClientSecret: String, + requestSurface: String, + requestOptions: ApiRequest.Options, + ): Result } @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @@ -314,6 +323,30 @@ class ConsumersApiServiceImpl( ) } + override suspend fun updateAvailableIncentives( + sessionId: String, + consumerSessionClientSecret: String, + requestSurface: String, + requestOptions: ApiRequest.Options, + ): Result { + return executeRequestWithResultParser( + stripeErrorJsonParser = stripeErrorJsonParser, + stripeNetworkClient = stripeNetworkClient, + request = apiRequestFactory.createPost( + url = updateAvailableIncentivesUrl, + options = requestOptions, + params = mapOf( + "request_surface" to requestSurface, + "session_id" to sessionId, + "credentials" to mapOf( + "consumer_session_client_secret" to consumerSessionClientSecret + ), + ), + ), + responseJsonParser = UpdateAvailableIncentivesJsonParser, + ) + } + internal companion object { /** @@ -356,6 +389,11 @@ class ConsumersApiServiceImpl( */ private val sharePaymentDetails: String = getApiUrl("consumers/payment_details/share") + /** + * @return `https://api.stripe.com/v1/consumers/incentives/update_available` + */ + private val updateAvailableIncentivesUrl: String = getApiUrl("consumers/incentives/update_available") + private fun getApiUrl(path: String): String { return "${ApiRequest.API_HOST}/v1/$path" } diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/ach/USBankAccountFormViewModel.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/ach/USBankAccountFormViewModel.kt index ac00609691b..a292e6e9b27 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/ach/USBankAccountFormViewModel.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/ach/USBankAccountFormViewModel.kt @@ -354,6 +354,7 @@ internal class USBankAccountFormViewModel @Inject internal constructor( financialConnectionsSessionId = null, mandateText = buildMandateText(isVerifyWithMicrodeposits = false), isVerifyingWithMicrodeposits = false, + eligibleForIncentive = result.eligibleForIncentive, ) ) }