PayCools Payment Gateway
Use this skill for PayCools (Malaysia) Open API integration work.
Source
Read references/paycools-api.md before giving factual API guidance or writing integration code. It contains the endpoint catalogue, the request envelope, the RSA signature method (verified against the docs' own example), every request/response/notification field table, channel codes, status enums, response codes, and documented gaps and inconsistencies.
Read references/account-setup.md when the user asks how to become a merchant, what credentials exist, how the RSA key exchange works, env var names, sandbox/UAT, channel activation, pricing, or settlement.
Read references/source-map.md when choosing which official PayCools docs page applies, or to check coverage.
Core Workflow
- Get from PayCools:
appId, the API base URL (not published in the docs), UAT/sandbox details if any, and the PayCools platform RSA public key. - Generate a 4096-bit RSA key pair; send the public key to PayCools; keep the private key server-side.
- Create a local pending payment with a unique alphanumeric
mchOrderId(max 32) and the expected amount in cents. - Build the business
paramobject, serialize it to a JSON string once, sign that exact string with SHA256withRSA, and POST{appId, sign, param}as JSON. - WebPay: call
POST /open-api/webpay/generatewith achannelCode, redirect the customer todata.payUrl. DuitNow QR: callPOST /open-api/qr/generateand renderdata.qrCodeContent. - Treat
redirectUrlas browser UX only. - On the merchant
notifyUrl, verifysignover the receivedparamstring with the PayCools public key before parsingparam. - Settle idempotently only when
transactionStatusisCOMPLETEDandmchOrderId,transactionId,amount, andchannelCodematch the local record. Reply{"code":10000,"message":"Success"}. - Requery with
POST /open-api/webpay/detailorPOST /open-api/qr/detailwhen a notification is missing or the order is still pending. - Refunds use
POST /open-api/payment/refundwith the PayCoolsoriginalTransactionId; settle refunds from the verified refund notification orPOST /open-api/refund/payments.
Core Facts
- Every merchant-to-PayCools call is
POST,Content-Type: application/json, body{"appId","sign","param"}. paramis a JSON-serialized string;signis Base64 of SHA256withRSA (RSASSA-PKCS1-v1_5) over the exactparambytes, signed with the merchant private key (PKCS#8).- PayCools-to-merchant notifications are
{"sign","param"}, signed with the PayCools private key; verify with the PayCools public key (X.509 SubjectPublicKeyInfo), which the merchant must request from PayCools. - RSA key length must be 4096.
- Response envelope:
{code, message, data};10000means success. Codes21000to21016are errors (sign, param, appId, duplicate order id, timestamp, IP forbidden, and others). - Amounts are integers in cents (sen) for WebPay, QR, refund, and payout.
- Every
paramcarriestimestamp(current time, documented as milliseconds on several pages). - WebPay create requires
timestamp,channelCode,mchOrderId,amount,notifyUrl,redirectUrl,customerName,email,mobile; optionalremark,expireSeconds(30 to 3600, default 3600). - Transaction status values:
PENDING,COMPLETED,FAILED. - WebPay notification events:
order.payment.success,order.payment.failed. QR events:qrcode.payment.success,qrcode.payment.failed. - Refund status enum:
PENDING,PROCESS,COMPLETED,FAILED,CANCELLED(examples also showCOMPLETE). Transaction fees are not returned on refunds. - WebPay channel codes include
FPX_*_URL,RPP_*_URL,TOUCHNGO_URL,BOOST_URL,GRPY_URL,MCASH_URL,CARD_URL. QR channel:DUITNOW_DYNAMIC_QR. - API base URL, sandbox host, callback retry policy, and PayCools egress IPs are not documented publicly (checked 2026-10-21).
Critical Rules
- Keep the merchant RSA private key server-side only. Never use the example key pair printed in the docs.
- Sign the exact string you send as
param; do not re-serialize after signing. - Verify notifications over the
paramstring value as received (after decoding the outer JSON), never over a re-serialized object. - Use a constant-time-safe library verify call; reject on any verify failure or missing
sign. - Do not mark paid from
redirectUrl,payUrlcreation, QR display, or aPENDINGstatus. - Check amount (cents),
mchOrderId,transactionId, andchannelCodeagainst the local record before settling. - Make notification handling idempotent; duplicate or late notifications must not double-fulfil.
mchOrderIdmust be unique (21007on duplicate); payouts also need a uniqueidempotencyId(21015).- Send
notifyUrlin WebPay create (the docs' example showscallbackUrl; the field table saysnotifyUrl). Confirm with PayCools. - Expect IP restrictions (
21016 Merchant ip forbidden); ask PayCools to allowlist your server egress IPs. - Confirm base URL, UAT environment, enabled channels, and pricing with PayCools before claiming production readiness.
Completion Criteria
A PayCools integration is done only when:
- All Open API calls run server-side with
appIdand a SHA256withRSA signature over the exactparamstring. - The local payment record exists before WebPay or QR creation and stores
mchOrderId,transactionId, amount in cents, andchannelCode. - Redirect returns only show status; fulfilment waits for a verified notification or a requery showing
COMPLETED. - The notification handler verifies
signwith the PayCools public key, parsesparamafter verification, settles idempotently, and replies{"code":10000,"message":"Success"}. - Requery via the detail endpoint covers missing notifications and pending orders.
- Tests cover invalid signature, tampered
param, duplicate notification, wrong amount, wrongmchOrderId,FAILED, expired order, and successfulCOMPLETED. - Deployment docs list
PAYCOOLS_APP_ID,PAYCOOLS_PRIVATE_KEY,PAYCOOLS_PLATFORM_PUBLIC_KEY,PAYCOOLS_API_BASE_URL, the notify URL, redirect URL, and UAT/live separation.
Loophole Review Loop
When asked whether a PayCools strategy is "100% confident", run this loop until no factual gaps remain:
- List trust boundaries: browser redirect, payUrl page, QR display, backend order creator, PayCools API, notification receiver, JSON parser, database, fulfilment job, refund job, payout job, and key storage.
- Check bypasses: fake redirect, forged notification, signature checked over re-serialized JSON, wrong public key (merchant key instead of PayCools key), UAT/live key mix, wrong amount unit (ringgit vs cents), reused
mchOrderId, stalePENDINGrequery, duplicate notification, refund statusCOMPLETEvsCOMPLETEDmismatch, timestamp unit mismatch, IP not allowlisted, and duplicate payout withoutidempotencyId. - Add fixes: server-side signing, local pending record first, verify-then-parse, strict amount/order/channel checks, durable event log, unique constraints, separate env config, requery fallback, and tolerant refund status mapping.
- Re-read
references/paycools-api.mdsections relevant to any gap. - State remaining uncertainty where it depends on undocumented items: base URL, UAT host, retry policy, notification source IPs, timestamp unit, and channel activation for the merchant account.


