0
votes

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?

1
Creating a PaymentIntent is not enough. To execute the transfer you have to confirm it. stripe.com/docs/js/payment_intents/confirm_card_paymentSergio Tulentsev
@SergioTulentsev Wow.. While you wrote the comment I just found some links talking about confirmation on Stripe's documentation too. But nothing mentions that this must be the next step after creating transfer nor that this is mandatory for the transfer to succeed. I wish there was a link in the PaymentIntent or transfer section talking about this. I am glad there seems to be light at the end of the tunnel :D Thank you very much. Gonna implement the function now.Tarvo Mäesepp
@SergioTulentsev I did implement the stripe.confirm endpoint but for some reason it does not confirm it but returns the same error.Tarvo Mäesepp
From client-side (JS), you should use stripe.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#webSergio Tulentsev
@SergioTulentsev I do ask credit card information in separate flow than the intent/transfer is made in. In settings, user is adding his/her card/source and I store the token. Now in the payment view, there is just list of sources that user can select and pay button. After the pay button click, I create intent, then instantly want to transfer the money from account A to B. This is not a checkout I am building.. But it seems like it is now working.. Gonna do more test and update the question or will add an answer.Tarvo Mäesepp

1 Answers

4
votes

It seems like you have access to dashboard and you have test account. In this case you can manually add funds from Payments -> New and then provide test card details that goes like as shown in photo attached and card number used here is 4000 0000 0000 0077.

As you mention you are generating payment Intent this will only credit the charges amount to your available funds only after you have a valid live account added till then the funds will always be on HOLD.

So for testing you can add funds manually as shown in linkpic of generating new payment manually