Skip to content

Home

The Developer-First MMQR Engine

Generate dynamic Myanmar QR codes in milliseconds.

Built for high-velocity engineering teams who need simple, stateless payment integration without the banking bureaucracy.

Get API Keys View API Reference


Integrate in 30 Seconds

Myan Myan Pay provides a RESTful JSON API that just works.

npm install mmpay-node-sdk --save
const { MMPaySDK } = require('mmpay-node-sdk');

const MMPay = new MMPaySDK({
    appId: "MMxxxxxxx",
    publishableKey: "pk_test_abcxxxxx",
    secretKey: "sk_test_abcxxxxx",
    apiBaseUrl: "https://xxxxxx"
})
// async
await MMPay.pay({ orderId, amount, items })
// sync
MMPay.pay({ orderId, amount, items })
    .then((response) => {
        console.log(response)
    }).catch((error) => {
        console.log(error)
    })
npm install mmpay-node-sdk --save
import { MMPaySDK } from 'mmpay-node-sdk';

const MMPay = new MMPaySDK({
    appId: "MMxxxxxxx",
    publishableKey: "pk_test_abcxxxxx",
    secretKey: "sk_test_abcxxxxx",
    apiBaseUrl: "https://xxxxxx"
})
// async
await MMPay.pay({ orderId, amount, items })
// sync
MMPay.pay({ orderId, amount, items })
    .then((response) => {
        console.log(response)
    }).catch((error) => {
        console.log(error)
    })
pip install mmpay-python-sdk
from mmpay import MMPaySDK

# Initialize the SDK
options = {
    "appId": "YOUR_APP_ID",
    "publishableKey": "YOUR_PUBLISHABLE_KEY",
    "secretKey": "YOUR_SECRET_KEY",
    "apiBaseUrl": "[https://xxx.myanmyanpay.com](https://xxx.myanmyanpay.com)" # Replace with actual API Base URL [ Register With Us]
}

sdk = MMPaySDK(options)
payment_request = {
    "orderId": "ORD-SANDBOX-001",
    "amount": 5000,             # Amount in minor units (e.g., cents) or as required
    "callbackUrl": "[https://your-site.com/webhook/mmpay](https://your-site.com/webhook/mmpay)",
    "items": [
        {
            "name": "Premium Subscription",
            "amount": 5000,
            "quantity": 1
        }
    ]
}
response = sdk.sandbox_pay(payment_request)
composer require myanmyanpay/mmpay-php-sdk
<?php

use MMPay\MMPay;

$options = [
    'appId'          => 'YOUR_APP_ID',
    'publishableKey' => 'YOUR_PUBLISHABLE_KEY',
    'secretKey'      => 'YOUR_SECRET_KEY',
    'apiBaseUrl'     => '[https://api.mmpay.com](https://api.mmpay.com)'
];
$sdk = new MMPay($options);
$params = [
    'orderId' => 'ORD-LIVE-888',
    'amount'  => 10000,
    'items'   => [
        ['name' => 'E-Commerce Item', 'amount' => 10000, 'quantity' => 1]
    ]
];
$response = $sdk->pay($params);

?php>
import com.mmpay.sdk.MMPaySDK;
import com.mmpay.sdk.SDKOptions;
import com.mmpay.sdk.model.PaymentRequest;
import com.mmpay.sdk.model.Item;
import java.util.List;
import java.util.ArrayList;

SDKOptions options = new SDKOptions(
    "YOUR_APP_ID",
    "YOUR_PUBLISHABLE_KEY",
    "YOUR_SECRET_KEY",
    "[https://api.mmpay.com](https://api.mmpay.com)"
);

MMPaySDK sdk = new MMPaySDK(options);


try {
    PaymentRequest request = new PaymentRequest();
    request.orderId = "ORD-SANDBOX-" + System.currentTimeMillis();
    request.amount = 5000;
    request.currency = "MMK";
    request.callbackUrl = "[https://yoursite.com/webhook](https://yoursite.com/webhook)";

    // Add Items
    request.items = new ArrayList<>();
    request.items.add(new Item("Premium Plan", 5000, 1));

    Map<String, Object> response = sdk.pay(request);
    System.out.println("Response: " + response);

} catch (Exception e) {
    e.printStackTrace();
}

