Search by

laranex / laravel-myanmar-payments

NayThuKhant

A Laravel package to deal with Payment Providers from Myanmar

Package info

github.com/laranex/laravel-myanmar-payments

pkg:composer/laranex/laravel-myanmar-payments

Statistics

Installs: 12 043

Dependents: 0

Suggesters: 0

Stars: 28

Open Issues: 0

v2.2.7 2026-09-18 05:11 UTC

README

Latest Version on Packagist Total Downloads

A Laravel Package to deal with Payment Providers from Myanmar. This package can take care of PGW payments.

Supported Payments are as follows.

  • Wave Money
  • KBZPay
  • Cybersource
  • AYA Payment Gateway
  • Yoma MMQR

Workflows

  • Client App - Server Workflow

Client App - Server Workflow

  • Server Side only Workflow

Server Side only Workflow

Installation

You can install the package via composer:

composer require laranex/laravel-myanmar-payments

Configuration

  php artisan vendor:publish --tag="laravel-myanmar-payments"

Upgrade Guide

  • v1 -> v2
    • Backup & Delete the existing config/laravel-myanmar-payments.php (if only published before)
    • Publish the new config/laravel-myanmar-payments, and re-merge the old config/laravel-myanmar-payments.php
    • Update .env (KBZ Pay is supported now)

Wave Money Configuration KBZ Pay Configuration

Usage

use Laranex\LaravelMyanmarPayments\LaravelMyanmarPaymentsFacade;


# WAVEMONEY
# Payment Screen
LaravelMyanmarPaymentsFacade::channel('wave_money')
    ->getPaymentScreenUrl($items, $orderId, $amount, $merchantReferenceId, $backendResultUrl, $frontendResultUrl, $paymentDescription)
# Validate Response Signature
Laranex\LaravelMyanmarPayments\LaravelMyanmarPaymentsFacade::channel("wave_money")
    ->verifyWaveSignature($request)

#KBZ PAY
# PWA URL
LaravelMyanmarPaymentsFacade::channel("kbz_pay.pwaapp")
    ->getPaymentScreenUrl($orderId, $amount, $nonceStr,  $backendResultUrl)
# QR Code
LaravelMyanmarPaymentsFacade::channel("kbz_pay.qr")
    ->getPaymentQr($orderId, $amount, $nonceStr,  $backendResultUrl)

# In App
LaravelMyanmarPaymentsFacade::channel("kbz_pay.app")->getPaymentData($orderId, $amount, $nonceStr, $backendResultUrl);

# $nonceStr should be at least 32 characters long, uppercase & numbers according to KbzPay Documentation

# Validate Response Signature
LaravelMyanmarPaymentsFacade::channel("kbz_pay.qr")
    verifySignature($request)

#Cybersource
# Secure Acceptance
LaravelMyanmarPaymentsFacade::channel("cyber_source.secure_acceptance")
    ->getPaymentData($transactionId, $referenceNumber, $amount)
    
# Validate Request Signature
LaravelMyanmarPaymentsFacade::channel("cyber_source.secure_acceptance")
    ->verifySignature($request)


# AYA Payment Gateway
# Request available payment methods
LaravelMyanmarPaymentsFacade::channel("aya_pgw")
    ->getPaymentServices()
# Payment Screen Payload
LaravelMyanmarPaymentsFacade::channel("aya_pgw")
    ->paymentRequest($orderId, $amount, $channel, $method, $currencyCode)
# Enquiry Payment Status
LaravelMyanmarPaymentsFacade::channel("aya_pgw")
    ->paymentEnquiry($orderId)
# Validate Response Payload Signature
LaravelMyanmarPaymentsFacade::channel("aya_pgw")
    ->verifySignature($payload, $checkSum)


# Yoma MMQR
# Checkout an order & generate its QR in one call
LaravelMyanmarPaymentsFacade::channel("yoma_mmqr")
    ->getPaymentQr($orderNumber, $amount, $description)
# Or run the two steps separately
LaravelMyanmarPaymentsFacade::channel("yoma_mmqr")
    ->checkoutOrder($orderNumber, $amount, $description)
LaravelMyanmarPaymentsFacade::channel("yoma_mmqr")
    ->generateQr($orderNumber)
# Enquiry Payment Status with the refLabel returned by the QR
LaravelMyanmarPaymentsFacade::channel("yoma_mmqr")
    ->checkPaymentStatus($refLabel)
# Validate Callback Signature
LaravelMyanmarPaymentsFacade::channel("yoma_mmqr")
    ->verifySignature($request)

