| 1 | import { MmgCheckoutUtil } from "mobile-money-sdk"; |
| 2 | import express from "express"; |
| 3 | |
| 4 | const app = express(); |
| 5 | app.use(express.json()); // Ensure your server can parse JSON bodies |
| 6 | |
| 7 | // Initialize the Checkout Utility |
| 8 | /** |
| 9 | process.env.* returns string | undefined — TypeScript will complain if you pass those directly to fields typed as string. The ! non-null assertion (or |
| 10 | runtime validation) is needed for publicKey, secretKey, merchantId, and clientId. |
| 11 | **/ |
| 12 | const checkoutUtil = new MmgCheckoutUtil({ |
| 13 | environment: "Live", // or "Sandbox" |
| 14 | publicKey: process.env.MMG_PUBLIC_KEY!, |
| 15 | privateKey: process.env.MMG_PRIVATE_KEY, |
| 16 | }); |
| 17 | |
| 18 | // The URL you provide to MMG during onboarding (or auto-generated) |
| 19 | app.post("/mmg-sdk/callback/:hash", async (req, res) => { |
| 20 | try { |
| 21 | // 1. Extract the encrypted token from the incoming request body |
| 22 | const encryptedToken = req.body.token; |
| 23 | |
| 24 | if (!encryptedToken) { |
| 25 | return res.status(400).send("Missing token"); |
| 26 | } |
| 27 | |
| 28 | // 2. Decrypt the token using the SDK utility |
| 29 | const response = checkoutUtil.decryptWebhookToken(encryptedToken); |
| 30 | |
| 31 | /* The decrypted response object looks like this: |
| 32 | { |
| 33 | merchantTransactionId: "ORD-1765472622", |
| 34 | transactionId: "999888777", |
| 35 | ResultCode: "0", |
| 36 | ResultMessage: "Transaction Successful", |
| 37 | htmlResponse: "..." |
| 38 | } |
| 39 | */ |
| 40 | |
| 41 | // 3. Process the Result |
| 42 | if (response.ResultCode === "0") { |
| 43 | // Payment was successful! |
| 44 | console.log(`Payment confirmed for order: ${response.merchantTransactionId}`); |
| 45 | // TODO: Update your database, mark order as paid, and fulfill goods. |
| 46 | } else { |
| 47 | // Payment failed, timed out, or was cancelled |
| 48 | console.warn(`Payment failed with code ${response.ResultCode}: ${response.ResultMessage}`); |
| 49 | // TODO: Mark order as failed in your database. |
| 50 | } |
| 51 | |
| 52 | // 4. Always return a 200 OK status to MMG so they know you received the webhook |
| 53 | res.status(200).send("OK"); |
| 54 | |
| 55 | } catch (error) { |
| 56 | console.error("Webhook decryption failed. Invalid key or payload.", error); |
| 57 | // Still return 200 to prevent MMG from endlessly retrying a bad payload |
| 58 | res.status(200).send("Processed with errors"); |
| 59 | } |
| 60 | }); |