AhaPay Payment Gateway
Use this skill for AhaPay (Malaysian BNPL) payment integration work.
Source
Read references/ahapay-api.md before giving factual API guidance or writing integration code. It holds the endpoint catalogue, environments, auth, every order-creation field, response fields, status values, callback format and verification, status requery, refunds, daily limit, health check, and the gaps the official docs leave open.
Read references/account-setup.md when the user asks how to register, which documents are needed, where the API key comes from, Merchant vs Partner keys, sandbox access, plugin setup (WooCommerce, EasyStore), minimum amounts, payout, fees, or support contacts.
Read references/source-map.md when choosing which AhaPay docs page or plugin file backs a fact, or to see which pages were read and what is not documented.
Core Workflow
- Identify environment: sandbox (
https://api.bnpl-my.dev.mychili.id) or production (https://api.ahapay.my). - Identify the key type: Merchant (Trade Outlet) API key for direct integration, or Partner API key (needs
external_merchant_idin every create request). - Identify the surface: online checkout (
POST /v2/orders/default), POS screen QR (POST /v2/orders/pos), QR POS scanner (POST /v2/orders/qr_pos), or a platform plugin (WooCommerce, EasyStore). - Create a local pending order first, with its expected amount in MYR.
- Create the AhaPay order server-side with
X-ApiKey, passing your local ID asreference_numberand an HTTPScallback_url. - Store
online_order_idfrom the response, then redirect the customer toonline_order_payment_link(online) or showlink_qr(POS). - Treat
success_redirect_url/failed_redirect_urlas browser UX only; never fulfil from a redirect. - On callback, check
X-Api-Key-Hashequals hex SHA-256 of your API key (constant-time), return HTTP 200 fast. - Because that hash does not sign the body, confirm with
GET /v2/orders/{online_order_id}/statusbefore settling, then matchonline_order_id,reference_number, amount, and status. - Settle idempotently on
PAYMENT_SUCCESSFUL; never let a latePAYMENT_IN_PROGRESSmove a paid order back.
Core Facts
- Production API base:
https://api.ahapay.my. Sandbox API base:https://api.bnpl-my.dev.mychili.id. - Auth header on every request:
X-ApiKey: <API_KEY>. The server decides whether it is a Merchant or Partner key. - Current API is V2:
POST /v2/orders/default,POST /v2/orders/pos,POST /v2/orders/qr_pos,GET /v2/orders/{online_order_id}/status,POST /v2/orders/{online_order_id}/refund,GET /v2/daily_limit_status,GET /v2/health. - Legacy V1 (
POST /v1/orders,GET /v1/orders/{id}/status,POST /v1/orders/{id}/refund,GET /v1/health) is still documented; the official plugin 1.1.0 migrated to V2. - Amounts are decimal MYR (ringgit, not sen), for example
288.90. MYR is the only documented currency. - Customer must be sent to
online_order_payment_link;linkis the V1 app deeplink per the official plugin. - Order statuses:
PAYMENT_IN_PROGRESS,PAYMENT_SUCCESSFUL,CANCELLED,REFUNDED,UNAVAILABLE. - Callback: POST to the order's
callback_urlon status change, headerX-Api-Key-Hash, body withonline_order_status. Must answer HTTP 200; timeout is 65 seconds. - Official plugin verifies
X-Api-Key-Hashashash('sha256', api_key)(64 hex chars) withhash_equals. - Refunds are full-order only:
POST /v2/orders/{online_order_id}/refund, no body. - QR POS
client_hashis sent without theaha-prefix and lives 10 minutes. - HTTP codes: 200 OK, 401 outdated/archived key or partner not bound to outlet, 404 outlet or partner not found, 500 invalid key format or key never created.
- Official WooCommerce plugin:
ahapay-buy-now-pay-lateron WordPress.org, default minimum order 55 MYR.
Critical Rules
- Keep the API key server-side only; it is both the request credential and the callback shared secret.
- Do not mark paid from a redirect, the AhaPay app screen, or a callback alone; requery status first.
- Compare
X-Api-Key-Hashwith a constant-time function and reject non-64-hex values. - The hash authenticates the sender, not the payload or the order: check that the callback's
online_order_idis the one you stored for thatreference_number. - Check the requeried amount equals your stored order total before fulfilment; flag mismatches for manual review.
- Make callback handling idempotent and lock per order; duplicates and out-of-order deliveries happen.
- Allow only
REFUNDEDout of a paid state; ignore regressions toPAYMENT_IN_PROGRESS. - Never send a second refund when the first one timed out or gave no clear answer; requery status first.
- Use the API key and base URL of the same environment; sandbox and production keys differ.
- For Partner keys, always send
external_merchant_id; for Merchant keys leave it out. - Confirm current fees, limits, payout schedule, and enabled outlets with AhaPay before claiming production certainty.
Completion Criteria
An AhaPay integration is done only when:
- Order creation runs server-side with
X-ApiKeyon the right base URL, withreference_numberandcallback_urlset. online_order_idand the expected amount are stored before the customer is redirected toonline_order_payment_link.- The callback endpoint is HTTPS, verifies
X-Api-Key-Hash, returns 200 within 65 seconds, and is reachable from the internet. - Settlement requeries
GET /v2/orders/{online_order_id}/statusand checks ID, reference, amount, and status. - Duplicate, out-of-order, and forged callbacks cannot double-fulfil or un-pay an order.
- Refund flow sends full refunds only, once, and reconciles unknown outcomes by requery.
- Tests cover a bad hash, missing hash, unknown order ID, amount mismatch, duplicate success, late in-progress after success, cancelled, unavailable, and refunded.
- Deployment docs list
AHAPAY_API_KEY,AHAPAY_BASE_URL, optionalAHAPAY_EXTERNAL_MERCHANT_ID, the callback URL, and sandbox/live separation.
Loophole Review Loop
When asked whether an AhaPay strategy is "100% confident", run this loop until no factual gaps remain:
- List trust boundaries: browser redirect, AhaPay payment page and app, backend order creator, AhaPay API, callback receiver, database, fulfilment job, refund job, plugin settings, and the AhaPay merchant portal.
- Check bypasses: fake success redirect, forged callback with a leaked key hash, callback naming another order's
reference_number, wrong environment key, wrong amount, stale status, duplicate callback, latePAYMENT_IN_PROGRESSafter success, retry link paid twice, refund timeout resent, partner key withoutexternal_merchant_id, daily limit reached, order below the minimum. - Add fixes: server-side creation, stored
online_order_idand amount, constant-time hash check, status requery before settlement, per-order locking, terminal-state guards, refund attempt stamp, environment-scoped config. - Re-read
references/ahapay-api.mdsections relevant to any gap. - State remaining uncertainty where the docs are silent: response envelope (
datawrapper), error body shape, callback retry policy, IP allowlist, partial refunds, and the Daily Limit parameter location.


