Yoma MMQR
Yoma MMQR is Yoma Bank's MMQR gateway: it issues ready-made QR images that customers scan with any MMQR wallet.
| Call | What it does | Returns |
|---|---|---|
yomaMmqr()->initiate($data) | Check out the order and generate its first QR | QrPayment |
yomaMmqr()->renewQr($orderId) | Generate a new QR for a checked-out order | QrPayment |
yomaMmqr()->status($reference) | Check a QR's payment status | PaymentStatusResult |
yomaMmqr()->handleCallback($request) | Verify the callback | PaymentCallback |
Responses shows what Yoma MMQR puts in each result.
How it works
Yoma checks each order out once, then issues QR codes that each stay payable for 120 seconds.
Initiating a Payment
use Laranex\LaravelMyanmarPayments\Facades\MyanmarPayments;
use Laranex\PhpMyanmarPayments\YomaMmqr\YomaMmqrPaymentData;
$data = new YomaMmqrPaymentData(
orderId: 'ORDER_'.$order->id,
amount: 10000,
description: 'Order #'.$order->id,
);
$payment = MyanmarPayments::yomaMmqr()->initiate($data);
$order->update(['qr_reference' => $payment->reference]);
return view('payments.qr', ['payment' => $payment]);<img src="{{ $payment->qrImageDataUri() }}" alt="Scan with any MMQR wallet">YomaMmqrPaymentData
| Parameter | Type | Required | Rules |
|---|---|---|---|
orderId | string | Yes | Unique order number, at most 20 characters |
amount | Amount|int | Yes | Whole kyat, greater than 0, e.g. 10000 or Amount::kyat(10000). Yoma documents no decimals or currency |
description | string | Yes | At most 50 characters |
QR Image
qrImage is an already rendered base64 PNG of the payment slip: display it as is, no QR library needed.
Handling Callbacks
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Laranex\LaravelMyanmarPayments\Facades\MyanmarPayments;
use Laranex\PhpMyanmarPayments\Exceptions\SignatureVerificationException;
Route::post('/payments/yoma/callback', function (Request $request) {
try {
$callback = MyanmarPayments::yomaMmqr()->handleCallback($request);
} catch (SignatureVerificationException) {
abort(400);
}
if ($callback->isSuccessful()) {
// $callback->orderId is your orderNumber
}
return MyanmarPayments::acknowledge($callback);
})->name('payments.yoma.callback');The callback URL is registered with Yoma, not sent per order. When YOMA_MMQR_WEBHOOK_SECRET is set, callbacks must carry it in the X-Webhook-Secret header. The hash is checked with HMAC-SHA256 keyed with your order number plus YOMA_MMQR_WEBHOOK_HASHKEY.
QR Lifetime and Renewal
A QR is payable for 120 seconds (YomaMmqr::QR_LIFETIME_SECONDS); $payment->expiresAt tells you when. Yoma accepts each order number once, so never call initiate() again for the same order. Renew the QR instead:
use Laranex\LaravelMyanmarPayments\Facades\MyanmarPayments;
$payment = MyanmarPayments::yomaMmqr()->renewQr('ORDER_'.$order->id);
$order->update(['qr_reference' => $payment->reference]);Each renewal retires the previous reference; only the newest one answers status checks.
Status Checks
use Laranex\LaravelMyanmarPayments\Facades\MyanmarPayments;
$result = MyanmarPayments::yomaMmqr()->status($order->qr_reference);
if ($result->isSuccessful()) {
// the QR was paid
}status() takes the QR's reference, not your orderId. An expired QR returns PaymentStatus::Expired instead of throwing.
Responses
What Yoma MMQR puts in each property. See Results and PaymentCallback & Status for the full classes. raw holds plain PHP values (JSON numbers stay strings with their exact text).
initiate() → QrPayment
| Property / Method | Yoma MMQR value |
|---|---|
orderId | Your orderId (Yoma orderNumber) |
qrString | Always null |
qrImage | Yoma qrString, a base64 PNG of the payment slip. Always set |
expiresAt | Now + 120 seconds (YomaMmqr::QR_LIFETIME_SECONDS). Always set |
reference | Yoma refLabel, e.g. 100000083331. Pass it to status(). Always set |
raw | The qr/generate response: refLabel, qrString, errorCode (null), errorDescription |
qrImageDataUri() | data:image/png;base64,… |
initiate() checks the order out (payment/checkout), then generates its first QR; the result comes from the generate call.
renewQr() → QrPayment
The same values as initiate() for the orderId you passed, with a new qrImage, reference and expiresAt. The previous reference stops answering status checks.
status() → PaymentStatusResult
| Property | Yoma MMQR value |
|---|---|
orderId | Always null: Yoma only returns the reference |
status | paymentStatus mapped, see Statuses. Expired for a QR EXPIRED error |
gatewayStatus | Yoma paymentStatus, trimmed, e.g. SUCCESS. QR EXPIRED for an expired QR |
gatewayReference | Yoma refLabel, falling back to the reference you passed. Always set |
amount | Always null: Yoma's status response has no amount |
raw | The payment/check-status response: refLabel, paymentStatus, errorCode, errorDescription |
handleCallback() → PaymentCallback
| Property / Method | Yoma MMQR value |
|---|---|
orderId | Yoma orderNumber (your orderId) |
status | status mapped case-insensitively, see Statuses |
gatewayStatus | Yoma status, trimmed, as sent, e.g. success |
gatewayReference | Always null: Yoma's callback has no reference |
amount | Always null: Yoma's callback has no amount |
raw | The verified body: orderNumber, status, hashValue |
acknowledgement | HTTP 200, empty body, Content-Type: text/plain |
MyanmarPayments::acknowledge($callback) turns acknowledgement into a CallbackResponse (Responsable). handleCallback() accepts an Illuminate\Http\Request or a CallbackRequest.
Statuses
| Yoma value | PaymentStatus |
|---|---|
success (callback), SUCCESS (status) | Successful |
PENDING | Pending |
fail (callback), FAILED (status) | Failed |
QR EXPIRED error | Expired |
| anything else | Unknown |
Access Tokens
Yoma authenticates with an OAuth token that lasts hours. The package caches it in your cache store (see Configuration) and fetches a new one, retrying once, when Yoma answers 401. MyanmarPayments::yomaMmqr()->forgetToken() drops the cached token, e.g. after rotating the client secret.
The token is cached under myanmar-payments.yoma-mmqr.token.<sha256(baseUrl|clientId)>, the same key every Laranex SDK uses, so services in different languages can share one cache (the cache store's own prefix still applies), for Yoma's expires_in minus 60 seconds (at least 60 seconds). expires_in is read from its leading digits, so 28800.0 is 28800 seconds; a missing or non-positive value means 3600.
Errors
| Call | Throws | When |
|---|---|---|
new YomaMmqrPaymentData(...) | InvalidPaymentDataException | A value breaks the rules above. Nothing is sent |
initiate() | ApiException | The token request fails, Yoma answers with an HTTP error or an errorCode (e.g. PAYMENT ALREADY EXISTS), checkOutStatus isn't true, or there is no qrString or refLabel |
renewQr() | ApiException | As initiate(), without the checkout |
status() | ApiException | The token request fails, or Yoma answers with an HTTP error or any errorCode other than QR EXPIRED |
handleCallback() | SignatureVerificationException | X-Webhook-Secret is missing or wrong (when a webhook secret is set), orderNumber is missing, orderNumber or status holds an object or array, or hashValue doesn't match |
Yoma reports business errors with HTTP 200 and an errorCode; ApiException carries it in gatewayCode and Yoma's errorDescription in gatewayMessage. When Yoma can't be reached, the calls throw ApiException with httpStatus 0.