Skip to content

AYA Pay ​

The AYA Payment Gateway is one hosted checkout for AYA Pay, other wallets (KBZ Pay, WavePay, UAB Pay, CB Pay…) and cards (VISA, Mastercard, JCB).

CallWhat it doesReturns
aya.Services(ctx)List the channels enabled for your account[]ayapay.Service
aya.Initiate(data)Signed form posted to AYA*FormPayment
aya.Status(ctx, orderID)Enquire an order*PaymentStatusResult
aya.HandleCallback(request)Verify the backend callback*PaymentCallback
aya.VerifyRedirect(request)Verify the customer's return*PaymentCallback

Responses shows what AYA Pay puts in each result.

How it works ​

AYA posts the result to your callback URL and also signs the query string it adds when sending the customer back.

AYA Pay: channels, signed form, callback, returnCustomerYour appAYA PayList enabled channelsaya.Services(ctx)Channels and methodse.g. aya_pay: QR, NOTIPick a channel and methodaya_pay + ayapay.MethodQRSigned form, auto-submitsaya.Initiate(data)Post the form and payPOST /v1/payment/requestBackend callbackpayload + checkSumVerified callback is proofaya.HandleCallback(request)Back on your return pagepayload + checkSum in the queryShow the right messageaya.VerifyRedirect(request)
AYA Pay: channels, signed form, callback, return

Channels and Methods ​

Channel is a lowercase key such as aya_pay, kbz_pay or visa; Method is how the customer pays through it. Which ones you have depends on your merchant account, so list them:

go
import "github.com/laranex/go-myanmar-payments/v4/ayapay"

aya, err := ayapay.New(ayapay.Config{
	AppKey:         "...",
	AppSecret:      "...",
	TimeoutSeconds: 30,
}, nil)
if err != nil {
	return err
}

services, err := aya.Services(ctx)
if err != nil {
	return err
}
for _, service := range services {
	// service.Name: "AYA Pay"
	// service.Key: "aya_pay", pass it as Channel
	// service.ImageURL: the channel's logo
	// service.Methods: []ayapay.Method{ayapay.MethodQR, ayapay.MethodNoti}
	if service.Supports(ayapay.MethodQR) {
		// offer the QR method
	}
}

Methods AYA lists that this package doesn't know yet are kept in service.UnknownMethods.

ayapay.MethodValueCustomer
ayapay.MethodWebWEBPays on a hosted web page (cards)
ayapay.MethodQRQRScans a QR with the wallet app
ayapay.MethodNotiNOTIApproves a push notification in the wallet app

Initiating a Payment ​

go
import (
	"fmt"
	"io"

	myanmarpayments "github.com/laranex/go-myanmar-payments/v4"
	"github.com/laranex/go-myanmar-payments/v4/ayapay"
)

data := ayapay.PaymentData{
	OrderID:     fmt.Sprintf("ORDER_%d", order.ID),
	Amount:      myanmarpayments.Kyat(10000),
	Channel:     "aya_pay",
	Method:      ayapay.MethodQR,
	ReturnURL:   "https://shop.test/payments/aya/return",
	Description: fmt.Sprintf("Order #%d", order.ID),
}

payment, err := aya.Initiate(data)
if err != nil {
	return err
}

// The page posts the signed form to AYA on load.
w.Header().Set("Content-Type", "text/html; charset=utf-8")
io.WriteString(w, payment.HTML())

ayapay.PaymentData ​

FieldTypeRequiredRules
OrderIDstringYesUnique, 6 to 40 characters (merchOrderId)
Amountmyanmarpayments.AmountYesWhole kyat, greater than 0, e.g. Kyat(10000). AYA documents no decimals and only accepts MMK (104)
ChannelstringYesA key from Services
Methodayapay.MethodYesMethodWeb, MethodQR or MethodNoti
ReturnURLstringNoAbsolute http or https URL. Empty uses the URL registered with AYA
DescriptionstringNoShown to the customer
UserRefs[]stringNoUp to 5 of your own values, echoed back in the callback

Form Encoding ​

AYA expects the form as multipart/form-data. payment.Enctype carries it; use it if you render the form yourself.

Handling Callbacks ​

AYA posts to the callback URL registered with them.

go
import (
	"net/http"

	myanmarpayments "github.com/laranex/go-myanmar-payments/v4"
)

// POST /payments/aya/callback
request, err := myanmarpayments.NewCallbackRequestFromHTTP(r)
if err != nil {
	http.Error(w, "invalid callback", http.StatusBadRequest)
	return
}
callback, err := aya.HandleCallback(request)
if err != nil {
	http.Error(w, "invalid callback", http.StatusBadRequest)
	return
}

if callback.IsSuccessful() {
	// callback.OrderID is your merchOrderId
	// callback.GatewayReference is AYA's tranId
}

callback.Acknowledgement.Write(w)

AYA signs only the fields present in its payload (wallet payments leave out the card fields); the package verifies them in the order the specification lists.

The Return Page ​

AYA signs the query string it adds when sending the customer back, so the return page can show the right message:

go
import (
	"io"
	"net/http"

	myanmarpayments "github.com/laranex/go-myanmar-payments/v4"
)

// GET /payments/aya/return
request, err := myanmarpayments.NewCallbackRequestFromHTTP(r)
if err != nil {
	http.Error(w, "invalid return", http.StatusBadRequest)
	return
}
result, err := aya.VerifyRedirect(request)
if err != nil {
	http.Error(w, "invalid return", http.StatusBadRequest)
	return
}

