Skip to content

Commit

Permalink
Confirm Link card brand payments (#9320)
Browse files Browse the repository at this point in the history
  • Loading branch information
tillh-stripe authored Sep 30, 2024
1 parent e9dfff0 commit b0a6dd8
Show file tree
Hide file tree
Showing 11 changed files with 95 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ internal class RealCreateInstantDebitsResult @Inject constructor(
consumerRepository.sharePaymentDetails(
paymentDetailsId = paymentDetails.id,
consumerSessionClientSecret = clientSecret,
expectedPaymentMethodType = "card",
expectedPaymentMethodType = elementsSessionContext.linkMode.expectedPaymentMethodType,
).paymentMethodId
} else {
repository.createPaymentMethod(
Expand Down
10 changes: 6 additions & 4 deletions payments-core/api/payments-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -2228,15 +2228,16 @@ public final class com/stripe/android/model/ConfirmPaymentIntentParams : com/str
public final fun component12 ()Lcom/stripe/android/model/ConfirmPaymentIntentParams$SetupFutureUsage;
public final fun component13 ()Lcom/stripe/android/model/ConfirmPaymentIntentParams$Shipping;
public final fun component14 ()Ljava/lang/String;
public final fun component15 ()Ljava/lang/String;
public final fun component2 ()Ljava/lang/String;
public final fun component3 ()Lcom/stripe/android/model/SourceParams;
public final fun component4 ()Ljava/lang/String;
public final fun component5 ()Ljava/lang/String;
public final fun component6 ()Ljava/lang/String;
public final fun component7 ()Ljava/lang/Boolean;
public final fun component9 ()Lcom/stripe/android/model/PaymentMethodOptionsParams;
public final fun copy (Lcom/stripe/android/model/PaymentMethodCreateParams;Ljava/lang/String;Lcom/stripe/android/model/SourceParams;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ZLcom/stripe/android/model/PaymentMethodOptionsParams;Ljava/lang/String;Lcom/stripe/android/model/MandateDataParams;Lcom/stripe/android/model/ConfirmPaymentIntentParams$SetupFutureUsage;Lcom/stripe/android/model/ConfirmPaymentIntentParams$Shipping;Ljava/lang/String;)Lcom/stripe/android/model/ConfirmPaymentIntentParams;
public static synthetic fun copy$default (Lcom/stripe/android/model/ConfirmPaymentIntentParams;Lcom/stripe/android/model/PaymentMethodCreateParams;Ljava/lang/String;Lcom/stripe/android/model/SourceParams;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ZLcom/stripe/android/model/PaymentMethodOptionsParams;Ljava/lang/String;Lcom/stripe/android/model/MandateDataParams;Lcom/stripe/android/model/ConfirmPaymentIntentParams$SetupFutureUsage;Lcom/stripe/android/model/ConfirmPaymentIntentParams$Shipping;Ljava/lang/String;ILjava/lang/Object;)Lcom/stripe/android/model/ConfirmPaymentIntentParams;
public final fun copy (Lcom/stripe/android/model/PaymentMethodCreateParams;Ljava/lang/String;Lcom/stripe/android/model/SourceParams;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ZLcom/stripe/android/model/PaymentMethodOptionsParams;Ljava/lang/String;Lcom/stripe/android/model/MandateDataParams;Lcom/stripe/android/model/ConfirmPaymentIntentParams$SetupFutureUsage;Lcom/stripe/android/model/ConfirmPaymentIntentParams$Shipping;Ljava/lang/String;Ljava/lang/String;)Lcom/stripe/android/model/ConfirmPaymentIntentParams;
public static synthetic fun copy$default (Lcom/stripe/android/model/ConfirmPaymentIntentParams;Lcom/stripe/android/model/PaymentMethodCreateParams;Ljava/lang/String;Lcom/stripe/android/model/SourceParams;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ZLcom/stripe/android/model/PaymentMethodOptionsParams;Ljava/lang/String;Lcom/stripe/android/model/MandateDataParams;Lcom/stripe/android/model/ConfirmPaymentIntentParams$SetupFutureUsage;Lcom/stripe/android/model/ConfirmPaymentIntentParams$Shipping;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/stripe/android/model/ConfirmPaymentIntentParams;
public static final fun create (Ljava/lang/String;)Lcom/stripe/android/model/ConfirmPaymentIntentParams;
public static final fun create (Ljava/lang/String;Lcom/stripe/android/model/ConfirmPaymentIntentParams$Shipping;)Lcom/stripe/android/model/ConfirmPaymentIntentParams;
public static final fun create (Ljava/lang/String;Lcom/stripe/android/model/ConfirmPaymentIntentParams$Shipping;Lcom/stripe/android/model/ConfirmPaymentIntentParams$SetupFutureUsage;)Lcom/stripe/android/model/ConfirmPaymentIntentParams;
Expand Down Expand Up @@ -2379,8 +2380,9 @@ public final class com/stripe/android/model/ConfirmSetupIntentParams : com/strip
public final fun component4 ()Ljava/lang/String;
public final fun component6 ()Ljava/lang/String;
public final fun component7 ()Lcom/stripe/android/model/MandateDataParams;
public final fun copy (Ljava/lang/String;Ljava/lang/String;Lcom/stripe/android/model/PaymentMethodCreateParams;Ljava/lang/String;ZLjava/lang/String;Lcom/stripe/android/model/MandateDataParams;)Lcom/stripe/android/model/ConfirmSetupIntentParams;
public static synthetic fun copy$default (Lcom/stripe/android/model/ConfirmSetupIntentParams;Ljava/lang/String;Ljava/lang/String;Lcom/stripe/android/model/PaymentMethodCreateParams;Ljava/lang/String;ZLjava/lang/String;Lcom/stripe/android/model/MandateDataParams;ILjava/lang/Object;)Lcom/stripe/android/model/ConfirmSetupIntentParams;
public final fun component8 ()Ljava/lang/String;
public final fun copy (Ljava/lang/String;Ljava/lang/String;Lcom/stripe/android/model/PaymentMethodCreateParams;Ljava/lang/String;ZLjava/lang/String;Lcom/stripe/android/model/MandateDataParams;Ljava/lang/String;)Lcom/stripe/android/model/ConfirmSetupIntentParams;
public static synthetic fun copy$default (Lcom/stripe/android/model/ConfirmSetupIntentParams;Ljava/lang/String;Ljava/lang/String;Lcom/stripe/android/model/PaymentMethodCreateParams;Ljava/lang/String;ZLjava/lang/String;Lcom/stripe/android/model/MandateDataParams;Ljava/lang/String;ILjava/lang/Object;)Lcom/stripe/android/model/ConfirmSetupIntentParams;
public static final fun create (Lcom/stripe/android/model/PaymentMethodCreateParams;Ljava/lang/String;)Lcom/stripe/android/model/ConfirmSetupIntentParams;
public static final fun create (Lcom/stripe/android/model/PaymentMethodCreateParams;Ljava/lang/String;Lcom/stripe/android/model/MandateDataParams;)Lcom/stripe/android/model/ConfirmSetupIntentParams;
public static final fun create (Lcom/stripe/android/model/PaymentMethodCreateParams;Ljava/lang/String;Lcom/stripe/android/model/MandateDataParams;Ljava/lang/String;)Lcom/stripe/android/model/ConfirmSetupIntentParams;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ sealed class ConfirmStripeIntentParamsFactory<out T : ConfirmStripeIntentParams>
abstract fun create(
paymentMethodId: String,
paymentMethodType: PaymentMethod.Type?,
expectedLinkPaymentMethodType: String?,
optionsParams: PaymentMethodOptionsParams?,
): T

Expand All @@ -35,6 +36,7 @@ sealed class ConfirmStripeIntentParamsFactory<out T : ConfirmStripeIntentParams>
return create(
paymentMethodId = paymentMethod.id.orEmpty(),
paymentMethodType = paymentMethod.type,
expectedLinkPaymentMethodType = null,
optionsParams = optionsParams,
)
}
Expand Down Expand Up @@ -67,6 +69,7 @@ internal class ConfirmPaymentIntentParamsFactory(
override fun create(
paymentMethodId: String,
paymentMethodType: PaymentMethod.Type?,
expectedLinkPaymentMethodType: String?,
optionsParams: PaymentMethodOptionsParams?,
): ConfirmPaymentIntentParams {
return ConfirmPaymentIntentParams.createWithPaymentMethodId(
Expand All @@ -76,6 +79,8 @@ internal class ConfirmPaymentIntentParamsFactory(
mandateData = MandateDataParams(MandateDataParams.Type.Online.DEFAULT)
.takeIf { paymentMethodType?.requiresMandate == true },
shipping = shipping
).copy(
expectedPaymentMethodType = expectedLinkPaymentMethodType
)
}

Expand All @@ -99,6 +104,7 @@ internal class ConfirmSetupIntentParamsFactory(
override fun create(
paymentMethodId: String,
paymentMethodType: PaymentMethod.Type?,
expectedLinkPaymentMethodType: String?,
optionsParams: PaymentMethodOptionsParams?,
): ConfirmSetupIntentParams {
return ConfirmSetupIntentParams.create(
Expand All @@ -107,6 +113,8 @@ internal class ConfirmSetupIntentParamsFactory(
mandateData = paymentMethodType?.requiresMandate?.let {
MandateDataParams(MandateDataParams.Type.Online.DEFAULT)
}
).copy(
expectedPaymentMethodType = expectedLinkPaymentMethodType
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.stripe.android.model

import android.os.Parcelable
import androidx.annotation.RestrictTo
import com.stripe.android.model.ConfirmPaymentIntentParams.SetupFutureUsage
import com.stripe.android.model.ConfirmPaymentIntentParams.SetupFutureUsage.OffSession
import com.stripe.android.model.ConfirmPaymentIntentParams.SetupFutureUsage.OnSession
Expand Down Expand Up @@ -116,7 +117,13 @@ data class ConfirmPaymentIntentParams internal constructor(
*
* See [receipt_email](https://stripe.com/docs/api/payment_intents/confirm#confirm_payment_intent-receipt_email).
*/
var receiptEmail: String? = null
var receiptEmail: String? = null,

/**
* Internal field to represent the expected payment method type when using the Link Card Brand.
*/
@field:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
var expectedPaymentMethodType: String? = null,
) : ConfirmStripeIntentParams {
fun shouldSavePaymentMethod(): Boolean {
return savePaymentMethod == true
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.stripe.android.model

import androidx.annotation.RestrictTo
import com.stripe.android.model.ConfirmStripeIntentParams.Companion.PARAM_CLIENT_SECRET
import com.stripe.android.model.ConfirmStripeIntentParams.Companion.PARAM_MANDATE_DATA
import com.stripe.android.model.ConfirmStripeIntentParams.Companion.PARAM_MANDATE_ID
Expand Down Expand Up @@ -44,7 +45,13 @@ data class ConfirmSetupIntentParams internal constructor(
*
* See [mandate_data](https://stripe.com/docs/api/setup_intents/confirm#confirm_setup_intent-mandate_data).
*/
var mandateData: MandateDataParams? = null
var mandateData: MandateDataParams? = null,

/**
* Internal field to represent the expected payment method type when using the Link Card Brand.
*/
@field:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
var expectedPaymentMethodType: String? = null,
) : ConfirmStripeIntentParams {

override fun shouldUseStripeSdk(): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,11 +317,17 @@ data class PaymentMethodCreateParams internal constructor(
}

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun instantDebitsPaymentMethodId(): String? {
fun linkBankPaymentMethodId(): String? {
val linkParams = (toParamMap()["link"] as? Map<*, *>) ?: return null
return linkParams["payment_method_id"] as? String
}

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun expectedPaymentMethodType(): String? {
val linkParams = (toParamMap()["link"] as? Map<*, *>) ?: return null
return linkParams["expected_payment_method_type"] as? String
}

@Parcelize
data class Card
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
Expand Down Expand Up @@ -1308,6 +1314,7 @@ data class PaymentMethodCreateParams internal constructor(
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // For paymentsheet
fun createInstantDebits(
paymentMethodId: String,
expectedPaymentMethodType: String?,
requiresMandate: Boolean,
productUsage: Set<String>,
): PaymentMethodCreateParams {
Expand All @@ -1317,6 +1324,7 @@ data class PaymentMethodCreateParams internal constructor(
overrideParamMap = mapOf(
"link" to mapOf(
"payment_method_id" to paymentMethodId,
"expected_payment_method_type" to expectedPaymentMethodType,
),
),
productUsage = productUsage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,8 @@ import androidx.annotation.RestrictTo
enum class LinkMode(val value: String) {
Passthrough("PASSTHROUGH"),
LinkPaymentMethod("LINK_PAYMENT_METHOD"),
LinkCardBrand("LINK_CARD_BRAND"),
LinkCardBrand("LINK_CARD_BRAND");

val expectedPaymentMethodType: String
get() = if (this == LinkCardBrand) "card" else "bank_account"
}
1 change: 1 addition & 0 deletions paymentsheet/detekt-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
<ID>LongMethod:USBankAccountForm.kt$@Composable internal fun BillingDetailsForm( instantDebits: Boolean, formArgs: FormArguments, isProcessing: Boolean, isPaymentFlow: Boolean, nameController: TextFieldController, emailController: TextFieldController, phoneController: PhoneNumberController, addressController: AddressController, lastTextFieldIdentifier: IdentifierSpec?, sameAsShippingElement: SameAsShippingElement?, )</ID>
<ID>LongMethod:USBankAccountForm.kt$@Composable internal fun USBankAccountForm( formArgs: FormArguments, usBankAccountFormArgs: USBankAccountFormArguments, modifier: Modifier = Modifier, )</ID>
<ID>LongMethod:USBankAccountForm.kt$@Composable private fun AccountDetailsForm( showCheckbox: Boolean, isProcessing: Boolean, bankName: String?, last4: String?, saveForFutureUseElement: SaveForFutureUseElement, onRemoveAccount: () -> Unit, )</ID>
<ID>LongMethod:USBankAccountFormViewModel.kt$USBankAccountFormViewModel$private fun createNewPaymentSelection( resultIdentifier: ResultIdentifier, last4: String, bankName: String, ): PaymentSelection.New.USBankAccount</ID>
<ID>LongMethod:USBankAccountFormViewModelTest.kt$USBankAccountFormViewModelTest$@Test fun `Restores screen state when re-opening screen`()</ID>
<ID>MagicNumber:BaseSheetActivity.kt$BaseSheetActivity$30</ID>
<ID>MagicNumber:NewPaymentMethodTabLayoutUI.kt$.3f</ID>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,13 +378,14 @@ internal class DefaultIntentConfirmationInterceptor @Inject constructor(
shipping = shippingValues,
)

val instantDebitsPaymentMethodId = paymentMethodCreateParams.instantDebitsPaymentMethodId()
val linkBankPaymentMethodId = paymentMethodCreateParams.linkBankPaymentMethodId()

val confirmParams = if (instantDebitsPaymentMethodId != null) {
// Instant Debits is a new payment selection, but we need to confirm the intent with a PaymentMethod ID.
val confirmParams = if (linkBankPaymentMethodId != null) {
// Link Bank Payments is a new payment selection, but we need to confirm the intent with a PaymentMethod ID.
paramsFactory.create(
paymentMethodId = instantDebitsPaymentMethodId,
paymentMethodId = linkBankPaymentMethodId,
paymentMethodType = PaymentMethod.Type.Link,
expectedLinkPaymentMethodType = paymentMethodCreateParams.expectedPaymentMethodType(),
optionsParams = paymentMethodOptionsParams,
)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,7 @@ internal class USBankAccountFormViewModel @Inject internal constructor(
is ResultIdentifier.PaymentMethod -> {
PaymentMethodCreateParams.createInstantDebits(
paymentMethodId = resultIdentifier.id,
expectedPaymentMethodType = args.linkMode?.expectedPaymentMethodType,
requiresMandate = true,
productUsage = setOf("PaymentSheet"),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,7 @@ class DefaultIntentConfirmationInterceptorTest {
paymentMethodId = paymentMethodId,
requiresMandate = false,
productUsage = emptySet(),
expectedPaymentMethodType = null,
)

val nextStep = interceptor.intercept(
Expand All @@ -602,6 +603,49 @@ class DefaultIntentConfirmationInterceptorTest {
)
}

@Test
fun `Creates correct confirm step when confirming Link Card Brand transaction`() = runTest {
val paymentMethodId = PaymentMethodFixtures.CARD_PAYMENT_METHOD.id!!
val clientSecret = "pi_1234_secret_4321"

val interceptor = DefaultIntentConfirmationInterceptor(
stripeRepository = mock(),
publishableKeyProvider = { "pk" },
stripeAccountIdProvider = { null },
isFlowController = false,
)

val createParams = PaymentMethodCreateParams.createInstantDebits(
paymentMethodId = paymentMethodId,
requiresMandate = false,
productUsage = emptySet(),
expectedPaymentMethodType = "card",
)

val nextStep = interceptor.intercept(
initializationMode = InitializationMode.PaymentIntent(clientSecret),
paymentMethodCreateParams = createParams,
paymentMethodOptionsParams = null,
shippingValues = null,
customerRequestedSave = false,
)

val expectedConfirmParams = ConfirmPaymentIntentParams.createWithPaymentMethodId(
paymentMethodId = paymentMethodId,
clientSecret = clientSecret,
mandateData = MandateDataParams(MandateDataParams.Type.Online.DEFAULT),
).copy(
expectedPaymentMethodType = "card",
)

assertThat(nextStep).isEqualTo(
IntentConfirmationInterceptor.NextStep.Confirm(
confirmParams = expectedConfirmParams,
isDeferred = false,
)
)
}

private fun succeedingCreateIntentCallback(
expectedPaymentMethod: PaymentMethod,
): CreateIntentCallback {
Expand Down

0 comments on commit b0a6dd8

Please sign in to comment.