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
waveMoney().initiate(data)Redirect to Wave's payment pageRedirectPayment
waveMoney().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 hashwaveMoney().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 proofwaveMoney().handleCallback(request)
Wave Money: payment request, authenticate, result

Initiating a Payment ​

ts
import type { WaveMoneyPaymentData } from '@laranex/myanmar-payments';
import { MyanmarPaymentsService } from '@laranex/nestjs-myanmar-payments';
import { Controller, Param, Post, Redirect } from '@nestjs/common';

import { OrdersService } from '../orders/orders.service';

@Controller('checkout')
export class WaveMoneyCheckoutController {
  constructor(
    private readonly payments: MyanmarPaymentsService,
    private readonly orders: OrdersService,
  ) {}

  @Post(':id/wave-money')
  @Redirect()
  async initiate(@Param('id') id: string): Promise<{ url: string }> {
    const order = await this.orders.findOrFail(id);

    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 this.payments.waveMoney().initiate(data);

    await this.orders.update(order, {
      waveReference: data.merchantReferenceId,
    });

    return { url: payment.url };
  }
}

WaveMoneyPaymentData ​

ParameterTypeRequiredRules
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
itemsreadonly WaveMoneyItem[]YesAt least one item
amountAmountInputNoWhole 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 takes a name and an amount (AmountInput) in whole kyat, greater than 0. The items are summed with exact integer arithmetic, never floats.

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() fills it in, so read it from data.merchantReferenceId afterwards.

Handling Callbacks ​

ts
import { PaymentCallback } from '@laranex/myanmar-payments';
import {
  AcknowledgeCallback,
  VerifiedCallback,
} from '@laranex/nestjs-myanmar-payments';
import { Controller, Post } from '@nestjs/common';

@Controller('payments')
export class WaveMoneyCallbackController {
  @Post('wave/callback')
  @AcknowledgeCallback()
  handle(
    @VerifiedCallback('wave-money') callback: PaymentCallback,
  ): PaymentCallback {
    if (callback.isSuccessful()) {
      // callback.orderId is your orderId
      // callback.raw.merchantReferenceId is the attempt's reference
      // callback.gatewayReference is Wave's transactionId
    }

    return callback;
  }
}

A bad signature answers 400 before the handler runs. callback.orderId falls back to merchantReferenceId when Wave's orderId is missing, null or empty.

Responses ​

What Wave Money puts in each property. See Results and PaymentCallback & Status for the full classes. raw holds plain JavaScript values, with JSON numbers kept as their exact text in a string (1000.50 stays '1000.50').

initiate() → RedirectPayment ​

PropertyWave Money value
orderIdYour 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 ​

Property / MethodWave 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

@AcknowledgeCallback() (or acknowledge(res, callback)) sends acknowledgement. The gateway's handleCallback() takes a CallbackRequest; this.payments.handleCallback('wave-money', request) also accepts a Nest request.

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()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() throws ApiError with httpStatus 0.

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