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

Fix issue with authenticators launching activity in background #5667

Merged

Conversation

tillh-stripe
Copy link
Collaborator

@tillh-stripe tillh-stripe commented Oct 4, 2022

Summary

Resolves: #5508

This pull request fixes an issue where launching a PaymentAuthenticator while in the background resulted in the payment being considered a failure in the SDK, even if the payment was successful. The problem seems to be limited to Android 10 and 11.

Instead of launching the authentication activity right away, we now observe the lifecycle of AuthActivityStarterHost and only start the activity when it’s resumed.

Motivation

Bugfix.

Testing

  • Added tests
  • Modified tests
  • Manually verified

Screenshots

N/A.

Changelog

Completed payments are no longer incorrectly reported as having failed if the app is backgrounded during confirmation on Android 10 and 11.

@github-actions
Copy link
Contributor

github-actions bot commented Oct 4, 2022

Diffuse output:

OLD: paymentsheet-example-release-master.apk (signature: none)
NEW: paymentsheet-example-release-pr.apk (signature: none)

          │          compressed          │           uncompressed           
          ├──────────┬──────────┬────────┼───────────┬───────────┬──────────
 APK      │ old      │ new      │ diff   │ old       │ new       │ diff     
──────────┼──────────┼──────────┼────────┼───────────┼───────────┼──────────
      dex │  5.2 MiB │  5.2 MiB │ +670 B │  13.5 MiB │  13.5 MiB │ +1.4 KiB 
     arsc │  1.9 MiB │  1.9 MiB │    0 B │   1.9 MiB │   1.9 MiB │      0 B 
 manifest │  4.1 KiB │  4.1 KiB │    0 B │  19.2 KiB │  19.2 KiB │      0 B 
      res │    1 MiB │    1 MiB │    0 B │   1.8 MiB │   1.8 MiB │      0 B 
   native │  2.5 MiB │  2.5 MiB │    0 B │   5.9 MiB │   5.9 MiB │      0 B 
    asset │    3 MiB │    3 MiB │   +7 B │     3 MiB │     3 MiB │     +7 B 
    other │ 82.5 KiB │ 82.5 KiB │    0 B │ 158.5 KiB │ 158.5 KiB │      0 B 
──────────┼──────────┼──────────┼────────┼───────────┼───────────┼──────────
    total │ 13.7 MiB │ 13.7 MiB │ +677 B │  26.3 MiB │  26.3 MiB │ +1.4 KiB 

         │          raw           │             unique              
         ├────────┬────────┬──────┼────────┬────────┬───────────────
 DEX     │ old    │ new    │ diff │ old    │ new    │ diff          
─────────┼────────┼────────┼──────┼────────┼────────┼───────────────
   files │      2 │      2 │    0 │        │        │               
 strings │  84308 │  84317 │   +9 │  70089 │  70094 │  +5 (+8 -3)   
   types │  21696 │  21699 │   +3 │  19525 │  19526 │  +1 (+2 -1)   
 classes │  17503 │  17504 │   +1 │  17503 │  17504 │  +1 (+2 -1)   
 methods │  93892 │  93898 │   +6 │  89915 │  89920 │  +5 (+31 -26) 
  fields │ 101806 │ 101821 │  +15 │ 100569 │ 100583 │ +14 (+14 -0)  

 ARSC    │ old  │ new  │ diff 
─────────┼──────┼──────┼──────
 configs │  334 │  334 │  0   
 entries │ 6252 │ 6252 │  0
APK
    compressed    │    uncompressed     │                                
─────────┬────────┼──────────┬──────────┤                                
 size    │ diff   │ size     │ diff     │ path                           
─────────┼────────┼──────────┼──────────┼────────────────────────────────
 2.1 MiB │ +646 B │  5.8 MiB │ +1.4 KiB │ ∆ classes2.dex                 
 3.1 MiB │  +24 B │  7.8 MiB │    -28 B │ ∆ classes.dex                  
   8 KiB │   +9 B │  7.8 KiB │     +9 B │ ∆ assets/dexopt/baseline.prof  
   766 B │   -2 B │    634 B │     -2 B │ ∆ assets/dexopt/baseline.profm 