if result.IsSuccessful() {
	io.WriteString(w, "Thank you, your payment was received.")
	return
}
io.WriteString(w, "Payment "+string(result.Status)+".")

payload may be standard base64 with or without its = padding; partial padding, the URL-safe alphabet, line breaks and payloads that aren't UTF-8 JSON are rejected. A + in the base64 payload that reached you as a space (an unencoded query string) is read back as + before decoding; the checksum is still verified. Still fulfill orders from the backend callback.

Status Checks ​

go
import "fmt"

result, err := aya.Status(ctx, fmt.Sprintf("ORDER_%d", order.ID))
if err != nil {
	return err
}

if result.IsSuccessful() {
	// result.GatewayReference is AYA's tranId
}

Status takes your OrderID. An order AYA doesn't know returns *myanmarpayments.APIError (20 Transaction not found).

Responses ​

What AYA Pay puts in each field. See Results and PaymentCallback & Status for the full structs. On error the result is nil. A field the gateway didn't send is "". Raw holds plain Go values (JSON numbers become json.Numbers), while the typed fields such as Amount keep the exact text AYA sent.

Services() → []ayapay.Service ​

ayapay.Service is AYA-only, so it is listed in full here.

Field / MethodAYA Pay value
NameAYA name, e.g. AYA Pay. Falls back to Key
KeyAYA key, e.g. aya_pay, kbz_pay, visa. Pass it as Channel. Always set
ImageURLAYA image_url, the channel's logo. "" when AYA sends none
Methods[]ayapay.Method this package knows, e.g. []ayapay.Method{ayapay.MethodQR, ayapay.MethodNoti}
UnknownMethods[]string of methods AYA listed that this package doesn't know yet. Usually nil
Supports(method)Whether Methods contains method

Entries AYA sends without a key are skipped.

Initiate() → *myanmarpayments.FormPayment ​

Field / MethodAYA Pay value
Flow()FlowForm
OrderIDYour data.OrderID
Action{BaseURL}/v1/payment/request, e.g. https://pgw.ayainnovation.com/v1/payment/request
FieldsThe signed fields below, in signing order. Post them unchanged
Enctypemultipart/form-data
HTML()A full HTML page that posts Fields to Action on load
Form fieldValue
merchOrderIddata.OrderID
amountdata.Amount, e.g. 10000
appKeyConfig.AppKey
timestampUnix time in seconds
userRef1 … userRef5data.UserRefs, "" when unused
descriptiondata.Description, "" when unset
currencyCode104 (MMK)
channeldata.Channel, e.g. aya_pay
methoddata.Method, e.g. QR
overrideFrontendRedirectUrldata.ReturnURL, "" when unset
checkSumHMAC-SHA256 of the values above joined with :

Initiate makes no HTTP call, so it takes no context. FormPayment has no Raw: nothing is sent to AYA until the customer's browser posts the form.

Status() → *myanmarpayments.PaymentStatusResult ​

FieldAYA Pay value
OrderIDAYA merchOrderId, falling back to the orderID you passed. Always set
StatusstatusCode mapped, see Statuses
GatewayStatusAYA statusCode, trimmed, e.g. 00
GatewayReferenceAYA tranId
AmountAYA amount, e.g. 10000
RawThe verified, decoded enquiry payload: merchOrderId, tranId, amount, currencyCode, statusCode, paymentCardNumber, paymentMobileNumber, cardTypeName, cardExpiryDate, nameOnCard, approvalCode, tranRef, userRef1–5, description, dateTime

AYA leaves out the fields that don't apply (wallet payments have no card fields), so Raw only has the keys AYA sent. Some payloads spell currencyCode as currenyCode.

HandleCallback() → *myanmarpayments.PaymentCallback ​

FieldAYA Pay value
OrderIDAYA merchOrderId (your OrderID)
StatusstatusCode mapped, see Statuses
GatewayStatusAYA statusCode, trimmed, e.g. 00
GatewayReferenceAYA tranId
AmountAYA amount, e.g. 10000
RawThe verified, decoded payload, with the same keys as Status()
AcknowledgementHTTP 200, empty body, Content-Type: text/plain

VerifyRedirect() → *myanmarpayments.PaymentCallback ​

The same values as HandleCallback(), read from the signed payload and checkSum AYA adds to your return URL (query string first, then the body). There is nothing to acknowledge: render your own page.

Statuses ​

AYA statusCodePaymentStatus
00StatusSuccessful
01StatusPending
02 (fail), 03 (reject)StatusFailed
04StatusExpired
anything elseStatusUnknown

Errors ​

CallReturnsWhen
Initiate()*InvalidPaymentDataErrordata.Validate() fails. Nothing is signed
Services()*APIErrorAYA answers with an HTTP error or a status other than 00
Status()*APIErrorAYA answers with an HTTP error or a status other than 00, e.g. 20 Transaction not found
Status()*SignatureVerificationErrorThe enquiry payload's checkSum doesn't match
HandleCallback(), VerifyRedirect()*SignatureVerificationErrorpayload is missing or not base64 JSON, a signed field holds an object or array, or checkSum doesn't match

The error types live in the root myanmarpayments package. *APIError carries AYA's status (e.g. 20 Transaction not found, 09 Duplicate order ID) in GatewayCode and its message in GatewayMessage. When AYA can't be reached or ctx is canceled, the calls return *APIError, which unwraps to the cause (errors.Is(err, context.DeadlineExceeded)).

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