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

Link Cvc & Expiry recollection VM #9882

Merged
merged 6 commits into from
Jan 14, 2025
Merged

Conversation

toluo-stripe
Copy link
Contributor

@toluo-stripe toluo-stripe commented Jan 9, 2025

Summary

  • Collect expiry date and cvc (JIRA)
  • Confirm payments (JIRA)

Testing

  • Added tests
  • Modified tests
  • Manually verified

Screenshots (UI not part of this PR)

Screen.Recording.2025-01-09.at.1.50.06.AM.mov

Changelog

@@ -23,7 +30,11 @@ internal data class WalletUiState(
val isExpired = card?.isExpired ?: false
val requiresCvcRecollection = card?.cvcCheck?.requiresRecollection ?: false

val disableButton = isExpired || requiresCvcRecollection
val isMissingExpiryDateInput = (expiryDateInput.isComplete && cvcInput.isComplete).not()
Copy link

Choose a reason for hiding this comment

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

The isMissingExpiryDateInput check appears to have an unintended dependency on cvcInput.isComplete. For validating expiry date input, the condition should be simplified to expiryDateInput.isComplete.not() since the CVC completion state is handled separately by isMissingCvcInput.

Spotted by Graphite Reviewer

Is this helpful? React 👍 or 👎 to let us know.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

expired cards requires both updated expiryDate and cvc

Copy link
Contributor

github-actions bot commented Jan 9, 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 │   3.9 MiB │   3.9 MiB │ +4.5 KiB │  8.6 MiB │  8.6 MiB │ +10.7 KiB 
     arsc │   2.3 MiB │   2.3 MiB │      0 B │  2.3 MiB │  2.3 MiB │       0 B 
 manifest │   5.1 KiB │   5.1 KiB │      0 B │ 25.2 KiB │ 25.2 KiB │       0 B 
      res │ 908.4 KiB │ 908.4 KiB │      0 B │  1.4 MiB │  1.4 MiB │       0 B 
   native │   2.6 MiB │   2.6 MiB │      0 B │    6 MiB │    6 MiB │       0 B 
    asset │   1.6 MiB │   1.6 MiB │     -1 B │  1.6 MiB │  1.6 MiB │      -1 B 
    other │   1.4 MiB │   1.4 MiB │     -4 B │  1.6 MiB │  1.6 MiB │       0 B 
──────────┼───────────┼───────────┼──────────┼──────────┼──────────┼───────────
    total │  12.6 MiB │  12.6 MiB │ +4.5 KiB │ 21.5 MiB │ 21.6 MiB │ +10.7 KiB 

 DEX     │ old   │ new   │ diff              
─────────┼───────┼───────┼───────────────────
   files │     1 │     1 │   0               
 strings │ 40731 │ 40763 │ +32 (+77 -45)     
   types │ 14046 │ 14065 │ +19 (+63 -44)     
 classes │ 11718 │ 11736 │ +18 (+19 -1)      
 methods │ 59741 │ 59810 │ +69 (+1483 -1414) 
  fields │ 39916 │ 39983 │ +67 (+1555 -1488) 

 ARSC    │ old  │ new  │ diff 
─────────┼──────┼──────┼──────
 configs │  243 │  243 │  0   
 entries │ 6209 │ 6209 │  0
APK
     compressed      │     uncompressed      │                                           
──────────┬──────────┼───────────┬───────────┤                                           
 size     │ diff     │ size      │ diff      │ path                                      
──────────┼──────────┼───────────┼───────────┼───────────────────────────────────────────
  3.9 MiB │ +4.5 KiB │   8.6 MiB │ +10.7 KiB │ ∆ classes.dex                             
  7.8 KiB │     -4 B │   7.6 KiB │      -4 B │ ∆ assets/dexopt/baseline.prof             
    989 B │     +3 B │     857 B │      +3 B │ ∆ assets/dexopt/baseline.profm            
 53.5 KiB │     -2 B │ 118.7 KiB │       0 B │ ∆ META-INF/CERT.SF                        
    271 B │     -1 B │     120 B │       0 B │ ∆ META-INF/version-control-info.textproto 
  1.2 KiB │     -1 B │   1.2 KiB │       0 B │ ∆ META-INF/CERT.RSA                       
──────────┼──────────┼───────────┼───────────┼───────────────────────────────────────────
    4 MiB │ +4.5 KiB │   8.7 MiB │ +10.7 KiB │ (total)
DEX
STRINGS:

   old   │ new   │ diff          
  ───────┼───────┼───────────────
   40731 │ 40763 │ +32 (+77 -45) 
  
  + , alertMessage=
  + , cvcInput=
  + , expiryDateInput=
  + Collection contains more than one matching element.
  + DefaultLinkConfirmationHandler: Failed to confirm payment
  + DefaultLinkConfirmationHandler: Payment confirmation returned null
  + Failed(message=
  + LG6/A;
  + LG6/B;
  + LG6/C;
  + LG6/D;
  + LG6/E;
  + LG6/F;
  + LG6/y;
  + LG6/z;
  + LLLLZLLLLI
  + LP6/s3;
  + LS6/U;
  + Lq3/i;
  + Lr6/o;
  + Lt6/b;
  + Lt6/c;
  + Lt6/d;
  + Lt6/e;
  + Lt6/f;
  + Lt6/g;
  + Lw6/o;
  + No client secret found.
  + VLLZLZLLLL
  + [LP6/A1;
  + [LP6/C;
  + [LP6/F0;
  + [LP6/H2;
  + [LP6/K;
  + [LP6/N2;
  + [LP6/N;
  + [LP6/S0;
  + [LP6/S;
  + [LP6/X0;
  + [LP6/X2;
  + [LP6/Y1;
  + [LP6/a0;
  + [LP6/c1;
  + [LP6/c2;
  + [LP6/c3;
  + [LP6/f0;
  + [LP6/f2;
  + [LP6/g1;
  + [LP6/h3;
  + [LP6/k0;
  + [LP6/n0;
  + [LP6/s3;
  + [LP6/t0;
  + [LP6/u1;
  + [LP6/x0;
  + [LP7/r;
  + [LU6/f;
  + [LZ7/c;
  + [La6/Q0;
  + [La8/A1;
  + [La8/E1;
  + [Landroidx/recyclerview/widget/C0;
  + [Lh3/d;
  + [Lh5/C;
  + [Lh5/l;
  + [Li8/b;
  + [Li8/j;
  + [Li8/p;
  + [Ln8/M;
  + [Lq6/B;
  + [Lq6/f;
  + [Lq6/o;
  + [Lv5/m;
  + consumers/payment_details/
  + cvcInput
  + expiryDateInput
  + ~~R8{"backend":"dex","compilation-mode":"release","has-checksums":false,"min-api":21,"pg-map-id":"455e565","r8-mode":"full","version":"8.7.14"}
  
  - Lq6/H;
  - [LP6/A0;
  - [LP6/B1;
  - [LP6/G0;
  - [LP6/G;
  - [LP6/I2;
  - [LP6/M2;
  - [LP6/M;
  - [LP6/P;
  - [LP6/T0;
  - [LP6/W;
  - [LP6/Y0;
  - [LP6/Y2;
  - [LP6/a2;
  - [LP6/b0;
  - [LP6/d1;
  - [LP6/d2;
  - [LP6/d3;
  - [LP6/g0;
  - [LP6/g2;
  - [LP6/h1;
  - [LP6/l0;
  - [LP6/l3;
  - [LP6/o0;
  - [LP6/u0;
  - [LP6/v1;
  - [LP7/q;
  - [LU6/e;
  - [LZ7/a;
  - [La6/P0;
  - [La8/D1;
  - [La8/x1;
  - [Landroidx/recyclerview/widget/B0;
  - [Lh3/c;
  - [Lh5/B;
  - [Lh5/k;
  - [Li8/a;
  - [Li8/h;
  - [Li8/o;
  - [Ln8/L;
  - [Lq6/C;
  - [Lq6/k;
  - [Lq6/t;
  - [Lv5/l;
  - ~~R8{"backend":"dex","compilation-mode":"release","has-checksums":false,"min-api":21,"pg-map-id":"51e4d42","r8-mode":"full","version":"8.7.14"}
  

TYPES:

   old   │ new   │ diff          
  ───────┼───────┼───────────────
   14046 │ 14065 │ +19 (+63 -44) 
  
  + LG6/A;
  + LG6/B;
  + LG6/C;
  + LG6/D;
  + LG6/E;
  + LG6/F;
  + LG6/y;
  + LG6/z;
  + LP6/s3;
  + LS6/U;
  + Lq3/i;
  + Lr6/o;
  + Lt6/b;
  + Lt6/c;
  + Lt6/d;
  + Lt6/e;
  + Lt6/f;
  + Lt6/g;
  + Lw6/o;
  + [LP6/A1;
  + [LP6/C;
  + [LP6/F0;
  + [LP6/H2;
  + [LP6/K;
  + [LP6/N2;
  + [LP6/N;
  + [LP6/S0;
  + [LP6/S;
  + [LP6/X0;
  + [LP6/X2;
  + [LP6/Y1;
  + [LP6/a0;
  + [LP6/c1;
  + [LP6/c2;
  + [LP6/c3;
  + [LP6/f0;
  + [LP6/f2;
  + [LP6/g1;
  + [LP6/h3;
  + [LP6/k0;
  + [LP6/n0;
  + [LP6/s3;
  + [LP6/t0;
  + [LP6/u1;
  + [LP6/x0;
  + [LP7/r;
  + [LU6/f;
  + [LZ7/c;
  + [La6/Q0;
  + [La8/A1;
  + [La8/E1;
  + [Landroidx/recyclerview/widget/C0;
  + [Lh3/d;
  + [Lh5/C;
  + [Lh5/l;
  + [Li8/b;
  + [Li8/j;
  + [Li8/p;
  + [Ln8/M;
  + [Lq6/B;
  + [Lq6/f;
  + [Lq6/o;
  + [Lv5/m;
  
  - Lq6/H;
  - [LP6/A0;
  - [LP6/B1;
  - [LP6/G0;
  - [LP6/G;
  - [LP6/I2;
  - [LP6/M2;
  - [LP6/M;
  - [LP6/P;
  - [LP6/T0;
  - [LP6/W;
  - [LP6/Y0;
  - [LP6/Y2;
  - [LP6/a2;
  - [LP6/b0;
  - [LP6/d1;
  - [LP6/d2;
  - [LP6/d3;
  - [LP6/g0;
  - [LP6/g2;
  - [LP6/h1;
  - [LP6/l0;
  - [LP6/l3;
  - [LP6/o0;
  - [LP6/u0;
  - [LP6/v1;
  - [LP7/q;
  - [LU6/e;
  - [LZ7/a;
  - [La6/P0;
  - [La8/D1;
  - [La8/x1;
  - [Landroidx/recyclerview/widget/B0;
  - [Lh3/c;
  - [Lh5/B;
  - [Lh5/k;
  - [Li8/a;
  - [Li8/h;
  - [Li8/o;
  - [Ln8/L;
  - [Lq6/C;
  - [Lq6/k;
  - [Lq6/t;
  - [Lv5/l;
  

METHODS:

   old   │ new   │ diff              
  ───────┼───────┼───────────────────
   59741 │ 59810 │ +69 (+1483 -1414) 
  
  + A5.I0 <init>(U, o, c, h, a, a)
  + A5.I0 <init>(U, a, a, a, a, a)
  + A5.O a(String, String, r3, d, o, p, q, r, c) → Object
  + A5.f0 a(f, B, Integer, a) → b
  + A5.r c(Throwable, r3) → Throwable
  + B6.p <init>(Q, q, n, c, d)
  + B6.q D() → T
  + B6.t <init>(G, q, Q, boolean, c, int)
  + B9.b O(R1, Z1) → q
  + B9.b P(String, u1, Z1) → q
  + B9.b X(K) → String
  + B9.b y(F, c, a, p, int)
  + B9.b y0(j3) → L
  + D1.f <init>(b, G, c, R1, S1)
  + D1.k g() → b
  + D8.g f(R1, String) → p
  + D8.g i(String) → u1
  + D8.g j(String) → o3
  + E0.f <init>(y0, boolean)
  + E6.w <init>(q, n, c, d)
  + F2.c d(j3, int) → String
  + F6.n <init>(b, n, c, d, a, c)
  + G5.Q L(j3) → int
  + G5.Q X(Map, String, bool
...✂

@toluo-stripe toluo-stripe force-pushed the tolu/link/cvc_expiry_vm branch from 69e29e5 to 7026722 Compare January 13, 2025 18:47
Comment on lines 189 to 197
is LinkConfirmationResult.Failed -> {
_uiState.update {
it.copy(errorMessage = result.message)
}
}
Copy link

Choose a reason for hiding this comment

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

The isProcessing state should be set to false when handling the error case, otherwise the UI will remain in a loading state after the error occurs. Consider updating the copy() call:

it.copy(
    errorMessage = result.message,
    isProcessing = false
)

Spotted by Graphite Reviewer

Is this helpful? React 👍 or 👎 to let us know.

@toluo-stripe toluo-stripe force-pushed the tolu/link/cvc_expiry_vm branch from 64aa78d to 76e4129 Compare January 13, 2025 20:25
@toluo-stripe toluo-stripe marked this pull request as ready for review January 13, 2025 20:28
@toluo-stripe toluo-stripe requested review from a team as code owners January 13, 2025 20:28
Comment on lines +187 to +189
when (result) {
LinkConfirmationResult.Canceled -> Unit
Copy link

Choose a reason for hiding this comment

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

The isProcessing state should be reset when the confirmation is canceled. Consider updating the case to:

LinkConfirmationResult.Canceled -> _uiState.update { it.copy(isProcessing = false) }

This maintains consistency with the error handling case and prevents the UI from being stuck in a processing state.

Spotted by Graphite Reviewer

Is this helpful? React 👍 or 👎 to let us know.

Copy link
Collaborator

@amk-stripe amk-stripe left a comment

Choose a reason for hiding this comment

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

the test suggestions are both optional -- just want to address the CardDetailsElement question before approving!

@Test
fun `performPaymentConfirmation skips update for non-expired card`() = runTest(dispatcher) {
val validCard = TestFactory.CONSUMER_PAYMENT_DETAILS_CARD.copy(expiryYear = 2099)
val linkAccountManager = object : FakeLinkAccountManager() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

wdyt of creating a shared FakeLinkAccountManager and re-using it across tests? A lot of these tests seem to be quite long and are creating their own very similar fakes. I think we could simplify + increase code re-use by sharing a fake

Add confirmation block

Update unit tests

Lint

Update WalletViewModelTest.kt

Update FakeLinkConfirmationHandler.kt
@toluo-stripe toluo-stripe force-pushed the tolu/link/cvc_expiry_vm branch from f6dc691 to b90d319 Compare January 14, 2025 01:04
Comment on lines +154 to +169
performPaymentDetailsUpdate(selectedPaymentDetails).fold(
onSuccess = { result ->
val updatedPaymentDetails = result.paymentDetails.single {
it.id == selectedPaymentDetails.id
}
performPaymentConfirmation(updatedPaymentDetails)
},
onFailure = { error ->
_uiState.update {
it.copy(
alertMessage = error.stripeErrorMessage(),
isProcessing = false
)
}
}
)
Copy link

Choose a reason for hiding this comment

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

The onFailure block needs to return early to prevent execution from continuing into payment confirmation. Add return after updating the UI state to ensure the error case fully terminates the flow.

Spotted by Graphite Reviewer

Is this helpful? React 👍 or 👎 to let us know.

@toluo-stripe toluo-stripe merged commit 4270a65 into master Jan 14, 2025
15 of 16 checks passed
@toluo-stripe toluo-stripe deleted the tolu/link/cvc_expiry_vm branch January 14, 2025 18:07
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.

2 participants