You open the 'Payments' page in Stripe and find a row of payments marked 'Incomplete'. Some carry a note saying the customer has not entered their payment method. Others say the payment requires action. A few belong to customers who swear they paid. It looks like money is slipping through your fingers.
Usually it isn't. Every Stripe payment is tracked by a PaymentIntent, and Stripe starts tracking it before the customer has done anything. 'Incomplete' is Stripe's summary for a PaymentIntent that started and never reached the end. No charge was made on any of them. The useful question is not 'why did it fail' but 'where did it stop', and Stripe tells you exactly that if you know where to look.
What 'Incomplete' actually covers
Stripe's payment status documentation maps three PaymentIntent statuses to the single Dashboard label 'Incomplete'. Each one stops at a different step, and each has a different fix.
- requires_payment_method: no card or other payment method was ever attached, or an attempt failed and the PaymentIntent went back to the start. Stripe says that after a decline, the status 'returns to requires_payment_method so that the payment can be retried'. This is where 'The customer has not entered their payment method' payments sit.
- requires_confirmation: the customer provided payment details but the payment was not confirmed. Stripe notes that most integrations skip this state entirely, so seeing many of these usually points at the checkout code, not the customer.
- requires_action: the payment needs another step from the customer, most often authenticating with 3D Secure. Stripe's own table of client outcomes lists requires_action as the status you get when 'the customer didn't complete the checkout'.
- Not part of Incomplete: processing shows as 'Pending', requires_capture as 'Uncaptured', canceled as 'Canceled' and succeeded as 'Succeeded'. Stripe also says that under some error conditions a payment can show as 'Failed' instead of 'Incomplete'.
How to see where a payment stopped
The Dashboard label is a summary. Stripe's documentation says it 'doesn't include the additional details provided by the PaymentIntent status', and recommends checking the exact value before you act on it.
- Open the 'Payments' page and click the incomplete payment. The payment's detail page shows its timeline, so you can see whether any attempt was made at all or whether it stopped at an authentication step.
- To see the exact status, open Workbench: press the tilde key (~) anywhere in the Dashboard, or choose 'Workbench' from the 'Developers' menu. On the payment's page, click the 'Inspector' icon. The 'Overview' tab shows the PaymentIntent as JSON. Search it for status.
- If the status is requires_action, look at next_action. A value of redirect_to_url means the customer was sent to their bank to authenticate.
- If an attempt failed, look at last_payment_error. Stripe's docs point to this property for the reason, such as a decline, and the 'Events' and 'Logs' tabs in the Inspector show each attempt.
- On card payments that reached authentication, the Charge's payment_method_details.card.three_d_secure.result shows the outcome. Stripe only fills this in when the customer attempted to authenticate.
Cause 1: The checkout was opened and abandoned
This is the largest group by a distance. Stripe tells developers to 'create the PaymentIntent as soon as you know the amount to charge so Stripe can record all attempted payments'. Its newer payment forms need a PaymentIntent before they can even be shown; Stripe's support page about this says the initial PaymentIntent 'has an Incomplete status when it is created'. So every visitor who reaches your payment page, looks at the price and closes the tab leaves an Incomplete payment with no payment method. That is the payment carrying 'The customer has not entered their payment method'.
There is nothing to fix on the payment itself. Nobody was charged and nothing needs refunding. If you want to win some of those people back and you use Stripe Checkout, Stripe has a built-in recovery flow:
- Stripe says Checkout Sessions expire 24 hours after they are created by default. You can shorten that with expires_at, to a minimum of 30 minutes, if you want to hear about abandonment sooner.
- When a session expires, Stripe sends the checkout.session.expired event. If you created the session with after_expiration.recovery enabled, the event includes a recovery URL that opens a copy of the original checkout.
- That recovery URL works for 30 days. Stripe classes cart-abandonment emails as promotional, so the customer has to have agreed to promotional emails before you send one; Checkout can collect that consent with consent_collection[promotions].
Cause 2: The customer was sent to 3D Secure and never finished
3D Secure is the extra check from the customer's bank: a one-time code by text, a prompt in their banking app, or a fingerprint. Stripe says it triggers 3DS automatically when regulations require it, such as Strong Customer Authentication in Europe and the UK, when an issuer asks for it with a soft decline, or when certain Stripe optimisations apply. Your Radar rules can request it too. Stripe also points out that while it starts the request, the requirement comes from the card issuer.
While the bank's check is on screen, the PaymentIntent sits at requires_action. If the customer closes the window, can't find their phone, or the code never arrives, it simply stays there and the payment shows 'Incomplete'. Stripe only attempts the charge after authentication, so no money moved. If the bank rejects the authentication instead, Stripe says the PaymentIntent goes back to requires_payment_method so the customer can retry or use another method.
One warning before you chase anyone: Stripe's guidance says to make sure the customer is not still authenticating an on-session payment before you treat it as abandoned. A payment that is two minutes old may still be in progress.
- Open Stripe's 'Authentication' analytics page to see how big the problem is. Its 'Failed 3DS challenge' outcome counts payments where 'the customer abandoned the authentication flow or the payment was rejected by the customer's bank'.
- The same page has '3DS not actioned', which Stripe describes as the customer not entering the 3DS flow 'or there was an issue with your integration'. A high number here is worth taking to whoever built your checkout.
- The 'request reasons' chart shows whether 3DS came from an API request, a Radar request, a Stripe request or a Bank request, which tells you which ones you have any control over.
Cause 3: An invoice or subscription is still waiting to be paid
If you use Stripe Billing, some Incomplete payments are simply unpaid bills. Stripe's support article on invoices says a PaymentIntent is created when an emailed invoice is finalized, and 'these PaymentIntents will be labeled as incomplete in your Dashboard until your customer attempts to pay their invoice'. Voiding the invoice or marking it as paid cancels the PaymentIntent automatically.
New subscriptions work the same way. When a subscription needs an immediate first payment, Stripe creates it as incomplete and gives the customer 23 hours to pay. If the first payment needs authentication, the PaymentIntent is requires_action and Stripe sends invoice.payment_action_required so you can bring the customer back to finish it. After 23 hours with no successful payment, the subscription becomes incomplete_expired and its invoice is voided. Stripe says these subscriptions don't bill the customer; to reactivate them, you create a new subscription.
When it is safe to ignore, and when it isn't
Use the exact status, not the label, to decide.
- Ignore: requires_payment_method payments with no attempts on them. These are visitors who looked and left.
- Ignore: a decline followed by a successful payment from the same customer a minute later. The first PaymentIntent went back to requires_payment_method and the retry worked.
- Look closer: a sudden rise in requires_action payments, or a high 'Failed 3DS challenge' share on the 'Authentication' page. Customers are getting stuck at their bank.
- Look closer: many requires_confirmation payments. Stripe says most integrations skip this state, so this usually means the checkout never sends the final confirmation.
- Act: a customer says they paid but the payment is Incomplete. They were not charged. Do not fulfil the order; send them back to pay, or a fresh payment link.
- Never fulfil on anything but a succeeded payment. Stripe recommends using the payment_intent.succeeded webhook for fulfilment rather than the checkout page, because customers can leave the page after paying.
How to reduce incomplete payments
You can't remove 3D Secure where the law or the bank requires it. Stripe says plainly that you can't use its APIs to turn 3DS off manually, and that disabling Radar doesn't stop 3DS from being triggered when it is necessary. What you can control is the friction you add on top.
- Check your own Radar rules. Stripe warns that 'indiscriminate use of 3DS might lower conversion rates'. In 'Radar' > 'Rules', look at any rule that requests 3D Secure. Stripe's advice is that if a 3DS rule has a high completion rate but few disputes or early fraud warnings, removing it may reduce friction for your customers.
- Only request 3DS manually if you have a reason to. Setting request_three_d_secure to any asks for a frictionless flow where possible, but Stripe notes the issuer decides the final flow, and a manual request overrides Radar's dynamic 3DS rules.
- Offer wallets. Stripe's authentication analytics exclude Apple Pay and Google Pay because they use device-based authentication such as biometrics instead of 3DS.
- Make the authentication step hard to lose. Stripe notes that the bank controls the look of the 3DS screen, but you choose where it appears. Stripe says most businesses show it in a modal over their payment page. Tell customers before they pay that their bank may ask them to confirm.
- Follow up when Stripe tells you to. For subscriptions, act on invoice.payment_action_required and send the customer back to authenticate within the 23-hour window. For Checkout, use the recovery URL from checkout.session.expired with customers who agreed to hear from you.
- Test it yourself in a sandbox. Stripe's test card 4000000000003220 always requires 3DS2, and the mock authentication page has a 'Failure' button, so you can see exactly what an abandoned or failed check looks like in your Dashboard.
Where OneSol fits
OneSol's checkout on Pro runs on your own Stripe (or PayPal, Razorpay, Square) account, so these statuses appear in your own Stripe dashboard exactly as described above.