Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move CustomerState.Permissions to PaymentMethodsMetadata.CustomerMetadata #10483

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
16 changes: 8 additions & 8 deletions paymentsheet/api/paymentsheet.api
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,14 @@ public final class com/stripe/android/lpmfoundations/paymentmethod/CustomerMetad
public synthetic fun newArray (I)[Ljava/lang/Object;
}

public final class com/stripe/android/lpmfoundations/paymentmethod/CustomerMetadata$Permissions$Creator : android/os/Parcelable$Creator {
public fun <init> ()V
public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/lpmfoundations/paymentmethod/CustomerMetadata$Permissions;
public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object;
public final fun newArray (I)[Lcom/stripe/android/lpmfoundations/paymentmethod/CustomerMetadata$Permissions;
public synthetic fun newArray (I)[Ljava/lang/Object;
}

public final class com/stripe/android/lpmfoundations/paymentmethod/DisplayableCustomPaymentMethod$Creator : android/os/Parcelable$Creator {
public fun <init> ()V
public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/lpmfoundations/paymentmethod/DisplayableCustomPaymentMethod;
Expand Down Expand Up @@ -2585,14 +2593,6 @@ public final class com/stripe/android/paymentsheet/state/CustomerState$DefaultPa
public synthetic fun newArray (I)[Ljava/lang/Object;
}

public final class com/stripe/android/paymentsheet/state/CustomerState$Permissions$Creator : android/os/Parcelable$Creator {
public fun <init> ()V
public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/paymentsheet/state/CustomerState$Permissions;
public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object;
public final fun newArray (I)[Lcom/stripe/android/paymentsheet/state/CustomerState$Permissions;
public synthetic fun newArray (I)[Ljava/lang/Object;
}

public final class com/stripe/android/paymentsheet/state/LinkState$Creator : android/os/Parcelable$Creator {
public fun <init> ()V
public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/paymentsheet/state/LinkState;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import com.stripe.android.googlepaylauncher.GooglePayEnvironment
import com.stripe.android.googlepaylauncher.GooglePayRepository
import com.stripe.android.lpmfoundations.luxe.LpmRepository
import com.stripe.android.lpmfoundations.luxe.SupportedPaymentMethod
import com.stripe.android.lpmfoundations.paymentmethod.CustomerMetadata
import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadata
import com.stripe.android.lpmfoundations.paymentmethod.PaymentSheetCardBrandFilter
import com.stripe.android.model.PaymentMethod
Expand Down Expand Up @@ -125,13 +126,22 @@ internal class DefaultCustomerSheetLoader(
if (isLiveModeProvider()) GooglePayEnvironment.Production else GooglePayEnvironment.Test
).isReady().first()

val customerMetadata = CustomerMetadata(
hasCustomerConfiguration = true,
isPaymentMethodSetAsDefaultEnabled = isPaymentMethodSyncDefaultEnabled,
permissions = CustomerMetadata.Permissions.createForCustomerSheet(
configuration = configuration,
customerSheetSession = customerSheetSession
)
)

return PaymentMethodMetadata.createForCustomerSheet(
elementsSession = elementsSession,
configuration = configuration,
paymentMethodSaveConsentBehavior = customerSheetSession.paymentMethodSaveConsentBehavior,
sharedDataSpecs = sharedDataSpecs,
isGooglePayReady = isGooglePayReadyAndEnabled,
isPaymentMethodSyncDefaultEnabled = isPaymentMethodSyncDefaultEnabled,
customerMetadata = customerMetadata,
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,93 @@
package com.stripe.android.lpmfoundations.paymentmethod

import android.os.Parcelable
import com.stripe.android.common.model.CommonConfiguration
import com.stripe.android.customersheet.CustomerSheet
import com.stripe.android.customersheet.data.CustomerSheetSession
import com.stripe.android.model.ElementsSession
import kotlinx.parcelize.Parcelize

@Parcelize
internal data class CustomerMetadata(
val hasCustomerConfiguration: Boolean,
val isPaymentMethodSetAsDefaultEnabled: Boolean,
) : Parcelable
val permissions: Permissions,
) : Parcelable {

@Parcelize
internal data class Permissions(
val canRemovePaymentMethods: Boolean,
val canRemoveLastPaymentMethod: Boolean,
val canRemoveDuplicates: Boolean,
) : Parcelable {
companion object {
internal fun createForPaymentSheetCustomerSession(
configuration: CommonConfiguration,
customer: ElementsSession.Customer,
): Permissions {
val mobilePaymentElementComponent = customer.session.components.mobilePaymentElement
val canRemovePaymentMethods = when (mobilePaymentElementComponent) {
is ElementsSession.Customer.Components.MobilePaymentElement.Enabled -> {
mobilePaymentElementComponent.isPaymentMethodRemoveEnabled
}
is ElementsSession.Customer.Components.MobilePaymentElement.Disabled -> false
}

val canRemoveLastPaymentMethod = configuration.allowsRemovalOfLastSavedPaymentMethod &&
mobilePaymentElementComponent is ElementsSession.Customer.Components.MobilePaymentElement.Enabled &&
mobilePaymentElementComponent.canRemoveLastPaymentMethod

return Permissions(
canRemovePaymentMethods = canRemovePaymentMethods,
canRemoveLastPaymentMethod = canRemoveLastPaymentMethod,
// Should always remove duplicates when using `customer_session`
canRemoveDuplicates = true,
)
}

internal fun createForPaymentSheetLegacyEphemeralKey(
configuration: CommonConfiguration,
): Permissions {
return Permissions(
/*
* Un-scoped legacy ephemeral keys have full permissions to remove/save/modify. This should
* always be set to true.
*/
canRemovePaymentMethods = true,
/*
* Un-scoped legacy ephemeral keys normally have full permissions to remove the last payment
* method, however we do have client-side configuration option to configure this ability. This
* should eventually be removed in favor of the server-side option available with customer
* sessions.
*/
canRemoveLastPaymentMethod = configuration.allowsRemovalOfLastSavedPaymentMethod,
/*
* Removing duplicates is not applicable here since we don't filter out duplicates for for
* un-scoped ephemeral keys.
*/
canRemoveDuplicates = false,
)
}

internal fun createForCustomerSheet(
configuration: CustomerSheet.Configuration,
customerSheetSession: CustomerSheetSession,
): Permissions {
return Permissions(
canRemovePaymentMethods = customerSheetSession.permissions.canRemovePaymentMethods,
canRemoveLastPaymentMethod = configuration.allowsRemovalOfLastSavedPaymentMethod,
canRemoveDuplicates = true,
)
}

// Native link uses PaymentMethodMetadata for DefaultFormHelper and doesn't use CustomerMetadata at all
internal fun createForNativeLink(): Permissions {
return Permissions(
canRemovePaymentMethods = false,
canRemoveLastPaymentMethod = false,
canRemoveDuplicates = false,
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -244,13 +244,9 @@ internal data class PaymentMethodMetadata(
isGooglePayReady: Boolean,
linkInlineConfiguration: LinkInlineConfiguration?,
linkState: LinkState?,
customerMetadata: CustomerMetadata,
): PaymentMethodMetadata {
val linkSettings = elementsSession.linkSettings
val customerMetadata =
CustomerMetadata(
hasCustomerConfiguration = configuration.customer != null,
isPaymentMethodSetAsDefaultEnabled = getDefaultPaymentMethodsEnabled(elementsSession)
)

return PaymentMethodMetadata(
stripeIntent = elementsSession.stripeIntent,
Expand Down Expand Up @@ -289,7 +285,7 @@ internal data class PaymentMethodMetadata(
paymentMethodSaveConsentBehavior: PaymentMethodSaveConsentBehavior,
sharedDataSpecs: List<SharedDataSpec>,
isGooglePayReady: Boolean,
isPaymentMethodSyncDefaultEnabled: Boolean,
customerMetadata: CustomerMetadata,
): PaymentMethodMetadata {
return PaymentMethodMetadata(
stripeIntent = elementsSession.stripeIntent,
Expand All @@ -304,10 +300,7 @@ internal data class PaymentMethodMetadata(
merchantName = configuration.merchantDisplayName,
defaultBillingDetails = configuration.defaultBillingDetails,
shippingDetails = null,
customerMetadata = CustomerMetadata(
hasCustomerConfiguration = true,
isPaymentMethodSetAsDefaultEnabled = isPaymentMethodSyncDefaultEnabled,
),
customerMetadata = customerMetadata,
sharedDataSpecs = sharedDataSpecs,
isGooglePayReady = isGooglePayReady,
linkInlineConfiguration = null,
Expand Down Expand Up @@ -343,6 +336,7 @@ internal data class PaymentMethodMetadata(
customerMetadata = CustomerMetadata(
hasCustomerConfiguration = true,
isPaymentMethodSetAsDefaultEnabled = false,
permissions = CustomerMetadata.Permissions.createForNativeLink(),
),
sharedDataSpecs = emptyList(),
externalPaymentMethodSpecs = emptyList(),
Expand All @@ -359,12 +353,5 @@ internal data class PaymentMethodMetadata(
financialConnectionsAvailability = GetFinancialConnectionsAvailability(elementsSession = null)
)
}

private fun getDefaultPaymentMethodsEnabled(elementsSession: ElementsSession): Boolean {
val mobilePaymentElement = elementsSession.customer?.session?.components?.mobilePaymentElement
as? ElementsSession.Customer.Components.MobilePaymentElement.Enabled
return mobilePaymentElement?.isPaymentMethodSetAsDefaultEnabled
?: false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.stripe.android.core.networking.NetworkTypeDetector
import com.stripe.android.core.utils.ContextUtils.packageInfo
import com.stripe.android.core.utils.DefaultDurationProvider
import com.stripe.android.core.utils.DurationProvider
import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadata
import com.stripe.android.paymentelement.AnalyticEventCallback
import com.stripe.android.paymentelement.ExperimentalAnalyticEventCallbackApi
import com.stripe.android.paymentelement.callbacks.PaymentElementCallbackIdentifier
Expand All @@ -29,10 +30,12 @@ import com.stripe.android.paymentsheet.analytics.DefaultEventReporter
import com.stripe.android.paymentsheet.analytics.EventReporter
import com.stripe.android.paymentsheet.repositories.CustomerApiRepository
import com.stripe.android.paymentsheet.repositories.CustomerRepository
import com.stripe.android.uicore.utils.mapAsStateFlow
import dagger.Binds
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Named
import javax.inject.Provider
import javax.inject.Singleton
Expand Down Expand Up @@ -127,8 +130,16 @@ internal interface EmbeddedCommonModule {
fun provideCustomerStateHolder(
savedStateHandle: SavedStateHandle,
selectionHolder: EmbeddedSelectionHolder,
paymentMethodMetadataFlow: StateFlow<PaymentMethodMetadata?>
): CustomerStateHolder {
return CustomerStateHolder(savedStateHandle, selectionHolder.selection)
val customerMetadataPermissions = paymentMethodMetadataFlow.mapAsStateFlow {
it?.customerMetadata?.permissions
}
return CustomerStateHolder(
savedStateHandle = savedStateHandle,
selection = selectionHolder.selection,
customerMetadataPermissions = customerMetadataPermissions
)
}

@Provides
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.stripe.android.core.utils.RealUserFacingLogger
import com.stripe.android.core.utils.UserFacingLogger
import com.stripe.android.googlepaylauncher.injection.GooglePayLauncherModule
import com.stripe.android.link.account.LinkAccountHolder
import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadata
import com.stripe.android.paymentelement.ExperimentalEmbeddedPaymentElementApi
import com.stripe.android.paymentelement.callbacks.PaymentElementCallbackIdentifier
import com.stripe.android.paymentelement.confirmation.ConfirmationHandler
Expand All @@ -31,13 +32,15 @@ import com.stripe.android.paymentsheet.state.LinkAccountStatusProvider
import com.stripe.android.paymentsheet.state.PaymentElementLoader
import com.stripe.android.ui.core.di.CardScanModule
import com.stripe.android.uicore.image.StripeImageLoader
import com.stripe.android.uicore.utils.mapAsStateFlow
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.plus
import javax.inject.Named
import javax.inject.Singleton
Expand Down Expand Up @@ -186,6 +189,15 @@ internal interface EmbeddedPaymentElementViewModelModule {
return confirmationHandlerFactory.create(coroutineScope + ioContext)
}

@Provides
fun providePaymentMethodMetadata(
confirmationStateHolder: EmbeddedConfirmationStateHolder
): StateFlow<PaymentMethodMetadata?> {
return confirmationStateHolder.stateFlow.mapAsStateFlow {
it?.paymentMethodMetadata
}
}

@Provides
fun providesConfirmationStateSupplier(
confirmationStateHolder: EmbeddedConfirmationStateHolder,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ import com.stripe.android.paymentsheet.SavedPaymentMethodMutator
import com.stripe.android.paymentsheet.analytics.EventReporter
import com.stripe.android.paymentsheet.injection.PaymentSheetLauncherComponent.Builder
import com.stripe.android.ui.core.di.CardScanModule
import com.stripe.android.uicore.utils.stateFlowOf
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Singleton

@Component(
Expand Down Expand Up @@ -95,6 +97,15 @@ internal interface ManageModule {
return factory.createSavedPaymentMethodMutator()
}

@Provides
fun providePaymentMethodMetadata(
paymentMethodMetadata: PaymentMethodMetadata
): StateFlow<PaymentMethodMetadata> {
return stateFlowOf(
paymentMethodMetadata
)
}

@Provides
@Singleton
@ViewModelScope
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package com.stripe.android.paymentsheet

import androidx.lifecycle.SavedStateHandle
import com.stripe.android.lpmfoundations.paymentmethod.CustomerMetadata
import com.stripe.android.model.PaymentMethod
import com.stripe.android.paymentsheet.model.PaymentSelection
import com.stripe.android.paymentsheet.state.CustomerState
import com.stripe.android.paymentsheet.viewmodels.BaseSheetViewModel
import com.stripe.android.uicore.utils.combineAsStateFlow
import com.stripe.android.uicore.utils.mapAsStateFlow
import kotlinx.coroutines.flow.StateFlow

internal class CustomerStateHolder(
private val customerMetadataPermissions: StateFlow<CustomerMetadata.Permissions?>,
private val savedStateHandle: SavedStateHandle,
private val selection: StateFlow<PaymentSelection?>,
) {
Expand All @@ -29,11 +32,13 @@ internal class CustomerStateHolder(
initialValue = (selection.value as? PaymentSelection.Saved)?.paymentMethod
)

val canRemove: StateFlow<Boolean> = customer.mapAsStateFlow { customerState ->
customerState?.run {
val hasRemovePermissions = customerState.permissions.canRemovePaymentMethods
val hasRemoveLastPaymentMethodPermissions = customerState.permissions.canRemoveLastPaymentMethod

val canRemove: StateFlow<Boolean> = combineAsStateFlow(
paymentMethods,
customerMetadataPermissions,
) { paymentMethods, customerMetadataPermissions ->
customerMetadataPermissions?.run {
val hasRemovePermissions = customerMetadataPermissions.canRemovePaymentMethods
val hasRemoveLastPaymentMethodPermissions = customerMetadataPermissions.canRemoveLastPaymentMethod
when (paymentMethods.size) {
0 -> false
1 -> hasRemoveLastPaymentMethodPermissions && hasRemovePermissions
Expand Down Expand Up @@ -68,6 +73,9 @@ internal class CustomerStateHolder(
return CustomerStateHolder(
savedStateHandle = viewModel.savedStateHandle,
selection = viewModel.selection,
customerMetadataPermissions = viewModel.paymentMethodMetadata.mapAsStateFlow {
it?.customerMetadata?.permissions
}
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ internal class SavedPaymentMethodMutator(
)
)

// You can remove duplicates in CustomerSessions and not with ephemeral keys
val canRemoveDuplicates = currentCustomer.customerSessionClientSecret != null

val currentSelection = (selection.value as? PaymentSelection.Saved)?.paymentMethod?.id
val didRemoveSelectedItem = currentSelection == paymentMethodId

Expand All @@ -165,7 +168,7 @@ internal class SavedPaymentMethodMutator(
customerSessionClientSecret = currentCustomer.customerSessionClientSecret,
),
paymentMethodId = paymentMethodId,
canRemoveDuplicates = currentCustomer.permissions.canRemoveDuplicates,
canRemoveDuplicates = canRemoveDuplicates,
)
}

Expand Down
Loading
Loading