Skip to content

Configuration ​

Environment Variables ​

Add the keys of the gateways you use. Every setting of those gateways is required except the URL overrides and Yoma's webhook secret: there are no defaults. A gateway is configured the first time you call it, and a missing or blank setting throws a ConfigurationError naming it, e.g. The kbz_pay configuration is missing [app_key]. The time settings must be whole numbers greater than 0; any other value throws the same error with the message The kbz_pay configuration [timeout_in_seconds] must be a whole number greater than 0.

The variable names are the ones the Node SDK reads and the same as the Laravel and Goravel packages, so one .env works for all of them. The module reads them from process.env unless you pass env (a record or ConfigService). The values after = are examples:

env
# Every gateway that calls an API (all but CyberSource)
MYANMAR_PAYMENTS_HTTP_TIMEOUT=30
# AYA Pay and CyberSource, while the form route is enabled
MYANMAR_PAYMENTS_FORM_TTL_MINUTES=30
# optional, encrypts form links, falls back to APP_KEY
MYANMAR_PAYMENTS_FORM_KEY=
# optional, scheme and host of form links
APP_URL=

# KBZ Pay
KBZ_PAY_APP_ID=
KBZ_PAY_APP_KEY=
KBZ_PAY_MERCHANT_CODE=
KBZ_PAY_BASE_URL=                     # optional override
KBZ_PAY_PWA_BASE_REDIRECT_URL=        # optional override

# Wave Money
WAVE_MONEY_MERCHANT_ID=
WAVE_MONEY_SECRET_KEY=
WAVE_MONEY_MERCHANT_NAME=
WAVE_MONEY_TIME_TO_LIVE_IN_SECONDS=300
WAVE_MONEY_BASE_URL=                  # optional override
WAVE_MONEY_AUTHENTICATE_URL=          # optional override

# AYA Payment Gateway (AYA_PGW_* names are read too)
AYA_PAY_APP_KEY=
AYA_PAY_APP_SECRET=
AYA_PAY_BASE_URL=                     # optional override

# Yoma MMQR
YOMA_MMQR_MERCHANT_ID=
YOMA_MMQR_CLIENT_ID=
YOMA_MMQR_CLIENT_SECRET=
YOMA_MMQR_WEBHOOK_HASHKEY=
YOMA_MMQR_API_VERSION=v1rc
YOMA_MMQR_WEBHOOK_SECRET=             # optional
YOMA_MMQR_BASE_URL=                   # optional override

# CyberSource Secure Acceptance
CYBER_SOURCE_PROFILE_ID=
CYBER_SOURCE_ACCESS_KEY=
CYBER_SOURCE_SECRET_KEY=
CYBER_SOURCE_BASE_URL=                # optional override

Settings ​

VariableModule optionRequiredDescription
MYANMAR_PAYMENTS_HTTP_TIMEOUTtimeoutSeconds of kbzPay, waveMoney, ayaPay and yomaMmqrYesSeconds before a gateway call gives up. Read by every gateway except CyberSource; a missing one is reported as timeout_in_seconds
MYANMAR_PAYMENTS_FORM_TTL_MINUTESformLink.ttlMinutesYesMinutes an auto-submit form link stays valid. Needed by AYA Pay and CyberSource while the form route is enabled
MYANMAR_PAYMENTS_FORM_KEYformLink.secretNoEncrypts form links; falls back to APP_KEY
APP_URLformLink.baseUrlNoThe scheme and host form links start with
KBZ_PAY_APP_IDkbzPay.appIdYesappid issued by KBZ
KBZ_PAY_APP_KEYkbzPay.appKeyYesSecret key used to sign requests
KBZ_PAY_MERCHANT_CODEkbzPay.merchantCodeYesmerch_code issued by KBZ
KBZ_PAY_BASE_URLkbzPay.apiUrlNoOverride the API base URL
KBZ_PAY_PWA_BASE_REDIRECT_URLkbzPay.pwaUrlNoOverride the PWA checkout URL
WAVE_MONEY_MERCHANT_IDwaveMoney.merchantIdYesMerchant ID issued by Wave
WAVE_MONEY_SECRET_KEYwaveMoney.secretKeyYesHash secret key issued by Wave
WAVE_MONEY_MERCHANT_NAMEwaveMoney.merchantNameYesShown on Wave's payment page
WAVE_MONEY_TIME_TO_LIVE_IN_SECONDSwaveMoney.timeToLiveSecondsYesSeconds the customer has to pay
WAVE_MONEY_BASE_URLwaveMoney.baseUrlNoOverride the API base URL
WAVE_MONEY_AUTHENTICATE_URLwaveMoney.authenticateUrlNoOverride the host the customer is redirected to
AYA_PAY_APP_KEYayaPay.appKeyYesPublic application key
AYA_PAY_APP_SECRETayaPay.appSecretYesSecret used for checksums
AYA_PAY_BASE_URLayaPay.baseUrlNoOverride the gateway base URL
YOMA_MMQR_MERCHANT_IDyomaMmqr.merchantIdYesMerchant ID issued by Yoma
YOMA_MMQR_CLIENT_IDyomaMmqr.clientIdYesOAuth client ID
YOMA_MMQR_CLIENT_SECRETyomaMmqr.clientSecretYesOAuth client secret
YOMA_MMQR_WEBHOOK_HASHKEYyomaMmqr.webhookHashKeyYesHash key issued by Yoma for verifying callbacks
YOMA_MMQR_API_VERSIONyomaMmqr.apiVersionYesThe {version} segment of Yoma's API paths, e.g. v1rc
YOMA_MMQR_WEBHOOK_SECRETyomaMmqr.webhookSecretNoWhen set, callbacks must carry it in X-Webhook-Secret
YOMA_MMQR_BASE_URLyomaMmqr.baseUrlNoOverride the API base URL
CYBER_SOURCE_PROFILE_IDcyberSource.profileIdYesSecure Acceptance profile ID
CYBER_SOURCE_ACCESS_KEYcyberSource.accessKeyYesProfile access key
CYBER_SOURCE_SECRET_KEYcyberSource.secretKeyYesProfile secret key used to sign fields
CYBER_SOURCE_BASE_URLcyberSource.baseUrlNoOverride the Secure Acceptance base URL

