Search code examples
node.jsexpressstripe-paymentsbackendpayment

Successfully process 2 payments or refund to both, using stripe, node, and express


I will be using node express and stripe to process payments of my users. There is an edge case where it must process payment for 2 users sequentially. And if the second payment fails to process, then the first payment must be refunded.

The user's payment info / options are stored.

How can this be implemented?


Solution

  • You can do that using stripe webhooks and a database.

    You can call all interactions orders. For example this would be an instance of the order

    interface Order {
      id: string;
      user1: string;
      user2: string;
    
      user1Succeeded: boolean;
      user2Succeeded: boolean;
    
      paymentIntent1: string;
      paymentIntent2: string;
    }
    

    And store the orders in some database.

    Create an order

    User1 makes a POST request to /create-order endpoint:

    router.post("/create-order", async (req, res) => {
    
      const orderId = "ORDERID"
    
      // create a payment intent and send it to User1
    
      const paymentIntent = await stripe.paymentIntents.create({
        amount: 123,
        currency: "USD",
        metadata: {
          orderId: orderId, // save order id so then when payment
                           // succeeded you know what order to handle  
          userId: "USER1" 
        }
      });
    
      // create an order and store it in database
      // so other users can join the order
    
      await createOrder({
        id: orderId,
        user1: "USER1",
        paymentIntent1: paymentIntent.id
      });
    
      res.send({ clientSecret: paymentIntent.client_secret });
    })
    

    Then a different user wants to join the order and makes a POST request to /join-order endpoint:

    router.post("/join-order/:orderId", async (req, res) => {
      const { orderId } = req.params;
    
      // create a payment intent and send it to User2
    
      const paymentIntent = await stripe.paymentIntents.create({
        amount: 123,
        currency: "USD",
        metadata: {
          orderId: orderId, // save orderId so when payment succeeds you
                           // know what order to change
          userId: "USER2"
        }
      });
    
      // JOIN an order and update it in database
    
      await updateOrder(orderId, {
        user2: "USER2",
        paymentIntent2: paymentIntent.id
      });
    
      res.send({ clientSecret: paymentIntent.client_secret });
    })
    

    Handle payment intents

    Use stripe webhooks to handle when payment intends succeeds:

    router.post("/stripe-webhook", async (req, res) => {
      if(event.type == "payment_intent.succeeded") {
        const data = event.data.object as Stripe.PaymentIntent;
    
        const orderId = data.metadata.orderId; // the order id that was saved earlier
        const userId = data.metadata.userId; // the user id that was saved earlier
    
    
        const order = await getOrder(orderId);
    
        // update the order so you know later that other user succeeded
        if(order.user1 == userId) {
          await updateOrder(orderId, { user1Succeeded: true });
    
          if(order.user2Succeeded) { // both users succeeded
            // ORDER COMPLETED, NO REFUNDS
          }
        } else if(order.user2 == userId) {
          await updateOrder(orderId, { user2Succeeded: true });
    
          if(order.user1Succeeded) { // both users succeeded
            // ORDER COMPLETED, NO REFUNDS
          }
        }
      }
    
    
      if(event.type == "charge.failed") { // one of the payments failed
                                          // cancel or refund other
        const data = event.data.object as Stripe.Charge;
    
        const { orderId } = data.metadata;
    
        const order = await getOrder(orderId);
    
        if(order.user1Succeeded) {
          // REFUND USER1
          stripe.refunds.create({ payment_intent: order.paymentIntent1 });
        } else {
          // if user1 didn't pay yet cancel the payment intent
          stripe.paymentIntents.cancel(order.paymentIntent1);
        }
        if(order.user1Succeeded) {
          // REFUND USER2
          stripe.refunds.create({ payment_intent: order.paymentIntent1 });
        } else {
          // if user2 didn't pay cancel his payment intent
          stripe.paymentIntents.cancel(order.paymentIntent2);
        }
        
      }
    });
    

    I'm guessing what you need is Stripe webhooks. Here is how to monitor Payment Intent status