─────────┼────────┼──────────┼──────────┼────────────────────────────────
 5.2 MiB │ +677 B │ 13.5 MiB │ +1.4 KiB │ (total)
DEX
STRINGS:

   old   │ new   │ diff       
  ───────┼───────┼────────────
   70089 │ 70094 │ +5 (+8 -3) 
  + ~~R8{backend:dex,compilation-mode:release,has-checksums:false,min-api:21,pg-map-id:5be23d1,r8-mode:compatibility,version:3.3.70}
  + _authenticatable
  + Lcom/stripe/android/payments/core/authentication/PaymentAuthenticator_authenticate_2_1;
  + Lcom/stripe/android/payments/core/authentication/PaymentAuthenticator_authenticate_2;
  + PaymentAuthenticator.kt
  + com.stripe.android.payments.core.authentication.PaymentAuthenticator_authenticate_2
  + com.stripe.android.payments.core.authentication.PaymentAuthenticator_authenticate_2_1
  + performAuthentication
  
  - ~~R8{backend:dex,compilation-mode:release,has-checksums:false,min-api:21,pg-map-id:49ff0aa,r8-mode:compatibility,version:3.3.70}
  - Lcom/stripe/android/payments/core/authentication/PaymentAuthenticator_DefaultImpls;
  - TAuthenticatable;>;)V
  

TYPES:

   old   │ new   │ diff       
  ───────┼───────┼────────────
   19525 │ 19526 │ +1 (+2 -1) 
  + Lcom/stripe/android/payments/core/authentication/PaymentAuthenticator_authenticate_2_1;
  + Lcom/stripe/android/payments/core/authentication/PaymentAuthenticator_authenticate_2;
  
  - Lcom/stripe/android/payments/core/authentication/PaymentAuthenticator_DefaultImpls;
  

METHODS:

   old   │ new   │ diff         
  ───────┼───────┼──────────────
   89915 │ 89920 │ +5 (+31 -26) 
  + com.stripe.android.payments.core.authentication.NoOpIntentAuthenticator performAuthentication(AuthActivityStarterHost, StripeIntent, ApiRequest_Options, d) → Object
  + com.stripe.android.payments.core.authentication.NoOpIntentAuthenticator performAuthentication(AuthActivityStarterHost, Object, ApiRequest_Options, d) → Object
  + com.stripe.android.payments.core.authentication.OxxoAuthenticator performAuthentication(AuthActivityStarterHost, StripeIntent, ApiRequest_Options, d) → Object
  + com.stripe.android.payments.core.authentication.OxxoAuthenticator performAuthentication(AuthActivityStarterHost, Object, ApiRequest_Options, d) → Object
  + com.stripe.android.payments.core.authentication.PaymentAuthenticator_authenticate_2_1 <init>(PaymentAuthenticator, AuthActivityStarterHost, Object, ApiRequest_Options, d)
  + com.stripe.android.payments.core.authentication.PaymentAuthenticator_authenticate_2_1 create(Object, d) → d
  + com.stripe.android.payments.core.authentication.PaymentAuthenticator_authenticate_2_1 invoke(Object, Object) → Object
  + com.stripe.android.payments.core.authentication.PaymentAuthenticator_authenticate_2_1 invoke(b0, d) → Object
  + com.stripe.android.payments.core.authentication.PaymentAuthenticator_authenticate_2_1 invokeSuspend(Object) → Object
  + com.stripe.android.payments.core.authentication.PaymentAuthenticator_authenticate_2 <init>(b0, PaymentAuthenticator, AuthActivityStarterHost, Object, ApiRequest_Options, d)
  + com.stripe.android.payments.core.authentication.PaymentAuthenticator_authenticate_2 create(Object, d) → d
  + com.stripe.android.payments.core.authentication.PaymentAuthenticator_authenticate_2 invoke(Object, Object) → Object
  + com.stripe.android.payments.core.authentication.PaymentAuthenticator_authenticate_2 invoke(b0, d) → Object
  + com.stripe.android.payments.core.authentication.PaymentAuthenticator_authenticate_2 invokeSuspend(Object) → Object
  + com.stripe.android.payments.core.authentication.PaymentAuthenticator <init>()
  + com.stripe.android.payments.core.authentication.PaymentAuthenticator onLauncherInvalidated()
  + com.stripe.android.payments.core.authentication.PaymentAuthenticator onNewActivityResultCaller(c, b)
  + com.stripe.android.payments.core.authentication.PaymentAuthenticator performAuthentication(AuthActivityStarterHost, Object, ApiRequest_Options, d) → Object
  + com.stripe.android.payments.core.authentication.SourceAuthenticator performAuthentication(AuthActivityStarterHost, Source, ApiRequest_Options, d) → Object
  + com.stripe.android.payments.core.authentication.SourceAuthenticator performAuthentication(AuthActivityStarterHost, Object, ApiRequest_Options, d) → Object
  + com.stripe.android.payments.core.authentication.UnsupportedAuthenticator performAuthentication(AuthActivityStarterHost, StripeIntent, ApiRequest_Options, d) → Object
  + com.stripe.android.payments.core.authentication.UnsupportedAuthenticator performAuthentication(AuthActivityStarterHost, Object, ApiRequest_Options, d) → Object
  + com.stri
