I am trying to implement Stripe's Connect into my application. I've done hours of research and trial and error method debugging and now I am in situation where I get no technical errors but error that says:
Insufficient funds in Stripe account. In test mode, you can add funds to your available balance (bypassing your pending balance) by creating a charge with 4000 0000 0000 0077 as the card number. You can use the the /v1/balance endpoint to view your Stripe balance (for more details, see stripe.com/docs/api#balance).
The paymentIntent in Stripe Dashboar show me PaymentIntent status: requires_confirmation
The error for me seems weird since The card I am testing with is exactly the one they suggested me to use. Note that I've also tried to use other cards.
I am using Google Cloud Functions as a backend for my Stipe API.
This is the function that creates account
and customer
. I am creating both of them just to be sure everything is working fine.
// When a user is created in firebase auth, register them with Stripe
exports.createStripeUser = functions.auth.user().onCreate(async (user) => {
const account = await stripe.accounts.create({type: 'custom', business_type: 'individual', individual: {email: user.email}, requested_capabilities: ['card_payments', 'transfers'], email: user.email});
const customer = await stripe.customers.create({email: user.email});
return admin.firestore().collection('stripe_customers').doc(user.uid).set({account_id: account.id, customer_id: customer.id});
});
Now I am adding card information:
// Add a payment source (card) for a user by writing a stripe payment source token to Cloud Firestore
exports.addPaymentSource = functions.firestore.document('/stripe_customers/{userId}/tokens/{pushId}').onCreate(async (snap, context) => {
const source = snap.data();
const token = source.token;
if (source === null){
return null;
}
try {
const snapshot = await admin.firestore().collection('stripe_customers').doc(context.params.userId).get();
const customer = snapshot.data().customer_id;
const response = await stripe.customers.createSource(customer, {source: token});
return admin.firestore().collection('stripe_customers').doc(context.params.userId).collection("sources").doc(response.fingerprint).set(response, {merge: true});
} catch (error) {
await snap.ref.set({'error':userFacingMessage(error)},{merge:true});
return reportError(error, {user: context.params.userId});
}
});
This is how I create my paymentIntent
:
// Create Stripe paymentIntent whenever an amount is created in Cloud Firestore
exports.createStripePaymentIntent = functions.firestore.document('stripe_customers/{userId}/charges/{id}').onCreate(async (snap, context) => {
const val = snap.data();
try {
// Look up the Stripe customer id written in createStripeUser
const snapshot = await admin.firestore().collection(`stripe_customers`).doc(context.params.userId).get()
const snapval = snapshot.data();
const customer = snapval.customer_id
const amount = val.amount;
const charge = {amount, currency, customer, transfer_group: val.transfer_group, payment_method: val.payment_method};
if (val.source !== null) {
charge.source = val.source;
}
const response = await stripe.paymentIntents.create(charge);
// If the result is successful, write it back to the database
return snap.ref.set(response, { merge: true });
} catch(error) {
// We want to capture errors and render them in a user-friendly way, while
// still logging an exception with StackDriver
console.log(error);
await snap.ref.set({error: userFacingMessage(error)}, { merge: true });
return reportError(error, {user: context.params.userId});
}
});
Since now everything seems to work as expected and now comes in the fun part, the TRANSFER. Which I am not able to make because of the error mentioned above. This is how I create my charge:
exports.createStripeTransfer = functions.firestore.document('stripe_customers/{userId}/transfers/{id}').onCreate(async (snap, context) => {
const val = snap.data();
try {
// Look up the Stripe account id written in createStripeUser
const snapshot = await admin.firestore().collection(`stripe_customers`).doc(context.params.userId).get()
const snapval = snapshot.data();
const destinationAccount = val.destination
const amount = val.amount;
const charge = {amount, currency, destination: destinationAccount, transfer_group: val.transfer_group};
if (val.source !== null) {
charge.source = val.source;
}
const response = await stripe.transfers.create(charge);
stripe.paymentIntents.confirm(response.id, {payment_method: response.payment_method})
// If the result is successful, write it back to the database
return snap.ref.set(response, { merge: true });
} catch(error) {
// We want to capture errors and render them in a user-friendly way, while
// still logging an exception with StackDriver
console.log(error);
await snap.ref.set({error: userFacingMessage(error)}, { merge: true });
return reportError(error, {user: context.params.userId});
}
});
Can anybody explain me what am I missing here? Why am I getting the error? I tried to manually top up account which did not help also.
Update 1: According to Sergio Tulentsev's comment it seems I gotta confirm the transfer for it to succeed. So I did implement the following line after successful transfer but the error remains the same:
stripe.paymentIntents.confirm(response.id, {payment_method: response.payment_method})
What is the difference between stripe.paymentIntent.confirm
vs stripe.confirmCardPayment
?
confirm
it. stripe.com/docs/js/payment_intents/confirm_card_payment – Sergio Tulentsevstripe.confirm
endpoint but for some reason it does not confirm it but returns the same error. – Tarvo Mäeseppstripe.confirmCardPayment
(if the payment is by card). It handles 3DS step, for example. Take a look at this guide: stripe.com/docs/payments/accept-a-payment#web – Sergio Tulentsev