diff --git a/example/src/main/java/com/stripe/example/activity/PaymentAuthActivity.kt b/example/src/main/java/com/stripe/example/activity/PaymentAuthActivity.kt index 3af18b34558..5aff57975d5 100644 --- a/example/src/main/java/com/stripe/example/activity/PaymentAuthActivity.kt +++ b/example/src/main/java/com/stripe/example/activity/PaymentAuthActivity.kt @@ -15,6 +15,7 @@ import com.stripe.android.SetupIntentResult import com.stripe.android.Stripe import com.stripe.android.model.ConfirmPaymentIntentParams import com.stripe.android.model.ConfirmSetupIntentParams +import com.stripe.android.model.PaymentMethodCreateParams import com.stripe.example.R import com.stripe.example.Settings import com.stripe.example.module.RetrofitFactory @@ -82,11 +83,10 @@ class PaymentAuthActivity : AppCompatActivity() { private fun confirmPaymentIntent(paymentIntentClientSecret: String) { statusTextView.append("\n\nStarting payment authentication") - stripe.confirmPayment(this, - ConfirmPaymentIntentParams.createWithPaymentMethodId( - PAYMENT_METHOD_3DS2_REQUIRED, - paymentIntentClientSecret, - RETURN_URL)) + stripe.confirmPayment( + this, + create3ds2ConfirmParams(paymentIntentClientSecret) + ) } private fun confirmSetupIntent(setupIntentClientSecret: String) { @@ -189,7 +189,7 @@ class PaymentAuthActivity : AppCompatActivity() { } private fun createPaymentIntentParams(stripeAccountId: String?): HashMap { - val params = hashMapOf( + val params = hashMapOf( "payment_method_types[]" to "card", "amount" to 1000, "currency" to "usd" @@ -247,9 +247,35 @@ class PaymentAuthActivity : AppCompatActivity() { /** * See https://stripe.com/docs/payments/3d-secure#three-ds-cards for more options. */ - private const val PAYMENT_METHOD_3DS2_REQUIRED = "pm_card_threeDSecure2Required" - private const val PAYMENT_METHOD_3DS_REQUIRED = "pm_card_threeDSecureRequired" - private const val PAYMENT_METHOD_AUTH_REQUIRED_ON_SETUP = "pm_card_authenticationRequiredOnSetup" + private const val PAYMENT_METHOD_AUTH_REQUIRED_ON_SETUP = + "pm_card_authenticationRequiredOnSetup" + + private fun create3ds2ConfirmParams( + paymentIntentClientSecret: String + ): ConfirmPaymentIntentParams { + return ConfirmPaymentIntentParams.createWithPaymentMethodId( + "pm_card_threeDSecure2Required", + paymentIntentClientSecret, + RETURN_URL + ) + } + + private fun create3ds1ConfirmParams( + paymentIntentClientSecret: String + ): ConfirmPaymentIntentParams { + return ConfirmPaymentIntentParams.createWithPaymentMethodCreateParams( + PaymentMethodCreateParams.create( + PaymentMethodCreateParams.Card.Builder() + .setNumber("4000000000003063") + .setExpiryMonth(1) + .setExpiryYear(2025) + .setCvc("123") + .build() + ), + paymentIntentClientSecret, + RETURN_URL + ) + } private const val RETURN_URL = "stripe://payment_auth" diff --git a/stripe/src/main/java/com/stripe/android/view/PaymentAuthWebView.kt b/stripe/src/main/java/com/stripe/android/view/PaymentAuthWebView.kt index 25e848ad0f0..aad7354a6fe 100644 --- a/stripe/src/main/java/com/stripe/android/view/PaymentAuthWebView.kt +++ b/stripe/src/main/java/com/stripe/android/view/PaymentAuthWebView.kt @@ -4,10 +4,12 @@ import android.annotation.SuppressLint import android.annotation.TargetApi import android.app.Activity import android.content.Context +import android.content.Intent import android.net.Uri import android.os.Build import android.util.AttributeSet import android.view.View +import android.webkit.URLUtil import android.webkit.WebResourceRequest import android.webkit.WebView import android.webkit.WebViewClient @@ -78,12 +80,29 @@ internal class PaymentAuthWebView : WebView { override fun shouldOverrideUrlLoading(view: WebView, urlString: String): Boolean { val uri = Uri.parse(urlString) - if (isReturnUrl(uri)) { + return if (isReturnUrl(uri)) { onAuthCompleted() - return true + true + } else if (!URLUtil.isNetworkUrl(urlString)) { + openNonNetworkUrlDeeplink(uri) + true + } else { + super.shouldOverrideUrlLoading(view, urlString) } + } - return super.shouldOverrideUrlLoading(view, urlString) + /** + * Non-network URLs are likely deep-links into banking apps. If the deep-link can be opened + * via an Intent, start it. Otherwise, stop the authentication attempt. + */ + private fun openNonNetworkUrlDeeplink(uri: Uri) { + val intent = Intent(Intent.ACTION_VIEW, uri) + if (intent.resolveActivity(activity.packageManager) != null) { + activity.startActivity(intent) + } else { + // complete auth if the deep-link can't be opened + onAuthCompleted() + } } @TargetApi(Build.VERSION_CODES.LOLLIPOP) diff --git a/stripe/src/test/java/com/stripe/android/view/PaymentAuthWebViewTest.java b/stripe/src/test/java/com/stripe/android/view/PaymentAuthWebViewTest.java index 705b9b9bc75..8eafef8db3b 100644 --- a/stripe/src/test/java/com/stripe/android/view/PaymentAuthWebViewTest.java +++ b/stripe/src/test/java/com/stripe/android/view/PaymentAuthWebViewTest.java @@ -4,6 +4,8 @@ import android.webkit.WebView; import android.widget.ProgressBar; +import androidx.test.core.app.ApplicationProvider; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -13,6 +15,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @RunWith(RobolectricTestRunner.class) public class PaymentAuthWebViewTest { @@ -24,6 +27,8 @@ public class PaymentAuthWebViewTest { @Before public void setup() { MockitoAnnotations.initMocks(this); + when(mActivity.getPackageManager()) + .thenReturn(ApplicationProvider.getApplicationContext().getPackageManager()); } @Test @@ -122,4 +127,14 @@ public void shouldOverrideUrlLoading_withOpaqueUri_shouldNotCrash() { "pi_123_secret_456", null); paymentAuthWebViewClient.shouldOverrideUrlLoading(mWebView, deepLink); } + + @Test + public void shouldOverrideUrlLoading_withUnsupportedDeeplink_shouldFinish() { + final String deepLink = "deep://link"; + final PaymentAuthWebView.PaymentAuthWebViewClient paymentAuthWebViewClient = + new PaymentAuthWebView.PaymentAuthWebViewClient(mActivity, mProgressBar, + "pi_123_secret_456", null); + paymentAuthWebViewClient.shouldOverrideUrlLoading(mWebView, deepLink); + verify(mActivity).finish(); + } }