Summary
STPPaymentHandler.confirmPayment completion handler never fires when network connectivity is lost mid-3DS authentication. This leaves the private static var inProgress flag permanently set to true, causing every subsequent confirmPayment/handleNextAction call to immediately fail with noConcurrentActionsErrorCode (error code 7) for the rest of the app session.
Since inProgress is private static (shared across all instances, including new ones) and there is no public API to reset it, the SDK becomes permanently unusable for any payment confirmation until the app process is killed.
In production, a user experienced 418 consecutive 3DS failures over ~2 hours across 11 different payment IDs before giving up.
Code to reproduce
Standard implementation per Stripe's 3DS docs — minimal STPAuthenticationContext (no prepare(forPresentation:) override), URL callback correctly wired via StripeAPI.handleURLCallback(with:) in our scene delegate.
- Call
STPPaymentHandler.sharedHandler.confirmPayment with a 3DS-required card (EU/SCA market)
- Lose network connectivity while the SDK is mid-3DS (after confirm API call, during ACS communication)
- The completion handler never fires —
Self.inProgress is never reset to false
- Restore connectivity, call
confirmPayment with a completely new client secret and payment intent
- Immediately fails with code 7:
"The current action is not yet completed. STPPaymentHandler does not support concurrent calls to its API."
- All subsequent calls fail identically — creating a new
STPPaymentHandler instance doesn't help since inProgress is a static property
Production error payload:
STPPaymentHandlerError( analyticsErrorCode: "noConcurrentActionsErrorCode", errorCode: 7, com.stripe.lib:ErrorMessageKey: "The current action is not yet completed. STPPaymentHandler does not support concurrent calls to its API." )
iOS version
iOS 26.1 (build 23B85), iPhone 12 (iPhone13,2)
Installation method
Swift Package Manager (stripe-ios-spm)
SDK version
25.11.0
Other information
Root cause in SDK source: In STPPaymentHandler.swift, Self.inProgress = false is only set inside wrappedCompletion. If the 3DS flow gets stuck due to network loss and never reaches any completion path, the flag stays true permanently. The authenticationTimeout on STPThreeDSCustomizationSettings (at default value) did not fire either — we never received timedOutErrorCode.
Production timeline:
- 18:57 —
confirmPayment called, 3DS flow starts. No completion callback ever received.
- 18:59 — Severe network disruption: socket timeouts, NSURLErrorDomain -999/-1001.
- 19:02–21:06 — User retries across 11 different payment IDs. Every call immediately returns error code 7 without ever reaching Stripe's servers. 418 total failures.
Requests:
- Guarantee wrappedCompletion always fires — every code path inside Stripe3DS2's STDSTransaction.doChallenge and ACS networking should eventually call the completion handler, even on network failure. Currently, it's possible for the SDK to get stuck with no callback and no timeout.
- Consider a public
cancelCurrentAction() API, or make inProgress resettable to let us unblock the user without forcing app restart
Related: #1897 (same error code, but triggered by developer not calling prepare(forPresentation:) completion — our case is the SDK itself getting stuck), #1383, #2094
Summary
STPPaymentHandler.confirmPaymentcompletion handler never fires when network connectivity is lost mid-3DS authentication. This leaves theprivate static var inProgressflag permanently set totrue, causing every subsequentconfirmPayment/handleNextActioncall to immediately fail withnoConcurrentActionsErrorCode(error code 7) for the rest of the app session.Since
inProgressisprivate static(shared across all instances, including new ones) and there is no public API to reset it, the SDK becomes permanently unusable for any payment confirmation until the app process is killed.In production, a user experienced 418 consecutive 3DS failures over ~2 hours across 11 different payment IDs before giving up.
Code to reproduce
Standard implementation per Stripe's 3DS docs — minimal
STPAuthenticationContext(noprepare(forPresentation:)override), URL callback correctly wired viaStripeAPI.handleURLCallback(with:)in our scene delegate.STPPaymentHandler.sharedHandler.confirmPaymentwith a 3DS-required card (EU/SCA market)Self.inProgressis never reset tofalseconfirmPaymentwith a completely new client secret and payment intent"The current action is not yet completed. STPPaymentHandler does not support concurrent calls to its API."STPPaymentHandlerinstance doesn't help sinceinProgressis a static propertyProduction error payload:
iOS version
iOS 26.1 (build 23B85), iPhone 12 (iPhone13,2)
Installation method
Swift Package Manager (
stripe-ios-spm)SDK version
25.11.0
Other information
Root cause in SDK source: In
STPPaymentHandler.swift,Self.inProgress = falseis only set insidewrappedCompletion. If the 3DS flow gets stuck due to network loss and never reaches any completion path, the flag staystruepermanently. TheauthenticationTimeoutonSTPThreeDSCustomizationSettings(at default value) did not fire either — we never receivedtimedOutErrorCode.Production timeline:
confirmPaymentcalled, 3DS flow starts. No completion callback ever received.Requests:
cancelCurrentAction()API, or makeinProgressresettable to let us unblock the user without forcing app restartRelated: #1897 (same error code, but triggered by developer not calling
prepare(forPresentation:)completion — our case is the SDK itself getting stuck), #1383, #2094