1
votes

I store users in firebase realtime database and stripe to handle payments. An authenticated user can click a button and will get redirected to Stripe Checkout. After making a payment, user gets redirected to success_url back in the app. Next I would like to update user Object in database - just save the information that the payment was successful and its id.

The problem: I don't know how to find a user which completed a payment, after redirection to success_url. I would like to update user's data on database after the payment.

The idea: I can save the payment_intent in the user profile, as this information is sent to the client side with a session. And then, after the payment completion I could search for the payment_intent and update the user which had this data. But is this a good approach? Is there any better way to find a user?

My code is based on Firebase Cloud Function HTTP Requests. Following the Stripe guidelines to accept a payment, I create a session for a user which wants to make a payment:

export const payment = functions.https.onRequest((request, response) => {
  cors(request, response, async () => {
    response.set("Access-Control-Allow-Headers", "Content-Type");
    response.set("Access-Control-Allow-Credentials", "true");
    response.set("Access-Control-Allow-Origin", "*");
    await stripe.checkout.sessions.create(
      {
        payment_method_types: ["card"],
        line_items: [
          {
            price_data: {
              currency: "usd",
              product_data: {
                name: "Test"
              },
              unit_amount: 1000
            },
            quantity: 1
          }
        ],
        mode: "payment",
        success_url: "https://example.com/success",
        cancel_url: "https://example.com/cancel"
      },
      function(err: Error, session: any) {
        response.send(session); 
        // the session is sent to the client and can be used to finalise a transaction
      }
    );
  });
});

On the client side I use axios to call a payment cloud function request and afterwards I pass the sessionId to Stripe.redirectToCheckout which starts the checkout redirection:

let sessionId = "";
await axios.post("payment function url request")
    .then(response => {
        sessionId = response.data.id;
    });
const stripe: any = await loadStripe("stripe key");
stripe.redirectToCheckout({
    sessionId: sessionId
});

After the customer completes a payment on Stripe Checkout, they’re redirected to the URL that is specified as success_url, it is advised to run a webhook from stripe when the checkout is completed. It allows to fire another cloud function, which sends a response to the client side about a successful payment response.json({ received: true }):

export const successfulPayment = functions.https.onRequest(
  (request, response) => {
    const sig = request.headers["stripe-signature"];
    let event;

    try {
      event = stripe.webhooks.constructEvent(
        request.rawBody,
        sig,
        endpointSecret
      );
    } catch (err) {
      return response.status(400).send(`Webhook Error: ${err.message}`);
    }
    // Handle the checkout.session.completed event
    // Should I update the user data in the database here?
    if (event.type === "checkout.session.completed") {
      const session = event.data.object;
      console.log(`Event passed: ${session}`);
    }
    // Return a response to acknowledge receipt of the event
    response.json({ received: true });
    // Or how do I use this response to then update the user from the client side?
  }
);

I will really appreciate all the help and suggestions :)

1

1 Answers

3
votes

Thanks for your question, kabugh!

Next I would like to update user Object in database - just save the information that the payment was successful and its id.

First, you likely do not want to use the success URL as a signal for storing successful payment. It's possible the customer closes their browser, the internet connection between your server and the customer drops out, or that the customer just navigates away after successful payment and before hitting the success URL. Instead, it's highly recommended to use webhooks, specifically listening for the checkout.session.completed event. From the success URL, you can retrieve the Checkout Session and show the status to the customer if they land there, but you don't want to use that for fulfillment or as the source of truth for the status of the payment.

I can save the payment_intent in the user profile, as this information is sent to the client side with a session. And then, after the payment completion I could search for the payment_intent and update the user which had this data. But is this a good approach? Is there any better way to find a user?

I believe you mean the Checkout Session ID, not the PaymentIntent ID here. The PaymentIntent ID will not be created until after the user is redirected. Yes, this is a good idea and a good practice to store a reference to the ID of the Checkout Session with the user profile somewhere in your db. You could also set a metadata value on the Checkout Session when you create it, with some reference to the user's ID in your system. That way when you receive the checkout.session.completed webhook event, you can look up the user by ID based on the ID stored in metadata or by the ID of the Checkout Session stored with the user profile as discussed earlier.

it is advised to run a webhook from stripe when the checkout is completed. It allows to fire another cloud function, which sends a response to the client side about a successful payment response.json({ received: true })

Note that the webhook event is a POST request from Stripe's servers directly to your server. It will not in any way render or send a response back to the customer's browser or client. The webhook response from your server goes back to Stripe and there is no concept of a shared session between those three parties. When sending response.json({ received: true }) as the result of a webhook handler, that is sent back to Stripe, not to the customer's browser.