Skip to content

Wave Money ​

Wave Money's payment gateway sends the customer to a Wave payment page to pay with their WavePay wallet.

CallWhat it doesReturns
wave.Initiate(ctx, data)Redirect to Wave's payment page*RedirectPayment
wave.HandleCallback(request)Verify the callback*PaymentCallback

wave is the *wavemoney.Gateway that paymentsfacades.MyanmarPayments().WaveMoney() returns. Wave Money has no status API in this package: the callback is the only payment result.

Responses shows what Wave Money puts in each result.

How it works ​

Wave sends the customer back to your return URL and posts the result to your callback URL separately.

Wave Money: payment request, authenticate, resultCustomerYour appWave MoneyCheck outPayment request with hashwave.Initiate(ctx, data)transaction_idRedirect to authenticate/authenticate?transaction_id=…Pay with WavePayBack to the frontend URLReturnURL: not proof of paymentBackend result URL callbackPOST to CallbackURL, hashValueVerified callback is proofwave.HandleCallback(request)
Wave Money: payment request, authenticate, result

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/wavemoney"
	paymentsfacades "github.com/laranex/goravel-myanmar-payments/v4/facades"
)

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

	wave, err := paymentsfacades.MyanmarPayments().WaveMoney()
	if err != nil {
		return ctx.Response().String(http.StatusInternalServerError, "%s", err)
	}
	data := &wavemoney.PaymentData{
		OrderID:     fmt.Sprintf("ORDER_%d", order.ID),
		CallbackURL: "https://shop.test/payments/wave/callback",
		ReturnURL:   fmt.Sprintf("https://shop.test/orders/%d", order.ID),
		Description: fmt.Sprintf("Order #%d", order.ID),
		Items: []wavemoney.Item{
			{Name: "Product A", Amount: myanmarpayments.Kyat(6000)},
			{Name: "Product B", Amount: myanmarpayments.Kyat(4000)},
		},
	}

	payment, err := wave.Initiate(ctx, data)
	if err != nil {
		return ctx.Response().String(http.StatusBadGateway, "%s", err)
	}

	order.WaveReference = data.MerchantReferenceID
	saveOrder(order) // your own persistence

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

wavemoney.PaymentData ​

FieldTypeRequiredRules
OrderIDstringYesYour order ID. One order can have several payment attempts
CallbackURLstringYesAbsolute http or https URL that Wave posts the result to. Wave may require HTTPS with a CA-issued certificate in production
ReturnURLstringYesAbsolute http or https URL Wave sends the customer back to. Not proof of payment
DescriptionstringYesShown to the customer
Items[]wavemoney.ItemYesAt least one item
Amountmyanmarpayments.AmountNoWhole kyat, greater than 0 (Wave doesn't accept decimals). Unset charges the sum of the items. Wave only accepts MMK
MerchantReferenceIDstringNoUnique ID of this attempt. Empty means a random ID

wavemoney.Item takes a Name and an Amount (myanmarpayments.Amount) in whole kyat, greater than 0. The items are summed with exact integer arithmetic, never floats. Initiate() takes a pointer to the data, so it can fill in MerchantReferenceID.

Merchant Reference ID ​

Wave rejects a reused merchant_reference_id (409 Record already exists), so every attempt, including a retry of the same order, needs a new one. Leave it empty to get a fresh random ID, and store it: Wave marks orderId as optional in callbacks, while merchantReferenceId is always present. Read it from data.MerchantReferenceID.

Handling Callbacks ​

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/wave/callback", func(
	ctx http.Context,
) http.Response {
	request, err := payments.CallbackRequestFromContext(ctx)
	if err != nil {
		return ctx.Response().String(http.StatusBadRequest, "bad request")
	}
	wave, err := paymentsfacades.MyanmarPayments().WaveMoney()
	if err != nil {
		return ctx.Response().String(http.StatusInternalServerError, "%s", err)
	}
	callback, err := wave.HandleCallback(request)
	if err != nil { // *myanmarpayments.SignatureVerificationError
		return ctx.Response().String(http.StatusBadRequest, "invalid")
	}

	if callback.IsSuccessful() {
		// callback.OrderID is your OrderID
		// callback.Raw["merchantReferenceId"] is the attempt's reference
		// callback.GatewayReference is Wave's transactionId
	}

	return payments.Acknowledge(ctx, callback)
})

callback.OrderID falls back to merchantReferenceId when Wave's orderId is missing, null or empty.

Responses ​

What Wave Money 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 Wave sent.

Initiate() → *RedirectPayment ​

FieldWave Money value
OrderIDYour OrderID
URL{authenticate_url}/authenticate?transaction_id=… (URL-encoded), e.g. https://payments.wavemoney.io/authenticate?transaction_id=…
GatewayReferenceWave transaction_id. Always set
RawWave's /payment response: message (success), transaction_id

The attempt's MerchantReferenceID is not on the result: read it from data.MerchantReferenceID.

HandleCallback() → *PaymentCallback ​

FieldWave Money value
OrderIDWave orderId, falling back to merchantReferenceId when it is missing, null or empty
Statusstatus mapped, see Statuses
GatewayStatusWave status, trimmed, e.g. PAYMENT_CONFIRMED
GatewayReferenceWave transactionId
AmountWave amount, e.g. 10000
RawThe verified body: status, merchantId, orderId, merchantReferenceId, frontendResultUrl, backendResultUrl, initiatorMsisdn, amount, timeToLiveSeconds, paymentDescription, currency, additionalField1–5, transactionId, paymentRequestId, requestTime, hashValue
AcknowledgementHTTP 200, empty body, 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 ​

Only PAYMENT_CONFIRMED means the customer paid.

Wave statusPaymentStatus
PAYMENT_CONFIRMEDStatusSuccessful
INSUFFICIENT_BALANCEStatusPending
ACCOUNT_LOCKED, BILL_COLLECTION_FAILEDStatusFailed
PAYMENT_REQUEST_CANCELLEDStatusCanceled
TRANSACTION_TIMED_OUT, SCHEDULER_TRANSACTION_TIMED_OUTStatusExpired
anything elseStatusUnknown

SCHEDULER_TRANSACTION_TIMED_OUT arrives up to 15 minutes after the time-to-live ends.

Errors ​

CallReturnsWhen
Initiate()*InvalidPaymentDataErrorA value breaks the rules above. Nothing is sent
Initiate()*APIErrorWave answers with an HTTP error, a message other than success, or no transaction_id
HandleCallback()*SignatureVerificationErrorhashValue doesn't match, or a hashed field holds an object or array

HTTPStatus tells Wave's rejections apart: 400 invalid hash, 404 unknown merchant, 409 reused reference, 422 validation (GatewayCode is VALIDATION_ERROR). When Wave can't be reached, Initiate() returns an *APIError with HTTPStatus 0.

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