Skip to content

Callbacks & Status ​

Every gateway notifies your server of the payment result. HandleCallback() verifies the gateway's signature and returns a *PaymentCallback. Build its request with payments.CallbackRequestFromContext(ctx): signatures are checked against the exact body the gateway sent.

Production setup

For production, follow Handling Webhooks (recommended): verify, store the call, acknowledge immediately, then process it once in the background with retries. The example below handles everything inline to show the API.

Every callback goes through the same steps; KBZ Pay is shown here.

Handling a KBZ Pay callbackYour appKBZ PayPayment notificationPOST to CallbackURLVerify the signaturekbz.HandleCallback(request)Invalid: 400, never fulfill*SignatureVerificationErrorFind the orderby callback.OrderIDFulfill onceskip if paid, match the amountAcknowledge: plain successpayments.Acknowledge(ctx, callback)No acknowledgement? Retryafter 60 s, then 600 s
Handling a KBZ Pay callback
go
import (
	"github.com/goravel/framework/contracts/http"
	myanmarpayments "github.com/laranex/go-myanmar-payments/v4"
	payments "github.com/laranex/goravel-myanmar-payments/v4"
	paymentsfacades "github.com/laranex/goravel-myanmar-payments/v4/facades"

	"yourapp/app/facades"
	"yourapp/app/models"
)

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 {
		return ctx.Response().String(http.StatusBadRequest, "invalid")
	}

	var order models.Order
	err = facades.Orm().Query().
		Where("reference", callback.OrderID).
		FirstOrFail(&order)
	if err != nil {
		return ctx.Response().String(http.StatusNotFound, "unknown order")
	}

	// order.Amount is a string such as "10000"; compare by value, not floats
	amount, err := myanmarpayments.ParseAmount(order.Amount)
	paid := err == nil && amount.Equals(callback.Amount)

	if callback.IsSuccessful() && !order.IsPaid() && paid {
		order.MarkAsPaid(callback.GatewayReference)
	}

	return payments.Acknowledge(ctx, callback)
})

yourapp/app/facades is the facades package Goravel generates in your app; replace yourapp with your module name. Gateways post from their own servers, so keep callback routes free of CSRF and authentication middleware.

Callback Helpers ​

HelperWhat it does
kbz.HandleCallback(request)Verifies the callback. Takes the SDK's *myanmarpayments.CallbackRequest
payments.CallbackRequestFromContext(ctx)Builds that request from the current Goravel request: the raw body, the headers and the query string
manager.HandleCallback(gateway, request)The same, by gateway name, for one route that serves every gateway; see Handling Webhooks
manager.Gateway(name)The gateway for kbz-pay, wave-money, aya-pay, yoma-mmqr or cyber-source (payments.GatewayNames() lists them), as a payments.CallbackHandler. An unknown name returns an error wrapping payments.ErrUnknownGateway
payments.Acknowledge(ctx, callback)The response the gateway expects, written as the Goravel response. With a nil callback, an empty 200
aya.VerifyRedirect(request)Verifies AYA's browser return; see AYA Pay

manager is paymentsfacades.MyanmarPayments().

Signatures are computed over the exact bytes the gateway sent, so always build the request with CallbackRequestFromContext, never from ctx.Request().All():

BodyWhat you get
JSON (KBZ Pay, Wave Money, Yoma MMQR)The body byte for byte
application/x-www-form-urlencoded or multipart/form-data (AYA Pay, CyberSource)Goravel's gin driver parses form bodies before your handler runs, which consumes them. The body is rebuilt from the parsed form fields as urlencoded and Content-Type says so. These gateways sign field values, so the result verifies the same

A body is read as JSON only when it is a single JSON object; any other body is read as a urlencoded form, skipping a malformed pair.

Rules ​

  • Verify, then trust. A callback that fails verification returns a *myanmarpayments.SignatureVerificationError, and so does one whose signed or hashed field holds an object or array instead of a single value, since no gateway signs nested values. Never act on its payload; it carries the unverified data in Raw for logging only.
  • Check the amount. Compare callback.Amount (as the gateway sent it, a string) with your order before fulfilling. A gateway may format it differently from your order (10000 or 10000.00); amount.Equals(callback.Amount), with amount parsed from your order by myanmarpayments.ParseAmount(), compares decimal strings exactly.
  • Be idempotent. Gateways retry and may deliver the same callback more than once.
  • Acknowledge. payments.Acknowledge(ctx, callback) returns the response the gateway expects, e.g. KBZ Pay's plain success. Without it, gateways keep retrying.

PaymentStatus ​

Every gateway's own status values are mapped onto one type. The original value stays in callback.GatewayStatus.

ConstantMeaning
myanmarpayments.StatusSuccessfulThe customer paid. The only status that means money was collected.
myanmarpayments.StatusPendingStill in progress or waiting on the customer.
myanmarpayments.StatusFailedAttempted and failed or rejected.
myanmarpayments.StatusCanceledCanceled or closed before completing.
myanmarpayments.StatusExpiredThe payment window ran out.
myanmarpayments.StatusUnknownA status this package does not recognize yet. Inspect GatewayStatus.

status.IsFinal() is false for StatusPending and StatusUnknown. Unknown statuses never return an error.

Each gateway page lists its exact mapping.

Status Checks ​

When a callback is late, ask KBZ Pay, AYA or Yoma directly; Wave Money and CyberSource have no status API.

Checking the status when the callback is lateYour appKBZ PayCallback late or missingAsk for the order statuskbz.Status(ctx, orderID)PaymentStatusResulttrade_status, e.g. PAY_SUCCESSSuccessful? Fulfill oncesame checks as the callbackNot final? Check again laterresult.Status.IsFinal()
Checking the status when the callback is late

When a callback is late or missing, ask the gateway directly. Status checks return a *PaymentStatusResult with the same Status, GatewayStatus, GatewayReference and Amount fields.

GatewayCall
KBZ PayKbzPay() → Status(ctx, orderID)
AYA Payment GatewayAyaPay() → Status(ctx, orderID)
Yoma MMQRYomaMmqr() → Status(ctx, payment.Reference)
Wave MoneyNo status API: rely on the callback
CyberSourceNo status API: rely on the callback
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() {
	// ...
}

See PaymentCallback & Status for every field.

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