Skip to content

KBZ Pay ​

KBZ Pay is KBZ Bank's mobile wallet: customers pay in the KBZ Pay PWA, by scanning a QR code, or from your mobile app.

CallWhat it doesReturns
kbz.PWA(ctx, data)Redirect to the KBZ Pay PWA*RedirectPayment
kbz.QR(ctx, data)Customer scans a QR*QrPayment
kbz.App(ctx, data)Your mobile app opens the KBZ Pay SDK*AppPayment
kbz.Status(ctx, orderID)Query an order*PaymentStatusResult
kbz.HandleCallback(request)Verify the notification*PaymentCallback

kbz is the *kbzpay.Gateway that paymentsfacades.MyanmarPayments().KbzPay() returns. Responses shows what KBZ Pay puts in each result.

How it works ​

Every KBZ Pay flow starts with the same precreate call and ends with KBZ's signed notification.

KBZ Pay: precreate, pay, notifyCustomerYour appKBZ PayPick the flowkbz.PWA() / QR() / App()Precreate the orderPWAAPP / PAY_BY_QRCODE / APPprepay_idplus qrCode for QRPWA URL, QR or signed orderURL / QRString / OrderInfo + SignPay in the KBZ Pay appNotify callbackUrlsigned JSON under RequestVerified callback is proofkbz.HandleCallback(request)Plain-text successwithin about 10 s, or KBZ retriesNo notify? Query the orderkbz.Status(ctx, orderID)
KBZ Pay: precreate, pay, notify

Initiating a Payment ​

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)
	}
	data := kbzpay.PaymentData{
		OrderID:     fmt.Sprintf("ORDER_%d", order.ID),
		Amount:      myanmarpayments.Kyat(10000),
		CallbackURL: "https://shop.test/payments/kbz/callback",
	}

	// PWA: send the customer to the KBZ Pay PWA
	payment, err := kbz.PWA(ctx, data)
	if err != nil {
		return ctx.Response().String(http.StatusBadGateway, "%s", err)
	}

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

	// QR: encode qr.QRString into a QR image
	// qr, err := kbz.QR(ctx, data)

	// In-app: hand the signed values to your mobile app
	// app, err := kbz.App(ctx, data)
	// return ctx.Response().Json(http.StatusOK, app)
}

kbzpay.PaymentData ​

FieldTypeRequiredRules
OrderIDstringYesUnique per order. Letters, digits and _ only, at most 40 characters
Amountmyanmarpayments.AmountYesKyat, greater than 0, at most 2 decimal places, e.g. Kyat(10000) or MustParseAmount("10000.50"). KBZ only accepts MMK
CallbackURLstringYesPublic URL KBZ posts the result to. At most 512 characters, no query string
TitlestringNoProduct name shown to the customer
TimeoutMinutes*intNo1 to 120, e.g. minutes := 30 then TimeoutMinutes: &minutes. nil leaves it to KBZ (120); 0 is rejected
CallbackInfostringNoFree text echoed back in the callback, at most 512 characters once URL-encoded

PWA Notes ​

  • The PWA only opens on a phone with the KBZ Pay app installed.
  • KBZ checks the redirect's Referer against the URL registered with them (error AOP08512). Redirect from that domain and don't strip the referrer.
  • After payment, KBZ sends the customer to the return URL registered with them during onboarding; it cannot be set per payment.

Handling Callbacks ​

KBZ Pay posts JSON nested under a Request key; pass the whole request.

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"

	"yourapp/app/facades"
)

facades.Route().Post("/payments/kbz/callback", func(
	ctx http.Context,
) http.Response {
	request, err := payments.CallbackRequestFromContext(ctx)
	if err != nil {
		return ctx.Response().String(http.StatusBadRequest, "bad request")
	}
	kbz, err := paymentsfacades.MyanmarPayments().KbzPay()
	if err != nil {
		return ctx.Response().String(http.StatusInternalServerError, "%s", err)
	}
	callback, err := kbz.HandleCallback(request)
	if err != nil { // *myanmarpayments.SignatureVerificationError
		return ctx.Response().String(http.StatusBadRequest, "invalid")
	}

	if callback.IsSuccessful() {
		// callback.OrderID is your merch_order_id
		// callback.GatewayReference is KBZ's mm_order_id
	}

	return payments.Acknowledge(ctx, callback) // plain-text "success"
})

KBZ requires an HTTP 200 with the plain-text body success, answered within about 10 seconds. Otherwise it retries after 60 and 600 seconds; when no callback arrives, query the order.

Status Checks ​