// Express.JS Example
MMPay
    .onTxCreate((tx) => console.log('Created:', tx.orderId))
    .onTxSuccess((tx) => console.log('Success:', tx.orderId))
    .onTxFail((tx) => console.log('Failed:', tx.orderId))
    .onTxRefund((tx) => console.log('Refunded:', tx.orderId))
    .onTxCancel((tx) => console.log('Cancelled:', tx.orderId))
    .onTxExpire((tx) => console.log('Expired:', tx.orderId))
    .onHeartbeat((tx) => console.log(tx.orderId)) // This means already send event coming in again
    .on('error', (err) => console.error(err));

app.post('/webhooks/mmpay-callback', async (req, res) => {
    const payload = JSON.stringify(req.body);
    const nonce = req.headers['x-mmpay-nonce'];
    const signature = req.headers['x-mmpay-signature'];
    await MMPay.listen(payload, nonce, signature);
    res.json({ received: true }); // please respond with 200 status
});
// Express.JS Example
interface MMPayIncomingCallbackScheme {
    orderId: string;
    amount: number;
    method: 'QR' | 'PIN' | 'PWA' | 'CARD';
    currency: 'MMK';
    vendor: string;
    status: 'PENDING' | 'SUCCESS' | 'FAILED' | 'REFUNDED' | 'CANCELLED' | 'EXPIRED';
    condition: 'PRISTINE' | 'TOUCHED' | 'EXPIRED';
    transactionRefId: string;
    callbackUrl?: string;
    customMessage?: string;
}

MMPay
    .onTxCreate((tx: MMPayIncomingCallbackScheme) => console.log('Created:', tx.orderId))
    .onTxSuccess((tx: MMPayIncomingCallbackScheme) => console.log('Success:', tx.orderId))
    .onTxFail((tx: MMPayIncomingCallbackScheme) => console.log('Failed:', tx.orderId))
    .onTxRefund((tx: MMPayIncomingCallbackScheme) => console.log('Refunded:', tx.orderId))
    .onTxCancel((tx: MMPayIncomingCallbackScheme) => console.log('Cancelled:', tx.orderId))
    .onTxExpire((tx: MMPayIncomingCallbackScheme) => console.log('Expired:', tx.orderId))
    .onHeartbeat((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId)) // This means already send event coming in again
    .on('error', (err) => console.error(err));

app.post('/webhooks/mmpay-callback', async (req: Request, res: Response) => {
    const payload = JSON.stringify(req.body);
    const nonce = req.headers['x-mmpay-nonce'] as string;
    const signature = req.headers['x-mmpay-signature'] as string;
    await MMPay.listen(payload, nonce, signature);
    res.json({ received: true }); // please respond with 200 status
});
# Flask Framwork Example
from flask import request

@app.route('/webhooks/mmpay', methods=['POST'])
def mmpay_webhook():
    payload_str = request.data.decode('utf-8')
    nonce = request.headers.get('X-Mmpay-Nonce')
    signature = request.headers.get('X-Mmpay-Signature')

    try:
        is_valid = sdk.verify_cb(payload_str, nonce, signature)
        if is_valid:
            return "Verified", 200
        else:
            return "Invalid Signature", 400

    except ValueError as e:
        return str(e), 400
// Controller File Laravel Example
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use MMPay\MMPay; // Import your package
class PaymentCallbackController extends Controller
{
    public function handle(Request $request)
    {
        $mmpay = new MMPay([
            'appId'          => env('MMPAY_APP_ID'),
            'publishableKey' => env('MMPAY_PUBLISHABLE_KEY'),
            'secretKey'      => env('MMPAY_SECRET_KEY'),
            'apiBaseUrl'     => env('MMPAY_API_URL'),
        ]);
        $rawPayload = $request->getContent();
        $nonce      = $request->header('X-Mmpay-Nonce');
        $signature  = $request->header('X-Mmpay-Signature');
        Log::info('MMPay Callback Received', [
            'nonce' => $nonce,
            'signature' => $signature
        ]);
        $isValid = $mmpay->verifyCb($rawPayload, $nonce, $signature);
        if (!$isValid) {
            Log::error('MMPay Signature Verification Failed');
            return response()->json(['message' => 'Invalid signature'], 403);
        }
        $data = json_decode($rawPayload, true);
        // ---------------------------------------------------
        // TODO: Add your business logic here
        // ---------------------------------------------------
        // Example:
        // $order = Order::where('order_id', $data['orderId'])->first();
        // if ($data['status'] === 'PAID') {
        //     $order->update(['status' => 'completed']);
        // }
        // ---------------------------------------------------

        return response()->json(['status' => 'success']);
    }
}
// routes/api.php
<?php
use App\Http\Controllers\PaymentCallbackController;
use Illuminate\Support\Facades\Route;

