Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -43,7 +43,8 @@ sealed class ConfirmStripeIntentParamsFactory<out T : ConfirmStripeIntentParams>
): T

abstract fun create(
confirmationTokenId: String
confirmationTokenId: String,
radarOptions: RadarOptions?,
): T

fun create(
Expand Down Expand Up @@ -132,11 +133,13 @@ internal class ConfirmPaymentIntentParamsFactory(
}

override fun create(
confirmationTokenId: String
confirmationTokenId: String,
radarOptions: RadarOptions?
): ConfirmPaymentIntentParams {
return ConfirmPaymentIntentParams(
confirmationTokenId = confirmationTokenId,
clientSecret = clientSecret,
radarOptions = radarOptions,
)
}
}
Expand Down Expand Up @@ -183,11 +186,13 @@ internal class ConfirmSetupIntentParamsFactory(
}

override fun create(
confirmationTokenId: String
confirmationTokenId: String,
radarOptions: RadarOptions?
): ConfirmSetupIntentParams {
return ConfirmSetupIntentParams(
confirmationTokenId = confirmationTokenId,
clientSecret = clientSecret,
radarOptions = radarOptions,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.stripe.android.model.ConfirmationTokenParams
import com.stripe.android.model.DeferredIntentParams
import com.stripe.android.model.MandateDataParams
import com.stripe.android.model.PaymentMethodOptionsParams
import com.stripe.android.model.RadarOptions
import com.stripe.android.model.StripeIntent
import com.stripe.android.model.setupFutureUsage
import com.stripe.android.networking.StripeRepository
Expand Down Expand Up @@ -59,6 +60,9 @@ internal class ConfirmationTokenConfirmationInterceptor @AssistedInject construc
intent = intent,
confirmationToken = confirmationToken,
shippingValues = shippingValues,
// For new PM, radar options is attached in paymentMethodData.radarOptions if provided.
// hCaptchaToken = null here means we don't need to send a separate one when confirming the intent.
hCaptchaToken = null,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this null?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For new PM, radar options is attached in paymentMethodData.radarOptions if provided. hCaptchaToken = null here means we don't need to send radar options when confirming the intent. Sending radar options when confirming the intent is only required for saved PM.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably worth a code comment saying this!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yh, this is accurate. For new PMs, radar options is added to the create_params which will be transformed to paymentMethodData.radarOptions

You can find more details here

)
},
onFailure = { error ->
Expand Down Expand Up @@ -97,6 +101,7 @@ internal class ConfirmationTokenConfirmationInterceptor @AssistedInject construc
intent = intent,
confirmationToken = confirmationToken,
shippingValues = shippingValues,
hCaptchaToken = confirmationOption.hCaptchaToken,
)
},
onFailure = { error ->
Expand All @@ -113,6 +118,7 @@ internal class ConfirmationTokenConfirmationInterceptor @AssistedInject construc
intent: StripeIntent,
confirmationToken: ConfirmationToken,
shippingValues: ConfirmPaymentIntentParams.Shipping?,
hCaptchaToken: String?,
): ConfirmationDefinition.Action<Args> {
val result = createIntentCallback.onCreateIntent(confirmationToken)

Expand All @@ -129,6 +135,7 @@ internal class ConfirmationTokenConfirmationInterceptor @AssistedInject construc
clientSecret = result.clientSecret,
confirmationTokenId = confirmationToken.id,
shippingValues = shippingValues,
hCaptchaToken = hCaptchaToken,
)
}
}
Expand All @@ -149,6 +156,7 @@ internal class ConfirmationTokenConfirmationInterceptor @AssistedInject construc
clientSecret: String,
confirmationTokenId: String,
shippingValues: ConfirmPaymentIntentParams.Shipping?,
hCaptchaToken: String?,
): ConfirmationDefinition.Action<Args> {
return stripeRepository.retrieveStripeIntent(
clientSecret = clientSecret,
Expand All @@ -174,7 +182,8 @@ internal class ConfirmationTokenConfirmationInterceptor @AssistedInject construc
isDeferred = true
) {
create(
confirmationTokenId = confirmationTokenId
confirmationTokenId = confirmationTokenId,
radarOptions = hCaptchaToken?.let { RadarOptions(it) },
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.stripe.android.model.PaymentMethodCreateParamsFixtures
import com.stripe.android.model.PaymentMethodExtraParams
import com.stripe.android.model.PaymentMethodFixtures
import com.stripe.android.model.PaymentMethodOptionsParams
import com.stripe.android.model.RadarOptions
import com.stripe.android.model.StripeIntent
import com.stripe.android.model.parsers.ConfirmationTokenJsonParser
import com.stripe.android.networking.StripeRepository
Expand Down Expand Up @@ -51,6 +52,7 @@ import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.robolectric.RobolectricTestRunner
import javax.inject.Provider
import kotlin.test.assertNull

@Suppress("LargeClass")
@RunWith(RobolectricTestRunner::class)
Expand Down Expand Up @@ -577,6 +579,7 @@ class ConfirmationTokenConfirmationInterceptorTest {

private fun createFakeStripeRepositoryForConfirmationToken(
observedParams: Turbine<ConfirmationTokenParams> = Turbine(),
retrievedIntentStatus: StripeIntent.Status = StripeIntent.Status.Succeeded,
): StripeRepository {
return object : AbsFakeStripeRepository() {
override suspend fun createConfirmationToken(
Expand All @@ -592,7 +595,9 @@ class ConfirmationTokenConfirmationInterceptorTest {
options: ApiRequest.Options,
expandFields: List<String>
): Result<StripeIntent> {
return Result.success(PaymentIntentFixtures.PI_SUCCEEDED)
return Result.success(
PaymentIntentFixtures.PI_SUCCEEDED.copy(status = retrievedIntentStatus)
)
}
}
}
Expand Down Expand Up @@ -1105,16 +1110,67 @@ class ConfirmationTokenConfirmationInterceptorTest {
}
}

@Test
fun `Saved PM - includes radarOptions when hCaptchaToken is provided for CSC flow`() {
runConfirmationTokenInterceptorScenario(
retrievedIntentStatus = StripeIntent.Status.RequiresConfirmation,
) { interceptor ->
val confirmationOption = PaymentMethodConfirmationOption.Saved(
paymentMethod = PaymentMethodFixtures.CARD_PAYMENT_METHOD,
optionsParams = null,
passiveCaptchaParams = null,
hCaptchaToken = "test_hcaptcha_token_123",
)

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

assertThat(nextAction.asConfirmParams<ConfirmPaymentIntentParams>()?.radarOptions)
.isEqualTo(RadarOptions("test_hcaptcha_token_123"))
}
}

@Test
fun `Saved PM - excludes radarOptions when hCaptchaToken is null for CSC flow`() {
runConfirmationTokenInterceptorScenario(
retrievedIntentStatus = StripeIntent.Status.RequiresConfirmation,
) { interceptor ->
val confirmationOption = PaymentMethodConfirmationOption.Saved(
paymentMethod = PaymentMethodFixtures.CARD_PAYMENT_METHOD,
optionsParams = null,
passiveCaptchaParams = null,
hCaptchaToken = null,
)

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

assertThat(nextAction)
.isInstanceOf<ConfirmationDefinition.Action.Launch<IntentConfirmationDefinition.Args>>()
assertNull(nextAction.asConfirmParams<ConfirmPaymentIntentParams>()?.radarOptions)
}
}

private fun runConfirmationTokenInterceptorScenario(
observedParams: Turbine<ConfirmationTokenParams> = Turbine(),
retrievedIntentStatus: StripeIntent.Status = StripeIntent.Status.Succeeded,
initializationMode: PaymentElementLoader.InitializationMode = DEFAULT_DEFERRED_INTENT,
block: suspend (IntentConfirmationInterceptor) -> Unit
) {
runInterceptorScenario(
initializationMode = initializationMode,
scenario = InterceptorTestScenario(
ephemeralKeySecret = "ek_test_123",
stripeRepository = createFakeStripeRepositoryForConfirmationToken(observedParams),
stripeRepository = createFakeStripeRepositoryForConfirmationToken(
observedParams,
retrievedIntentStatus,
),
intentCreationConfirmationTokenCallbackProvider = Provider {
succeedingCreateIntentWithConfirmationTokenCallback(confirmationToken)
},
Expand Down
Loading