Skip to content

Commit

Permalink
Update Bacs UI and add support to PaymentSheet & FlowController.
Browse files Browse the repository at this point in the history
  • Loading branch information
samer-stripe committed Nov 20, 2023
1 parent 4cd5ca9 commit 2505f06
Show file tree
Hide file tree
Showing 64 changed files with 756 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -505,9 +505,31 @@ data class PaymentMethodCreateParams internal constructor(
)
}

private companion object {
internal companion object {
private const val PARAM_ACCOUNT_NUMBER: String = "account_number"
private const val PARAM_SORT_CODE: String = "sort_code"

internal fun fromParams(params: PaymentMethodCreateParams): BacsDebit? {
val code = PaymentMethod.Type.BacsDebit.code

val bacsParams = params.toParamMap()[code] as? Map<*, *>

val accountNumber = bacsParams?.get(
PARAM_ACCOUNT_NUMBER
) as? String

val sortCode = bacsParams?.get(
PARAM_SORT_CODE
) as? String

return when {
accountNumber != null && sortCode != null -> BacsDebit(
accountNumber = accountNumber,
sortCode = sortCode
)
else -> null
}
}
}
}

Expand Down Expand Up @@ -1091,5 +1113,42 @@ data class PaymentMethodCreateParams internal constructor(
productUsage = productUsage
)
}

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun createBacsFromParams(
params: PaymentMethodCreateParams
): BacsDebit? {
return BacsDebit.fromParams(params)
}

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun getNameFromParams(
params: PaymentMethodCreateParams
): String? {
return params.billingDetails?.name ?: getBillingDetailsValueFromOverrideParams(
params,
PaymentMethod.BillingDetails.PARAM_NAME
)
}

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun getEmailFromParams(
params: PaymentMethodCreateParams
): String? {
return params.billingDetails?.email ?: getBillingDetailsValueFromOverrideParams(
params,
PaymentMethod.BillingDetails.PARAM_EMAIL
)
}

private fun getBillingDetailsValueFromOverrideParams(
params: PaymentMethodCreateParams,
key: String
): String? {
val billingDetailsParams = params.overrideParamMap
?.get(PARAM_BILLING_DETAILS) as? Map<*, *>

return billingDetailsParams?.get(key) as? String
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,105 @@ class PaymentMethodCreateParamsTest {
)
}

@Test
fun `createBacsFromParams should return a 'BacsDebit' instance correctly`() {
val params = PaymentMethodCreateParams.create(
bacsDebit = PaymentMethodCreateParams.BacsDebit(
accountNumber = "00012345",
sortCode = "108800"
),
billingDetails = PaymentMethod.BillingDetails()
)

assertThat(
PaymentMethodCreateParams.createBacsFromParams(params)
).isEqualTo(
PaymentMethodCreateParams.BacsDebit(
accountNumber = "00012345",
sortCode = "108800"
)
)

val overrideParams = PaymentMethodCreateParams.createWithOverride(
code = "bacs_debit",
overrideParamMap = mapOf(
"bacs_debit" to mapOf(
"account_number" to "00012345",
"sort_code" to "108800"
)
),
productUsage = setOf(),
requiresMandate = false
)

assertThat(
PaymentMethodCreateParams.createBacsFromParams(overrideParams)
).isEqualTo(
PaymentMethodCreateParams.BacsDebit(
accountNumber = "00012345",
sortCode = "108800"
)
)
}

@Test
fun `getNameFromOverrideParams should return name from billing details if available`() {
val params = PaymentMethodCreateParams.create(
card = PaymentMethodCreateParams.Card(),
billingDetails = PaymentMethod.BillingDetails(
name = "John Doe"
)
)

assertThat(
PaymentMethodCreateParams.getNameFromParams(params)
).isEqualTo("John Doe")

val overrideParams = PaymentMethodCreateParams.createWithOverride(
code = "bacs_debit",
overrideParamMap = mapOf(
"billing_details" to mapOf(
"name" to "John Doe"
)
),
productUsage = setOf(),
requiresMandate = false
)

assertThat(
PaymentMethodCreateParams.getNameFromParams(overrideParams)
).isEqualTo("John Doe")
}

