Skip to content

Payment Flows ​

Starting a payment always follows the same pattern: build the gateway's payment data, call the gateway, then act on the typed result. Each flow has its own result class with exactly the fields that flow needs, and every result implements PaymentResult, whose flow() returns a PaymentFlow (Redirect, Form, Qr or App), so a match or instanceof check tells them apart.

ResultWhat you doReturned by
RedirectPaymentRedirect the customer to $payment->url$kbz->pwa(), $wave->initiate()
FormPaymentEcho $payment->toHtml()$aya->initiate(), $cs->initiate()
QrPaymentShow the QR to the customer$kbz->qr(), $yoma->initiate(), $yoma->renewQr()
AppPaymentReturn the signed payload to your mobile app$kbz->app()

Every payment data class validates its values when you create it and throws InvalidPaymentDataException before any request is sent, so building the data object is how you check a request early, e.g. while handling a form. $data->validate() runs the same checks again. The customer finishing on the gateway's side is never proof of payment: fulfill orders from the verified callback or a status check.

The samples on this page and the gateway pages are plain PHP scripts; Framework Integration shows Symfony and PSR-15 handlers.

Redirect Payments ​

Here is the flow with the KBZ Pay PWA; Wave Money works the same way with its own payment page.

Redirect payment with the KBZ Pay PWACustomerYour appKBZ PayCheck outStart the payment$kbz->pwa($data)Create the orderprecreate, trade_type PWAAPPprepay_idRedirect to the PWA302 to $payment->urlPay in the KBZ Pay appPayment notificationPOST to callbackUrlVerified callback is proof$kbz->handleCallback($request)
Redirect payment with the KBZ Pay PWA

The gateway hosts its own payment page. Send the customer there.

php
use Laranex\PhpMyanmarPayments\KbzPay\KbzPayPaymentData;

$data = new KbzPayPaymentData(
    orderId: 'ORDER_1',
    amount: 10000,
    callbackUrl: 'https://shop.test/payments/kbz/callback',
);
$payment = $kbz->pwa($data);

header('Location: '.$payment->url);
exit;

$payment->gatewayReference holds the gateway's ID for the attempt (KBZ prepay_id, Wave transaction_id).

Form Payments ​

Here is the flow with AYA Pay; CyberSource works the same way with its hosted checkout.

Form payment with AYA PayCustomerYour appAYA PayCheck outSign the form, no API call$aya->initiate($data)Auto-submitting form pageecho $payment->toHtml()Post the signed formPOST /v1/payment/requestBack to your return URLnot proof of paymentBackend callbackpayload + checkSumVerified callback is proof$aya->handleCallback($request)
Form payment with AYA Pay

The gateway expects the customer's browser to POST a signed form. toHtml() returns a complete page that submits the form as soon as it loads, with every value escaped:

php
// no network call: it only signs
$payment = $aya->initiate($data);

header('Content-Type: text/html; charset=UTF-8');
echo $payment->toHtml();

To build the form yourself, use action, fields (an array of name => value, in order) and enctype with your template engine, and escape every value:

php
<form id="payment-form" method="POST"
      action="<?= htmlspecialchars($payment->action) ?>"
      enctype="<?= htmlspecialchars($payment->enctype) ?>">
    <?php foreach ($payment->fields as $name => $value): ?>
        <input type="hidden"
               name="<?= htmlspecialchars($name) ?>"
               value="<?= htmlspecialchars($value) ?>">
    <?php endforeach ?>
</form>

Post the fields unchanged: they are signed. $payment->fields['merchOrderId'] looks up one value. AYA expects multipart/form-data, which enctype carries.

autoSubmitUrl is null outside Laravel. If you host your own page that renders the form, attach its URL with $payment->withAutoSubmitUrl($url), which returns a new FormPayment.

QR Payments ​

Here is the flow with Yoma MMQR, whose QR expires after 120 seconds; a KBZ Pay QR follows the same steps without renewals.

QR payment with Yoma MMQRCustomerYour appYoma MMQRCheck outCheck out the order$yoma->initiate($data)Generate the first QRqr/generateQR image and refLabelpayable for 120 secondsShow the QRqrImage, a base64 PNGExpired? Renew the QR$yoma->renewQr($orderId)Scan with an MMQR walletPayment callbackorderNumber, status, hashValueVerified callback is proof$yoma->handleCallback($request)
QR payment with Yoma MMQR

Gateways return QR codes in two shapes:

PropertyGatewayUse it as
qrStringKBZ PayA payload: encode it into a QR image with any QR library, e.g. endroid/qr-code
qrImageYoma MMQRA base64 image: display it as is, e.g. with qrImageDataUri()
php
$payment = $yoma->initiate($data);
?>
<img src="<?= htmlspecialchars($payment->qrImageDataUri()) ?>"
     alt="Scan to pay">
<p>Payable until <?= $payment->expiresAt?->format('H:i:s') ?></p>

expiresAt is a DateTimeImmutable when the gateway limits how long the QR is payable (null otherwise), and reference holds the ID used for status checks (Yoma refLabel, KBZ prepay_id).

App Payments ​

Here is the flow with the KBZ Pay mobile SDK.

In-app payment with the KBZ Pay SDKCustomerYour appKBZ PayPay in your mobile appStart the payment$kbz->app($data)Create the orderprecreate, trade_type APPprepay_idorderInfo, sign, signTypejson_encode($payment->toArray())KBZPay.startPay()the customer pays in KBZ PayPayment screen closednot proof of paymentPayment notificationPOST to callbackUrlVerified callback is proof$kbz->handleCallback($request)
In-app payment with the KBZ Pay SDK

The KBZ Pay mobile SDK needs a signed order string. AppPayment::toArray() returns the values with the SDK's names, so return it to your app as JSON; the app passes the values to KBZPay.startPay():

php
$payment = $kbz->app($data);

header('Content-Type: application/json');
// {"orderId", "orderInfo", "sign", "signType"}
echo json_encode($payment->toArray());

The SDK's own result only means the payment screen closed; rely on the callback or $kbz->status().

Handling Any Result ​

php
use Laranex\PhpMyanmarPayments\Results\AppPayment;
use Laranex\PhpMyanmarPayments\Results\FormPayment;
use Laranex\PhpMyanmarPayments\Results\PaymentResult;
use Laranex\PhpMyanmarPayments\Results\QrPayment;
use Laranex\PhpMyanmarPayments\Results\RedirectPayment;

function respond(PaymentResult $payment): void
{
    match (true) {
        $payment instanceof RedirectPayment
            => header('Location: '.$payment->url),
        $payment instanceof FormPayment => print $payment->toHtml(),
        $payment instanceof QrPayment
            => print $payment->qrImageDataUri() ?? $payment->qrString,
        $payment instanceof AppPayment
            => print json_encode($payment->toArray()),
    };
}

See Results for every property.

Released under the MIT License, except where a package says otherwise.