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
await wave.initiate(data)Redirect to Wave's payment pageRedirectPayment
wave.handleCallback(request)Verify the callbackPaymentCallback

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 hashawait wave.initiate(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 ​

ts
import {
  WaveMoney,
  type WaveMoneyPaymentData,
} from '@laranex/myanmar-payments/wave-money';

const wave = new WaveMoney({
  merchantId: '...',
  secretKey: '...',
  merchantName: 'My Shop',
  timeToLiveSeconds: 300,
  timeoutSeconds: 30,
});

const data: WaveMoneyPaymentData = {
  orderId: `ORDER_${order.id}`,
  callbackUrl: 'https://shop.test/payments/wave/callback',
  returnUrl: `https://shop.test/orders/${order.id}`,
  description: `Order #${order.id}`,
  items: [
    { name: 'Product A', amount: 6000 },
    { name: 'Product B', amount: 4000 },
  ],
};

const payment = await wave.initiate(data);

// Store data.merchantReferenceId with the order: initiate() filled it in.

res.writeHead(302, { Location: payment.url }).end();

WaveMoneyPaymentData ​

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
itemsWaveMoneyItem[]YesAt least one item
amountAmount | number | bigintNoWhole 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. Unset or empty means a random ID

WaveMoneyItem has a name and an amount in whole kyat, greater than 0. The items are summed with exact bigint arithmetic, never floats; WaveMoney.resolvedAmount(data) returns the total that will be charged.

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. initiate() writes the generated ID to data.merchantReferenceId once data passes validation, so keep a reference to the object you pass.

Handling Callbacks ​

ts
import { CallbackRequest } from '@laranex/myanmar-payments';

// POST /payments/wave/callback
try {
  const request = await CallbackRequest.fromNodeRequest(req);
  const callback = wave.handleCallback(request);

  if (callback.isSuccessful()) {
    // callback.orderId is your orderId
    // callback.raw.merchantReferenceId is the attempt's reference
    // callback.gatewayReference is Wave's transactionId
  }

  callback.acknowledgement.send(res);
} catch {
  res.writeHead(400).end('invalid 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 classes. A field the gateway didn't send is undefined. raw holds plain JavaScript values, with JSON numbers kept as their exact text in a string (1000.50 stays "1000.50").

initiate() → RedirectPayment ​

FieldWave Money value
flow'redirect'
orderIdYour data.orderId
url{authenticateUrl}/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

Statuses ​

Only PAYMENT_CONFIRMED means the customer paid.

Wave statusPaymentStatus
PAYMENT_CONFIRMEDsuccessful
INSUFFICIENT_BALANCEpending
ACCOUNT_LOCKED, BILL_COLLECTION_FAILEDfailed
PAYMENT_REQUEST_CANCELLEDcanceled
TRANSACTION_TIMED_OUT, SCHEDULER_TRANSACTION_TIMED_OUTexpired
anything elseunknown

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

Errors ​

CallThrowsWhen
initiate()InvalidPaymentDataErrorWaveMoney.validate(data) fails. Nothing is sent and data is left untouched. Item errors use items.0.amount keys
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

The async calls reject with these errors. 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, the request times out or the signal aborts, initiate() throws ApiError with the original error as cause.

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