Only the gateways you call need their settings: an app that only uses KBZ Pay never reads the Wave Money keys. A gateway option object passed to the module wins over the environment for that gateway and needs every required setting.

Endpoints ​

Every gateway uses its production endpoints. There is no switch between test and production: to test against a gateway's UAT environment, or to go through a proxy, set the URL overrides (see Testing Against UAT). A blank override means unset.

Production Endpoints ​

GatewayURL
KBZ Pay APIhttps://api.kbzpay.com/payment/gateway
KBZ Pay PWAhttps://wap.kbzpay.com/pgw/pwa/#/
Wave Money APIhttps://payments.wavemoney.io
Wave Money authenticate redirecthttps://payments.wavemoney.io
AYA Payment Gatewayhttps://pgw.ayainnovation.com
Yoma MMQRhttps://paymenthubapi.yomabank.com
CyberSourcehttps://secureacceptance.cybersource.com

Testing Against UAT ​

Each gateway issues separate UAT credentials. To use them, set the URL overrides to the gateway's UAT endpoints together with the UAT credentials:

GatewayVariableModule optionUAT value
KBZ PayKBZ_PAY_BASE_URLkbzPay.apiUrlhttp://api-uat.kbzpay.com/payment/gateway/uat
KBZ PayKBZ_PAY_PWA_BASE_REDIRECT_URLkbzPay.pwaUrlhttps://static.kbzpay.com/pgw/uat/pwa/#/
Wave MoneyWAVE_MONEY_BASE_URLwaveMoney.baseUrlhttps://preprodpayments.wavemoney.io:8107
Wave MoneyWAVE_MONEY_AUTHENTICATE_URLwaveMoney.authenticateUrlhttps://preprodpayments.wavemoney.io
AYA Payment GatewayAYA_PAY_BASE_URLayaPay.baseUrlhttps://uat-pgw.ayainnovation.com
Yoma MMQRYOMA_MMQR_BASE_URLyomaMmqr.baseUrlhttps://devapi.yomabank.net
CyberSourceCYBER_SOURCE_BASE_URLcyberSource.baseUrlhttps://testsecureacceptance.cybersource.com

Wave serves its API on port 8107 and the page the customer is redirected to on the same host without the port. Remove the overrides, and switch to the production credentials, when you go live. Quote the KBZ PWA URL in .env, because of its #:

env
# UAT
KBZ_PAY_BASE_URL=http://api-uat.kbzpay.com/payment/gateway/uat
KBZ_PAY_PWA_BASE_REDIRECT_URL="https://static.kbzpay.com/pgw/uat/pwa/#/"
WAVE_MONEY_BASE_URL=https://preprodpayments.wavemoney.io:8107
WAVE_MONEY_AUTHENTICATE_URL=https://preprodpayments.wavemoney.io
AYA_PAY_BASE_URL=https://uat-pgw.ayainnovation.com
YOMA_MMQR_BASE_URL=https://devapi.yomabank.net
CYBER_SOURCE_BASE_URL=https://testsecureacceptance.cybersource.com

Module Options ​

forRoot ​

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

MyanmarPaymentsModule.forRoot(); // process.env
MyanmarPaymentsModule.forRoot({
  env: {
    MYANMAR_PAYMENTS_HTTP_TIMEOUT: '30',
    KBZ_PAY_APP_ID: '...',
    KBZ_PAY_APP_KEY: '...',
    KBZ_PAY_MERCHANT_CODE: '...',
  },
});
MyanmarPaymentsModule.forRoot({
  isGlobal: true,
  kbzPay: {
    appId: '...',
    appKey: '...',
    merchantCode: '...',
    timeoutSeconds: 30,
  },
});

forRootAsync ​

With @nestjs/config, hand the ConfigService to env; the package reads each variable through config.get(), so values from .env files, load factories and validation all apply:

ts
import { MyanmarPaymentsModule } from '@laranex/nestjs-myanmar-payments';
import { ConfigModule, ConfigService } from '@nestjs/config';