Route::post('/mmpay/callback', [PaymentCallbackController::class, 'handle']);
// Example in a Spring Boot Controller
@PostMapping("/webhook")
public ResponseEntity<String> handleWebhook(
    @RequestBody String payload,
    @RequestHeader("X-Mmpay-Nonce") String nonce,
    @RequestHeader("X-Mmpay-Signature") String signature
) {

    boolean isValid = sdk.verifyCb(payload, nonce, signature);

    if (isValid) {
        // ✅ Process order
        return ResponseEntity.ok("Verified");
    } else {
        // ❌ Invalid signature
        return ResponseEntity.status(400).body("Invalid Signature");
    }
}

Express JS Framwork Usage Full Example

const express = require("express");
const bodyParser = require("body-parser");
const app = express();
const PORT = process.env.PORT || 3000;
const { MMPaySDK } = require('mmpay-node-sdk');

app.use(bodyParser.json());

const MMPay = new MMPaySDK({
    appId: "MMxxxxxxx",
    publishableKey: "pk_live_abcxxxxx",
    secretKey: "sk_live_abcxxxxx",
    apiBaseUrl: "https://xxxxxx"
});

interface MMPayIncomingCallbackScheme {
    orderId: string;
    amount: number;
    method: 'QR' | 'PIN' | 'PWA' | 'CARD';
    currency: 'MMK';
    vendor: string;
    status: 'PENDING' | 'SUCCESS' | 'FAILED' | 'REFUNDED' | 'CANCELLED' | 'EXPIRED';
    condition: 'PRISTINE' | 'TOUCHED';
    transactionRefId: string;
    callbackUrl?: string;
    customMessage?: string;
}

MMPay
    .onTxCreate((tx: MMPayIncomingCallbackScheme) => console.log('Created:', tx.orderId))
    .onTxSuccess((tx: MMPayIncomingCallbackScheme) => console.log('Success:', tx.orderId))
    .onTxFail((tx: MMPayIncomingCallbackScheme) => console.log('Failed:', tx.orderId))
    .onTxRefund((tx: MMPayIncomingCallbackScheme) => console.log('Refunded:', tx.orderId))
    .onTxCancel((tx: MMPayIncomingCallbackScheme) => console.log('Cancelled:', tx.orderId))
    .onTxExpire((tx: MMPayIncomingCallbackScheme) => console.log('Expired:', tx.orderId))
    .onHeartbeat((tx: MMPayIncomingCallbackScheme) => console.log('Heartbeat:', tx.orderId))
    .on('error', (err) => console.error(err));
// Creating Order
app.post("/create-order", async (req, res) => {
    const { amount, items } = req.body;
    const orderId = ''; // GET YOUR ORDER ID FROM YOUR BIZ LOGIC
    const payload = {
        orderId: 'ORD-199399933',
        amount: 5000,
        items: [{ name: "Pencil", amount: 5000, quantity: 1 }],
        customMessage: '', // max 150 char  string
        callbackUrl: 'https://abcdef/callback' // [optional] overrides default callbackURL
    }
    let payResponse = await MMPay.pay(payload);
    res.status(200).json(payResponse);
});
// Listening Callback
app.post('/webhooks/mmpay-callback', async (req: Request, res: Response) => {
    const payload = JSON.stringify(req.body);
    const nonce = req.headers['x-mmpay-nonce'] as string;
    const signature = req.headers['x-mmpay-signature'] as string;
    await MMPay.listen(payload, nonce, signature);
    res.json({ received: true }); // please respond with 200 status
});

app.listen(PORT, () => console.log(`Server is running on port ${PORT}`));

Plugins

We Love Typescript, so here are our favourite framework plugins implementations

FastifyJS With Plugin Usage Full Example (Fast)

