Skip to content

Conversation

@toluo-stripe
Copy link
Contributor

@toluo-stripe toluo-stripe commented Oct 16, 2025

Summary

Send correct status for stripe_android.paymenthandler.confirm.finished event.
The status field should only be 'succeeded', 'canceled', or 'failed'. That is not the case on android today (see query)

Motivation

android event status types

ios event statuses

https://jira.corp.stripe.com/browse/RUN_MOBILESDK-4730

Testing

  • Added tests
  • Modified tests
  • Manually verified

Changelog

Changes:
- Update PaymentLauncherViewModel to send 'succeeded', 'canceled', or 'failed'
  status values based on InternalPaymentResult instead of intent status codes
- Add tests to verify correct status values in analytics events

This ensures the paymenthandler.confirm.finished analytics event includes
the correct status corresponding to the result vended to the merchant,
fixing the issue where no 'failed' status events were being reported.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Committed-By-Agent: claude
@github-actions
Copy link
Contributor

github-actions bot commented Oct 16, 2025

Diffuse output:

OLD: paymentsheet-example-release-master.apk (signature: V1, V2)
NEW: paymentsheet-example-release-pr.apk (signature: V1, V2)

          │          compressed           │         uncompressed          
          ├───────────┬───────────┬───────┼───────────┬───────────┬───────
 APK      │ old       │ new       │ diff  │ old       │ new       │ diff  
──────────┼───────────┼───────────┼───────┼───────────┼───────────┼───────
      dex │   4.8 MiB │   4.8 MiB │ +26 B │  10.7 MiB │  10.7 MiB │ +28 B 
     arsc │   2.6 MiB │   2.6 MiB │   0 B │   2.6 MiB │   2.6 MiB │   0 B 
 manifest │   5.8 KiB │   5.8 KiB │   0 B │  30.4 KiB │  30.4 KiB │   0 B 
      res │ 929.9 KiB │ 929.9 KiB │   0 B │   1.5 MiB │   1.5 MiB │   0 B 
   native │   3.5 MiB │   3.5 MiB │   0 B │   8.5 MiB │   8.5 MiB │   0 B 
    asset │   1.6 MiB │   1.6 MiB │  -1 B │   1.6 MiB │   1.6 MiB │  -1 B 
    other │ 198.8 KiB │ 198.8 KiB │ +14 B │ 375.5 KiB │ 375.5 KiB │   0 B 
──────────┼───────────┼───────────┼───────┼───────────┼───────────┼───────
    total │  13.7 MiB │  13.7 MiB │ +39 B │  25.3 MiB │  25.3 MiB │ +27 B 

         │         raw          │          unique           
         ├───────┬───────┬──────┼───────┬───────┬───────────
 DEX     │ old   │ new   │ diff │ old   │ new   │ diff      
─────────┼───────┼───────┼──────┼───────┼───────┼───────────
   files │     2 │     2 │    0 │       │       │           
 strings │ 55023 │ 55023 │    0 │ 50291 │ 50291 │ 0 (+1 -1) 
   types │ 19840 │ 19840 │    0 │ 17540 │ 17540 │ 0 (+0 -0) 
 classes │ 14808 │ 14808 │    0 │ 14808 │ 14808 │ 0 (+0 -0) 
 methods │ 76026 │ 76026 │    0 │ 72346 │ 72346 │ 0 (+0 -0) 
  fields │ 49710 │ 49710 │    0 │ 48246 │ 48246 │ 0 (+0 -0) 

 ARSC    │ old  │ new  │ diff 
─────────┼──────┼──────┼──────
 configs │  242 │  242 │  0   
 entries │ 6365 │ 6365 │  0
APK
    compressed     │   uncompressed    │                               
───────────┬───────┼───────────┬───────┤                               
 size      │ diff  │ size      │ diff  │ path                          
───────────┼───────┼───────────┼───────┼───────────────────────────────
   4.3 MiB │ +25 B │   9.5 MiB │ +28 B │ ∆ classes.dex                 
  51.7 KiB │  +7 B │ 122.5 KiB │   0 B │ ∆ META-INF/MANIFEST.MF        
   1.2 KiB │  +4 B │   1.2 KiB │   0 B │ ∆ META-INF/CERT.RSA           
  55.2 KiB │  +3 B │ 122.5 KiB │   0 B │ ∆ META-INF/CERT.SF            
   9.5 KiB │  -1 B │   9.4 KiB │  -1 B │ ∆ assets/dexopt/baseline.prof 
 562.5 KiB │  +1 B │   1.3 MiB │   0 B │ ∆ classes2.dex                
───────────┼───────┼───────────┼───────┼───────────────────────────────
     5 MiB │ +39 B │    11 MiB │ +27 B │ (total)
DEX
STRINGS:

   old   │ new   │ diff      
  ───────┼───────┼───────────
   50291 │ 50291 │ 0 (+1 -1) 
  
  + ~~R8{"backend":"dex","compilation-mode":"release","has-checksums":false,"min-api":21,"pg-map-id":"3876cc1","r8-mode":"full","version":"8.8.34"}
  
  - ~~R8{"backend":"dex","compilation-mode":"release","has-checksums":false,"min-api":21,"pg-map-id":"c645238","r8-mode":"full","version":"8.8.34"}

@toluo-stripe toluo-stripe marked this pull request as ready for review October 16, 2025 19:20
@toluo-stripe toluo-stripe requested review from a team as code owners October 16, 2025 19:20
val intentParams = mapOf(
"intent_id" to intent?.clientSecret?.toStripeId(),
"status" to intent?.status?.code,
"status" to resultStatus,
Copy link
Contributor

Choose a reason for hiding this comment

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

This would affect handle_next_action.finished events, right? Is this expected?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That would map to what iOS is doing - https://hubble.corp.stripe.com/saved-queries/42d7a3b5-313b-4fe8-a550-121557b7c1da
@yuki-stripe Should this change affect handle_next_action.finished as well?

Copy link
Collaborator

Choose a reason for hiding this comment

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

100%

These bindings have two basically APIs - confirm and handleNextAction.

Our analytics should report when the APIs are called and when they return with the result we return to the merchant (reported as "status").

PaymentAnalyticsEvent.PaymentLauncherNextActionFinished
}

val resultStatus = when (stripeInternalResult) {
Copy link
Contributor

Choose a reason for hiding this comment

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

How would those requires_capture, requires_payment_method be mapped?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It first gets mapped here and then here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

requires_capture will map to success and requires_payment_method will map to failed

fun `verify confirm finished analytics includes succeeded status for completed result`() = runTest {
verifyConfirmFinishedAnalyticsStatus(
expectedStatus = "succeeded",
setup = { whenever(paymentIntent.requiresAction()).thenReturn(false) },
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens if requiresAction is true?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The goal here is to test that the analytics event's status is succeeded when stripeInternalResult is succeeded. Setting requiresAction to true could yield a success, canceled or failed state based on its configuration. So setting requiresAction to false gives us a simpler test configuration for StripeInternalResult.Success

@toluo-stripe toluo-stripe merged commit 6e9ccbd into master Oct 17, 2025
22 checks passed
@toluo-stripe toluo-stripe deleted the tolu/run/fix_finished_analytics branch October 17, 2025 17:36
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.

4 participants