...✂

@tillh-stripe tillh-stripe force-pushed the tillh/5508-fix-issue-launch-activity-in-background branch 2 times, most recently from 1284244 to e37e132 Compare October 7, 2022 14:43
Comment on lines +34 to +38
val lifecycleOwner = host.lifecycleOwner
lifecycleOwner.lifecycleScope.launch {
lifecycleOwner.whenResumed {
performAuthentication(host, authenticatable, requestOptions)
}
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Using this in the public authenticate() method and calling a protected method that subclasses can override. Seemed better to me than making every single authenticator implement the lifecycle observation. Thoughts?

@@ -43,6 +49,8 @@ sealed class AuthActivityStarterHost {
override val statusBarColor: Int?
) : AuthActivityStarterHost() {

override val lifecycleOwner: LifecycleOwner = fragment.requireActivity()
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Any concerns with this as the lifecycle owner?

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we guarantee that the activity is a lifecycle owner?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, because every activity is. I was just wondering if there are any downsides to using the activity lifecycle instead of the fragment lifecycle. Since we call fragment.startActivityForResult() below, I’ll just change the lifecycleOwner to also point to the fragment.


@RunWith(RobolectricTestRunner::class)
@OptIn(ExperimentalCoroutinesApi::class)
class GenericAuthenticatorTest {
Copy link
Collaborator Author

@tillh-stripe tillh-stripe Oct 7, 2022

Choose a reason for hiding this comment

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

Using this to test the lifecycle observation behavior. The tests for the authenticator subclasses stay (mostly) unchanged.

@tillh-stripe tillh-stripe marked this pull request as ready for review October 7, 2022 14:51
@tillh-stripe tillh-stripe force-pushed the tillh/5508-fix-issue-launch-activity-in-background branch from e1d0d71 to 4b70ade Compare October 14, 2022 19:03
Copy link
Contributor

@brnunes-stripe brnunes-stripe left a comment

Choose a reason for hiding this comment

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

Adding @ccen-stripe to reviewers

}

@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
abstract suspend fun performAuthentication(
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this be protected?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I made this public so that I could test it in unit tests, and added the VisibleForTesting annotation to limit its visibility outside of tests.

But now I’m thinking that I should instead call authenticate() in these tests and just mock host.lifecycleOwner. Will update the pull request!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Pull request updated, so this is now protected

@tillh-stripe tillh-stripe force-pushed the tillh/5508-fix-issue-launch-activity-in-background branch from fe5ed6e to 8f4412a Compare October 17, 2022 19:36
@tillh-stripe tillh-stripe force-pushed the tillh/5508-fix-issue-launch-activity-in-background branch from 8f4412a to 1742afc Compare October 17, 2022 19:38
@tillh-stripe tillh-stripe merged commit e51f7b6 into master Oct 17, 2022
@tillh-stripe tillh-stripe deleted the tillh/5508-fix-issue-launch-activity-in-background branch October 17, 2022 21:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[BUG] PaymentRelayActivity is not started and it results in PaymentResult.Failed
4 participants