SmartPay Agent API — интеграцийн заавар
SmartPay Agent API нь терминал (POS, касс, апп) болон EPay төлбөрийн шилжүүлэгчийн хооронд байрлах хаалга юм. Тэрээр хоёр ажил хийнэ: нэхэмжлэх/QR-ын REST үйлчилгээ үзүүлэх, мөн банкнаас төлбөр орсон агшинд яг тухайн терминал руу Socket.IO-оор шууд мэдэгдэл түлхэх.
Терминалын програм хангамж (POS/касс/mobile) хөгжүүлэгч талд. Энэ заавар нь
smartpay-agent-api сервисийн бодит зан төлөвийг тайлбарлана — юу
заавал, юу сонголт, аль нь чимээгүй бүтэлгүйтдэгийг нь.
Тойм#
Сервис нь дараах хоёр сувгийг зэрэг барина:
| Суваг | Чиглэл | Юунд ашиглагдах |
|---|---|---|
| REST HTTP | Терминал → Agent API | Нэхэмжлэх үүсгэх, MOF QR бэлдэх, төлбөрийн төлөв шалгах |
| Socket.IO WS | Agent API → Терминал | Банкнаас төлбөр орсныг терминалд шууд мэдэгдэх |
Хамгийн чухал ойлголт: төлбөр орсныг та лавлаж мэдэхгүй, харин мэдэгдэл
хүлээж авна. Терминал нь Socket.IO холболтоо байнга нээлттэй барьж
notify event-ийг сонсох ёстой.
Гүйлгээний бүтэн урсгал#
Орчин ба хаяг#
| Орчин | REST base URL | Socket.IO URL |
|---|---|---|
| Production | https://agent.epayment.mn/api |
https://agent.epayment.mn |
| Dev | https://dev-agent.epayment.mn/api |
https://dev-agent.epayment.mn |
| Local | http://localhost:3010/api |
http://localhost:3010 |
/api угтвар БАЙХГҮЙ
/api угтвар нь зөвхөн HTTP route-д хамаарна. Socket.IO нь өөрийн
/socket.io/ замаар ажилладаг тул холбогдохдоо
https://agent.epayment.mn гэж үндсэн хаягийг өг —
.../api гэвэл холбогдохгүй.
Сервис амьд эсэхийг шалгах хамгийн хурдан арга:
curl https://agent.epayment.mn/api
# {"message":"Hello API"}
Нэвтрэлт#
Танд EPay-ээс нэг client key олгогдоно. Түүнийгээ хоёр өөр газар өөр нэрээр дамжуулна:
| Суваг | Хаана бичих | Жишээ |
|---|---|---|
| REST | HTTP header | X-Api-Key: <ТАНЫ_KEY> |
| Socket.IO | Handshake-ийн auth объект |
auth: { apiKey: '<ТАНЫ_KEY>' } |
Endpoint тус бүрийн шаардлага:
| Endpoint | X-Api-Key | Тайлбар |
|---|---|---|
GET /api | — | Health check |
POST /api/invoices | Заавал | Байхгүй бол 401 |
GET /api/check | Заавал | Байхгүй бол 401 |
POST /api/generate-qr | — | Зөвхөн тооцоолол, гадагш хандахгүй |
POST /api/notify | — | Банк/МОФ талаас дуудагддаг |
Client key-г хөтчийн кодод шууд бүү суулга. Вэб дээрээс холбох шаардлагатай бол өөрийн backend-ээрээ дамжуул. Түлхүүр алдагдсан бол EPay-д нэн даруй мэдэгдэж соль.
WebSocket — холболт үүсгэх#
Socket.IO v4 ашиглана (серверийнхтэй үндсэн хувилбар таарах ёстой).
Үндсэн namespace /. Холбогдохдоо handshake-ийн auth-д
apiKey болон terminalId хоёрыг өгнө.
Node.js / React Native
import { io } from 'socket.io-client'; // ^4
const craccount = '100900020012'; // танай хүлээн авах данс
const terminalNo = '1'; // терминалын дугаар (зөвхөн цифр)
const socket = io('https://agent.epayment.mn', {
auth: {
apiKey: process.env.EPAY_CLIENT_KEY,
terminalId: `${craccount}-${terminalNo}`,
},
// transports-ыг ЗААВАЛ default-аар нь үлдээ — доорх анхааруулгыг үз
reconnection: true,
reconnectionDelay: 1000,
reconnectionDelayMax: 10000,
});
socket.on('connect', () => {
console.log('holbogdloo', socket.id, socket.io.engine.transport.name);
});
socket.on('notify', ({ body, wallResponse }) => {
// body — банкнаас ирсэн гүйлгээний мэдээлэл
// wallResponse — EPay switch рүү дамжуулсны хариу (алдаатай ч байж болно)
console.log('tolbor orloo', body.tamount, body.bank_refnum);
});
socket.on('connect_error', (err) => console.error('connect_error', err.message));
socket.on('disconnect', (reason) => console.warn('salsan', reason));
Хөтөч
<script src="https://cdn.socket.io/4.8.1/socket.io.min.js"></script>
<script>
const socket = io('https://agent.epayment.mn', {
auth: { apiKey: 'CLIENT_KEY', terminalId: '100900020012-1' },
});
socket.on('notify', (data) => console.log('notify', data));
</script>
transports: ['websocket'] гэж бүү бич
Сервис нь Cloudflare-ийн ард байрладаг бөгөөд цэвэр WebSocket
upgrade амжилтгүй болдог нь хэмжигдсэн. transports-ыг
зааж өгөхгүй орхивол Socket.IO эхлээд polling-оор холбогдож,
боломжтой үед өөрөө upgrade хийнэ — энэ нь найдвартай ажилладаг.
transports: ['websocket'] гэж хатуу заавал холболт
connect_error: websocket error өгч огт бүтэхгүй.
Олон жишээ код (манай хуучин README ч оруулаад) үүнийг зөвлөсөн байдаг —
бүү дага.
Room нэршил#
Сервер нь холбогдсон socket бүрийг terminalId-тэй яг ижил нэртэй
room-д оруулна. Мэдэгдэл ирэхэд дараах томьёогоор room-ыг олж түлхэнэ:
room = "<craccount>-<terminalId>"
│ └── notify биетийн tmean доторх ST: -ийн ард байгаа ЦИФРҮҮД
└── notify биетийн craccount талбар (хүлээн авагчийн данс)
Жишээ: craccount = 100900020012
tmean = "QPAY 915023802387369 ST:1,REF:0000093;"
→ room = "100900020012-1"
Тиймээс таны терминал auth.terminalId-даа яг энэ мөрийг
дамжуулах ёстой. Ганц тэмдэгт зөрвөл (жишээ нь ST:01 гэж бодоод
...-01 гэж холбогдвол) мэдэгдэл огт хүрэхгүй бөгөөд
ямар ч алдаа гарахгүй.
Сервер tmean-ээс терминалын дугаарыг ST:-ийн ард
байгаа цифрүүдээр л таньдаг. ST: байхгүй, эсвэл
араас нь цифр биш зүйл ирвэл мэдэгдэл 400 болж унана.
notify event#
Терминал зөвхөн нэг event сонсоно — notify. Payload нь хоёр хэсэгтэй:
{
"body": {
"crBankbic": "MOFUMNUB",
"dbBankbic": "KHANMNUB",
"craccount": "100900020012",
"dbaccount": "5027328504",
"craccountname": "ТӨРИЙН САН",
"dbaccountname": "БАТ",
"tmean": "QPAY 915023802387369 ST:1,REF:0000093;",
"bank_refnum": "001240573605",
"tdate": "2026-09-08 11:42:07",
"tamount": 1000,
"add_data": "",
"debt_bankid": "",
"crbankcode": "",
"ref_num": "0000093"
},
"wallResponse": {
"success": true,
"message": "Received",
"switchResponse": { }
}
}
| Талбар | Утга |
|---|---|
body.tamount | Төлөгдсөн дүн (тоо, MNT) |
body.bank_refnum | Банкны гүйлгээний дугаар — баримт дээр хэвлэх/тулгахад |
body.ref_num | Нэхэмжлэхийн лавлах дугаар |
body.tmean | Гүйлгээний утга — ST (терминал), REF-ийг үүнээс салгана |
body.tdate | Банкны гүйлгээний огноо |
body.dbaccountname | Төлөгчийн нэр |
wallResponse |
EPay switch рүү дамжуулсны үр дүн. undefined байж болно
(сервер тохиргоогүй үед), success: false ч байж болно.
|
wallResponse-оор төлбөрийг битгий үгүйсгэ
notify event өөрөө банк мөнгө шилжүүлсэн гэсэн үг.
wallResponse.success === false гэдэг нь EPay-ийн дотоод бүртгэлийн
алхам бүтсэнгүй гэсэн үг болохоос төлбөр болоогүй гэсэн үг биш.
Кассын дэлгэц дээр төлбөрийг баталгаажуулахдаа body-г үндэслэ.
Заавал мөрдөх дүрэм#
-
terminalIdзаавал. Түүнгүйгээр холбогдвол сервер холболтыг тэр дор нь таслана (disconnect). - Холболтоо байнга нээлттэй бай. Мэдэгдэл ирэх агшинд тухайн room-д нэг ч socket байхгүй бол сервер юу ч хийхгүй — мэдэгдэл алга болно. Дараалал, дахин илгээлт, хадгалалт байхгүй.
-
Reconnect-ээ асаа. Socket.IO-гийн үндсэн reconnect-ыг унтраахгүй.
Сүлжээ тасарсан богино хугацаанд орсон төлбөр буцаж ирэхгүй тул
(2-р дүрэм) сэргэсний дараа
GET /check-ээр нөхөж тулгах нь зүйтэй. -
Нэг room-д олон socket байж болно. Хоёр төхөөрөмж ижил
terminalId-гаар холбогдвол хоёуланд нь мэдэгдэл очно. Давхардлаас сэргийлэхийн тулдbank_refnum-аар дедупликац хий. -
Идэмхий бус (idempotent) боловсруул. Ижил
bank_refnumдахин ирвэл хоёр дахь баримт бүү хэвлэ.
REST endpoint-ууд#
Health check. Сервис амьд эсэхийг шалгана.
curl https://agent.epayment.mn/api
# 200 {"message":"Hello API"}
МОФ (Төрийн сан)-д нэхэмжлэх үүсгэнэ. Agent API нь биетийг шалгаад МОФ-ийн
/epayment руу дамжуулж, хариуг нь өөрчлөхгүйгээр буцаана.
Хүсэлт
{
"header": {
"from_": {
"system_owner": "…",
"institution_id": "…",
"institution_name": "…"
},
"to_": {
"system_owner": "…",
"institution_id": "…",
"institution_name": "…"
},
"version": "…",
"bussines_service": "…",
"bussines_message_type": "…",
"creation_date": "…",
"expire_date": "…",
"timestamp": "…"
},
"detail": {
"ref_num": "0000093",
"terminal_id": "1",
"tamount": 1000,
"tmean": "…",
"url": "…"
}
}
-
header-ийн бүх талбар болонfrom_/to_-ийн гурван талбар заавал, бүгд мөр (string) байх ёстой. detail.ref_numзаавал, мөр байна.detail.terminal_idзаавал — мөр эсвэл тоо аль нь ч болно.-
detail-ийн бусад талбар (tamount,tmean,url…) шалгагдахгүй, шууд дамжина — утгуудыг МОФ-ийн гэрээгээр бөглөнө.
Хариу
МОФ-ийн хариу тэр чигтээ буцна. Гол талбарууд нь detail.qr_code
(QR-ын EMVCo payload) болон detail.deep_link (банкны аппуудын жагсаалт).
| Статус | Утга |
|---|---|
200 | Амжилттай — МОФ-ийн биет |
400 | Биетийн бүтэц буруу (алдааны мессежид ямар талбар дутууг заана) |
401 | X-Api-Key буруу эсвэл байхгүй |
502 | МОФ-той холбогдож чадсангүй |
| бусад | МОФ-ийн статус, биетийг тэр чигт нь дамжуулна |
МОФ форматын EMVCo QR-ын payload-ыг локалаар үүсгэнэ. Гадагш ямар ч дуудлага хийхгүй, ямар ч нэхэмжлэх бүртгэхгүй — зөвхөн мөр эвлүүлж CRC-г нь бодно.
curl -X POST https://agent.epayment.mn/api/generate-qr \
-H 'Content-Type: application/json' \
-d '{
"amount": "1000.00",
"merchantName": "TEST MERCHANT",
"billNumber": "BILL-1",
"storeLabel": "STORE-1",
"referenceLabel": "REF-1",
"registerNO": "1234567"
}'
{
"qrCode": "00020101021226540014A00000084300010108MOFUMNUB0220MN63009001009000000052049399530349654071000.005502025802MN5913TEST MERCHANT6011Ulaanbaatar62540106BILL-10307STORE-10505REF-1060712345670809MOFQRCODE7403GOV8002036304A000"
}
Үүсэх QR-ын тогтмол утгууд
| Tag | Утга | Тайлбар |
|---|---|---|
01 | 12 | Динамик QR |
26.00 | A0000008430001 | Globally unique identifier |
26.01 | MOFUMNUB | Acquirer — Төрийн сан |
26.02 | MN630090010090000000 | IBAN |
52 | 9399 | Төрийн үйлчилгээний MCC |
53 | 496 | MNT |
54 | таны amount | Мөр хэлбэрээр шууд тавигдана |
58 / 60 | MN / Ulaanbaatar | Улс, хот |
62.01 | billNumber | Нэхэмжлэхийн дугаар |
62.03 | storeLabel | Салбар / дэлгүүр |
62.05 | referenceLabel | Лавлах |
62.06 | registerNO | Регистр |
62.08 | MOFQRCODE | МОФ урсгалыг таних тэмдэг |
74 / 80 | GOV / 03 | Merchant type, transaction type |
Энэ endpoint дээр биетийн шалгалт ажилладаггүй. Талбар дутуу
явуулбал 400 гарахгүй — 200-той хамт
худалдагчийн нэр, нэхэмжлэхийн дугаар нь дутуу QR буцна. Ийм QR
уншигдах ч төлбөр нь буруу газар очих, эсвэл таних боломжгүй болно.
Тиймээс зургаан талбарыг бүгдийг нь дүүргэж, буцсан
qrCode-ын уртыг өөрийн талдаа шалгаж бай.
amount-ыг форматлаж өг
amount нь мөр бөгөөд QR-ын tag 54-д яг тэр хэвээрээ ордог.
Сервер бөөрөнхийлөх, аравтын орон нэмэх зэрэг юу ч хийхгүй. МОФ-ийн жишгээр
"1000.00" гэж хоёр орны нарийвчлалтай явуулахыг зөвлөнө.
tpay.gov.mn дээрх нэхэмжлэхийн төлвийг лавлана. Мэдэгдэл алдагдсан үед
нөхөн тулгах зориулалттай.
curl -H 'X-Api-Key: CLIENT_KEY' \
'https://agent.epayment.mn/api/check?ref=0000093'
Боломжит хариунууд
| Статус | Биет | Утга |
|---|---|---|
200 |
{"status":"paid", …} | Төлөгдсөн. |
200 |
{"status":"pending","deeplinks":[…],"qpay":{…}} | Хараахан төлөгдөөгүй. Банкны deep link-үүд болон qPay-ийн хариу дагалдана. |
404 |
{"status":"not_found", …} | Ийм ref-тэй нэхэмжлэх олдсонгүй. |
400 |
ref parameter is required | ?ref= дамжуулаагүй. |
422 |
{"status":"unknown"|"error", …} | Хуудсаас төлөв ч, deep link ч танигдсангүй. |
502 |
Failed to reach tpay.gov.mn | Гадаад систем хариу өгсөнгүй (15 сек timeout). |
Энэ endpoint нь tpay.gov.mn-ийн HTML хуудсыг уншиж боловсруулдаг тул
удаан бөгөөд гадны системээс хамаарна. Гүйлгээ бүр дээр давтан дуудах
(polling) хэрэглээнд тохиромжгүй — үндсэн замаа
notify event байлга, энэ endpoint-ыг
зөвхөн эргэлзээтэй тохиолдол, өдрийн эцсийн тулгалтад хэрэглэ.
Банк / МОФ талаас дуудагддаг callback. Терминалын програм үүнийг дуудахгүй — гэхдээ юу болж байгааг ойлгоход хэрэгтэй тул баримтжуулав.
Хүсэлт хүлээж авмагц сервер дараах гурван зүйлийг хийнэ:
tmean-ээсST:<цифр>-ийг салгаж терминалыг тодорхойлно.- EPay-ийн
epay-to-customer-wallруу дамжуулж switch-д «paid» бүртгүүлнэ. -
<craccount>-<terminalId>room рууnotifyevent түлхэнэ.
| Статус | Нөхцөл |
|---|---|
200 | {"message":"Successfully","error":false,"wallResponse":…} |
400 | tmean байхгүй, эсвэл ST:<цифр> / craccount олдсонгүй |
Wall руу дамжуулах алхам амжилтгүй болбол алдаа нь
wallResponse.success = false болж биет дотор ирнэ,
HTTP статус нь 200 хэвээр үлдэнэ. Мөн notify event
нэгэн адил түлхэгдэнэ.
Түгээмэл алдаа#
| Алдаа | Үр дагавар | Зөв нь |
|---|---|---|
transports: ['websocket'] гэж хатуу заасан |
Огт холбогдохгүй | transports-ыг огт заахгүй орхи |
Socket.IO-д https://…/api гэж холбогдсон |
Огт холбогдохгүй | /api-гүй үндсэн хаяг өг |
terminalId-д зөвхөн терминалын дугаар өгсөн |
Холбогдоно, гэхдээ мэдэгдэл хэзээ ч ирэхгүй | <craccount>-<terminalId> бүтнээр нь өг |
Терминалын дугаарыг 01 гэж тэглэсэн |
Room зөрж мэдэгдэл ирэхгүй | ST:-ийн ард яг байгаа цифрийг нь ашигла |
| Гүйлгээ бүрийн дараа socket-оо хаадаг | Дараагийн мэдэгдэл алга болно | Холболтоо байнга нээлттэй барь |
generate-qr-д талбар дутуу өгсөн |
200, гэхдээ QR нь эвдэрсэн |
Зургаан талбарыг бүгдийг нь дүүрг |
bank_refnum-аар дедупликац хийгээгүй |
Давхар баримт хэвлэгдэнэ | Боловсруулсан bank_refnum-уудаа хадгал |
wallResponse.success-ээр төлбөрийг үгүйсгэсэн |
Төлсөн үйлчлүүлэгч татгалзагдана | notify ирсэн = төлбөр орсон |
Оношилгоо#
1. Сервис амьд эсэх
curl -i https://agent.epayment.mn/api
# 200 {"message":"Hello API"} → сервис амьд
2. Socket.IO хүрч байгаа эсэх
curl 'https://agent.epayment.mn/socket.io/?EIO=4&transport=polling'
# 0{"sid":"…","upgrades":["websocket"],"pingInterval":25000,…}
Ийм хариу ирвэл сүлжээ, Cloudflare, ingress бүгд журамтай — асуудал таны клиентийн
тохиргоонд байна (ихэвчлэн transports эсвэл URL).
3. Холбогдсон боловч мэдэгдэл ирэхгүй байна
Дараах дарааллаар шалга:
-
socket.connected === trueмөн үү?disconnectevent-ээ лог руу бич. -
Холбогдохдоо өгсөн
terminalId-гаа хэвлэ. Тэр нь<craccount>-<ST-ийн цифр>хэлбэртэй байна уу? -
Бодит гүйлгээний
tmean-ийг банкнаас/МОФ-оос ав. Түүн доторхST:-ийн ард байгаа цифр таныterminalId-ийн сүүлийн хэсэгтэй яг таарч байна уу? -
Таарахгүй байвал room зөрсөн байна —
terminalId-гаа засаад дахин холбогд. -
Бүгд таарч байгаад ирэхгүй бол EPay-д хандаж, тухайн
bank_refnum-ыг хэлж лог шалгуулна.
Мэдэгдэл ирээд room хоосон байвал сервер
No active sockets for terminalId: <room> гэсэн анхааруулга
үлдээдэг. Асуудал мэдүүлэхдээ гүйлгээний цаг, дүн,
bank_refnum гурвыг хамт өгвөл хамгийн хурдан олдоно.
Нэвтрүүлэх өмнөх шалгалт#
- Socket.IO клиент v4 эсэх.
transportsзаагаагүй (default) эсэх.- Socket.IO URL-д
/apiбайхгүй эсэх. -
terminalIdнь<craccount>-<terminalId>хэлбэртэй, дансны дугаар нь бодитой эсэх. - Reconnect асаалттай,
disconnect/connect_errorлоглогдож байгаа эсэх. bank_refnum-аар дедупликац хийдэг эсэх.- Апп нээгдмэгц socket холбогдож, хаагдтал нээлттэй үлддэг эсэх.
- Client key нь тохиргооноос ирдэг, эх кодод хатуу бичигдээгүй эсэх.
- Эхлээд
dev-agent.epayment.mnдээр туршсан эсэх.