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

Add setup_future_usage for saved Link payment methods from PM and passthrough mode. #8117

Merged
merged 6 commits into from
Mar 22, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ import com.stripe.android.model.SetupIntent
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
sealed class ConfirmStripeIntentParamsFactory<out T : ConfirmStripeIntentParams> {

abstract fun create(paymentMethod: PaymentMethod): T
abstract fun create(
paymentMethod: PaymentMethod,
requiresSaveOnConfirmation: Boolean = false,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have tests for passing the right value into this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some network tests for ensuring the value is properly passed in PM and passthrough mode.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to have unit tests as well?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added unit tests in PaymentSheetViewModel.

): T

abstract fun create(
createParams: PaymentMethodCreateParams,
Expand All @@ -29,7 +32,7 @@ sealed class ConfirmStripeIntentParamsFactory<out T : ConfirmStripeIntentParams>

fun createFactory(
clientSecret: String,
shipping: ConfirmPaymentIntentParams.Shipping?
shipping: ConfirmPaymentIntentParams.Shipping?,
) = when {
PaymentIntent.ClientSecret.isMatch(clientSecret) -> {
ConfirmPaymentIntentParamsFactory(clientSecret, shipping)
Expand All @@ -49,14 +52,16 @@ internal class ConfirmPaymentIntentParamsFactory(
private val shipping: ConfirmPaymentIntentParams.Shipping?
) : ConfirmStripeIntentParamsFactory<ConfirmPaymentIntentParams>() {

override fun create(paymentMethod: PaymentMethod): ConfirmPaymentIntentParams {
override fun create(paymentMethod: PaymentMethod, requiresSaveOnConfirmation: Boolean): ConfirmPaymentIntentParams {
return ConfirmPaymentIntentParams.createWithPaymentMethodId(
paymentMethodId = paymentMethod.id.orEmpty(),
clientSecret = clientSecret,
paymentMethodOptions = when (paymentMethod.type) {
PaymentMethod.Type.Card -> {
PaymentMethodOptionsParams.Card(
setupFutureUsage = ConfirmPaymentIntentParams.SetupFutureUsage.Blank
setupFutureUsage = ConfirmPaymentIntentParams.SetupFutureUsage.OffSession?.takeIf {
requiresSaveOnConfirmation
} ?: ConfirmPaymentIntentParams.SetupFutureUsage.Blank
)
}
PaymentMethod.Type.USBankAccount -> {
Expand Down Expand Up @@ -90,7 +95,7 @@ internal class ConfirmPaymentIntentParamsFactory(
internal class ConfirmSetupIntentParamsFactory(
private val clientSecret: String,
) : ConfirmStripeIntentParamsFactory<ConfirmSetupIntentParams>() {
override fun create(paymentMethod: PaymentMethod): ConfirmSetupIntentParams {
override fun create(paymentMethod: PaymentMethod, requiresSaveOnConfirmation: Boolean): ConfirmSetupIntentParams {
return ConfirmSetupIntentParams.create(
paymentMethodId = paymentMethod.id.orEmpty(),
clientSecret = clientSecret,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,44 @@ class ConfirmPaymentIntentParamsFactoryTest {
assertThat(result.shipping).isEqualTo(shippingDetails)
}

@Test
fun `create() with saved card and does not require save on confirmation`() {
val factoryWithConfig = ConfirmPaymentIntentParamsFactory(
clientSecret = CLIENT_SECRET,
shipping = null,
)

val result = factoryWithConfig.create(
paymentMethod = PaymentMethodFixtures.CARD_PAYMENT_METHOD,
requiresSaveOnConfirmation = false
)

assertThat(result.paymentMethodOptions).isEqualTo(
PaymentMethodOptionsParams.Card(
setupFutureUsage = ConfirmPaymentIntentParams.SetupFutureUsage.Blank
)
)
}

@Test
fun `create() with saved card and requires save on confirmation`() {
val factoryWithConfig = ConfirmPaymentIntentParamsFactory(
clientSecret = CLIENT_SECRET,
shipping = null,
)

val result = factoryWithConfig.create(
paymentMethod = PaymentMethodFixtures.CARD_PAYMENT_METHOD,
requiresSaveOnConfirmation = true
)

assertThat(result.paymentMethodOptions).isEqualTo(
PaymentMethodOptionsParams.Card(
setupFutureUsage = ConfirmPaymentIntentParams.SetupFutureUsage.OffSession
)
)
}

private companion object {
private const val CLIENT_SECRET = "client_secret"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,121 @@ internal class LinkTest {
page.clickPrimaryButton()
}

@Test
fun testSuccessfulCardPaymentWithLinkSignUpAndSaveForFutureUsage() =
activityScenarioRule.runLinkTest(
integrationType = integrationType,
paymentOptionCallback = { paymentOption ->
assertThat(paymentOption?.label).endsWith("4242")

@Suppress("DEPRECATION")
assertThat(paymentOption?.drawableResourceId).isEqualTo(R.drawable.stripe_ic_paymentsheet_link)
},
resultCallback = ::assertCompleted,
) { testContext ->
networkRule.enqueue(
host("api.stripe.com"),
method("GET"),
path("/v1/elements/sessions"),
) { response ->
response.testBodyFromFile("elements-sessions-requires_payment_method.json")
}

networkRule.enqueue(
host("api.stripe.com"),
method("GET"),
path("/v1/customers/cus_1"),
) { response ->
response.testBodyFromFile("customer-get-success.json")
}

networkRule.enqueue(
host("api.stripe.com"),
method("GET"),
path("/v1/payment_methods"),
) { response ->
response.testBodyFromFile("payment-methods-get-success-empty.json")
}

networkRule.enqueue(
method("POST"),
path("/v1/consumers/sessions/lookup"),
) { response ->
response.testBodyFromFile("consumer-session-lookup-success.json")
}

networkRule.enqueue(
method("POST"),
path("/v1/consumers/sessions/lookup"),
) { response ->
response.testBodyFromFile("consumer-session-lookup-success.json")
}

testContext.launch(
configuration = PaymentSheet.Configuration(
merchantDisplayName = "Merchant, Inc.",
customer = PaymentSheet.CustomerConfiguration(
id = "cus_1",
ephemeralKeySecret = "123"
)
)
)

page.fillOutCardDetails()
page.clickOnSaveForFutureUsage("Merchant, Inc.")

closeSoftKeyboard()

networkRule.enqueue(
method("POST"),
path("/v1/consumers/sessions/lookup"),
) { response ->
response.testBodyFromFile("consumer-session-lookup-success.json")
}

page.fillOutLinkPhone()

closeSoftKeyboard()

networkRule.enqueue(
method("POST"),
path("/v1/consumers/sessions/lookup"),
) { response ->
response.testBodyFromFile("consumer-session-lookup-success.json")
}

networkRule.enqueue(
method("POST"),
path("/v1/consumers/accounts/sign_up"),
) { response ->
response.testBodyFromFile("consumer-accounts-signup-success.json")
}

networkRule.enqueue(
method("POST"),
path("/v1/consumers/payment_details"),
) { response ->
response.testBodyFromFile("consumer-payment-details-success.json")
}

networkRule.enqueue(
method("POST"),
path("/v1/payment_intents/pi_example/confirm"),
bodyPart("payment_method_options%5Bcard%5D%5Bsetup_future_usage%5D", "off_session"),
) { response ->
response.testBodyFromFile("payment-intent-confirm.json")
}

networkRule.enqueue(
method("POST"),
path("/v1/consumers/sessions/log_out"),
) { response ->
response.testBodyFromFile("consumer-session-logout-success.json")
}

page.clickPrimaryButton()
}

@Test
fun testSuccessfulCardPaymentWithLinkSignUpAndCardBrandChoice() = activityScenarioRule.runLinkTest(
integrationType = integrationType,
Expand Down Expand Up @@ -306,6 +421,128 @@ internal class LinkTest {
page.clickPrimaryButton()
}

@Test
fun testSuccessfulCardPaymentWithLinkSignUpAndLinkPassthroughModeAndSaveForFutureUsage() =
activityScenarioRule.runLinkTest(
integrationType = integrationType,
paymentOptionCallback = { paymentOption ->
assertThat(paymentOption?.label).endsWith("4242")

@Suppress("DEPRECATION")
assertThat(paymentOption?.drawableResourceId).isEqualTo(R.drawable.stripe_ic_paymentsheet_link)
},
resultCallback = ::assertCompleted,
) { testContext ->
networkRule.enqueue(
host("api.stripe.com"),
method("GET"),
path("/v1/elements/sessions"),
) { response ->
response.testBodyFromFile("elements-sessions-requires_pm_with_link_ps_mode.json")
}

networkRule.enqueue(
host("api.stripe.com"),
method("GET"),
path("/v1/customers/cus_1"),
) { response ->
response.testBodyFromFile("customer-get-success.json")
}

networkRule.enqueue(
host("api.stripe.com"),
method("GET"),
path("/v1/payment_methods"),
) { response ->
response.testBodyFromFile("payment-methods-get-success-empty.json")
}

networkRule.enqueue(
method("POST"),
path("/v1/consumers/sessions/lookup"),
) { response ->
response.testBodyFromFile("consumer-session-lookup-success.json")
}

networkRule.enqueue(
method("POST"),
path("/v1/consumers/sessions/lookup"),
) { response ->
response.testBodyFromFile("consumer-session-lookup-success.json")
}

testContext.launch(
configuration = PaymentSheet.Configuration(
merchantDisplayName = "Merchant, Inc.",
customer = PaymentSheet.CustomerConfiguration(
id = "cus_1",
ephemeralKeySecret = "123"
)
)
)

page.fillOutCardDetails()
page.clickOnSaveForFutureUsage("Merchant, Inc.")

closeSoftKeyboard()

networkRule.enqueue(
method("POST"),
path("/v1/consumers/sessions/lookup"),
) { response ->
response.testBodyFromFile("consumer-session-lookup-success.json")
}

page.fillOutLinkPhone()

closeSoftKeyboard()

networkRule.enqueue(
method("POST"),
path("/v1/consumers/sessions/lookup"),
) { response ->
response.testBodyFromFile("consumer-session-lookup-success.json")
}

networkRule.enqueue(
method("POST"),
path("/v1/consumers/accounts/sign_up"),
) { response ->
response.testBodyFromFile("consumer-accounts-signup-success.json")
}

networkRule.enqueue(
method("POST"),
path("/v1/consumers/payment_details"),
) { response ->
response.testBodyFromFile("consumer-payment-details-success.json")
}

networkRule.enqueue(
method("POST"),
path("/v1/consumers/payment_details/share"),
) { response ->
response.testBodyFromFile("consumer-payment-details-share-success.json")
}

networkRule.enqueue(
method("POST"),
path("/v1/payment_intents/pi_example/confirm"),
bodyPart("payment_method_options%5Bcard%5D%5Bsetup_future_usage%5D", "off_session"),
) { response ->
response.testBodyFromFile("payment-intent-confirm.json")
}

networkRule.enqueue(
method("POST"),
path("/v1/consumers/sessions/log_out"),
) { response ->
response.testBodyFromFile("consumer-session-logout-success.json")
}

page.clickPrimaryButton()
}

@Test
fun testSuccessfulCardPaymentWithLinkSignUpPassthroughModeAndCardBrandChoice() = activityScenarioRule.runLinkTest(
integrationType = integrationType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ internal class PaymentSheetPage(
clickViewWithText("Save your info for secure 1-click checkout with Link")
}

fun clickOnSaveForFutureUsage(merchantName: String) {
Espresso.onIdle()
composeTestRule.waitForIdle()

waitForText("Save for future $merchantName payments", true)
clickViewWithText("Save for future $merchantName payments")
}

fun clickOnLinkCheckbox() {
Espresso.onIdle()
composeTestRule.waitForIdle()
Expand Down Expand Up @@ -72,8 +80,8 @@ internal class PaymentSheetPage(
Espresso.onIdle()
composeTestRule.waitForIdle()

waitForText("Phone number")
replaceText("Phone number", phoneNumber)
waitForText("Phone number", true)
replaceText("Phone number", phoneNumber, true)
}

fun fillOutLinkName() {
Expand Down Expand Up @@ -147,8 +155,8 @@ internal class PaymentSheetPage(
.performClick()
}

fun replaceText(label: String, text: String) {
composeTestRule.onNode(hasText(label))
fun replaceText(label: String, text: String, isLabelSubstring: Boolean = false) {
composeTestRule.onNode(hasText(label, substring = isLabelSubstring))
.performScrollTo()
.performTextReplacement(text)
}
Expand Down
Loading
Loading