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 it returns. Each flow has its own result type with exactly the fields that flow needs.

ResultWhat you doReturned by
*RedirectPaymentRedirect the customer to payment.URLKbzPay() → PWA(), WaveMoney() → Initiate()
*FormPaymentRedirect to payments.AutoSubmitURL(form)AyaPay() → Initiate(), CyberSource() → Initiate()
*QrPaymentShow the QR to the customerKbzPay() → QR(), YomaMmqr() → Initiate(), RenewQR()
*AppPaymentReturn the signed payload to your mobile appKbzPay() → App()

The customer finishing on the gateway's side is never proof of payment. Fulfill orders from the verified callback or a status check.

Goravel's http.Context is a context.Context, so pass ctx straight to gateway calls that hit the network.

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 paymentkbz.PWA(ctx, data)Create the orderprecreate, trade_type PWAAPPprepay_idRedirect to the PWARedirect(http.StatusFound, payment.URL)Pay in the KBZ Pay appPayment notificationPOST to CallbackURLVerified callback is proofkbz.HandleCallback(request)
Redirect payment with the KBZ Pay PWA

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

go
import (
	"fmt"

	"github.com/goravel/framework/contracts/http"
	myanmarpayments "github.com/laranex/go-myanmar-payments/v4"
	"github.com/laranex/go-myanmar-payments/v4/kbzpay"
	paymentsfacades "github.com/laranex/goravel-myanmar-payments/v4/facades"
)

func (r *CheckoutController) KbzPay(ctx http.Context) http.Response {
	order := findOrder(ctx) // your own order lookup

	kbz, err := paymentsfacades.MyanmarPayments().KbzPay()
	if err != nil {
		return ctx.Response().String(http.StatusInternalServerError, "%s", err)
	}
	payment, err := kbz.PWA(ctx, kbzpay.PaymentData{
		OrderID:     fmt.Sprintf("ORDER_%d", order.ID),
		Amount:      myanmarpayments.Kyat(10000),
		CallbackURL: "https://shop.test/payments/kbz/callback",
	})
	if err != nil {
		return ctx.Response().String(http.StatusBadGateway, "%s", err)
	}

	return ctx.Response().Redirect(http.StatusFound, payment.URL)
}

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 and encrypt the formaya.Initiate(data), payments.AutoSubmitURL(form)Redirect to autoSubmitUrlencrypted, expires after ttl_minutesOpen the auto-submit routeGET myanmar-payments/formAuto-submitting form page410 Gone once the link expiresPost the signed formPOST /v1/payment/requestBack to your return URLnot proof of paymentBackend callbackpayload + checkSumVerified callback is proofaya.HandleCallback(request)
Form payment with AYA Pay

The gateway expects the customer's browser to POST a signed form. The package hosts a page that renders the form and submits it immediately, so a redirect is enough:

go
import (
	"github.com/goravel/framework/contracts/http"
	payments "github.com/laranex/goravel-myanmar-payments/v4"
	paymentsfacades "github.com/laranex/goravel-myanmar-payments/v4/facades"
)

aya, err := paymentsfacades.MyanmarPayments().AyaPay()
form, err := aya.Initiate(data)
link, err := payments.AutoSubmitURL(form)

return ctx.Response().Redirect(http.StatusFound, link)

The link is an encrypted, expiring link to the package's myanmar-payments.form route. See Configuration. The Go SDK has no auto-submit URL of its own, so the link is returned by AutoSubmitURL instead of being a field of the form.

To render the form yourself, for example with your own loading state, return form.HTML() or use Action, Fields and Enctype:

go
import "github.com/goravel/framework/contracts/http"

html := []byte(form.HTML())

return ctx.Response().Data(http.StatusOK, "text/html; charset=utf-8", html)
html
<form id="payment-form" method="POST" action="{{ .Action }}"
      enctype="{{ .Enctype }}">
    {{ range .Fields }}
        <input type="hidden" name="{{ .Name }}" value="{{ .Value }}">
    {{ end }}
</form>
<script>document.getElementById('payment-form').submit();</script>

Post the fields unchanged: they are signed. Fields is a slice in signing order; form.Values() returns them as a map and form.Field(name) reads one.

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 orderyoma.Initiate(ctx, data)Generate the first QRqr/generateQR image and refLabelpayable for 120 secondsShow the QRpayment.QRImageDataURI(mimeType)Expired? Renew the QRyoma.RenewQR(ctx, orderID)Scan with an MMQR walletPayment callbackorderNumber, status, hashValueVerified callback is proofyoma.HandleCallback(request)
QR payment with Yoma MMQR

Gateways return QR codes in two shapes:

FieldGatewayUse it as
QRStringKBZ PayA payload: encode it into a QR image with any QR library
QRImageYoma MMQRA base64 image: display it as is, e.g. with QRImageDataURI("")
go
import (
	"html/template"

	"github.com/goravel/framework/contracts/http"
	paymentsfacades "github.com/laranex/goravel-myanmar-payments/v4/facades"
)

yoma, err := paymentsfacades.MyanmarPayments().YomaMmqr()
payment, err := yoma.Initiate(ctx, data)

return ctx.Response().View().Make("payments/qr.tmpl", map[string]any{
	// template.URL keeps html/template from rejecting the data URI
	"qr":      template.URL(payment.QRImageDataURI("")),
	"expires": payment.ExpiresAt.Format("15:04:05"),
})
html
<img src="{{ .qr }}" alt="Scan to pay">
<p>Valid until {{ .expires }}</p>

ExpiresAt is set when the gateway limits how long the QR is payable (the zero time.Time otherwise), and Reference holds the ID used for status checks (Yoma refLabel).

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 paymentkbz.App(ctx, data)Create the orderprecreate, trade_type APPprepay_idorderInfo, sign, signTypeJson(http.StatusOK, payment)KBZPay.startPay()the customer pays in KBZ PayPayment screen closednot proof of paymentPayment notificationPOST to CallbackURLVerified callback is proofkbz.HandleCallback(request)
In-app payment with the KBZ Pay SDK

The KBZ Pay mobile SDK needs a signed order string. Return it to your app, which passes it to KBZPay.startPay():

go
import (
	"github.com/goravel/framework/contracts/http"
	paymentsfacades "github.com/laranex/goravel-myanmar-payments/v4/facades"
)

kbz, err := paymentsfacades.MyanmarPayments().KbzPay()
payment, err := kbz.App(ctx, data)

// orderId, orderInfo, sign, signType (Raw is not encoded)
return ctx.Response().Json(http.StatusOK, payment)

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

See Results for every field.

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