go
import (
	"fmt"

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

kbz, err := paymentsfacades.MyanmarPayments().KbzPay()
result, err := kbz.Status(ctx, fmt.Sprintf("ORDER_%d", order.ID))

if err == nil && result.IsSuccessful() {
	// result.GatewayReference is KBZ's mm_order_id
}

Status() takes your OrderID. An order KBZ doesn't know returns an *APIError.

Responses ​

What KBZ Pay puts in each field. See Results and PaymentCallback & Status for the full types. Raw holds plain Go values (JSON numbers become json.Numbers), while the typed fields such as Amount keep the exact text KBZ sent.

PWA() → *RedirectPayment ​

FieldKBZ Pay value
OrderIDYour OrderID
URL{pwa_url}?appid=…&merch_code=…&nonce_str=…&prepay_id=…&timestamp=…&sign=…
GatewayReferenceKBZ prepay_id. Always set
RawThe precreate response: result, code, msg, merch_order_id, prepay_id, nonce_str, sign_type, sign

QR() → *QrPayment ​

Field / MethodKBZ Pay value
OrderIDYour OrderID
QRStringKBZ qrCode, a payload to encode into a QR image. Always set
QRImageAlways empty
ExpiresAtNow + TimeoutMinutes. The zero time.Time when TimeoutMinutes is nil (KBZ then allows 120 minutes)
ReferenceKBZ prepay_id. Always set
RawThe precreate response, as for PWA() plus qrCode
QRImageDataURI()Always empty, as QRImage is

App() → *AppPayment ​

FieldKBZ Pay value
OrderIDYour OrderID
OrderInfoappid=…&merch_code=…&nonce_str=…&prepay_id=…&timestamp=…
SignSHA-256 signature of OrderInfo, uppercase hex. See Signing
SignTypeSHA256
RawThe precreate response, as for PWA()

Encoded as JSON, an AppPayment has orderId, orderInfo, sign and signType, without Raw.

Status() → *PaymentStatusResult ​

FieldKBZ Pay value
OrderIDKBZ merch_order_id, falling back to the orderID you passed. Always set
Statustrade_status mapped, see Statuses
GatewayStatusKBZ trade_status, trimmed, e.g. PAY_SUCCESS
GatewayReferenceKBZ mm_order_id. Empty until KBZ has created the payment
AmountKBZ total_amount, e.g. 10000
RawThe queryorder response: result, code, msg, merch_order_id, mm_order_id, total_amount, trans_currency, trade_status, trans_end_time, nonce_str, sign_type, sign

HandleCallback() → *PaymentCallback ​

FieldKBZ Pay value
OrderIDKBZ merch_order_id (your OrderID)
Statustrade_status mapped, see Statuses
GatewayStatusKBZ trade_status, trimmed, e.g. PAY_SUCCESS
GatewayReferenceKBZ mm_order_id
AmountKBZ total_amount, e.g. 10000
RawThe verified Request: appid, notify_time, merch_code, merch_order_id, mm_order_id, total_amount, trans_currency, trade_status, trans_end_time, callback_info, nonce_str, sign_type, sign
AcknowledgementHTTP 200, body success, Content-Type: text/plain

payments.Acknowledge(ctx, callback) writes Acknowledgement as the Goravel response. HandleCallback() takes the *myanmarpayments.CallbackRequest that payments.CallbackRequestFromContext(ctx) builds.

Statuses ​

KBZ trade_statusPaymentStatus
PAY_SUCCESSStatusSuccessful
WAIT_PAY, PAYINGStatusPending
PAY_FAILEDStatusFailed
ORDER_CLOSEDStatusCanceled
ORDER_EXPIREDStatusExpired
anything elseStatusUnknown

Signing ​

KBZ signs requests, the in-app orderInfo and notifications the same way: every non-empty field except sign and sign_type, sorted by key, joined as raw key=value pairs, with &key=<app key> appended, hashed with SHA-256 and uppercased. The package signs every request and verifies every notification for you. The SDK exposes the signer as kbz.Signer() (a kbzpay.Signer; kbzpay.NewSigner(appKey) builds one) for custom calls and test fixtures.

Errors ​

CallReturnsWhen
PWA(), QR(), App()*InvalidPaymentDataErrorA value breaks the rules above. Nothing is sent
PWA(), QR(), App()*APIErrorKBZ answers with an HTTP error, result other than SUCCESS or code other than 0, or without a prepay_id
QR()*APIErrorKBZ returns no qrCode
Status()*APIErrorKBZ answers with an HTTP error, result other than SUCCESS or code other than 0, e.g. for an unknown order
HandleCallback()*SignatureVerificationErrorsign doesn't match, or a field holds an object or array

*APIError carries KBZ's code (e.g. ORDER_ID_USED, AOP08508) in GatewayCode and its msg in GatewayMessage. When KBZ can't be reached, the calls return an *APIError with HTTPStatus 0.

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