Skip to content

Fix Satispay mandate_data missing with setup_future_usage#12232

Merged
toluo-stripe merged 6 commits intomasterfrom
tolu/satispay/fix
Jan 20, 2026
Merged

Fix Satispay mandate_data missing with setup_future_usage#12232
toluo-stripe merged 6 commits intomasterfrom
tolu/satispay/fix

Conversation

@toluo-stripe
Copy link
Contributor

Summary

Fixes an issue where confirming a PaymentIntent with Satispay and setup_future_usage = off_session fails because mandate_data is not included in the confirmation request.

Motivation

https://stripe.slack.com/archives/CFA2HJ99A/p1768416799593999

Testing

  • Added tests
  • Modified tests
  • Manually verified

Changelog

@github-actions
Copy link
Contributor

github-actions bot commented Jan 15, 2026

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.9 MiB │     4.9 MiB │  -92 B │    11 MiB │    11 MiB │ -12 B 
     arsc │     3.6 MiB │     3.6 MiB │    0 B │   3.6 MiB │   3.6 MiB │   0 B 
 manifest │     5.7 KiB │     5.7 KiB │    0 B │  30.2 KiB │  30.2 KiB │   0 B 
      res │ 1,005.4 KiB │ 1,005.4 KiB │    0 B │   1.7 MiB │   1.7 MiB │   0 B 
   native │   949.9 KiB │   949.9 KiB │    0 B │   2.5 MiB │   2.5 MiB │   0 B 
    asset │    26.2 KiB │    26.2 KiB │    0 B │  46.7 KiB │  46.7 KiB │   0 B 
    other │   205.4 KiB │   205.4 KiB │  -13 B │ 405.7 KiB │ 405.7 KiB │   0 B 
──────────┼─────────────┼─────────────┼────────┼───────────┼───────────┼───────
    total │    10.6 MiB │    10.6 MiB │ -105 B │  19.2 MiB │  19.2 MiB │ -12 B 

         │         raw          │            unique            
         ├───────┬───────┬──────┼───────┬───────┬──────────────
 DEX     │ old   │ new   │ diff │ old   │ new   │ diff         
─────────┼───────┼───────┼──────┼───────┼───────┼──────────────
   files │     2 │     2 │    0 │       │       │              
 strings │ 58693 │ 58693 │    0 │ 52216 │ 52216 │  0 (+2 -2)   
   types │ 20681 │ 20681 │    0 │ 17468 │ 17468 │  0 (+0 -0)   
 classes │ 14696 │ 14696 │    0 │ 14696 │ 14696 │  0 (+0 -0)   
 methods │ 78341 │ 78341 │    0 │ 73355 │ 73355 │  0 (+30 -30) 
  fields │ 51367 │ 51366 │   -1 │ 49020 │ 49019 │ -1 (+0 -1)   

 ARSC    │ old  │ new  │ diff 
─────────┼──────┼──────┼──────
 configs │  325 │  325 │  0   
 entries │ 6948 │ 6948 │  0
APK
     compressed     │   uncompressed    │                        
───────────┬────────┼───────────┬───────┤                        
 size      │ diff   │ size      │ diff  │ path                   
───────────┼────────┼───────────┼───────┼────────────────────────
   4.1 MiB │  -91 B │   9.2 MiB │ -12 B │ ∆ classes.dex          
  57.6 KiB │  -10 B │ 127.4 KiB │   0 B │ ∆ META-INF/CERT.SF     
  54.4 KiB │   -5 B │ 127.3 KiB │   0 B │ ∆ META-INF/MANIFEST.MF 
   1.2 KiB │   +2 B │   1.2 KiB │   0 B │ ∆ META-INF/CERT.RSA    
 812.4 KiB │   -1 B │   1.8 MiB │   0 B │ ∆ classes2.dex         
───────────┼────────┼───────────┼───────┼────────────────────────
     5 MiB │ -105 B │  11.2 MiB │ -12 B │ (total)
