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

Restore selected payment method when user returns to Link #5148

Merged
merged 7 commits into from Jun 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 6 additions & 16 deletions link/api/link.api
Expand Up @@ -18,8 +18,8 @@ public final class com/stripe/android/link/LinkActivityContract$Args : com/strip
public static final field $stable I
public static final field CREATOR Landroid/os/Parcelable$Creator;
public static final field Companion Lcom/stripe/android/link/LinkActivityContract$Args$Companion;
public final fun copy (Lcom/stripe/android/model/StripeIntent;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/stripe/android/link/LinkActivityContract$Args$InjectionParams;)Lcom/stripe/android/link/LinkActivityContract$Args;
public static synthetic fun copy$default (Lcom/stripe/android/link/LinkActivityContract$Args;Lcom/stripe/android/model/StripeIntent;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/stripe/android/link/LinkActivityContract$Args$InjectionParams;ILjava/lang/Object;)Lcom/stripe/android/link/LinkActivityContract$Args;
public final fun copy (Lcom/stripe/android/model/StripeIntent;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/stripe/android/link/LinkPaymentDetails;Lcom/stripe/android/link/LinkActivityContract$Args$InjectionParams;)Lcom/stripe/android/link/LinkActivityContract$Args;
public static synthetic fun copy$default (Lcom/stripe/android/link/LinkActivityContract$Args;Lcom/stripe/android/model/StripeIntent;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/stripe/android/link/LinkPaymentDetails;Lcom/stripe/android/link/LinkActivityContract$Args$InjectionParams;ILjava/lang/Object;)Lcom/stripe/android/link/LinkActivityContract$Args;
public fun describeContents ()I
public fun equals (Ljava/lang/Object;)Z
public fun hashCode ()I
Expand Down Expand Up @@ -121,21 +121,11 @@ public final class com/stripe/android/link/LinkActivityViewModel_Factory_Members
public static fun injectViewModel (Lcom/stripe/android/link/LinkActivityViewModel$Factory;Lcom/stripe/android/link/LinkActivityViewModel;)V
}

