#dai-developers_docs
1 messages · Page 1 of 1 (latest)
👋 Welcome to your new thread!
⏲️ We'll be here soon! Typically we respond in a few minutes, but sometimes we might take a bit longer if the server is busy or if you have a particularly tricky question.
⏱️ We close idle threads, which makes them read-only. Once a thread is closed it won't be reopened, but you can always start a new thread if you have another question.
🔗 This thread will always be available, even after it's closed. You can find it again using Discord's search, or you can save this link: https://discord.com/channels/841573134531821608/1481463394480423047
📝 Have more to share? Add more details, code, screenshots, videos, etc. below.
Below are links to other discussions we've had with you in the past week in case you want to review that information. If your question is related to one of these previous discussions, please provide a comprehensive summary of the current state and what you need help with now. We help many users simultaneously, so a summary allows us to resolve your issue as soon as possible.
- dai-developers_docs, 1 day ago, 13 messages
- dai-developers_docs, 1 day ago, 8 messages
Could you share what issue you're facing, or any question you have?
Sorry, it seems I probably hit the character limit of the form. The continuation of my question is as follows.
We verified the behavior for non-3DS cards using the test card 378282246310005 listed in the documentation.
The same documentation also provides a 3DS frictionless flow success card (4000000032200000). However, when using that card, the paymentIntent status becomes requires_action, so we cannot confirm whether our logic works correctly.
Is there a test card that results in a successful 3DS frictionless flow and a paymentIntent status of requires_capture?
Could you share an example Payment Intent for me to take a look?
When I use frictionless test card (4000000032200000) and confirm at client side using stripe.confirmPayment(), the status goes to requires_capture immediately and its latest_charge.payment_method_details.card.three_d_secure.authentication_flow is shown as frictionless
I can't reproduce the behavior you're describing
I will organize and explain the current payment logic, the issue that is occurring, and the changes we made this time. Please let me know if there are any inconsistencies or unclear points.
-
Current payment logic
We develop and provide EC websites to our customers.
If the PaymentIntent status is requires_capture, we determine that the card may not support 3DS, and we treat the transaction as a payment error in all cases. -
Current issue
One of our customers who uses our EC site reported the following issue:
In the Stripe Dashboard, some payments are shown as frictionless success, but on the EC site they are treated as payment errors, preventing customers from completing their purchases.
They requested that when frictionless authentication succeeds, the transaction should not be treated as a payment error and the payment process should continue.
- Changes we made
We updated our logic based on the understanding that even if the PaymentIntent status is requires_capture, it does not necessarily mean that the card does not support 3DS.
In the Charge object, the field
latest_charge.payment_method_details.card.three_d_secure.authentication_flow
can have one of the following values:
-- challenge
-- frictionless
-- null
If the value is null, it means the card does not support 3DS.
Therefore, we modified our logic so that only when the value is null the transaction is treated as a payment error.
Thanks for sharing the background context! It sounds to me that you would like to reproduce frictionless 3DS flow in Sandbox / test mode that is similar to the one in production based on your initial question, so that you can verify whether the changes you made will solve your use case.
In my own testing, I have no problem to create a frictionless 3DS flow using the frictionless test card (4000000032200000). Could you share the Payment Intent (pi_123) that you didn't manage to reproduce frictionless flow, so that I can take a look why this happened?
In my own testing, I have no problem to create a frictionless 3DS flow using the frictionless test card (4000000032200000).
Rather than a problem, we are confused because when we use the frictionless test card (4000000032200000), the PaymentIntent status becomes requires_action instead of requires_capture, which is different from the situation we are encountering in our production environment.
In the case of a frictionless flow, does the PaymentIntent status always become requires_action?
Or is it possible for it to become either requires_capture or requires_action?
If you use frictionless test card, it shouldn't be in requires_action status. My own testing allows to go to requires_capture status when frictionless test card is used
Without an example Payment Intent, I won't be able to investigate why yours is in requires_action status
Here is an example of the actual PaymentIntent.
[2026-03-12 11:14:07] local.DEBUG: PaymentIntent: Stripe\PaymentIntent JSON: {
"id": "pi_3T9ylHF0xJHccp7D1OmvfEFo",
"object": "payment_intent",
"amount": 42900,
"amount_capturable": 0,
"amount_details": {
"tip": []
},
"amount_received": 0,
"application": null,
"application_fee_amount": null,
"automatic_payment_methods": {
"allow_redirects": "always",
"enabled": true
},
"canceled_at": null,
"cancellation_reason": null,
"capture_method": "manual",
"client_secret": "pi_3T9ylHF0xJHccp7D1OmvfEFo_secret_7D8XAOJ6ecZSFGn1QRF2dOKz0",
"confirmation_method": "automatic",
"created": 1773281647,
"currency": "jpy",
"customer": null,
"customer_account": null,
"description": null,
"excluded_payment_method_types": null,
"invoice": null,
"last_payment_error": null,
"latest_charge": null,
"livemode": false,
"metadata": [],
"next_action": {
"redirect_to_url": {
"return_url": "https://develop-ssl.bcart.dv/payment_3ds_callback",
"url": "https://hooks.stripe.com/3d_secure_2/hosted?merchant=acct_19spmOF0xJHccp7D&payment_intent=pi_3T9ylHF0xJHccp7D1OmvfEFo&payment_intent_client_secret=pi_3T9ylHF0xJHccp7D1OmvfEFo_secret_7D8XAOJ6ecZSFGn1QRF2dOKz0&publishable_key=pk_test_rNXIvSNEI3sem5MiVpMGc7kB&source=payatt_3T9ylHF0xJHccp7D1RHtwcy0"
},
"type": "redirect_to_url"
},
"on_behalf_of": null,
"payment_method": "pm_1T9ylGF0xJHccp7DAhyH63tR",
"payment_method_configuration_details": {
"id": "pmc_1Q7B76F0xJHccp7D9yf06aPi",
"parent": null
},
"payment_method_options": {
"card": {
"installments": {
"available_plans": [],
"enabled": true,
"plan": null
},
"mandate_options": null,
"network": null,
"request_three_d_secure": "any"
},
"link": {
"persistent_token": null
}
},
"payment_method_types": [
"card",
"link"
],
"processing": null,
"receipt_email": null,
"review": null,
"setup_future_usage": "off_session",
"shipping": null,
"source": null,
"statement_descriptor": null,
"statement_descriptor_suffix": null,
"status": "requires_action",
"transfer_data": null,
"transfer_group": null
}
Thanks for sharing! It looks like you're confirming the Payment Intent at the server side in https://dashboard.stripe.com/acct_19spmOF0xJHccp7D/test/logs/req_kPX20YyEBJmZ2S instead of client side for this specific Payment Intent. It is likely the reason why frictionless 3DS test card doesn't work.
Could you share your production / live mode Payment Intent (pi_123) that went through frictionless 3DS, so that I can double check how your integration was like in production, and whether similar flow can be reproduced in Sandbox / test mode?
Sign in to the Stripe Dashboard to manage business payments and operations in your account. Manage payments and refunds, respond to disputes and more.
⛔️ Stripe developers have stepped away for a short while
Please leave your questions here, and we’ll respond as soon as we're back! If you need help urgently, you can contact Stripe support for help.
Got it. I'll try it.
I have a question. I understand that when switching to live mode, test cards (such as 4000000032200000) can no longer be used. Is my understanding correct?
If that is correct, how can we trigger or test a frictionless 3DS flow in live mode?
yes, it's correct that in livemode, you cannot use test cards.
You shouldn't be testing in livemode. That aside, while you can indicate a preference : https://docs.stripe.com/api/payment_intents/create#create_payment_intent-payment_method_options-card-request_three_d_secure, there's no guarantee that the issuer will use that 3DS flow
Stripe can’t guarantee your preference because the issuer determines the ultimate authentication flow.
You shouldn't be testing in livemode.
OK.
Let me organize the situation.
Originally, we assumed that if the PaymentIntent status was requires_capture, it meant that the card did not support 3DS (we set capture_method to manual). Since we do not want payments to be made with cards that do not support 3DS, our system was designed to treat any payment with a PaymentIntent status of requires_capture as a payment error.
However, in our production environment, we observed a payment where the PaymentIntent status was requires_capture, but the payment had actually completed 3DS authentication via a frictionless flow. Despite the 3DS authentication being successfully completed (and exempted via frictionless flow), our system treated it as a payment error according to our logic, which prevented the purchase from being completed.
To resolve this issue, we updated our logic as follows:
When the PaymentIntent status is requires_capture, we check the field
latest_charge.payment_method_details.card.three_d_secure.authentication_flow in the Charge object.
If the value is null, we treat it as a card that does not support 3DS, and the payment is treated as an error.
If the value is frictionless or challenge, we allow the payment to proceed as a normal successful transaction.
At this point in the explanation, are there any unclear parts or any misunderstandings in my assumptions?
looks correct to me
In that case, I have a question.
When we test using the 3DS frictionless flow test card (4000000032200000), the PaymentIntent status becomes requires_action, so we are unable to reproduce the situation we see in production: a frictionless 3DS flow with the PaymentIntent status being requires_capture.
Is there any way for us to test whether the logic change we implemented works correctly?
Let me walk you through the lifecycle of a PaymentIntent to explain how it all fits together.
requires_payment_method→ You create a PaymentIntent (withcapture_method: manualfor your use case).requires_confirmation→ A payment method is attached.requires_action→ After confirmation, if 3DS is triggered (even frictionless), the PaymentIntent moves torequires_action. This is where the 3DS authentication needs to be handled on the client side.- Client-side handling → You need to use your client-side integration to complete the 3DS step. In a frictionless flow, this happens automatically without any user-facing challenge — the 3DS authentication completes silently in the background. However, it still needs to be invoked client-side.
requires_capture→ Once 3DS authentication is successfully completed (handled client-side), the PaymentIntent will move torequires_capture, since you're using manual capture.
In short, you need to handle the requires_action step by confirming the PaymentIntent, so that the frictionless 3DS flow will occur, and it will move to the requires_capture state.
So what should we do?
Does this mean that the following understanding was incorrect?
To resolve this issue, we updated our logic as follows: When the PaymentIntent status is requires_capture, we check the field latest_charge.payment_method_details.card.three_d_secure.authentication_flow in the Charge object. If the value is null, we treat it as a card that does not support 3DS, and the payment is treated as an error. If the value is frictionless or challenge, we allow the payment to proceed as a normal successful transaction.
So what should we do?
have you confirmed the PaymentIntent in your frontend / client-side?
In our implementation, we do not call confirmPayment() or handleNextAction() separately on the frontend.
Instead, we handle it on the server side by creating and confirming the PaymentIntent, and then processing 3DS via a redirect to the return_url.
`$payload = array_merge($payload, [
// ...
'confirm' => true,
// ...
'return_url' => $callback_url,
]);
$paymentIntent = $this->stripeClient->paymentIntents->create($payload);`
In other words, our approach is server-side create + confirm, followed by a redirect to the return_url to handle the 3DS flow.
okay, since the PaymentIntent is in requires_action status, can you redirect to the return_url to handle the 3DS flow?
Yes, that's exactly what we're doing.
Current code flow:
- request3dsCheck() creates a PaymentIntent with confirm: true + return_url → becomes requires_action → returns success
- get3dsRedirectHtml() redirects to next_action.redirect_to_url.url
- After 3DS completion, Stripe redirects back to return_url (= callback_url)
- callback3dsResult() receives the result
Right, so your flow makes sense — you're creating and confirming the PaymentIntent server-side, then redirecting to next_action.redirect_to_url.url to handle 3DS.
But what I'm asking is — for that specific PaymentIntent that's currently stuck in requires_action, have you actually completed the redirect? As in, did you redirect to the 3DS URL and let Stripe redirect back to your return_url?
Because until that redirect flow is completed, the PaymentIntent will stay in requires_action. Once you go through the redirect (even with the frictionless test card 4000000032200000, it'll resolve silently), the PaymentIntent should move to requires_capture, and you'll be able to test your logic change.
Could you give that a try and let me know what status the PaymentIntent ends up in after the redirect completes? Also, share the PaymentIntent ID which you're trying things out with
Yes, we are completing the redirect flow. Here's how our server-side code works:
- In request3dsCheck(), we create the PaymentIntent with confirm: true and return_url. If the status is requires_action, we redirect the user to next_action.redirect_to_url.url.
- After 3DS completes, Stripe redirects back to our return_url, and we handle the result in callback3dsResult(), where we retrieve the PaymentIntent and check its status.
Test result:
- PaymentIntent ID: pi_3TA2MTF0xJHccp7D0vCy15h2
- Status after redirect: requires_capture
The redirect flow is working as expected — the PaymentIntent successfully moved from requires_action to requires_capture after the 3DS redirect completed.
So the flow is working as expected then. After completing the 3DS redirect, the PaymentIntent moved to requires_capture.
Going back to your original question about testing your logic change: since the PaymentIntent is now in requires_capture, you should be able to check latest_charge.payment_method_details.card.three_d_secure.authentication_flow on that charge and verify that your updated logic handles the frictionless flow correctly.
Understood on that. Let me change my question.
Based on the lifecycle you described, it seems like requires_action is always the expected status after confirmation when 3DS is triggered (even for frictionless), and requires_capture only occurs after the 3DS redirect flow is completed.
In our code, we create the PaymentIntent with confirm: true and capture_method: manual, then immediately check $paymentIntent->status in a switch statement. We have a requires_capture case there that checks the Charge object's authentication_flow to detect 3DS-unsupported cards.
But given the lifecycle, is there any scenario where the PaymentIntent status would be requires_capture immediately after creation with confirm: true, before going through the redirect flow? Or should we assume that it will always be requires_action at that point when 3DS is requested?
Hey, sorry for taking a while to get back to you, I missed out on this message!
Yes, there are scenarios where the PaymentIntent can go straight to requires_capture immediately after creation with confirm: true, without going through requires_action.
This happens when 3DS is not triggered at all
In those cases, the flow skips requires_action entirely and goes straight to requires_capture since you're using manual capture.
So to answer your question directly: if 3DS is requested, the PaymentIntent will always go to requires_action first. It will only move to requires_capture after the redirect flow is completed. But if 3DS is not requested, it can land on requires_capture right away if authorisation is successful.
But if 3DS is not requested, it can land on requires_capture right away if authorisation is successful.
Is there a test card that can reproduce that behavior?
https://docs.stripe.com/testing#three-ds-cards - you can use the card ending with 0005
Is there a test card that is frictionless but skips requires_action and moves directly to requires_capture?
Or is such a situation not possible according to Stripe’s specifications?
No, there isn't a test card like that, and it wouldn't really make sense for one to exist either.
If 3DS is triggered (whether frictionless or challenge), the PaymentIntent will always go through requires_action first. That's just how the flow works. "Frictionless" means the customer doesn't have to do anything manually, but the authentication step still needs to happen, which is why requires_action is needed.
A card that skips requires_action and goes straight to requires_capture would mean 3DS wasn't triggered at all. So by definition, you can't have a frictionless 3DS flow that skips requires_action, because frictionless is still 3DS.