Node.js

Payment Gateway

Integrate Midtrans and Stripe payment gateways in Node.js — create transactions, Snap tokens, payment intents, and verify webhooks.

Midtrans SDK Install: Install the official Midtrans Node.js client and configure with your server key.
📄terminal
JS
# Install
npm install midtrans-client

# .env
MIDTRANS_SERVER_KEY=SB-Mid-server-xxxxxxxxxxxx
MIDTRANS_CLIENT_KEY=SB-Mid-client-xxxxxxxxxxxx
MIDTRANS_IS_PRODUCTION=false
Midtrans Snap Token: Create a Snap transaction token to initialise the Midtrans payment popup on the frontend.
📄controllers/paymentController.js
JS
const midtransClient = require('midtrans-client');

const snap = new midtransClient.Snap({
    isProduction: process.env.MIDTRANS_IS_PRODUCTION === 'true',
    serverKey:    process.env.MIDTRANS_SERVER_KEY,
    clientKey:    process.env.MIDTRANS_CLIENT_KEY,
});

exports.createSnapToken = async (req, res) => {
    const { orderId, amount, customerName, customerEmail } = req.body;

    const parameter = {
        transaction_details: {
            order_id:     orderId,
            gross_amount: amount,
        },
        customer_details: {
            first_name: customerName,
            email:      customerEmail,
        },
    };

    try {
        const transaction = await snap.createTransaction(parameter);
        res.json({ token: transaction.token, redirect_url: transaction.redirect_url });
    } catch (err) {
        res.status(500).json({ message: err.message });
    }
};
Midtrans Webhook Notification: Verify and handle payment status notifications sent by Midtrans to your server endpoint.
📄controllers/paymentController.js
JS
exports.handleNotification = async (req, res) => {
    const apiClient = new midtransClient.CoreApi({
        isProduction: process.env.MIDTRANS_IS_PRODUCTION === 'true',
        serverKey:    process.env.MIDTRANS_SERVER_KEY,
    });

    try {
        const statusResponse = await apiClient.transaction.notification(req.body);
        const { order_id, transaction_status, fraud_status } = statusResponse;

        let orderStatus = 'pending';

        if (transaction_status === 'capture') {
            orderStatus = fraud_status === 'accept' ? 'paid' : 'fraud';
        } else if (transaction_status === 'settlement') {
            orderStatus = 'paid';
        } else if (['cancel', 'deny', 'expire'].includes(transaction_status)) {
            orderStatus = 'cancelled';
        }

        // Update your order in DB
        await Order.findOneAndUpdate({ orderId: order_id }, { status: orderStatus });
        res.status(200).json({ message: 'OK' });
    } catch (err) {
        res.status(500).json({ message: err.message });
    }
};
Stripe SDK Install: Install the Stripe Node.js SDK and initialise with your secret key.
📄terminal
JS
# Install
npm install stripe

# .env
STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxx
Stripe Payment Intent: Create a PaymentIntent on the server and send the client_secret to the frontend to complete the payment.
📄controllers/stripeController.js
JS
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

exports.createPaymentIntent = async (req, res) => {
    const { amount, currency = 'usd' } = req.body;

    try {
        const paymentIntent = await stripe.paymentIntents.create({
            amount:   amount * 100,   // Stripe uses cents
            currency,
            automatic_payment_methods: { enabled: true },
        });

        res.json({ clientSecret: paymentIntent.client_secret });
    } catch (err) {
        res.status(500).json({ message: err.message });
    }
};
Stripe Webhook Verification: Verify the Stripe webhook signature and handle payment_intent.succeeded events securely. Requires raw body — use express.raw() for this route.
📄controllers/stripeController.js
JS
exports.handleWebhook = (req, res) => {
    const sig = req.headers['stripe-signature'];

    let event;
    try {
        event = stripe.webhooks.constructEvent(
            req.body,                               // raw Buffer
            sig,
            process.env.STRIPE_WEBHOOK_SECRET
        );
    } catch (err) {
        return res.status(400).json({ message: `Webhook Error: ${err.message}` });
    }

    switch (event.type) {
        case 'payment_intent.succeeded':
            const intent = event.data.object;
            console.log('Payment succeeded:', intent.id);
            // fulfill order...
            break;
        case 'payment_intent.payment_failed':
            console.log('Payment failed');
            break;
        default:
            console.log(`Unhandled event type: ${event.type}`);
    }

    res.json({ received: true });
};

// In routes file — MUST use raw body parser for this route
// app.post('/stripe/webhook', express.raw({ type: 'application/json' }), stripeController.handleWebhook);