public final class com/stripe/android/link/LinkPaymentDetails : android/os/Parcelable {
public abstract class com/stripe/android/link/LinkPaymentDetails : android/os/Parcelable {
public static final field $stable I
public static final field CREATOR Landroid/os/Parcelable$Creator;
public fun <init> (Lcom/stripe/android/model/ConsumerPaymentDetails$PaymentDetails;Lcom/stripe/android/model/PaymentMethodCreateParams;)V
public final fun component1 ()Lcom/stripe/android/model/ConsumerPaymentDetails$PaymentDetails;
public final fun component2 ()Lcom/stripe/android/model/PaymentMethodCreateParams;
public final fun copy (Lcom/stripe/android/model/ConsumerPaymentDetails$PaymentDetails;Lcom/stripe/android/model/PaymentMethodCreateParams;)Lcom/stripe/android/link/LinkPaymentDetails;
public static synthetic fun copy$default (Lcom/stripe/android/link/LinkPaymentDetails;Lcom/stripe/android/model/ConsumerPaymentDetails$PaymentDetails;Lcom/stripe/android/model/PaymentMethodCreateParams;ILjava/lang/Object;)Lcom/stripe/android/link/LinkPaymentDetails;
public fun describeContents ()I
public fun equals (Ljava/lang/Object;)Z
public final fun getPaymentDetails ()Lcom/stripe/android/model/ConsumerPaymentDetails$PaymentDetails;
public final fun getPaymentMethodCreateParams ()Lcom/stripe/android/model/PaymentMethodCreateParams;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public fun writeToParcel (Landroid/os/Parcel;I)V
public synthetic fun <init> (Lcom/stripe/android/model/ConsumerPaymentDetails$PaymentDetails;Lcom/stripe/android/model/PaymentMethodCreateParams;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun getPaymentDetails ()Lcom/stripe/android/model/ConsumerPaymentDetails$PaymentDetails;
public fun getPaymentMethodCreateParams ()Lcom/stripe/android/model/PaymentMethodCreateParams;
}

public final class com/stripe/android/link/LinkPaymentLauncher_Factory {
Expand Down
3 changes: 2 additions & 1 deletion link/build.gradle
Expand Up @@ -105,7 +105,8 @@ android {
kotlinOptions {
freeCompilerArgs = [
"-Xuse-experimental=androidx.compose.ui.ExperimentalComposeUiApi",
"-Xuse-experimental=kotlinx.coroutines.FlowPreview"
"-Xuse-experimental=kotlinx.coroutines.FlowPreview",
"-Xopt-in=kotlin.RequiresOptIn"
]
jvmTarget = "1.8"
}
Expand Down
Expand Up @@ -325,6 +325,7 @@ internal class WalletScreenTest {
WalletBody(
isProcessing = isProcessing,
paymentDetails = paymentDetails,
initiallySelectedId = null,
primaryButtonLabel = primaryButtonLabel,
errorMessage = errorMessage,
onAddNewPaymentMethodClick = onAddNewPaymentMethodClick,
Expand Down
14 changes: 12 additions & 2 deletions link/src/main/java/com/stripe/android/link/LinkActivity.kt
Expand Up @@ -167,11 +167,21 @@ internal class LinkActivity : ComponentActivity() {
}
}

composable(LinkScreen.PaymentMethod.route) {
composable(
LinkScreen.PaymentMethod.route,
arguments = listOf(
navArgument(LinkScreen.PaymentMethod.loadArg) {
type = NavType.BoolType
}
)
) { backStackEntry ->
val loadFromArgs = backStackEntry.arguments
?.getBoolean(LinkScreen.PaymentMethod.loadArg) ?: false
linkAccount?.let { account ->
PaymentMethodBody(
account,
viewModel.injector
viewModel.injector,
loadFromArgs
)
}
}
Expand Down
Expand Up @@ -31,6 +31,7 @@ class LinkActivityContract :
* @param merchantName The customer-facing business name.
* @param customerEmail Email of the customer, used to pre-fill the form.
* @param customerPhone Phone number of the customer, used to pre-fill the form.
* @param selectedPaymentDetails The payment method previously selected by the user.
* @param injectionParams Parameters needed to perform dependency injection.
* If null, a new dependency graph will be created.
*/
Expand All @@ -41,6 +42,7 @@ class LinkActivityContract :
internal val merchantName: String,
internal val customerEmail: String? = null,
internal val customerPhone: String? = null,
internal val selectedPaymentDetails: LinkPaymentDetails? = null,
internal val injectionParams: InjectionParams? = null
) : ActivityStarter.Args {

Expand Down
Expand Up @@ -3,6 +3,7 @@ package com.stripe.android.link
import android.os.Parcelable
import com.stripe.android.model.ConsumerPaymentDetails
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.ui.core.forms.convertToFormValuesMap
import kotlinx.parcelize.Parcelize

/**
Expand All @@ -13,8 +14,36 @@ import kotlinx.parcelize.Parcelize
* @param paymentMethodCreateParams The [PaymentMethodCreateParams] to be used to confirm
* the Stripe Intent.
*/
@Parcelize
data class LinkPaymentDetails(
val paymentDetails: ConsumerPaymentDetails.PaymentDetails,
val paymentMethodCreateParams: PaymentMethodCreateParams
) : Parcelable
sealed class LinkPaymentDetails(
open val paymentDetails: ConsumerPaymentDetails.PaymentDetails,
open val paymentMethodCreateParams: PaymentMethodCreateParams
) : Parcelable {

/**
* A [ConsumerPaymentDetails.PaymentDetails] that is already saved to the consumer's account.
*/
@Parcelize
internal class Saved(
override val paymentDetails: ConsumerPaymentDetails.PaymentDetails,
override val paymentMethodCreateParams: PaymentMethodCreateParams
) : LinkPaymentDetails(paymentDetails, paymentMethodCreateParams)

/**
* A new [ConsumerPaymentDetails.PaymentDetails], whose data was just collected from the user.
* Must hold the original [PaymentMethodCreateParams] too in case we need to populate the form
* fields with the user-entered values.
*/
@Parcelize
internal class New(
override val paymentDetails: ConsumerPaymentDetails.PaymentDetails,
override val paymentMethodCreateParams: PaymentMethodCreateParams,
private val originalParams: PaymentMethodCreateParams
) : LinkPaymentDetails(paymentDetails, paymentMethodCreateParams) {

/**
* Build a flat map of the values entered by the user when creating this payment method,
* in a format that can be used to set the initial values in the FormController.
*/
fun buildFormValues() = convertToFormValuesMap(originalParams.toParamMap())
}
}
Expand Up @@ -112,14 +112,17 @@ class LinkPaymentLauncher @AssistedInject internal constructor(
* @param stripeIntent the PaymentIntent or SetupIntent.
* @param completePayment whether the payment should be completed, or the selected payment
* method should be returned as a result.
* @param selectedPaymentDetails the payment method previously selected by the user, if they are
* returning to Link. It will be the initially selected value.
* @param coroutineScope the coroutine scope used to collect the account status flow.
*/
suspend fun setup(
stripeIntent: StripeIntent,
completePayment: Boolean,
selectedPaymentDetails: LinkPaymentDetails?,
coroutineScope: CoroutineScope
): AccountStatus {
val component = setupDependencies(stripeIntent, completePayment)
val component = setupDependencies(stripeIntent, completePayment, selectedPaymentDetails)
accountStatus = component.linkAccountManager.accountStatus.stateIn(coroutineScope)
linkAccountManager = component.linkAccountManager
return accountStatus.value
Expand Down Expand Up @@ -152,20 +155,22 @@ class LinkPaymentLauncher @AssistedInject internal constructor(
paymentMethodCreateParams: PaymentMethodCreateParams
): Result<LinkPaymentDetails> =
linkAccountManager.createPaymentDetails(
SupportedPaymentMethod.Card(),
SupportedPaymentMethod.Card,
paymentMethodCreateParams
)

private fun setupDependencies(
stripeIntent: StripeIntent,
completePayment: Boolean
completePayment: Boolean,
selectedPaymentDetails: LinkPaymentDetails?
): LinkPaymentLauncherComponent {
val args = LinkActivityContract.Args(
stripeIntent,
completePayment,
merchantName,
customerEmail,
customerPhone,
selectedPaymentDetails,
LinkActivityContract.Args.InjectionParams(
injectorKey,
productUsage,
Expand Down
11 changes: 10 additions & 1 deletion link/src/main/java/com/stripe/android/link/LinkScreen.kt
Expand Up @@ -12,7 +12,16 @@ internal sealed class LinkScreen(
object Loading : LinkScreen("Loading")
object Verification : LinkScreen("Verification")
object Wallet : LinkScreen("Wallet")
object PaymentMethod : LinkScreen("PaymentMethod")

class PaymentMethod(loadFromArgs: Boolean = false) :
LinkScreen("PaymentMethod?$loadArg=$loadFromArgs") {
override val route = super.route

companion object {
const val loadArg = "loadFromArgs"
const val route = "PaymentMethod?$loadArg={$loadArg}"
}
}

class CardEdit(paymentDetailsId: String) :
LinkScreen("CardEdit?$idArg=${paymentDetailsId.urlEncode()}") {
Expand Down
Expand Up @@ -9,7 +9,6 @@ import com.stripe.android.link.model.LinkAccount
import com.stripe.android.link.repositories.LinkRepository
import com.stripe.android.link.ui.inline.UserInput
import com.stripe.android.link.ui.paymentmethod.SupportedPaymentMethod
import com.stripe.android.model.ConsumerPaymentDetailsCreateParams
import com.stripe.android.model.ConsumerPaymentDetailsUpdateParams
import com.stripe.android.model.ConsumerSession
import com.stripe.android.model.PaymentMethodCreateParams
Expand Down Expand Up @@ -193,9 +192,10 @@ internal class LinkAccountManager @Inject constructor(
): Result<LinkPaymentDetails> =
linkAccount.value?.let { account ->
createPaymentDetails(
paymentMethod.createParams(paymentMethodCreateParams, account.email),
args.stripeIntent,
paymentMethod.extraConfirmationParams(paymentMethodCreateParams)
paymentMethod,
paymentMethodCreateParams,
account.email,
args.stripeIntent
)
} ?: Result.failure(
IllegalStateException("A non-null Link account is needed to create payment details")
Expand All @@ -205,14 +205,16 @@ internal class LinkAccountManager @Inject constructor(
* Create a new payment method in the signed in consumer account.
*/
suspend fun createPaymentDetails(
paymentDetails: ConsumerPaymentDetailsCreateParams,
stripeIntent: StripeIntent,
extraConfirmationParams: Map<String, Any>?
paymentMethod: SupportedPaymentMethod,
paymentMethodCreateParams: PaymentMethodCreateParams,
userEmail: String,
stripeIntent: StripeIntent
) = retryingOnAuthError { clientSecret ->
linkRepository.createPaymentDetails(
paymentDetails,
paymentMethod,
paymentMethodCreateParams,
userEmail,
stripeIntent,
extraConfirmationParams,
clientSecret,
consumerPublishableKey
)
Expand Down
Expand Up @@ -7,11 +7,12 @@ import com.stripe.android.core.injection.STRIPE_ACCOUNT_ID
import com.stripe.android.core.networking.ApiRequest
import com.stripe.android.link.LinkPaymentDetails
import com.stripe.android.link.confirmation.ConfirmStripeIntentParamsFactory
import com.stripe.android.link.ui.paymentmethod.SupportedPaymentMethod
import com.stripe.android.model.ConsumerPaymentDetails
import com.stripe.android.model.ConsumerPaymentDetailsCreateParams
import com.stripe.android.model.ConsumerPaymentDetailsUpdateParams
import com.stripe.android.model.ConsumerSession
import com.stripe.android.model.ConsumerSessionLookup
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.model.StripeIntent
import com.stripe.android.networking.StripeRepository
import kotlinx.coroutines.withContext
Expand Down Expand Up @@ -209,31 +210,33 @@ internal class LinkApiRepository @Inject constructor(
}

override suspend fun createPaymentDetails(
paymentDetails: ConsumerPaymentDetailsCreateParams,
paymentMethod: SupportedPaymentMethod,
paymentMethodCreateParams: PaymentMethodCreateParams,
userEmail: String,
stripeIntent: StripeIntent,
extraConfirmationParams: Map<String, Any>?,
consumerSessionClientSecret: String,
consumerPublishableKey: String?
): Result<LinkPaymentDetails> = withContext(workContext) {
runCatching {
stripeRepository.createPaymentDetails(
consumerSessionClientSecret,
paymentDetails,
paymentMethod.createParams(paymentMethodCreateParams, userEmail),
consumerPublishableKey?.let {
ApiRequest.Options(it)
} ?: ApiRequest.Options(
publishableKeyProvider(),
stripeAccountIdProvider()
)
)?.paymentDetails?.first()?.let {
LinkPaymentDetails(
LinkPaymentDetails.New(
it,
ConfirmStripeIntentParamsFactory.createFactory(stripeIntent)
.createPaymentMethodCreateParams(
consumerSessionClientSecret,
it,
extraConfirmationParams
)
paymentMethod.extraConfirmationParams(paymentMethodCreateParams)
),
paymentMethodCreateParams
)
}
}.fold(
Expand Down
@@ -1,11 +1,12 @@
package com.stripe.android.link.repositories

import com.stripe.android.link.LinkPaymentDetails
import com.stripe.android.link.ui.paymentmethod.SupportedPaymentMethod
import com.stripe.android.model.ConsumerPaymentDetails
import com.stripe.android.model.ConsumerPaymentDetailsCreateParams
import com.stripe.android.model.ConsumerPaymentDetailsUpdateParams
import com.stripe.android.model.ConsumerSession
import com.stripe.android.model.ConsumerSessionLookup
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.model.StripeIntent

/**
Expand Down Expand Up @@ -71,9 +72,10 @@ internal interface LinkRepository {
* Create a new payment method in the consumer account.
*/
suspend fun createPaymentDetails(
paymentDetails: ConsumerPaymentDetailsCreateParams,
paymentMethod: SupportedPaymentMethod,
paymentMethodCreateParams: PaymentMethodCreateParams,
userEmail: String,
stripeIntent: StripeIntent,
extraConfirmationParams: Map<String, Any>? = null,
consumerSessionClientSecret: String,
consumerPublishableKey: String?
): Result<LinkPaymentDetails>
Expand Down