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 mandate handling in Payment Element #10171

Merged
merged 4 commits into from
Feb 18, 2025

Conversation

samer-stripe
Copy link
Collaborator

@samer-stripe samer-stripe commented Feb 13, 2025

Summary

Ensures that MandateData is only sent for intents intending to setup the provided PaymentMethod for future usage. Also sets requiresMandate to true for public PaymentMethod API for Klarna, AmazonPay, CashApp, and RevolutPay.

Motivation

Fix PI w/ SFU for deferred intents.

Testing

  • Added tests
  • Modified tests
  • Manually verified

Copy link
Contributor

github-actions bot commented Feb 13, 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 MiB │     4 MiB │ +373 B │  8.8 MiB │  8.8 MiB │ +1.2 KiB 
     arsc │   2.4 MiB │   2.4 MiB │    0 B │  2.4 MiB │  2.4 MiB │      0 B 
 manifest │   5.1 KiB │   5.1 KiB │    0 B │ 25.7 KiB │ 25.7 KiB │      0 B 
      res │ 910.9 KiB │ 910.9 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 │   -3 B │  1.6 MiB │  1.6 MiB │     -3 B 
    other │   1.4 MiB │   1.4 MiB │   -9 B │  1.6 MiB │  1.6 MiB │      0 B 
──────────┼───────────┼───────────┼────────┼──────────┼──────────┼──────────
    total │  12.8 MiB │  12.8 MiB │ +361 B │ 21.8 MiB │ 21.9 MiB │ +1.2 KiB 

 DEX     │ old   │ new   │ diff             
─────────┼───────┼───────┼──────────────────
   files │     1 │     1 │  0               
 strings │ 41274 │ 41281 │ +7 (+14 -7)      
   types │ 14293 │ 14295 │ +2 (+7 -5)       
 classes │ 11944 │ 11946 │ +2 (+2 -0)       
 methods │ 60623 │ 60629 │ +6 (+1672 -1666) 
  fields │ 40892 │ 40895 │ +3 (+405 -402)   

 ARSC    │ old  │ new  │ diff 
─────────┼──────┼──────┼──────
 configs │  243 │  243 │  0   
 entries │ 6250 │ 6250 │  0
APK
    compressed     │     uncompressed     │                                           
──────────┬────────┼───────────┬──────────┤                                           
 size     │ diff   │ size      │ diff     │ path                                      
──────────┼────────┼───────────┼──────────┼───────────────────────────────────────────
    4 MiB │ +373 B │   8.8 MiB │ +1.2 KiB │ ∆ classes.dex                             
 50.4 KiB │  -10 B │ 118.9 KiB │      0 B │ ∆ META-INF/MANIFEST.MF                    
  1,006 B │   -5 B │     874 B │     -5 B │ ∆ assets/dexopt/baseline.profm            
  7.7 KiB │   +2 B │   7.6 KiB │     +2 B │ ∆ assets/dexopt/baseline.prof             
    272 B │   +1 B │     120 B │      0 B │ ∆ META-INF/version-control-info.textproto 
 53.7 KiB │   +1 B │   119 KiB │      0 B │ ∆ META-INF/CERT.SF                        
  1.2 KiB │   -1 B │   1.2 KiB │      0 B │ ∆ META-INF/CERT.RSA                       
──────────┼────────┼───────────┼──────────┼───────────────────────────────────────────
  4.1 MiB │ +361 B │     9 MiB │ +1.2 KiB │ (total)
