Skip to content

Commit

Permalink
Remove CVC Feature Flag and RestrictTo annotations (#8717)
Browse files Browse the repository at this point in the history
* Remove CVC Feature Flag and RestrictTo annotations

* API Dump

* Update CHANGELOG.md
  • Loading branch information
tjclawson-stripe authored Jul 1, 2024
1 parent 44ab701 commit f6131fd
Show file tree
Hide file tree
Showing 11 changed files with 19 additions and 120 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## XX.XX.XX - 20XX-XX-XX

### PaymentSheet
* [ADDED][8717](https://github.com/stripe/stripe-android/pull/8717) Add CVC Recollection functionality to `PaymentSheet` and `PaymentSheet.FlowController`

## 20.47.4 - 2024-06-27

### PaymentSheet
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.stripe.android.paymentsheet.example.playground.settings

import com.stripe.android.core.utils.FeatureFlags
import com.stripe.android.paymentsheet.example.playground.model.CheckoutRequest

internal object RequireCvcRecollectionDefinition : BooleanSettingsDefinition(
Expand All @@ -14,6 +13,5 @@ internal object RequireCvcRecollectionDefinition : BooleanSettingsDefinition(

override fun configure(value: Boolean, checkoutRequestBuilder: CheckoutRequest.Builder) {
checkoutRequestBuilder.requireCvcRecollection(value)
FeatureFlags.cvcRecollection.setEnabled(value)
}
}
10 changes: 10 additions & 0 deletions paymentsheet/api/paymentsheet.api
Original file line number Diff line number Diff line change
Expand Up @@ -248,9 +248,16 @@ public final class com/stripe/android/paymentsheet/CreateIntentResult$Success :
public fun <init> (Ljava/lang/String;)V
}

public abstract interface class com/stripe/android/paymentsheet/CvcRecollectionEnabledCallback {
public abstract fun isCvcRecollectionRequired ()Z
}

public abstract interface annotation class com/stripe/android/paymentsheet/DelicatePaymentSheetApi : java/lang/annotation/Annotation {
}

public abstract interface annotation class com/stripe/android/paymentsheet/ExperimentalCvcRecollectionApi : java/lang/annotation/Annotation {
}

public abstract interface annotation class com/stripe/android/paymentsheet/ExperimentalPaymentSheetDecouplingApi : java/lang/annotation/Annotation {
}

Expand Down Expand Up @@ -561,6 +568,7 @@ public final class com/stripe/android/paymentsheet/PaymentSheet$Builder {
public final fun build (Landroidx/compose/runtime/Composer;I)Lcom/stripe/android/paymentsheet/PaymentSheet;
public final fun build (Landroidx/fragment/app/Fragment;)Lcom/stripe/android/paymentsheet/PaymentSheet;
public final fun createIntentCallback (Lcom/stripe/android/paymentsheet/CreateIntentCallback;)Lcom/stripe/android/paymentsheet/PaymentSheet$Builder;
public final fun cvcRecollectionEnabledCallback (Lcom/stripe/android/paymentsheet/CvcRecollectionEnabledCallback;)Lcom/stripe/android/paymentsheet/PaymentSheet$Builder;
public final fun externalPaymentMethodConfirmHandler (Lcom/stripe/android/paymentsheet/ExternalPaymentMethodConfirmHandler;)Lcom/stripe/android/paymentsheet/PaymentSheet$Builder;
}

Expand Down Expand Up @@ -681,6 +689,7 @@ public final class com/stripe/android/paymentsheet/PaymentSheet$Configuration$Bu
public final fun externalPaymentMethods (Ljava/util/List;)Lcom/stripe/android/paymentsheet/PaymentSheet$Configuration$Builder;
public final fun googlePay (Lcom/stripe/android/paymentsheet/PaymentSheet$GooglePayConfiguration;)Lcom/stripe/android/paymentsheet/PaymentSheet$Configuration$Builder;
public final fun merchantDisplayName (Ljava/lang/String;)Lcom/stripe/android/paymentsheet/PaymentSheet$Configuration$Builder;
public final fun paymentMethodLayout (Lcom/stripe/android/paymentsheet/PaymentSheet$PaymentMethodLayout;)Lcom/stripe/android/paymentsheet/PaymentSheet$Configuration$Builder;
public final fun paymentMethodOrder (Ljava/util/List;)Lcom/stripe/android/paymentsheet/PaymentSheet$Configuration$Builder;
public final fun preferredNetworks (Ljava/util/List;)Lcom/stripe/android/paymentsheet/PaymentSheet$Configuration$Builder;
public final fun primaryButtonColor (Landroid/content/res/ColorStateList;)Lcom/stripe/android/paymentsheet/PaymentSheet$Configuration$Builder;
Expand Down Expand Up @@ -764,6 +773,7 @@ public final class com/stripe/android/paymentsheet/PaymentSheet$FlowController$B
public final fun build (Landroidx/compose/runtime/Composer;I)Lcom/stripe/android/paymentsheet/PaymentSheet$FlowController;
public final fun build (Landroidx/fragment/app/Fragment;)Lcom/stripe/android/paymentsheet/PaymentSheet$FlowController;
public final fun createIntentCallback (Lcom/stripe/android/paymentsheet/CreateIntentCallback;)Lcom/stripe/android/paymentsheet/PaymentSheet$FlowController$Builder;
public final fun cvcRecollectionEnabledCallback (Lcom/stripe/android/paymentsheet/CvcRecollectionEnabledCallback;)Lcom/stripe/android/paymentsheet/PaymentSheet$FlowController$Builder;
public final fun externalPaymentMethodConfirmHandler (Lcom/stripe/android/paymentsheet/ExternalPaymentMethodConfirmHandler;)Lcom/stripe/android/paymentsheet/PaymentSheet$FlowController$Builder;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
package com.stripe.android.paymentsheet

import androidx.annotation.RestrictTo

/**
* Callback to be used when you use [PaymentSheet] or [PaymentSheet.FlowController] and intend to
* create a [PaymentIntent] on your server and confirm on the client.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@ExperimentalCvcRecollectionApi
fun interface CvcRecollectionEnabledCallback {
/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package com.stripe.android.paymentsheet

import androidx.annotation.RestrictTo

@RequiresOptIn(
level = RequiresOptIn.Level.ERROR,
message = "This API is under construction. It can be changed or removed at any time (use at your own risk)"
)
@Retention(AnnotationRetention.BINARY)
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
annotation class ExperimentalCvcRecollectionApi
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,6 @@ class PaymentSheet internal constructor(
* CVC recollection field.
*
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@ExperimentalCvcRecollectionApi
fun cvcRecollectionEnabledCallback(callback: CvcRecollectionEnabledCallback) = apply {
cvcRecollectionEnabledCallback = callback
Expand Down Expand Up @@ -851,7 +850,6 @@ class PaymentSheet internal constructor(
this.externalPaymentMethods = externalPaymentMethods
}

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@ExperimentalPaymentMethodLayoutApi
fun paymentMethodLayout(paymentMethodLayout: PaymentMethodLayout): Builder = apply {
this.paymentMethodLayout = paymentMethodLayout
Expand Down Expand Up @@ -1743,7 +1741,6 @@ class PaymentSheet internal constructor(
* CVC recollection field.
*
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@ExperimentalCvcRecollectionApi
fun cvcRecollectionEnabledCallback(callback: CvcRecollectionEnabledCallback) = apply {
cvcRecollectionEnabledCallback = callback
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import com.stripe.android.common.exception.stripeErrorMessage
import com.stripe.android.core.Logger
import com.stripe.android.core.injection.IOContext
import com.stripe.android.core.strings.resolvableString
import com.stripe.android.core.utils.FeatureFlags
import com.stripe.android.core.utils.requireApplication
import com.stripe.android.googlepaylauncher.GooglePayEnvironment
import com.stripe.android.googlepaylauncher.GooglePayPaymentMethodLauncher
Expand Down Expand Up @@ -880,12 +879,12 @@ internal class PaymentSheetViewModel @Inject internal constructor(
}
}

internal fun isCvcRecollectionEnabled(): Boolean = FeatureFlags.cvcRecollection.isEnabled &&
internal fun isCvcRecollectionEnabled(): Boolean =
((paymentMethodMetadata.value?.stripeIntent as? PaymentIntent)?.requireCvcRecollection == true)

internal fun isCvcRecollectionEnabledForDeferred(): Boolean = FeatureFlags.cvcRecollection.isEnabled &&
internal fun isCvcRecollectionEnabledForDeferred(): Boolean =
CvcRecollectionCallbackHandler.isCvcRecollectionEnabledForDeferredIntent() &&
args.initializationMode is PaymentSheet.InitializationMode.DeferredIntent
args.initializationMode is PaymentSheet.InitializationMode.DeferredIntent

private fun mapViewStateToCheckoutIdentifier(
viewState: PaymentSheetViewState?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStoreOwner
import com.stripe.android.PaymentConfiguration
import com.stripe.android.core.injection.ENABLE_LOGGING
import com.stripe.android.core.utils.FeatureFlags
import com.stripe.android.googlepaylauncher.GooglePayEnvironment
import com.stripe.android.googlepaylauncher.GooglePayPaymentMethodLauncher
import com.stripe.android.googlepaylauncher.GooglePayPaymentMethodLauncherContractV2
Expand Down Expand Up @@ -413,8 +412,7 @@ internal class DefaultFlowController @Inject internal constructor(
}

private fun isCvcRecollectionEnabled(state: PaymentSheetState.Full): Boolean {
return FeatureFlags.cvcRecollection.isEnabled &&
(state.stripeIntent as? PaymentIntent)?.requireCvcRecollection == true ||
return (state.stripeIntent as? PaymentIntent)?.requireCvcRecollection == true ||
(
CvcRecollectionCallbackHandler.isCvcRecollectionEnabledForDeferredIntent() &&
initializationMode is PaymentSheet.InitializationMode.DeferredIntent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import com.stripe.android.core.StripeError
import com.stripe.android.core.exception.APIException
import com.stripe.android.core.networking.AnalyticsRequestFactory
import com.stripe.android.core.strings.resolvableString
import com.stripe.android.core.utils.FeatureFlags
import com.stripe.android.googlepaylauncher.GooglePayPaymentMethodLauncher
import com.stripe.android.googlepaylauncher.injection.GooglePayPaymentMethodLauncherFactory
import com.stripe.android.link.LinkActivityResult
Expand Down Expand Up @@ -94,7 +93,6 @@ import com.stripe.android.paymentsheet.viewmodels.BaseSheetViewModel
import com.stripe.android.paymentsheet.viewmodels.BaseSheetViewModel.Companion.SAVE_PROCESSING
import com.stripe.android.paymentsheet.viewmodels.BaseSheetViewModel.UserErrorMessage
import com.stripe.android.testing.FakeErrorReporter
import com.stripe.android.testing.FeatureFlagTestRule
import com.stripe.android.testing.PaymentIntentFactory
import com.stripe.android.testing.PaymentMethodFactory
import com.stripe.android.testing.SessionTestRule
Expand Down Expand Up @@ -144,12 +142,6 @@ internal class PaymentSheetViewModelTest {
@get:Rule
val sessionRule = SessionTestRule()

@get:Rule
val cvcRecollectionFeatureRule = FeatureFlagTestRule(
featureFlag = FeatureFlags.cvcRecollection,
isEnabled = true
)

@get:Rule
val intentConfirmationInterceptorTestRule = IntentConfirmationInterceptorTestRule()

Expand Down Expand Up @@ -2816,7 +2808,7 @@ internal class PaymentSheetViewModelTest {
}

@Test
fun `getCvcRecollectionState returns correct screen for complete flow`() = runTest {
fun `getCvcRecollectionState returns correct state for complete flow`() = runTest {
var stripeIntent = PaymentIntentFactory.create(
paymentMethodOptionsJsonString = getPaymentMethodOptionJsonStringWithCvcRecollectionValue(true)
)
Expand All @@ -2840,7 +2832,7 @@ internal class PaymentSheetViewModelTest {

@OptIn(ExperimentalCvcRecollectionApi::class)
@Test
fun `getCvcRecollectionState returns correct screen for deferred flow`() = runTest {
fun `getCvcRecollectionState returns correct state for deferred flow`() = runTest {
var enabled = false
CvcRecollectionCallbackHandler.isCvcRecollectionEnabledCallback = CvcRecollectionEnabledCallback { enabled }
val viewModel = createViewModel(args = ARGS_DEFERRED_INTENT)
Expand All @@ -2859,32 +2851,6 @@ internal class PaymentSheetViewModelTest {
.isInstanceOf(SelectSavedPaymentMethods.CvcRecollectionState.NotRequired::class.java)
}

@Test
fun `getCvcRecollectionState returns NotRequired for deferred when feature flag is false`() = runTest {
cvcRecollectionFeatureRule.setEnabled(false)

val stripeIntent = PaymentIntentFactory.create(
paymentMethodOptionsJsonString = getPaymentMethodOptionJsonStringWithCvcRecollectionValue(true)
)
val viewModel = createViewModel(
stripeIntent = stripeIntent
)

assertThat(viewModel.getCvcRecollectionState())
.isInstanceOf(SelectSavedPaymentMethods.CvcRecollectionState.NotRequired::class.java)
}

@OptIn(ExperimentalCvcRecollectionApi::class)
@Test
fun `getCvcRecollectionState returns NotRequired for complete flow when feature flag is false`() = runTest {
cvcRecollectionFeatureRule.setEnabled(false)
CvcRecollectionCallbackHandler.isCvcRecollectionEnabledCallback = CvcRecollectionEnabledCallback { true }
val viewModel = createViewModel(args = ARGS_DEFERRED_INTENT)

assertThat(viewModel.getCvcRecollectionState())
.isInstanceOf(SelectSavedPaymentMethods.CvcRecollectionState.NotRequired::class.java)
}

private suspend fun testProcessDeathRestorationAfterPaymentSuccess(loadStateBeforePaymentResult: Boolean) {
val stripeIntent = PaymentIntentFactory.create(status = StripeIntent.Status.Succeeded)
val savedStateHandle = SavedStateHandle(initialState = mapOf("AwaitingPaymentResult" to true))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import com.google.common.truth.Truth.assertThat
import com.stripe.android.ApiKeyFixtures
import com.stripe.android.PaymentConfiguration
import com.stripe.android.core.exception.APIConnectionException
import com.stripe.android.core.utils.FeatureFlags
import com.stripe.android.googlepaylauncher.GooglePayPaymentMethodLauncher
import com.stripe.android.googlepaylauncher.GooglePayPaymentMethodLauncherContractV2
import com.stripe.android.link.LinkActivityContract
Expand Down Expand Up @@ -86,7 +85,6 @@ import com.stripe.android.paymentsheet.ui.SepaMandateContract
import com.stripe.android.paymentsheet.ui.SepaMandateResult
import com.stripe.android.paymentsheet.utils.RecordingGooglePayPaymentMethodLauncherFactory
import com.stripe.android.testing.FakeErrorReporter
import com.stripe.android.testing.FeatureFlagTestRule
import com.stripe.android.uicore.image.StripeImageLoader
import com.stripe.android.utils.FakeIntentConfirmationInterceptor
import com.stripe.android.utils.FakePaymentSheetLoader
Expand Down Expand Up @@ -128,12 +126,6 @@ internal class DefaultFlowControllerTest {
@get:Rule
val intentConfirmationInterceptorRule = IntentConfirmationInterceptorTestRule()

@get:Rule
val cvcRecollectionFeatureRule = FeatureFlagTestRule(
featureFlag = FeatureFlags.cvcRecollection,
isEnabled = true
)

private val paymentOptionCallback = mock<PaymentOptionCallback>()
private val paymentResultCallback = mock<PaymentSheetResultCallback>()

Expand Down Expand Up @@ -1724,63 +1716,6 @@ internal class DefaultFlowControllerTest {
verify(paymentResultCallback).onPaymentSheetResult(eq(PaymentSheetResult.Completed))
}

@Test
fun `Does not launch CVC Recollection if feature flag is false`() = runTest {
cvcRecollectionFeatureRule.setEnabled(false)
fakeIntentConfirmationInterceptor.enqueueCompleteStep()

val onResult = argumentCaptor<ActivityResultCallback<CvcRecollectionResult>>()
val launcher = mock<CvcRecollectionLauncher> {
on { launch(any(), any(), any()) } doAnswer {
onResult.firstValue.onActivityResult(CvcRecollectionResult.Confirmed("123"))
}
}
val launcherFactory = mock<CvcRecollectionLauncherFactory> {
on { create(any()) } doReturn launcher
}

whenever(
activityResultRegistry.register(
any(),
any<CvcRecollectionContract>(),
onResult.capture()
)
).thenReturn(mock())

val flowController = createFlowController(
cvcRecollectionLauncherFactory = launcherFactory,
stripeIntent = PaymentIntentFixtures.PI_REQUIRES_PAYMENT_METHOD_CVC_RECOLLECTION
)

verify(launcherFactory).create(any())

flowController.configureExpectingSuccess(
clientSecret = PaymentSheetFixtures.SETUP_CLIENT_SECRET
)

val paymentMethod = PaymentMethodFixtures.CARD_PAYMENT_METHOD
val savedSelection = PaymentSelection.Saved(paymentMethod)

flowController.onPaymentOptionResult(
PaymentOptionResult.Succeeded(savedSelection)
)

flowController.confirm()

verify(launcher, never()).launch(
eq(
CvcRecollectionData(
lastFour = "4242",
brand = CardBrand.Visa
)
),
eq(PaymentSheet.Appearance()),
eq(false)
)

verify(paymentResultCallback).onPaymentSheetResult(eq(PaymentSheetResult.Completed))
}

@Test
fun `On complete internal payment result in PI mode & should reuse, should save payment selection`() = runTest {
selectionSavedTest(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import com.stripe.android.core.BuildConfig
@Suppress("unused")
object FeatureFlags {
// Add any feature flags here
val cvcRecollection = FeatureFlag()
}

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
Expand Down

0 comments on commit f6131fd

Please sign in to comment.