# $orderNumber is limited to 20 characters and $description to 50 characters. The QR comes
# back as a base64 PNG that is only payable for 120 seconds, see the Yoma MMQR section below.

For more api options, you can read the composition of the

Yoma MMQR

Yoma issues a MyanmarPay QR per order. It is the only gateway in this package that authenticates with an access token instead of signing each request on its own, and that token is deliberately not cached, so a fresh one is requested per call and nothing here holds state between requests. Cache it in your application if the extra round trip ever matters.

Environment

YOMA_MMQR_BASE_URL=https://devapi.yomabank.net
YOMA_MMQR_API_VERSION=v1rc
YOMA_MMQR_MERCHANT_ID=
YOMA_MMQR_CLIENT_ID=
YOMA_MMQR_CLIENT_SECRET=
YOMA_MMQR_WEBHOOK_SECRET=
YOMA_MMQR_WEBHOOK_HASHKEY=

The merchant id, the client id & secret and the webhook hash key all come from Yoma. The base url and api version above are the UAT pair, swap both for the production pair Yoma hands over. The webhook secret is the one you shared with Yoma for callbacks, see below, and can be left unset; the webhook hash key is a separate credential and is required to verify callbacks.

Paying an order

An order is checked out once, then a QR is generated against it. getPaymentQr() does both.

$qr = LaravelMyanmarPaymentsFacade::channel("yoma_mmqr")
    ->getPaymentQr($orderNumber, $amount, $description);

# ["refLabel" => "100000083331", "qrString" => "iVBORw0KGgo...", "expiresInSeconds" => 120]

$orderNumber is limited to 20 characters, $description to 50, and $amount must be numeric and greater than 0. Anything else throws before a request is sent.

Yoma rejects a second checkout of an order number it already knows, so renewing a lapsed QR means calling generateQr() on its own rather than getPaymentQr() again. Run the two steps separately when an order outlives its first QR.

LaravelMyanmarPaymentsFacade::channel("yoma_mmqr")
    ->checkoutOrder($orderNumber, $amount, $description)   # once per order
LaravelMyanmarPaymentsFacade::channel("yoma_mmqr")
    ->generateQr($orderNumber)                             # once per QR

Displaying the QR

qrString is an already rendered PNG in base64, a full MyanmarPay slip showing the merchant and the amount, so display it as is and skip the QR encoding library. This differs from kbz_pay.qr, which returns a QR payload string you render yourself.

<img src="data:image/png;base64,{{ $qr['qrString'] }}">

A QR stays payable for 120 seconds. Yoma reports no deadline of its own, so expiresInSeconds is added by this package and counts from the moment the response lands, which makes it stale as soon as it is stored. Turn it into a deadline on arrival instead.

const expiresAt = Date.now() + data.expiresInSeconds * 1000

Payment status

Every generateQr() mints a new refLabel and retires the one before it, so only the newest label of an order answers.

LaravelMyanmarPaymentsFacade::channel("yoma_mmqr")
    ->checkPaymentStatus($refLabel)

# ["refLabel" => "100000083331", "paymentStatus" => "PENDING"]   # SUCCESS once paid

Yoma answers business failures with a 200 status and an errorCode in the body, so this package inspects the body and throws rather than handing back a misleading payload. A label that can no longer be paid therefore raises an Exception instead of returning a status: QR EXPIRED ("Qr has been expired.") for a lapsed QR, MMQR FAILURE ("Mmqr responds empty response.") for one that has been retired by a newer QR. Poll with the newest label and treat a throw as a cue to generate the next QR.

Callback

Yoma posts orderNumber, status and hashValue to the callback url registered with them.

Route::post("/payments/yoma-mmqr/callback", function (Request $request) {
    abort_unless(
        LaravelMyanmarPaymentsFacade::channel("yoma_mmqr")->verifySignature($request),
        401
    );

    # $request->get("orderNumber"), $request->get("status")
})->withoutMiddleware(VerifyCsrfToken::class);

The hash is HMAC-SHA256 of the payload orderNumber=xxxx&status=xxxx, keyed with the order number prefixed to the webhook hash key, as Yoma's payment hub computes it. verifySignature() throws when no hash key is configured, since without one the signature would be forgeable.

Set YOMA_MMQR_WEBHOOK_SECRET to the secret you shared with Yoma and the X-Webhook-Secret header of the callback is asserted against it as well, before the hash is even computed. Leave it unset and the header is not checked, which is what merchants who never shared a secret with Yoma want, since none is sent to them.

Changelog

Please see CHANGELOG for more information what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security

If you discover any security related issues, please email naythukhant644@gmail.com instead of using the issue tracker.

Contributors

License

The MIT License (MIT). Please see License File for more information.