Skip to content

Configuration ​

Each gateway has a config class (KbzPayConfig, WaveMoneyConfig, AyaPayConfig, YomaMmqrConfig, CyberSourceConfig) that takes keyword arguments. Gateways take the config object or a mapping of its keyword arguments (KbzPay({"app_id": "...", ...})). Every setting is required except the URL overrides and Yoma's webhook secret: there are no defaults, and a missing or blank setting raises a ConfigurationError naming it:

python
from python_myanmar_payments import ConfigurationError, KbzPay, KbzPayConfig

try:
    config = KbzPayConfig(
        app_id="...",
        app_key="",
        merchant_code="...",
        timeout_seconds=30,
    )
except ConfigurationError as error:
    # e.g. kbz_pay "app_key"
    print(f'missing {error.gateway} setting "{error.key}"')
    raise

kbz = KbzPay(config)

str(error) reads The kbz_pay configuration is missing [app_key].

The time settings (timeout_seconds, and Wave Money's time_to_live_seconds) must be whole numbers greater than 0. Any other value raises the same error with the message The kbz_pay configuration [timeout_in_seconds] must be a whole number greater than 0.

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. Each config object exposes the URL actually used as an attribute (config.api_url, config.base_url, …).

Config Options ​

KbzPayConfig ​

ArgumentTypeRequiredDescription
app_idstrYesappid issued by KBZ
app_keystrYesSecret key used to sign requests
merchant_codestrYesmerch_code issued by KBZ
timeout_secondsint | strYesSeconds before the default HTTP client gives up. A missing one is reported as timeout_in_seconds
api_urlstrNoOverride the API base URL
pwa_urlstrNoOverride the PWA checkout URL. Normalized to end with /, e.g. …/pwa/#/

WaveMoneyConfig ​

ArgumentTypeRequiredDescription
merchant_idstrYesMerchant ID issued by Wave
secret_keystrYesHash secret key issued by Wave
merchant_namestrYesShown on Wave's payment page
time_to_live_secondsint | strYesSeconds the customer has to pay. A missing one is reported as time_to_live_in_seconds
timeout_secondsint | strYesSeconds before the default HTTP client gives up. A missing one is reported as timeout_in_seconds
base_urlstrNoOverride the API base URL
authenticate_urlstrNoOverride the host the customer is redirected to

AyaPayConfig ​

ArgumentTypeRequiredDescription
app_keystrYesPublic application key
app_secretstrYesSecret used for checksums
timeout_secondsint | strYesSeconds before the default HTTP client gives up. A missing one is reported as timeout_in_seconds
base_urlstrNoOverride the gateway base URL

YomaMmqrConfig ​

ArgumentTypeRequiredDescription
merchant_idstrYesMerchant ID issued by Yoma
client_idstrYesOAuth client ID
client_secretstrYesOAuth client secret
webhook_hash_keystrYesHash key issued by Yoma for verifying callbacks. A missing one is reported as webhook_hashkey
api_versionstrYesThe {version} segment of Yoma's API paths, e.g. v1rc
timeout_secondsint | strYesSeconds before the default HTTP client gives up. A missing one is reported as timeout_in_seconds
webhook_secretstrNoWhen set, callbacks must carry it in X-Webhook-Secret
base_urlstrNoOverride the API base URL

CyberSourceConfig ​

ArgumentTypeRequiredDescription
profile_idstrYesSecure Acceptance profile ID
access_keystrYesProfile access key
secret_keystrYesProfile secret key used to sign fields
base_urlstrNoOverride the Secure Acceptance base URL

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

The URLs are class constants on the config classes: KbzPayConfig.PRODUCTION_API_URL, KbzPayConfig.PRODUCTION_PWA_URL, WaveMoneyConfig.PRODUCTION_URL, WaveMoneyConfig.PRODUCTION_AUTHENTICATE_URL, and PRODUCTION_URL on AyaPayConfig, YomaMmqrConfig and CyberSourceConfig.

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:

GatewayVariableConfig argumentUAT value
KBZ PayKBZ_PAY_BASE_URLapi_urlhttp://api-uat.kbzpay.com/payment/gateway/uat
KBZ PayKBZ_PAY_PWA_BASE_REDIRECT_URLpwa_urlhttps://static.kbzpay.com/pgw/uat/pwa/#/
Wave MoneyWAVE_MONEY_BASE_URLbase_urlhttps://preprodpayments.wavemoney.io:8107
Wave MoneyWAVE_MONEY_AUTHENTICATE_URLauthenticate_urlhttps://preprodpayments.wavemoney.io
AYA Payment GatewayAYA_PAY_BASE_URLbase_urlhttps://uat-pgw.ayainnovation.com
Yoma MMQRYOMA_MMQR_BASE_URLbase_urlhttps://devapi.yomabank.net
CyberSourceCYBER_SOURCE_BASE_URLbase_urlhttps://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.

From Environment Variables ​

Every config class and gateway has from_env(env=None), which reads os.environ by default and takes any mapping instead, such as a dict in tests. The variable names match the Laravel package, so one .env file works for both.

python
from python_myanmar_payments import KbzPay, KbzPayConfig

kbz = KbzPay.from_env()
# or
config = KbzPayConfig.from_env()
kbz = KbzPay(config)

Every variable is required unless it is marked optional. The values after = are examples:

env
# Every gateway that calls an API
MYANMAR_PAYMENTS_HTTP_TIMEOUT=30

# 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
CYBER_SOURCE_PROFILE_ID=
CYBER_SOURCE_ACCESS_KEY=
CYBER_SOURCE_SECRET_KEY=
CYBER_SOURCE_BASE_URL=                # optional override

MYANMAR_PAYMENTS_HTTP_TIMEOUT sets timeout_in_seconds for KBZ Pay, Wave Money, AYA and Yoma MMQR. The package never reads files itself: load the .env file with your framework or with python-dotenv (load_dotenv() before from_env(), or pass dotenv_values(".env") as env).

One Object for Every Gateway ​

MyanmarPayments builds each gateway from its config object, on first use, and reuses it. Only the gateways you call need to be configured:

python
from python_myanmar_payments import (
    KbzPayConfig,
    MyanmarPayments,
    YomaMmqrConfig,
)

payments = MyanmarPayments(
    kbz_pay=KbzPayConfig(
        app_id="...",
        app_key="...",
        merchant_code="...",
        timeout_seconds=30,
    ),
    yoma_mmqr=YomaMmqrConfig(
        merchant_id="...",
        client_id="...",
        client_secret="...",
        webhook_hash_key="...",
        api_version="v1rc",
        timeout_seconds=30,
    ),
)

payments.kbz_pay()  # KbzPay, the same instance on every call
# raises ConfigurationError:
# The wave_money configuration is missing [merchant_id].
payments.wave_money()

# reads each gateway's variables on first use
from_env = MyanmarPayments.from_env()

The keyword arguments kbz_pay, wave_money, aya_pay, yoma_mmqr and cyber_source take the config objects or mappings of their keyword arguments, and the facade also takes the http_client and token_cache options below, shared by every gateway it builds. payments.cyber_source() returns the one CyberSource class.

AsyncMyanmarPayments is the async twin: same arguments, but kbz_pay() returns an AsyncKbzPay, wave_money() an AsyncWaveMoney and so on, and http_client takes an httpx.AsyncClient.

HTTP Client ​

Gateways that call an API take keyword options after the config:

OptionTypeDescription
http_clienthttpx.Client (async classes: httpx.AsyncClient)Sends every request. Use it for proxies, tracing, retries or test transports. Without one, the gateway creates an httpx client with the config's timeout_seconds
python
import httpx
from python_myanmar_payments import KbzPay, KbzPayConfig

client = httpx.Client(timeout=10, proxy="http://proxy.internal:3128")
kbz = KbzPay(KbzPayConfig.from_env(), http_client=client)

from_env() takes the same option: KbzPay.from_env(http_client=client).

A client you pass keeps its own timeout; timeout_seconds is still required.

Without http_client, a gateway creates its client on first use and keeps it for later calls, so create gateways once at startup and share them. Close the client it created with close() (async: await aclose()) or a with (async: async with) block; a client you passed stays open, since you own it:

python
from python_myanmar_payments import AsyncMyanmarPayments, MyanmarPayments

with MyanmarPayments.from_env() as payments:
    result = payments.kbz_pay().status("ORDER_1")
    print(result.status)

async def check() -> None:
    async with AsyncMyanmarPayments.from_env() as payments:
        result = await payments.kbz_pay().status("ORDER_1")
        print(result.status)

A failed request (connection error, timeout, invalid URL) raises an ApiError whose __cause__ is the original httpx error. An httpx.AsyncClient belongs to the event loop it first ran on, so create async gateways inside the running loop, e.g. in a FastAPI lifespan, rather than at import time in code that starts several loops.

CyberSource takes no options: it only signs fields and makes no HTTP calls.

Token Cache ​

Yoma MMQR authenticates with an OAuth access token that lasts hours. YomaMmqr keeps it in a TokenCache, under a key derived from the base URL and client ID that every Laranex payments SDK shares (see Access Tokens). TokenCache is a protocol with three methods:

python
from typing import Protocol

class TokenCache(Protocol):
    def get(self, key: str) -> str | None: ...

    # ttl_seconds of 0 or less never expires
    def set(self, key: str, value: str, ttl_seconds: int) -> None: ...

    def delete(self, key: str) -> None: ...

The default is a thread-safe MemoryTokenCache, which lives as long as the process: create one YomaMmqr (or one MyanmarPayments) at startup and share it across requests. When you run several processes, implement the protocol on top of Redis:

python
import redis
from python_myanmar_payments import YomaMmqr

class RedisTokenCache:
    def __init__(self, client: redis.Redis) -> None:
        self.client = client

    def get(self, key: str) -> str | None:
        value = self.client.get(key)
        return None if value is None else value.decode()

    def set(self, key: str, value: str, ttl_seconds: int) -> None:
        self.client.set(key, value, ex=ttl_seconds if ttl_seconds > 0 else None)

    def delete(self, key: str) -> None:
        self.client.delete(key)

yoma = YomaMmqr.from_env(token_cache=RedisTokenCache(redis.Redis()))

AsyncYomaMmqr and AsyncMyanmarPayments also accept an AsyncTokenCache, the same protocol with async def methods, e.g. on top of redis.asyncio. Pass token_cache to MyanmarPayments to share one cache with the Yoma gateway it builds.

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