DEX
STRINGS:

   old   │ new   │ diff      
  ───────┼───────┼───────────
   52216 │ 52216 │ 0 (+2 -2) 
  
  + r8-map-id-9c51289a15cc72f3cb9c6985b8fc16f792c1fd46061d0988cbb7fc5a7d9f5518
  + ~~R8{"backend":"dex","compilation-mode":"release","has-checksums":false,"min-api":21,"pg-map-id":"9c51289a15cc72f3cb9c6985b8fc16f792c1fd46061d0988cbb7fc5a7d9f5518","r8-mode":"full","version":"8.13.17"}
  
  - r8-map-id-a1c9536b6048c8a92ac909728e56ff3366432cb3c6144b8cf52cf2b5c2efb156
  - ~~R8{"backend":"dex","compilation-mode":"release","has-checksums":false,"min-api":21,"pg-map-id":"a1c9536b6048c8a92ac909728e56ff3366432cb3c6144b8cf52cf2b5c2efb156","r8-mode":"full","version":"8.13.17"}
  

METHODS:

   old   │ new   │ diff        
  ───────┼───────┼─────────────
   73355 │ 73355 │ 0 (+30 -30) 
  
  + qg.i K(c0)
  + qg.i L(l, k)
  + qg.i M(h) → boolean
  + qg.i N(Context, f, Resources, int) → Typeface
  + qg.i O(Context, g[], int) → Typeface
  + qg.i P(Context, InputStream) → Typeface
  + qg.i Q(Context, Resources, int, String, int) → Typeface
  + qg.i R(int) → byte
  + qg.i S(g[], int) → g
  + qg.i T(o) → b
  + qg.i U(String, String, String, Context) → String
  + qg.i V(Throwable) → String
  + qg.i W(c0) → f
  + qg.i X(o) → c
  + qg.i Y(c0, int) → f
  + qg.i a0(o) → e
  + qg.i b0(Context) → boolean
  + qg.i c0(b6, boolean, p7, o) → n4
  + r3.h N(Context, f, Resources, int) → Typeface
  + r3.h O(Context, g[], int) → Typeface
  + r3.i N(Context, f, Resources, int) → Typeface
  + r3.i O(Context, g[], int) → Typeface
  + r3.j N(Context, f, Resources, int) → Typeface
  + r3.j O(Context, g[], int) → Typeface
  + r3.j Q(Context, Resources, int, String, int) → Typeface
  + r3.l N(Context, f, Resources, int) → Typeface
  + r3.l O(Context, g[], int) → Typeface
  + r3.l P(Context, InputStream) → Typeface
  + r3.l Q(Context, Resources, int, String, int) → Typeface
  + r3.l S(g[], int) → g
  
  - qg.i K(x9, b6, p7, o) → n4
  - qg.i L(c0)
  - qg.i M(l, k)
  - qg.i N(h) → boolean
  - qg.i O(Context, f, Resources, int) → Typeface
  - qg.i P(Context, g[], int) → Typeface
  - qg.i Q(Context, InputStream) → Typeface
  - qg.i R(Context, Resources, int, String, int) → Typeface
  - qg.i S(int) → byte
  - qg.i T(g[], int) → g
  - qg.i U(o) → b
  - qg.i V(String, String, String, Context) → String
  - qg.i W(Throwable) → String
  - qg.i X(c0) → f
  - qg.i Y(o) → c
  - qg.i a0(c0, int) → f
  - qg.i b0(o) → e
  - qg.i c0(Context) → boolean
  - r3.h O(Context, f, Resources, int) → Typeface
  - r3.h P(Context, g[], int) → Typeface
  - r3.i O(Context, f, Resources, int) → Typeface
  - r3.i P(Context, g[], int) → Typeface
  - r3.j O(Context, f, Resources, int) → Typeface
  - r3.j P(Context, g[], int) → Typeface
  - r3.j R(Context, Resources, int, String, int) → Typeface
  - r3.l O(Context, f, Resources, int) → Typeface
  - r3.l P(Context, g[], int) → Typeface
  - r3.l Q(Context, InputStream) → Typeface
  - r3.l R(Context, Resources, int, String, int) → Typeface
  - r3.l T(g[], int) → g
  