DEX
STRINGS:

   old   │ new   │ diff        
  ───────┼───────┼─────────────
   41274 │ 41281 │ +7 (+14 -7) 
  
  + 
          Encountered an invalid client secret "
  + "
    
  + " for intent type "
  + LI4/N;
  + LI9/b;
  + LLLLLLZ
  + PaymentIntent
  + [LI4/C;
  + [LI4/E;
  + [LI4/k;
  + [LI4/n;
  + [LI4/y;
  + invalidClientSecretProvided
  + ~~R8{"backend":"dex","compilation-mode":"release","has-checksums":false,"min-api":21,"pg-map-id":"6c90db5","r8-mode":"full","version":"8.7.14"}
  
  - Encountered an invalid client secret "
  - [LI4/B;
  - [LI4/D;
  - [LI4/d;
  - [LI4/l;
  - [LI4/s;
  - ~~R8{"backend":"dex","compilation-mode":"release","has-checksums":false,"min-api":21,"pg-map-id":"0eebba6","r8-mode":"full","version":"8.7.14"}
  

TYPES:

   old   │ new   │ diff       
  ───────┼───────┼────────────
   14293 │ 14295 │ +2 (+7 -5) 
  
  + LI4/N;
  + LI9/b;
  + [LI4/C;
  + [LI4/E;
  + [LI4/k;
  + [LI4/n;
  + [LI4/y;
  
  - [LI4/B;
  - [LI4/D;
  - [LI4/d;
  - [LI4/l;
  - [LI4/s;
  

METHODS:

   old   │ new   │ diff             
  ───────┼───────┼──────────────────
   60623 │ 60629 │ +6 (+1672 -1666) 
  
  + C0.c A(c, String, s, a, p, a, p, int)
  + C0.c A0(Rect) → Size
  + C0.c B(c, s, a, String, p, int)
  + C0.c B0(g) → e
  + C0.c C(p, w, p, int, int)
  + C0.c C0(int) → byte[]
  + C0.c D(p, w, boolean, boolean, boolean, a, a, p, int)
  + C0.c D0(c) → c
  + C0.c E(t, c, p, int)
  + C0.c E0(c, String, boolean) → l
  + C0.c F(int, p)
  + C0.c F0(View, int) → boolean
  + C0.c G(s, c, p, int)
  + C0.c G0(int, int) → g
  + C0.c H(boolean, boolean, k, d3, float, float, p, int) → W
  + C0.c I(m) → boolean
  + C0.c J(A0, float, o, c) → Object
  + C0.c K(n, a, c) → Object
  + C0.c L(long, float, long, long) → float
  + C0.c M(View, int) → int
  + C0.c N(View, int) → int
  + C0.c O(float, float) → float
  + C0.c P(float, float) → float
  + C0.c Q(double, double, double) → double
  + C0.c R(float, float, float) → float
  + C0.c S(int, int, int) → int
  + C0.c T(long, long, long) → long
  + C0.c U(long, i) → long
  + C0.c V(Comparable, d) → Comparable
  + C0.c W(int, int) → int
  + C0.c X(d, float, float) → boolean
  + C0.c Y(View, KeyEvent) → boolean
  + C0.c Z(l, View, Window_Callback, KeyEvent) → boolean
  + C0.c a(boolean, String, Integer, c, String, Integer, c, String, Integer, c, p, int)
  + C0.c a0(long, boolean, int, float) → long
  + C0.c b(a, a, p, e, e, e, P, long, long, o, p, int, int)
  + C0.c b0(Class, Field) → Method
  + C0.c c(a, a, p, e, e, P, long, long, o, p, int, int)
  + C0.c c0(Class) → Constructor
  + C0.c d(p, m, c, p, int)
  + C0.c d0(Context, int, int) → int
  + C0.c e(p, r, p, int)
  + C0.c e0(View, int) → int
  + C0.c f(float, long) → w
  + C0.c f0(KeyEvent) → long
  + C0.c g(int, int, p, String, String, p)
  + C0.c g0(String[], int) → float
  + C0.c h(o, a, p, v0, p, int)
  + C0.c h0(Class) → String[]
  + C0.c i(o, a, v0, p, boolean, a, p, int)
  + C0.c i0(H, int) → h
  + C0.c j(u0, p, E, c, a, p, int)
  + C0.c j0(KeyEvent) → int
  + C0.c k(Object, p, E, String, a, p, int, int)
  + C0.c k0(View) → int
  + C0.c l(T, c, a, p, int)
  + C0.c l0() → int
  + C0.c m(int, p)
  + C0.c m0(int) → boolean
  + C0.c n(Throwable, p, int)
  + C0.c n0(String, String) → boolean
  + C0.c o(L, c, a, a, p, int)
  + C0.c o0(Class) → boolean
  + C0.c p(boolean, String, Integer, int, String, c, p, int)
  + C0.c p0(float, int, int) → int
  + C0.c q(p, A, V, boolean, h, e, m, boolean, c, p, int, int)
  + C0.c q0(Throwable)
  + C0.c r(p, A, V, boolean, e, f, m, boolean, c, p, int, int)
  + C0.c r0(E0)
  + C0.c s(v1, c, a, p, int)
  + C0.c s0(View, int)
  + C0.c t(p, long, p, int)
  + C0.c t0(int)
  + C0.c u(List, c, p, int)
  + C0.c u0(View, int, int)
  + C0.c v(String, Integer, String, Integer, String, Integer, boolean, e, e, c, c, c, a, c, a, p, int, int)
  + C0.c v0(View, float, float)
  + C0.c w(boolean, boolean, a, p, int)
  + C0.c w0(Context, int, int) → int
  + C0.c x(j, e, String, Integer, String, Integer, String, Integer, c, c, c, boolean, a, a, p, int, int)
  + C0.c x0(Context, int, Interpolator) → TimeInterpolator
  + C0.c y(int, p)
  + C0.c y0(A0, float, c) → Object
  + C0.c z(boolean, boolean, a, p, int)
  + C0.c z0(ImageView, ColorStateList)
  + C3.b h0(int)
  + C3.b i0(Typeface, boolean)
  + C3.b z0(int)
  + C4.a b0(Class, Field) → Method
  + C4.a c0(Class) → Constructor
  + C4.a h0(Class) → String[]
  + C4.a o0(Class) → boolean
  + C4.b b0(Class, Field) → Method
  + C4.b c0(Class) → Constructor
  + C4.b h0(
...✂

@samer-stripe samer-stripe force-pushed the samer/fix-payment-method-types-requires-mandate branch from beb790d to 7258aae Compare February 14, 2025 03:23
@samer-stripe samer-stripe changed the title Set requiresMandate to true for public API PaymentMethod for Klarna, 'AmazonPay', and CashApp Set requiresMandate to true for public API PaymentMethod for Klarna, AmazonPay, CashApp, and RevolutPay Feb 14, 2025
@samer-stripe samer-stripe changed the title Set requiresMandate to true for public API PaymentMethod for Klarna, AmazonPay, CashApp, and RevolutPay Set requiresMandate to true for public PaymentMethod API for Klarna, AmazonPay, CashApp, and RevolutPay Feb 14, 2025
@samer-stripe samer-stripe marked this pull request as ready for review February 14, 2025 05:13
@samer-stripe samer-stripe requested review from a team as code owners February 14, 2025 05:13
@samer-stripe
Copy link
Collaborator Author

Seems like nothing broke! Think I can add one E2E test for each that tests in PI w/ SFU with client-side confirmation to cover these cases in the future.

tillh-stripe
tillh-stripe previously approved these changes Feb 14, 2025
cttsai-stripe
cttsai-stripe previously approved these changes Feb 14, 2025
@samer-stripe samer-stripe force-pushed the samer/fix-payment-method-types-requires-mandate branch from de39ab8 to c40b474 Compare February 14, 2025 18:56
@samer-stripe samer-stripe changed the title Set requiresMandate to true for public PaymentMethod API for Klarna, AmazonPay, CashApp, and RevolutPay Fix mandate handling in Payment Element Feb 14, 2025
StripeIntent.Usage.OneTime -> false
StripeIntent.Usage.OnSession,
StripeIntent.Usage.OffSession -> true
}
Copy link
Collaborator Author

@samer-stripe samer-stripe Feb 14, 2025

Choose a reason for hiding this comment

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

Interestingly, we have this OneTime usage value that I don't think the API returns anymore but is defined. We should probably remove it? Would need to evaluate outside this PR.

SetupIntent.ClientSecret.isMatch(clientSecret) -> {
ConfirmSetupIntentParamsFactory(clientSecret)
intent is SetupIntent && SetupIntent.ClientSecret.isMatch(clientSecret) -> {
ConfirmSetupIntentParamsFactory(clientSecret, intent)
}
else -> {
error("Encountered an invalid client secret \"$clientSecret\"")
Copy link
Collaborator Author

@samer-stripe samer-stripe Feb 14, 2025

Choose a reason for hiding this comment

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

Of all the changes, this feels the riskiest somehow. I don't think we have any unexpected issues with the client secret type being different than the actual intent type. We could just rely on the client secret from the intent itself but the StripeIntent type has it as potentially nullable.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Seems like it would fail server side if this happened, right? Can we either change this to a client side error, rather than crashing, or just assume it's right?

@samer-stripe samer-stripe force-pushed the samer/fix-payment-method-types-requires-mandate branch from c40b474 to d0a80d4 Compare February 18, 2025 17:04
),
savePaymentMethod = null,
setupFutureUsage = null,
clientSecret = "seti_123_secret_123",
)
)
)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Failed after adding the intent type check above. Needed to update to actually create CreateSetupIntentParams

@samer-stripe samer-stripe force-pushed the samer/fix-payment-method-types-requires-mandate branch from 854fd21 to 355c5b6 Compare February 18, 2025 20:56
@@ -308,7 +308,7 @@ constructor(
"paypal",
isReusable = false,
isVoucher = false,
requiresMandate = false,
requiresMandate = true,
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Paypal is another one that was also not handled properly.

@@ -86,7 +86,5 @@ internal fun confirmSetupIntentParams(): RequestMatcher {
return RequestMatchers.composite(
bodyPart("payment_method", "pm_12345"),
bodyPart("client_secret", "seti_12345_secret_12345"),
bodyPart(urlEncode("mandate_data[customer_acceptance][type]"), "online"),
bodyPart(urlEncode("mandate_data[customer_acceptance][online][infer_from_client]"), "true"),
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

A result of the changes we made. mandate_data is now only sent when required rather than all the time with SetupIntent.

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 tested this against cards in Payment Element and it ran fine without mandate_data

Copy link
Collaborator

Choose a reason for hiding this comment

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

But we show mandate data for cards now, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We do but I don't think that it is server-recognized.

@samer-stripe samer-stripe force-pushed the samer/fix-payment-method-types-requires-mandate branch from c429a00 to 69d7816 Compare February 18, 2025 21:57
@samer-stripe samer-stripe force-pushed the samer/fix-payment-method-types-requires-mandate branch from 69d7816 to f339063 Compare February 18, 2025 22:26
@samer-stripe samer-stripe merged commit 064778c into master Feb 18, 2025
16 checks passed
@samer-stripe samer-stripe deleted the samer/fix-payment-method-types-requires-mandate branch February 18, 2025 23:29
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