import Fastify, { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
import { mmpayPlugin } from './plugins/mmpayPlugin';

interface MMPayIncomingCallbackScheme {
    orderId: string;
    amount: number;
    method: 'QR' | 'PIN' | 'PWA' | 'CARD';
    currency: string;
    vendor: string;
    status: 'PENDING' | 'SUCCESS' | 'FAILED' | 'REFUNDED' | 'CANCELLED' | 'EXPIRED';
    condition: 'PRISTINE' | 'TOUCHED';
    transactionRefId: string;
    callbackUrl?: string;
    customMessage?: string;
}

const fastify: FastifyInstance = Fastify({ logger: true });

fastify.register(mmpayPlugin, {
    appId: process.env.MMPAY_APP_ID!,
    uatPubKey: process.env.MMPAY_UAT_PUBKEY!,
    uatSecKey: process.env.MMPAY_UAT_SECKEY!,
    prdPubKey: process.env.MMPAY_PRD_PUBKEY!,
    prdSecKey: process.env.MMPAY_PRD_SECKEY!,
});

fastify.ready(() => {
    fastify.mmpay.production
        .onTxCreate((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId))
        .onTxSuccess((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId))
        .onTxFail((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId))
        .onTxRefund((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId))
        .onTxCancel((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId))
        .onTxExpire((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId))
        .onHeartbeat((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId)) // This means already send event coming in again
        .on('error', (err) => console.error(err));

    fastify.mmpay.sandbox
        .onTxCreate((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId))
        .onTxSuccess((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId))
        .onTxFail((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId))
        .onTxRefund((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId))
        .onTxCancel((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId))
        .onTxExpire((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId))
        .onHeartbeat((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId)) // This means already send event coming in again
        .on('error', (err) => console.error(err));
});

fastify.post('/create-order', async (request: FastifyRequest, reply: FastifyReply) => {
    const { amount, orderId, items, customMessage } = request.body as any;
    const payResponse = await fastify.mmpay.production.pay({
        amount,
        orderId,
        items,
        customMessage
    });
    reply.send(payResponse);
});

fastify.post('/webhooks/mmpay-callback', async (request: FastifyRequest, reply: FastifyReply) => {
    const incomingSignature = request.headers['x-mmpay-signature'] as string;
    const incomingNonce = request.headers['x-mmpay-nonce'] as string;
    const payloadString = JSON.stringify(request.body);
    await fastify.mmpay.production.listen(
        payloadString,
        incomingNonce,
        incomingSignature
    );
    reply.code(200).send({ message: "Callback Processed" });
});

fastify.post('/create-order-sandbox', async (request: FastifyRequest, reply: FastifyReply) => {
    const { amount, orderId, items, customMessage } = request.body as any;
    const payResponse = await fastify.mmpay.sandbox.sandboxPay({
        amount,
        orderId,
        items,
        customMessage
    });
    reply.send(payResponse);
});

fastify.post('/webhooks/mmpay-callback-sandbox', async (request: FastifyRequest, reply: FastifyReply) => {
    const incomingSignature = request.headers['x-mmpay-signature'] as string;
    const incomingNonce = request.headers['x-mmpay-nonce'] as string;
    const payloadString = JSON.stringify(request.body);
    await fastify.mmpay.sandbox.listen(
        payloadString,
        incomingNonce,
        incomingSignature
    );
    reply.code(200).send({ message: "Callback Processed" });
});

fastify.listen({ port: 3000 });
// plguins/mmpayPlugin.ts
import { FastifyInstance, FastifyPluginAsync } from 'fastify';
import fastifyPlugin from 'fastify-plugin';
import { MMPaySDK } from 'mmpay-node-sdk';

declare module 'fastify' {
    interface FastifyInstance {
        mmpay: {
            sandbox: ReturnType<typeof MMPaySDK>;
            production: ReturnType<typeof MMPaySDK>;
        };
    }
}

interface MMPayPluginOptions {
    appId: string;
    uatPubKey: string;
    uatSecKey: string;
    prdPubKey: string;
    prdSecKey: string;
}

const mmpayPlugin: FastifyPluginAsync<MMPayPluginOptions> = fastifyPlugin(async (fastify: FastifyInstance, options: MMPayPluginOptions) => {
    const sandbox = MMPaySDK({
        appId: options.appId,
        publishableKey: options.uatPubKey,
        secretKey: options.uatSecKey,
        apiBaseUrl: 'https://sandbox.myanmyanpay.com'
    });

    const production = MMPaySDK({
        appId: options.appId,
        publishableKey: options.prdPubKey,
        secretKey: options.prdSecKey,
        apiBaseUrl: 'https://api.myanmyanpay.com'
    });

    fastify.decorate('mmpay', {
        sandbox,
        production
    });
});

export { mmpayPlugin };

ElysiaJS With Plugin Usage Full Example (Bun Native - Extremely Fast)

// server.ts
import {Elysia} from 'elysia';
import {mmpayPlugin} from './plugins/mmpayPlugin';

interface MMPayIncomingCallbackScheme {
    orderId: string;
    amount: number;
    method: 'QR' | 'PIN' | 'PWA' | 'CARD';
    currency: string;
    vendor: string;
    status: 'PENDING' | 'SUCCESS' | 'FAILED' | 'REFUNDED' | 'CANCELLED' | 'EXPIRED';
    condition: 'PRISTINE' | 'TOUCHED';
    transactionRefId: string;
    callbackUrl?: string;
    customMessage?: string;
}

const app = new Elysia()
.use(mmpayPlugin(
    {
        sbxAppId: process.env.SBX_APP_ID!,
        sbxPubKey: process.env.SBX_PUB_KEY!,
        sbxSecKey: process.env.SBX_SEC_KEY!,
        sbxBaseUrl: process.env.SBX_BASE_URL!,
        pdxAppId: process.env.PDX_APP_ID!,
        pdxPubKey: process.env.PDX_PUB_KEY!,
        pdxSecKey: process.env.PDX_SEC_KEY!,
        pdxBaseUrl: process.env.PDX_BASE_URL!,
    },
    ({ sandbox, production }) => {
        production
            .onTxCreate((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId))
            .onTxSuccess((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId))
            .onTxFail((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId))
            .onTxRefund((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId))
            .onTxCancel((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId))
            .onTxExpire((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId))
            .onHeartbeat((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId)) // This means already send event coming in again
            .on('error', (err) => console.error(err));

        sandbox
            .onTxCreate((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId))
            .onTxSuccess((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId))
            .onTxFail((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId))
            .onTxRefund((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId))
            .onTxCancel((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId))
            .onTxExpire((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId))
            .onHeartbeat((tx: MMPayIncomingCallbackScheme) => console.log(tx.orderId)) // This means already send event coming in again
            .on('error', (err) => console.error(err));
    })
)
.post('/create-order', async ({ body, mmpay }) => {
    const { amount, orderId, items, customMessage } = body as any;
    return await mmpay.production.pay({ amount, orderId, items, customMessage });
})
.post('/create-order-sandbox', async ({ body, mmpay }) => {
    const { amount, orderId, items, customMessage } = body as any;
    return await mmpay.sandbox.sandboxPay({ amount, orderId, items, customMessage });
})
.post('/webhooks/mmpay-callback', async ({ body, headers, mmpay }) => {
    const incomingSignature = headers['x-mmpay-signature'] as string;
    const incomingNonce = headers['x-mmpay-nonce'] as string;
    const payloadString = JSON.stringify(body);
    await mmpay.production.listen(payloadString, incomingNonce, incomingSignature);
    return { message: 'Callback Processed' };
})
.post('/webhooks/mmpay-callback-sandbox', async ({ body, headers, mmpay }) => {
    const incomingSignature = headers['x-mmpay-signature'] as string;
    const incomingNonce = headers['x-mmpay-nonce'] as string;
    const payloadString = JSON.stringify(body);
    await mmpay.sandbox.listen(payloadString, incomingNonce, incomingSignature);
    return { message: 'Callback Processed' };
})
.listen(3000);
// plguins/mmpayPlugin.ts
import { Elysia } from 'elysia';
import { MMPaySDK } from 'mmpay-node-sdk';

export interface MMPayConfig {
    sbxAppId: string;
    sbxPubKey: string;
    sbxSecKey: string;
    sbxBaseUrl: string;
    pdxAppId: string;
    pdxPubKey: string;
    pdxSecKey: string;
    pdxBaseUrl: string;
}

export const mmpayPlugin = (
config: MMPayConfig,
setup?: (instances: { sandbox: ReturnType<typeof MMPaySDK>; production: ReturnType<typeof MMPaySDK> }) => void ) => {
    const sandbox = MMPaySDK({
        appId: config.sbxAppId,
        publishableKey: config.sbxPubKey,
        secretKey: config.sbxSecKey,
        apiBaseUrl: config.sbxBaseUrl,
    });

    const production = MMPaySDK({
        appId: config.pdxAppId,
        publishableKey: config.pdxPubKey,
        secretKey: config.pdxSecKey,
        apiBaseUrl: config.pdxBaseUrl,
    });

    if (setup) {
        setup({ sandbox, production });
    }

    return new Elysia({ name: 'plugin.mmpay' }).decorate('mmpay', {
        sandbox,
        production,
    });
};

Why Developers Choose Us

  • Sub-Millisecond Latency

    Our edge-cached generation engine creates MMQR strings faster than you can render the DOM. No waiting for upstream banking handshakes.

  • Stateless & Secure

    We simply translate your payment intent into a valid, scannable MMQR string compatible with KBZPay, Wave, and CB.

  • Type-Safe SDKs

    First-class support for TypeScript. Autocomplete for every parameter. If it compiles, it works.