@Test
fun `getEmailFromOverrideParams should return email from billing details if available`() {
val params = PaymentMethodCreateParams.create(
card = PaymentMethodCreateParams.Card(),
billingDetails = PaymentMethod.BillingDetails(
email = "[email protected]"
)
)

assertThat(
PaymentMethodCreateParams.getEmailFromParams(params)
).isEqualTo("[email protected]")

val overrideParams = PaymentMethodCreateParams.createWithOverride(
code = "card",
overrideParamMap = mapOf(
"billing_details" to mapOf(
"email" to "[email protected]"
)
),
productUsage = setOf(),
requiresMandate = false
)

assertThat(
PaymentMethodCreateParams.getEmailFromParams(overrideParams)
).isEqualTo("[email protected]")
}

private fun createFpx(): PaymentMethodCreateParams {
return PaymentMethodCreateParams.create(
PaymentMethodCreateParams.Fpx(bank = "hsbc"),
Expand Down
8 changes: 8 additions & 0 deletions paymentsheet/api/paymentsheet.api
Original file line number Diff line number Diff line change
Expand Up @@ -1612,6 +1612,14 @@ public final class com/stripe/android/paymentsheet/paymentdatacollection/bacs/Ba
public synthetic fun newArray (I)[Ljava/lang/Object;
}

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

public final class com/stripe/android/paymentsheet/paymentdatacollection/bacs/ComposableSingletons$BacsMandateConfirmationFormKt {
public static final field INSTANCE Lcom/stripe/android/paymentsheet/paymentdatacollection/bacs/ComposableSingletons$BacsMandateConfirmationFormKt;
public static field lambda-1 Lkotlin/jvm/functions/Function2;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import androidx.lifecycle.lifecycleScope
import com.stripe.android.common.ui.BottomSheet
import com.stripe.android.common.ui.rememberBottomSheetState
import com.stripe.android.googlepaylauncher.GooglePayPaymentMethodLauncherContractV2
import com.stripe.android.paymentsheet.paymentdatacollection.bacs.BacsMandateConfirmationContract
import com.stripe.android.paymentsheet.ui.BaseSheetActivity
import com.stripe.android.paymentsheet.ui.PaymentSheetScreen
import com.stripe.android.uicore.StripeTheme
Expand Down Expand Up @@ -58,6 +59,13 @@ internal class PaymentSheetActivity : BaseSheetActivity<PaymentSheetResult>() {
)
)

viewModel.setupBacsMandateConfirmation(
registerForActivityResult(
BacsMandateConfirmationContract(),
viewModel::onBacsMandateResult
)
)

setContent {
StripeTheme {
val isProcessing by viewModel.processing.collectAsState()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import com.stripe.android.model.ConfirmPaymentIntentParams
import com.stripe.android.model.ConfirmSetupIntentParams
import com.stripe.android.model.ConfirmStripeIntentParams
import com.stripe.android.model.PaymentIntent
import com.stripe.android.model.PaymentMethod
import com.stripe.android.model.SetupIntent
import com.stripe.android.model.StripeIntent
import com.stripe.android.payments.paymentlauncher.InternalPaymentResult
Expand All @@ -44,6 +45,11 @@ import com.stripe.android.paymentsheet.model.PaymentSelection
import com.stripe.android.paymentsheet.model.PaymentSheetViewState
import com.stripe.android.paymentsheet.model.currency
import com.stripe.android.paymentsheet.navigation.PaymentSheetScreen
import com.stripe.android.paymentsheet.paymentdatacollection.bacs.BacsMandateConfirmationContract
import com.stripe.android.paymentsheet.paymentdatacollection.bacs.BacsMandateConfirmationLauncher
import com.stripe.android.paymentsheet.paymentdatacollection.bacs.BacsMandateConfirmationLauncherFactory
import com.stripe.android.paymentsheet.paymentdatacollection.bacs.BacsMandateConfirmationResult
import com.stripe.android.paymentsheet.paymentdatacollection.bacs.BacsMandateData
import com.stripe.android.paymentsheet.repositories.CustomerRepository
import com.stripe.android.paymentsheet.state.GooglePayState
import com.stripe.android.paymentsheet.state.PaymentSheetLoader
Expand Down Expand Up @@ -89,6 +95,7 @@ internal class PaymentSheetViewModel @Inject internal constructor(
lpmRepository: LpmRepository,
private val paymentLauncherFactory: StripePaymentLauncherAssistedFactory,
private val googlePayPaymentMethodLauncherFactory: GooglePayPaymentMethodLauncherFactory,
private val bacsMandateConfirmationLauncherFactory: BacsMandateConfirmationLauncherFactory,
logger: Logger,
@IOContext workContext: CoroutineContext,
savedStateHandle: SavedStateHandle,
Expand Down Expand Up @@ -159,6 +166,8 @@ internal class PaymentSheetViewModel @Inject internal constructor(

private var googlePayPaymentMethodLauncher: GooglePayPaymentMethodLauncher? = null

private var bacsMandateConfirmationLauncher: BacsMandateConfirmationLauncher? = null

private var deferredIntentConfirmationType: DeferredIntentConfirmationType? = null

@VisibleForTesting
Expand Down Expand Up @@ -347,6 +356,12 @@ internal class PaymentSheetViewModel @Inject internal constructor(
}
}

fun setupBacsMandateConfirmation(
activityLauncher: ActivityResultLauncher<BacsMandateConfirmationContract.Args>
) {
bacsMandateConfirmationLauncher = bacsMandateConfirmationLauncherFactory.create(activityLauncher)
}

private fun resetViewState(userErrorMessage: String? = null) {
viewState.value =
PaymentSheetViewState.Reset(userErrorMessage?.let { UserErrorMessage(it) })
Expand Down Expand Up @@ -393,6 +408,32 @@ internal class PaymentSheetViewModel @Inject internal constructor(
label = args.googlePayConfig?.label,
)
}
} else if (
paymentSelection is PaymentSelection.New.GenericPaymentMethod &&
paymentSelection.paymentMethodCreateParams.typeCode == PaymentMethod.Type.BacsDebit.code
) {
BacsMandateData.fromPaymentSelection(paymentSelection)?.let { data ->
runCatching {
requireNotNull(bacsMandateConfirmationLauncher)
}.onSuccess { launcher ->
launcher.launch(
data = data,
appearance = config.appearance
)
}.onFailure {
resetViewState(
userErrorMessage = getApplication<Application>()
.resources
.getString(R.string.stripe_something_went_wrong)
)
}
} ?: run {
resetViewState(
userErrorMessage = getApplication<Application>()
.resources
.getString(R.string.stripe_something_went_wrong)
)
}
} else {
confirmPaymentSelection(paymentSelection)
}
Expand Down Expand Up @@ -642,6 +683,23 @@ internal class PaymentSheetViewModel @Inject internal constructor(
}
}

internal fun onBacsMandateResult(result: BacsMandateConfirmationResult) {
when (result) {
is BacsMandateConfirmationResult.Confirmed -> {
val paymentSelection = selection.value

if (
paymentSelection is PaymentSelection.New.GenericPaymentMethod &&
paymentSelection.paymentMethodCreateParams.typeCode == PaymentMethod.Type.BacsDebit.code
) {
confirmPaymentSelection(paymentSelection)
}
}
is BacsMandateConfirmationResult.ModifyDetails,
is BacsMandateConfirmationResult.Cancelled -> resetViewState(userErrorMessage = null)
}
}

override fun onFatal(throwable: Throwable) {
logger.error("Payment Sheet error", throwable)
mostRecentError = throwable
Expand Down
Loading

0 comments on commit 2505f06

Please sign in to comment.