FIELDS:

   old   │ new   │ diff       
  ───────┼───────┼────────────
   49020 │ 49019 │ -1 (+0 -1) 
  
  - ub.d b: b8

@toluo-stripe toluo-stripe marked this pull request as ready for review January 15, 2026 20:09
@toluo-stripe toluo-stripe requested review from a team as code owners January 15, 2026 20:09
*
* For saved payment methods, this checks the payment method type's requiresMandate property.
*/
private fun PaymentMethodConfirmationOption.requiresMandateData(): Boolean {
Copy link
Collaborator

Choose a reason for hiding this comment

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

@samer-stripe does this look right?

Copy link
Collaborator

Choose a reason for hiding this comment

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

This seems off from what we do in the client-side confirm flow. We should be re-using the logic here which handles the setup intent and payment intent use cases.

private fun mandateData(
    intent: StripeIntent,
    paymentMethodType: PaymentMethod.Type?,
    optionsParams: PaymentMethodOptionsParams?,
    intentConfigSetupFutureUsage: ConfirmPaymentIntentParams.SetupFutureUsage?
): MandateDataParams? {
    return paymentMethodType?.let { type ->
        val supportsAddingMandateData = when (intent) {
            is PaymentIntent ->
                intent.canSetupFutureUsage(paymentMethodType.code) ||
                    type.requiresMandateForPaymentIntent ||
                    optionsParams?.setupFutureUsage()?.hasIntentToSetup() == true ||
                    intentConfigSetupFutureUsage?.hasIntentToSetup() == true
            is SetupIntent -> true
        }

        return MandateDataParams(MandateDataParams.Type.Online.DEFAULT).takeIf {
            supportsAddingMandateData && type.requiresMandate
        }
    }
}

) {
}

fun configure(
Copy link
Collaborator

Choose a reason for hiding this comment

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

What's going on here?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Seems unused and can be removed.

toluo-stripe and others added 5 commits January 19, 2026 16:01
When confirming a PaymentIntent with Satispay and setup_future_usage
set to off_session, the SDK was not including mandate_data in the
request, causing the API to reject the request.

The root cause was that Satispay had requiresMandate = false in
PaymentMethod.kt. The mandateData() function in
ConfirmStripeIntentParamsFactory only adds mandate_data when both:
1. The intent supports setup for future usage (true for off_session)
2. The payment method requiresMandate is true

This change sets requiresMandate = true for Satispay, ensuring
mandate_data is included when setup_future_usage is set.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Committed-By-Agent: claude
When confirming with a Confirmation Token (deferred intent), the SDK
wasn't including mandate_data when Payment Method Options (PMO) Setup
Future Usage (SFU) was set. This caused errors for payment methods
like Satispay that require mandate_data when SFU is set.

The fix adds a check in ConfirmationTokenConfirmationInterceptor to
also include mandate_data when:
1. The payment method type has requiresMandate = true
2. PMO SFU is set in optionsParams (populated from IntentConfiguration)

Also updates playground to only send PMO SFU for Normal integration
type to avoid hitting this issue in deferred flows.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Committed-By-Agent: claude
* @param intentConfigSetupFutureUsage The intent configuration level SFU
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun mandateDataForDeferredIntent(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: Naming here should just be mandateData since we don't know whether or not the intent is deferred.

@toluo-stripe toluo-stripe merged commit 3a0a5b9 into master Jan 20, 2026
22 checks passed
@toluo-stripe toluo-stripe deleted the tolu/satispay/fix branch January 20, 2026 15:33
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.

3 participants