Skip to content

Commit

Permalink
Add synthetic payment method for LinkCardBrand
Browse files Browse the repository at this point in the history
  • Loading branch information
tillh-stripe committed Sep 16, 2024
1 parent 3480837 commit cfc2417
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,15 @@ internal class ElementsSessionJsonParser(
val elementsSessionId = json.optString(FIELD_ELEMENTS_SESSION_ID)
val customer = parseCustomer(json.optJSONObject(FIELD_CUSTOMER))

val linkSettings = parseLinkSettings(json)
val linkSettings = json.optJSONObject(FIELD_LINK_SETTINGS)
val linkFundingSources = linkSettings?.optJSONArray(FIELD_LINK_FUNDING_SOURCES)

val stripeIntent = parseStripeIntent(
elementsSessionId = elementsSessionId,
paymentMethodPreference = paymentMethodPreference,
orderedPaymentMethodTypes = orderedPaymentMethodTypes,
unactivatedPaymentMethodTypes = unactivatedPaymentMethodTypes,
linkFundingSources = linkSettings.linkFundingSources,
linkFundingSources = linkFundingSources,
countryCode = countryCode
)

Expand All @@ -61,7 +62,7 @@ internal class ElementsSessionJsonParser(

return if (stripeIntent != null) {
ElementsSession(
linkSettings = linkSettings,
linkSettings = parseLinkSettings(linkSettings, linkFundingSources),
paymentMethodSpecs = paymentMethodSpecs,
stripeIntent = stripeIntent,
customer = customer,
Expand All @@ -80,7 +81,7 @@ internal class ElementsSessionJsonParser(
paymentMethodPreference: JSONObject?,
orderedPaymentMethodTypes: JSONArray?,
unactivatedPaymentMethodTypes: List<String>,
linkFundingSources: List<String>,
linkFundingSources: JSONArray?,
countryCode: String
): StripeIntent? {
return (paymentMethodPreference?.optJSONObject(params.type) ?: JSONObject()).let { json ->
Expand Down Expand Up @@ -135,18 +136,16 @@ internal class ElementsSessionJsonParser(
}

private fun parseLinkSettings(
json: JSONObject,
json: JSONObject?,
linkFundingSources: JSONArray?,
): ElementsSession.LinkSettings {
val linkSettings = json.optJSONObject(FIELD_LINK_SETTINGS)

val linkFundingSources = linkSettings?.optJSONArray(FIELD_LINK_FUNDING_SOURCES)
val disableLinkSignup = linkSettings?.optBoolean(FIELD_DISABLE_LINK_SIGNUP) ?: false
val disableLinkSignup = json?.optBoolean(FIELD_DISABLE_LINK_SIGNUP) ?: false

val linkMode = linkSettings?.optString(FIELD_LINK_MODE)?.let { mode ->
val linkMode = json?.optString(FIELD_LINK_MODE)?.let { mode ->
LinkMode.entries.firstOrNull { it.value == mode }
}

val linkFlags = linkSettings?.let { linkSettingsJson ->
val linkFlags = json?.let { linkSettingsJson ->
parseLinkFlags(linkSettingsJson)
} ?: emptyMap()

Expand Down Expand Up @@ -313,7 +312,6 @@ internal class ElementsSessionJsonParser(
private const val FIELD_ORDERED_PAYMENT_METHOD_TYPES = "ordered_payment_method_types"
private const val FIELD_LINK_SETTINGS = "link_settings"
private const val FIELD_LINK_FUNDING_SOURCES = "link_funding_sources"
private const val FIELD_LINK_PASSTHROUGH_MODE_ENABLED = "link_passthrough_mode_enabled"
private const val FIELD_LINK_MODE = "link_mode"
private const val FIELD_DISABLE_LINK_SIGNUP = "link_mobile_disable_signup"
private const val FIELD_MERCHANT_COUNTRY = "merchant_country"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.stripe.android.lpmfoundations.paymentmethod

import com.stripe.android.model.LinkMode
import com.stripe.android.model.PaymentIntent
import com.stripe.android.model.PaymentMethod.Type.USBankAccount

Expand Down Expand Up @@ -59,13 +60,25 @@ internal enum class AddPaymentMethodRequirement {
/** Requires that Instant Debits are possible for this transaction. */
InstantDebits {
override fun isMetBy(metadata: PaymentMethodMetadata): Boolean {
val paymentMethodTypes = metadata.stripeIntent.paymentMethodTypes
val noUsBankAccount = USBankAccount.code !in paymentMethodTypes
val supportsBankAccounts = "bank_account" in metadata.stripeIntent.linkFundingSources
val isDeferred = metadata.stripeIntent.clientSecret == null
return noUsBankAccount && supportsBankAccounts && !isDeferred
return metadata.linkMode != LinkMode.LinkCardBrand && metadata.supportsMobileInstantDebitsFlow
}
},

/** Requires that LinkCardBrand is possible for this transaction. */
LinkCardBrand {
override fun isMetBy(metadata: PaymentMethodMetadata): Boolean {
return metadata.linkMode == LinkMode.LinkCardBrand && metadata.supportsMobileInstantDebitsFlow
}
};

abstract fun isMetBy(metadata: PaymentMethodMetadata): Boolean
}

private val PaymentMethodMetadata.supportsMobileInstantDebitsFlow: Boolean
get() {
val paymentMethodTypes = stripeIntent.paymentMethodTypes
val noUsBankAccount = USBankAccount.code !in paymentMethodTypes
val supportsBankAccounts = "bank_account" in stripeIntent.linkFundingSources
val isDeferred = stripeIntent.clientSecret == null
return noUsBankAccount && supportsBankAccounts && !isDeferred
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ internal interface PaymentMethodDefinition {
fun uiDefinitionFactory(): UiDefinitionFactory
}

internal fun PaymentMethodDefinition.isSupported(metadata: PaymentMethodMetadata): Boolean {
if (type.code !in metadata.stripeIntent.paymentMethodTypes) {
internal fun PaymentMethodDefinition.isSupported(
metadata: PaymentMethodMetadata,
requireToBeIncludedInIntentPaymentMethodTypes: Boolean = true,
): Boolean {
if (requireToBeIncludedInIntentPaymentMethodTypes && type.code !in metadata.stripeIntent.paymentMethodTypes) {
return false
}
return requirementsToBeUsedAsNewPaymentMethod(metadata.hasIntentToSetup()).all { requirement ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import com.stripe.android.customersheet.ExperimentalCustomerSheetApi
import com.stripe.android.lpmfoundations.FormHeaderInformation
import com.stripe.android.lpmfoundations.luxe.SupportedPaymentMethod
import com.stripe.android.lpmfoundations.paymentmethod.definitions.ExternalPaymentMethodUiDefinitionFactory
import com.stripe.android.lpmfoundations.paymentmethod.definitions.LinkCardBrandDefinition
import com.stripe.android.lpmfoundations.paymentmethod.link.LinkInlineConfiguration
import com.stripe.android.model.ElementsSession
import com.stripe.android.model.LinkMode
import com.stripe.android.model.PaymentIntent
import com.stripe.android.model.PaymentMethod
import com.stripe.android.model.SetupIntent
Expand Down Expand Up @@ -45,6 +47,7 @@ internal data class PaymentMethodMetadata(
val isGooglePayReady: Boolean,
val linkInlineConfiguration: LinkInlineConfiguration?,
val paymentMethodSaveConsentBehavior: PaymentMethodSaveConsentBehavior,
val linkMode: LinkMode?,
val financialConnectionsAvailable: Boolean = DefaultIsFinancialConnectionsAvailable(),
) : Parcelable {
fun hasIntentToSetup(): Boolean {
Expand Down Expand Up @@ -134,16 +137,31 @@ internal data class PaymentMethodMetadata(
}

private fun supportedPaymentMethodDefinitions(): List<PaymentMethodDefinition> {
return stripeIntent.paymentMethodTypes.mapNotNull {
PaymentMethodRegistry.definitionsByCode[it]
}.filter {
it.isSupported(this)
}.filterNot {
stripeIntent.isLiveMode &&
stripeIntent.unactivatedPaymentMethods.contains(it.type.code)
}.filter { paymentMethodDefinition ->
paymentMethodDefinition.uiDefinitionFactory().canBeDisplayedInUi(paymentMethodDefinition, sharedDataSpecs)
return stripeIntent.paymentMethodTypes
.mapNotNull { PaymentMethodRegistry.definitionsByCode[it] }
.filter { it.isSupported(this) }
.withLinkCardBrandIfSupported()
.filterNot {
stripeIntent.isLiveMode &&
stripeIntent.unactivatedPaymentMethods.contains(it.type.code)
}
.filter { paymentMethodDefinition ->
paymentMethodDefinition.uiDefinitionFactory()
.canBeDisplayedInUi(paymentMethodDefinition, sharedDataSpecs)
}
}

private fun List<PaymentMethodDefinition>.withLinkCardBrandIfSupported(): List<PaymentMethodDefinition> {
if (linkMode == LinkMode.LinkCardBrand) {
val isSupported = LinkCardBrandDefinition.isSupported(
metadata = this@PaymentMethodMetadata,
requireToBeIncludedInIntentPaymentMethodTypes = false,
)
if (isSupported) {
return this + LinkCardBrandDefinition
}
}
return this
}

fun amount(): Amount? {
Expand Down Expand Up @@ -272,6 +290,7 @@ internal data class PaymentMethodMetadata(
externalPaymentMethodSpecs = externalPaymentMethodSpecs,
paymentMethodSaveConsentBehavior = elementsSession.toPaymentSheetSaveConsentBehavior(),
linkInlineConfiguration = linkInlineConfiguration,
linkMode = elementsSession.linkSettings?.linkMode,
isGooglePayReady = isGooglePayReady,
)
}
Expand Down Expand Up @@ -303,6 +322,7 @@ internal data class PaymentMethodMetadata(
linkInlineConfiguration = null,
financialConnectionsAvailable = isFinancialConnectionsAvailable(),
paymentMethodSaveConsentBehavior = PaymentMethodSaveConsentBehavior.Legacy,
linkMode = elementsSession.linkSettings?.linkMode,
externalPaymentMethodSpecs = emptyList(),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.stripe.android.lpmfoundations.paymentmethod.definitions.IdealDefiniti
import com.stripe.android.lpmfoundations.paymentmethod.definitions.InstantDebitsDefinition
import com.stripe.android.lpmfoundations.paymentmethod.definitions.KlarnaDefinition
import com.stripe.android.lpmfoundations.paymentmethod.definitions.KonbiniDefinition
import com.stripe.android.lpmfoundations.paymentmethod.definitions.LinkCardBrandDefinition
import com.stripe.android.lpmfoundations.paymentmethod.definitions.MobilePayDefinition
import com.stripe.android.lpmfoundations.paymentmethod.definitions.MultibancoDefinition
import com.stripe.android.lpmfoundations.paymentmethod.definitions.OxxoDefinition
Expand Down Expand Up @@ -62,6 +63,7 @@ internal object PaymentMethodRegistry {
InstantDebitsDefinition,
KlarnaDefinition,
KonbiniDefinition,
LinkCardBrandDefinition,
MobilePayDefinition,
MultibancoDefinition,
OxxoDefinition,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.stripe.android.lpmfoundations.paymentmethod.definitions

import com.stripe.android.lpmfoundations.luxe.SupportedPaymentMethod
import com.stripe.android.lpmfoundations.paymentmethod.AddPaymentMethodRequirement
import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodDefinition
import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadata
import com.stripe.android.lpmfoundations.paymentmethod.UiDefinitionFactory
import com.stripe.android.model.PaymentMethod
import com.stripe.android.uicore.elements.FormElement
import com.stripe.android.ui.core.R as PaymentsUiCoreR

internal object LinkCardBrandDefinition : PaymentMethodDefinition {

override val type: PaymentMethod.Type = PaymentMethod.Type.Link

override val supportedAsSavedPaymentMethod: Boolean = false

override fun requirementsToBeUsedAsNewPaymentMethod(
hasIntentToSetup: Boolean
): Set<AddPaymentMethodRequirement> = setOf(
AddPaymentMethodRequirement.FinancialConnectionsSdk,
AddPaymentMethodRequirement.LinkCardBrand,
)

override fun requiresMandate(metadata: PaymentMethodMetadata): Boolean = true

override fun uiDefinitionFactory(): UiDefinitionFactory = LinkCardBrandDefinitionFactory
}

private object LinkCardBrandDefinitionFactory : UiDefinitionFactory.Simple {

override fun createSupportedPaymentMethod(): SupportedPaymentMethod {
return SupportedPaymentMethod(
code = InstantDebitsDefinition.type.code,
displayNameResource = PaymentsUiCoreR.string.stripe_paymentsheet_payment_method_instant_debits,
iconResource = PaymentsUiCoreR.drawable.stripe_ic_paymentsheet_pm_bank,
iconRequiresTinting = true,
lightThemeIconUrl = null,
darkThemeIconUrl = null,
)
}

// Link Card Brand uses its own mechanism, not these form elements.
override fun createFormElements(
metadata: PaymentMethodMetadata,
arguments: UiDefinitionFactory.Arguments,
): List<FormElement> {
return emptyList()
}
}

0 comments on commit cfc2417

Please sign in to comment.