Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.stripe.android.paymentelement.confirmation.ConfirmationHandler
import com.stripe.android.paymentelement.confirmation.PaymentMethodConfirmationOption
import com.stripe.android.paymentelement.confirmation.intent.IntentConfirmationDefinition.Args
import com.stripe.android.paymentelement.confirmation.utils.ConfirmActionHelper
import com.stripe.android.paymentelement.confirmation.utils.toConfirmParamsSetupFutureUsage
import com.stripe.android.payments.DefaultReturnUrl
import com.stripe.android.paymentsheet.CreateIntentResult
import com.stripe.android.paymentsheet.PaymentSheet
Expand Down Expand Up @@ -210,7 +211,8 @@ internal class ConfirmationTokenConfirmationInterceptor @AssistedInject construc
returnUrl = DefaultReturnUrl.create(context).value,
paymentMethodId = (confirmationOption as? PaymentMethodConfirmationOption.Saved)?.paymentMethod?.id,
paymentMethodData = (confirmationOption as? PaymentMethodConfirmationOption.New)?.createParams,
setUpFutureUsage = confirmationOption.optionsParams?.setupFutureUsage(),
setUpFutureUsage = confirmationOption.optionsParams?.setupFutureUsage()
?: intentConfiguration.mode.setupFutureUse?.toConfirmParamsSetupFutureUsage(),
shipping = shippingValues,
mandateDataParams = MandateDataParams(MandateDataParams.Type.Online.DEFAULT).takeIf {
when (confirmationOption) {
Expand Down Expand Up @@ -246,7 +248,8 @@ internal class ConfirmationTokenConfirmationInterceptor @AssistedInject construc
mode = mode.code,
currency = mode.currency,
// Use paymentMethodOptions to correctly set PMO SFU value
setupFutureUsage = paymentMethodOptions?.setupFutureUsage(),
setupFutureUsage = paymentMethodOptions?.setupFutureUsage()
?: intentConfiguration.mode.setupFutureUse?.toConfirmParamsSetupFutureUsage(),
captureMethod = (mode as? DeferredIntentParams.Mode.Payment)?.captureMethod?.code,
paymentMethodTypes = paymentMethodTypes,
onBehalfOf = onBehalfOf,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -990,88 +990,118 @@ class ConfirmationTokenConfirmationInterceptorTest {
assertThat(observedParams[0].clientContext?.requireCvcRecollection).isEqualTo(true)
}

@OptIn(PaymentMethodOptionsSetupFutureUsagePreview::class)
@Test
fun `SFU priority - user checkbox takes highest priority over PMO SFU for New payment method`() {
val observedParams = Turbine<ConfirmationTokenParams>()
runConfirmationTokenInterceptorScenario(
observedParams = observedParams,
fun `ClientContext SFU uses intent SFU as fallback when no PMO SFU in test mode`() = runTest {
val observedParams = mutableListOf<ConfirmationTokenParams>()

val interceptor = createIntentConfirmationInterceptor(
ephemeralKeySecret = "ek_test_123",
publishableKeyProvider = { "pk_test_123" },
initializationMode = PaymentElementLoader.InitializationMode.DeferredIntent(
intentConfiguration = PaymentSheet.IntentConfiguration(
mode = PaymentSheet.IntentConfiguration.Mode.Payment(
amount = 1099L,
currency = "usd",
paymentMethodOptions = PaymentSheet.IntentConfiguration.Mode.Payment.PaymentMethodOptions(
mapOf(
PaymentMethod.Type.Card to PaymentSheet.IntentConfiguration.SetupFutureUse.OnSession
)
)
setupFutureUse = PaymentSheet.IntentConfiguration.SetupFutureUse.OnSession,
),
)
),
) { interceptor ->
val confirmationOption = PaymentMethodConfirmationOption.New(
createParams = PaymentMethodCreateParamsFixtures.DEFAULT_CARD,
optionsParams = PaymentMethodOptionsParams.Card(
setupFutureUsage = ConfirmPaymentIntentParams.SetupFutureUsage.OffSession
),
extraParams = null,
shouldSave = true,
passiveCaptchaParams = null,
)
stripeRepository = object : AbsFakeStripeRepository() {
override suspend fun createConfirmationToken(
confirmationTokenParams: ConfirmationTokenParams,
options: ApiRequest.Options
): Result<ConfirmationToken> {
observedParams += confirmationTokenParams
return Result.success(confirmationToken)
}

interceptor.intercept(
intent = PaymentIntentFactory.create(),
confirmationOption = confirmationOption,
shippingValues = null,
)
override suspend fun retrieveStripeIntent(
clientSecret: String,
options: ApiRequest.Options,
expandFields: List<String>
): Result<StripeIntent> {
return Result.success(PaymentIntentFixtures.PI_SUCCEEDED)
}
},
intentCreationConfirmationTokenCallbackProvider = Provider {
CreateIntentWithConfirmationTokenCallback { _ ->
CreateIntentResult.Success("pi_123_secret_456")
}
},
)

// User checkbox sets OffSession, should not be overridden by PMO SFU
assertThat(observedParams.awaitItem().setUpFutureUsage)
.isEqualTo(ConfirmPaymentIntentParams.SetupFutureUsage.OffSession)
}
val confirmationOption = PaymentMethodConfirmationOption.New(
createParams = PaymentMethodCreateParamsFixtures.DEFAULT_CARD,
optionsParams = null,
extraParams = null,
shouldSave = false,
passiveCaptchaParams = null,
)

interceptor.intercept(
intent = PaymentIntentFactory.create(),
confirmationOption = confirmationOption,
shippingValues = null,
)

assertThat(observedParams).hasSize(1)
// ClientContext should use intent SFU when no PMO SFU is provided
assertThat(observedParams[0].clientContext?.setupFutureUsage)
.isEqualTo(ConfirmPaymentIntentParams.SetupFutureUsage.OnSession)
}

@Test
@OptIn(PaymentMethodOptionsSetupFutureUsagePreview::class)
fun `SFU priority - user checkbox takes highest priority over PMO SFU for New payment method`() {
runSfuPriorityTest(
pmoSfu = PaymentSheet.IntentConfiguration.SetupFutureUse.OnSession,
userCheckbox = true,
expectedSfu = ConfirmPaymentIntentParams.SetupFutureUsage.OffSession
)
}

@Test
@OptIn(PaymentMethodOptionsSetupFutureUsagePreview::class)
fun `SFU priority - PMO SFU used when no user checkbox for New payment method`() {
val observedParams = Turbine<ConfirmationTokenParams>()
runConfirmationTokenInterceptorScenario(
observedParams = observedParams,
initializationMode = PaymentElementLoader.InitializationMode.DeferredIntent(
intentConfiguration = PaymentSheet.IntentConfiguration(
mode = PaymentSheet.IntentConfiguration.Mode.Payment(
amount = 1099L,
currency = "usd",
paymentMethodOptions = PaymentSheet.IntentConfiguration.Mode.Payment.PaymentMethodOptions(
mapOf(
PaymentMethod.Type.Card to PaymentSheet.IntentConfiguration.SetupFutureUse.OffSession
)
)
),
)
),
) { interceptor ->
interceptor.interceptDefaultNewPaymentMethod()
runSfuPriorityTest(
pmoSfu = PaymentSheet.IntentConfiguration.SetupFutureUse.OffSession,
expectedSfu = ConfirmPaymentIntentParams.SetupFutureUsage.OffSession
)
}

// No user checkbox, should use PMO SFU from IntentConfiguration
assertThat(observedParams.awaitItem().setUpFutureUsage)
.isEqualTo(ConfirmPaymentIntentParams.SetupFutureUsage.OffSession)
}
@Test
@OptIn(PaymentMethodOptionsSetupFutureUsagePreview::class)
fun `SFU priority - user checkbox takes priority over intent SFU`() {
runSfuPriorityTest(
intentSfu = PaymentSheet.IntentConfiguration.SetupFutureUse.OnSession,
userCheckbox = true,
expectedSfu = ConfirmPaymentIntentParams.SetupFutureUsage.OffSession
)
}

@Test
fun `SFU priority - no SFU when no user checkbox and no PMO SFU`() {
val observedParams = Turbine<ConfirmationTokenParams>()
runConfirmationTokenInterceptorScenario(
observedParams = observedParams,
initializationMode = DEFAULT_DEFERRED_INTENT,
) { interceptor ->
interceptor.interceptDefaultNewPaymentMethod()
@OptIn(PaymentMethodOptionsSetupFutureUsagePreview::class)
fun `SFU priority - PMO SFU takes priority over intent SFU`() {
runSfuPriorityTest(
intentSfu = PaymentSheet.IntentConfiguration.SetupFutureUse.OnSession,
pmoSfu = PaymentSheet.IntentConfiguration.SetupFutureUse.OffSession,
expectedSfu = ConfirmPaymentIntentParams.SetupFutureUsage.OffSession
)
}

// No user checkbox, no PMO SFU
assertThat(observedParams.awaitItem().setUpFutureUsage).isNull()
}
@Test
fun `SFU priority - intent SFU used as fallback when no user checkbox and no PMO SFU`() {
runSfuPriorityTest(
intentSfu = PaymentSheet.IntentConfiguration.SetupFutureUse.OnSession,
expectedSfu = ConfirmPaymentIntentParams.SetupFutureUsage.OnSession
)
}

@Test
fun `SFU priority - no SFU when no user checkbox, no PMO SFU, and no intent SFU`() {
runSfuPriorityTest(
expectedSfu = null
)
}

@Test
Expand Down Expand Up @@ -1312,4 +1342,64 @@ class ConfirmationTokenConfirmationInterceptorTest {
test = block
)
}

/**
* Helper to test SFU priority scenarios with cleaner syntax.
*
* @param intentSfu Setup future usage on the intent configuration
* @param pmoSfu Setup future usage in payment method options (PMO)
* @param userCheckbox Whether user checked the "save for future use" checkbox (highest priority)
* @param expectedSfu Expected SFU result
*/
@OptIn(PaymentMethodOptionsSetupFutureUsagePreview::class)
private fun runSfuPriorityTest(
intentSfu: PaymentSheet.IntentConfiguration.SetupFutureUse? = null,
pmoSfu: PaymentSheet.IntentConfiguration.SetupFutureUse? = null,
userCheckbox: Boolean = false,
expectedSfu: ConfirmPaymentIntentParams.SetupFutureUsage?,
) {
val observedParams = Turbine<ConfirmationTokenParams>()

// Build payment mode with optional PMO
val paymentMode = PaymentSheet.IntentConfiguration.Mode.Payment(
amount = 1099L,
currency = "usd",
setupFutureUse = intentSfu,
paymentMethodOptions = pmoSfu?.let {
PaymentSheet.IntentConfiguration.Mode.Payment.PaymentMethodOptions(
mapOf(PaymentMethod.Type.Card to it)
)
}
)

runConfirmationTokenInterceptorScenario(
observedParams = observedParams,
initializationMode = PaymentElementLoader.InitializationMode.DeferredIntent(
intentConfiguration = PaymentSheet.IntentConfiguration(mode = paymentMode)
),
) { interceptor ->
if (userCheckbox) {
// Test with user checkbox checked (always OffSession)
val confirmationOption = PaymentMethodConfirmationOption.New(
createParams = PaymentMethodCreateParamsFixtures.DEFAULT_CARD,
optionsParams = PaymentMethodOptionsParams.Card(
setupFutureUsage = ConfirmPaymentIntentParams.SetupFutureUsage.OffSession
),
extraParams = null,
shouldSave = true,
passiveCaptchaParams = null,
)
interceptor.intercept(
intent = PaymentIntentFactory.create(),
confirmationOption = confirmationOption,
shippingValues = null,
)
} else {
// Test without user checkbox
interceptor.interceptDefaultNewPaymentMethod()
}

assertThat(observedParams.awaitItem().setUpFutureUsage).isEqualTo(expectedSfu)
}
}
}
Loading