MyanmarPaymentsModule.forRootAsync({
  isGlobal: true,
  imports: [ConfigModule],
  inject: [ConfigService],
  useFactory: (config: ConfigService) => ({ env: config }),
});

useClass and useExisting take a class implementing MyanmarPaymentsOptionsFactory:

ts
import {
  MyanmarPaymentsModule,
  type MyanmarPaymentsModuleOptions,
  type MyanmarPaymentsOptionsFactory,
} from '@laranex/nestjs-myanmar-payments';
import { Injectable } from '@nestjs/common';

@Injectable()
class PaymentsConfig implements MyanmarPaymentsOptionsFactory {
  createMyanmarPaymentsOptions(): MyanmarPaymentsModuleOptions {
    return { env: process.env, formLink: { ttlMinutes: 15 } };
  }
}

MyanmarPaymentsModule.forRootAsync({ useClass: PaymentsConfig });

Options ​

OptionMeaning
envWhere variables are read: process.env (default), a record, or anything with get(key) such as ConfigService
kbzPay, waveMoney, ayaPay, yomaMmqr, cyberSourceA gateway's config, as options ({ appId, appKey, ... }) or an SDK config instance (new KbzPayConfig(...)). Wins over the environment for that gateway and needs every required setting, including timeoutSeconds; see the SDK's config options
fetchThe fetch gateways call, e.g. a fake one in tests
httpClientAn SDK HttpClient; takes precedence over fetch and keeps its own timeout (timeoutSeconds is still required)
tokenCacheAn SDK TokenCache for Yoma MMQR access tokens
useCacheManagerUse @nestjs/cache-manager for the tokens when it is available (default true)
formLinksecret, ttlMinutes and baseUrl of the auto-submit form links

Extras, given to forRoot() and forRootAsync() directly (never through a factory):

ExtraMeaning
isGlobalRegister the module globally (default false)
formRoute{ enabled, path, guards } of the auto-submit form route (default { enabled: true, path: 'myanmar-payments/form' })

The options object is available under the MYANMAR_PAYMENTS_OPTIONS token.

HTTP Client ​

Gateway calls go through the SDK's fetch client with each gateway's timeoutSeconds, read from MYANMAR_PAYMENTS_HTTP_TIMEOUT (required, no default; a missing one throws The kbz_pay configuration is missing [timeout_in_seconds]. when you first call KBZ Pay). Pass fetch to replace the function it calls, for example a fake one in tests, or httpClient to send requests yourself (proxies, tracing, retries); see Testing. When a gateway can't be reached, the call throws ApiError with httpStatus 0.

Auto-submit Form Route ​

AYA Pay and CyberSource need the customer's browser to POST a signed form. The module registers a GET myanmar-payments/form route that renders that form and submits it, and MyanmarPaymentsService.autoSubmitUrl(form) returns an encrypted link to it. Links expire after formLink.ttlMinutes; an invalid or expired link answers 410 Gone. The page is sent with Cache-Control: no-store.

SettingMeaning
formRoute.enabledRegister the route (default true). When false, autoSubmitUrl() throws
formRoute.pathThe route path (default myanmar-payments/form). The app's global prefix applies
formRoute.guardsGuards for the route, classes or instances, e.g. a rate-limiting ThrottlerGuard. Don't add authentication: the customer may be redirected from a gateway or another device
formLink.secretEncrypts the links (AES-256-GCM, key derived with HKDF-SHA256). Defaults to MYANMAR_PAYMENTS_FORM_KEY, then APP_KEY; a base64: prefix is decoded first
formLink.ttlMinutesMinutes a link stays valid. Defaults to MYANMAR_PAYMENTS_FORM_TTL_MINUTES; required, no default. Without it autoSubmitUrl() throws The form_route configuration is missing [ttl_minutes]., and the invalid-value message for a value that is not a whole number greater than 0
formLink.baseUrlThe scheme and host links start with. Defaults to APP_URL; without it links are relative

Links are encrypted with the form link secret and start with formLink.baseUrl or APP_URL. Set formRoute.enabled to false to drop the route and build the form yourself, see Form Payments.

Cache ​

Yoma MMQR access tokens last several hours and are reused until they expire. When @nestjs/cache-manager's CacheModule is registered globally, or passed in forRootAsync's imports, they are kept in that cache (CacheManagerTokenCache); otherwise each process keeps its own token in memory. Use a shared store (Redis, for example) when you run more than one server. The token is stored under myanmar-payments.yoma-mmqr.token.<sha256(baseUrl|clientId)>, the same key in every Laranex SDK, so services written in different languages can share one store.

ts
import { MyanmarPaymentsModule } from '@laranex/nestjs-myanmar-payments';
import { CacheModule } from '@nestjs/cache-manager';
import { Module } from '@nestjs/common';

@Module({
  imports: [
    CacheModule.register({ isGlobal: true }),
    MyanmarPaymentsModule.forRoot(),
  ],
})
export class AppModule {}

Pass useCacheManager: false to keep tokens in memory anyway, or tokenCache to use your own TokenCache.

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