I'm attempting to link an external bank account to a Stripe connected account. The account type is custom. I successfully created a SetupIntent associated to a connected account as seen below and received a client secret:
params := &stripe.SetupIntentParams{
AttachToSelf: stripe.Bool(true),
FlowDirections: stripe.StringSlice([]string{
*stripe.String(string(stripe.SetupIntentFlowDirectionInbound)),
*stripe.String(string(stripe.SetupIntentFlowDirectionOutbound)),
}),
PaymentMethodOptions: &stripe.SetupIntentPaymentMethodOptionsParams{
USBankAccount: &stripe.SetupIntentPaymentMethodOptionsUSBankAccountParams{
FinancialConnections: &stripe.SetupIntentPaymentMethodOptionsUSBankAccountFinancialConnectionsParams{
Permissions: stripe.StringSlice([]string{*stripe.String("balances"), *stripe.String("payment_method")}),
},
VerificationMethod: stripe.String("instant"),
},
},
PaymentMethodTypes: stripe.StringSlice([]string{
*stripe.String(string(stripe.PaymentMethodTypeUSBankAccount)),
}),
}
params.SetStripeAccount(connectedId)
resp, err := setupintent.New(params)
if err != nil {
return nil, fmt.Errorf("failed to setup intent: %w", err)
}
return resp, nil
The client_secret is passed to the frontend where we kick off the authorization flow to collect bank info to be able to link an external account.
const publishableKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY || ''
if (!publishableKey) {
throw new Error('missing stripe publishable key')
}
export default function Page() {
const [clientSecret, setClientSecret] = useState('')
const [showModal, setShowModal] = useState(false)
const [stripeInstance, setStripeInstance] = useState<Stripe | null>(null)
const [linkingBankLoading, setLinkingBankLoading] = useState(false)
useEffect(() => {
const loadAndSetStripe = async () => {
try {
const loadedStripe = await loadStripe(publishableKey)
setStripeInstance(loadedStripe)
} catch (err) {
console.error('stripe publishable key failed to load', err)
}
}
loadAndSetStripe()
}, [])
const linkExternalBank = async () => {
setLinkingBankLoading(true)
try {
const { data } = await linkExternalBankMutation({
context: {
headers: { authorization: `Bearer ${accessToken}` },
},
})
if (data?.linkExternalBank.clientSecret) {
setClientSecret(data.linkExternalBank.clientSecret)
setShowStripeModal(true)
}
} catch (err) {
console.error('error linking external bank', err)
} finally {
setLinkingBankLoading(false)
}
}
<div className="flex flex-col gap-3 md:flex-row">
<AddFinancialConnectionCard
linkExternalBank={linkExternalBank}
linkingBankLoading={linkingBankLoading}
/>
{showModal && stripeInstance && clientSecret && (
<Modal
isOpen={showModal}
onClose={() => setShowModal(false)}
clientSecret={clientSecret}
stripe={stripeInstance}
/>
)}
</div>
}
And finally in the Modal:
interface ModalProps {
isOpen: boolean
onClose: () => void
clientSecret: string
stripe: Stripe | null
}
export const Modal = ({ isOpen, onClose, clientSecret, stripe }: ModalProps) => {
if (!isOpen || !stripe || !clientSecret) {
return null
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
<div className="w-full max-w-lg rounded bg-white p-4 shadow-lg">
<button onClick={onClose}>Close</button>
<Elements stripe={stripe} options={{ clientSecret }}>
<BankDetailsForm clientSecret={clientSecret} />
</Elements>
</div>
</div>
)
}
It attempts to load the element and displays for a quick second before it crashes. When i look at the network tab, we received the error: client_secret provided does not match any associated SetupIntent on this account.
But also when I run a curl command to retrieve the SetupIntent, I can successfully see it has been created for that account. I stumped to why this error occurs.
Also I have doubled checked the API keys generated and I'm using the right keys.
I have gone through the Stripe documentation but still running into the Issue. Also tried hard coding the client secret and publishable when generated via Curl but still running into the same error.
This fails when passing the client secret to the Element component.
You're making the serverside call on behalf of a connected account:
params.SetStripeAccount(connectedId)
But on the frontend, you're not:
const loadedStripe = await loadStripe(publishableKey)
So Stripe is looking for an intent on your account to confirm, not the connected account.
You must authenticate everything the same way - in your case that means authenticating your frontend on behalf of your connected account ID:
const stripePromise = loadStripe('{{PLATFORM_PUBLISHABLE_KEY}}', {
stripeAccount: '{{CONNECTED_STRIPE_ACCOUNT_ID}}',
});
Related Stripe documentation.