Event-ийн лавлах¶
Платформын илгээдэг 12 event тус бүр: хэзээ, юуны дараа, хэн өдөөх, livemode юу байх, data.object ямар
объект, жишээ payload. Handler бичихдээ хэрэгтэй event-ээ олоод уншина уу; эхлээд Idempotent handler-ийг.
Жишээ payload-ууд нь SDK-ийн тест векторуудын (sdk/testdata/vectors.json, платформын кодоос үүсгэсэн) бодит
body-г уншихад эвтэйгээр задалсан хувилбар. Утсаар ирэх byte-д <, >, & нь \u003c, \u003e, \u0026 гэж
бичигдэнэ — гарын үсгийг үргэлж түүхий byte дээр шалга (Гарын үсэг). «Жишээ» гэж тэмдэглэсэн гурав
нь векторт байхгүй тул бодит бүтцээр бичсэн дүрслэл.
Event-ийн бүтэц¶
{
"id": "evt_Vec0000000000000000000001",
"type": "payment_intent.succeeded",
"created_at": "2026-10-22T09:00:00Z",
"livemode": true,
"app": "esim",
"data": { "object": { "…": "…" } }
}
| Талбар | Утга |
|---|---|
id |
evt_ + 24 тэмдэгт base62. Давхардал арилгах түлхүүр; Gerege-Event-Id толгойтой ижил. Цаг дараалсан биш |
type |
Доорх 12-ын нэг; minor хувилбарт шинэ төрөл нэмэгдэж болно — мэдэхгүйд 2xx |
created_at |
Event үүссэн агшин, UTC, секундийн нарийвчлалтай (RFC 3339) |
livemode |
true — бодит мөнгө / нийтэд нөлөөлсөн; false — test |
app |
Апп-ын slug |
data.object |
Өөрчлөгдсөн объект — API-ийн хариутай ижил хэлбэр (PaymentIntent, Refund, …) |
Body нь өөрчлөлт commit хийгдэх үед нэг удаа үүснэ: retry-д ч, endpoint бүрт ч, хожуу ирсэн ч тэр агшны төлөвийг
харуулна; Events API ч яг энэ body-г буцаана. Одоогийн төлөвийг GET-ээр уншина.
Төрлүүд товчоор¶
type |
Хэн өдөөх | data.object |
|---|---|---|
payment_intent.succeeded |
хүн (automatic) · таны backend (manual capture) | PaymentIntent |
payment_intent.requires_capture |
хүн | PaymentIntent |
payment_intent.canceled |
таны backend · хүн · job (app intent-ийн хугацаа) |
PaymentIntent |
payment_intent.failed |
хүн (ledger татгалзсан) · таны backend (capture) | PaymentIntent |
payment_intent.claimed |
хүн | PaymentIntent |
payment_intent.expired |
job / уншилт (claimless intent-ийн хугацаа) | PaymentIntent |
refund.succeeded |
таны backend · job | Refund |
refund.failed |
таны backend · job | Refund |
app.status_changed |
хөгжүүлэгч · оператор | AppStatus |
consent.revoked |
хүн | Consent |
settlement.paid |
job (оператор асаасан тооцоо) | Settlement |
webhook.test |
хөгжүүлэгч (консол, /webhook-endpoints/{eid}/test) |
WebhookTest |
Бүх объектын талбар бүрийн тайлбар: Объектууд.
payment_intent.succeeded¶
Мөнгө хүлээн авагч руу шилжсэн — бараа олгох дохио.
capture_method: "automatic": хүн Gerege App-д баталгаажуулж, барьцаа ба capture нэг дор амжилттай болсны дараа.capture_method: "manual": таны backendPOST /payment-intents/{id}/captureхийж, capture амжилттай болсны дараа.status: "succeeded",confirmed_atбөглөгдсөн. Тооцоо асаалттай апп-дfee,netтоо; бусад үедnull.- Test горимд ledger-гүй, шууд (дүнгийн сүүлийн хоёр оронгоор — Тест дүн).
{
"id": "evt_Vec0000000000000000000001",
"type": "payment_intent.succeeded",
"created_at": "2026-10-22T09:00:00Z",
"livemode": true,
"app": "esim",
"data": {
"object": {
"id": "pi_4fQ2mX8kLp0aZr7TbN3cVw9E",
"object": "payment_intent",
"status": "succeeded",
"amount": 45000,
"currency": "MNT",
"description": "Japan 5GB · 15 хоног <roaming & data>",
"reference": "order_8812",
"user_ref": "u_9f2c4e7a1b3d5f60718293a4b5c6d7e8",
"livemode": true,
"capture_method": "automatic",
"refunded_amount": 0,
"fee": null,
"net": null,
"failure_code": null,
"expires_at": "2026-10-22T09:10:00Z",
"capture_before": null,
"confirmed_at": "2026-10-22T08:59:55Z",
"created_at": "2026-10-22T08:59:00Z",
"metadata": { "package": "JC071" },
"presentment": "app",
"terminal": null,
"sub_merchant": null,
"return_url": null,
"claimed_at": null,
"min_age": null,
"capture_within": null
}
}
}
payment_intent.requires_capture¶
manual intent-ийн мөнгө хүний дансанд барьцаалагдсан (hold) — одоо таны ээлж: бараа гарвал
POST …/capture, гараагүй бол POST …/cancel (барьцааг суллана).
capture_beforeхүртэл шийдэхгүй бол барьцаа автоматаар суллагдаж intentexpiredболно (failure_code: "authorization_expired"; event нь доорхpayment_intent.expiredэсвэлappintent-дpayment_intent.canceled).capture_withinнь баталгаажуулснаас хойших цонх секундээр (анхдагч 604 800 = 7 хоног).
{
"id": "evt_Vec0000000000000000000002",
"type": "payment_intent.requires_capture",
"created_at": "2026-10-22T09:00:00Z",
"livemode": false,
"app": "esim",
"data": {
"object": {
"id": "pi_8HkWq3ZsT0yLm5Nc2Vb7Xp1R",
"object": "payment_intent",
"status": "requires_capture",
"amount": 45000,
"currency": "MNT",
"description": "Japan 5GB · 15 хоног <roaming & data>",
"reference": null,
"user_ref": "u_9f2c4e7a1b3d5f60718293a4b5c6d7e8",
"livemode": false,
"capture_method": "manual",
"refunded_amount": 0,
"fee": null,
"net": null,
"failure_code": null,
"expires_at": "2026-10-22T09:10:00Z",
"capture_before": "2026-10-29T09:00:00Z",
"confirmed_at": null,
"created_at": "2026-10-22T08:59:00Z",
"metadata": {},
"presentment": "app",
"terminal": null,
"sub_merchant": null,
"return_url": null,
"claimed_at": null,
"min_age": null,
"capture_within": 604800
}
}
}
payment_intent.canceled¶
Intent цуцлагдсан — мөнгө хөдлөөгүй (эсвэл барьцаа суллагдсан). data.object.status-аас аль нь болохыг ял:
status |
Шалтгаан |
|---|---|
canceled |
Таны backend POST …/cancel хийсэн (төлөгдөөгүй intent, эсвэл manual барьцааг суллах); хүн төлөх цонхыг цуцалсан; test горимд дүн …02 |
expired |
Зөвхөн app presentment (mini app): expires_at өнгөрсөн, эсвэл manual барьцааны capture_before өнгөрсөн (failure_code: "authorization_expired") |
app intent-ийн хугацаа дуусахад payment_intent.canceled ирдэг нь OMAS-д бичсэн backend-ууд эвдрэхгүйн тулд;
claimless (qr/redirect/terminal) intent-д оронд нь payment_intent.expired ирнэ.
Жишээ (дүрслэл — mini app-ын intent хугацаандаа төлөгдөөгүй):
{
"id": "evt_3Jd8sKq0Lw5Nz2Xc7Vb9Mp1R",
"type": "payment_intent.canceled",
"created_at": "2026-10-22T09:10:20Z",
"livemode": true,
"app": "esim",
"data": {
"object": {
"id": "pi_Ws5Tq8Lm2Kx0Pz7Rb4Nc9Vd1E",
"object": "payment_intent",
"status": "expired",
"amount": 45000,
"currency": "MNT",
"description": "Japan 5GB · 15 хоног",
"reference": "order_8813",
"user_ref": "u_9f2c4e7a1b3d5f60718293a4b5c6d7e8",
"livemode": true,
"capture_method": "automatic",
"refunded_amount": 0,
"fee": null,
"net": null,
"failure_code": null,
"expires_at": "2026-10-22T09:10:00Z",
"capture_before": null,
"confirmed_at": null,
"created_at": "2026-10-22T09:00:00Z",
"metadata": {},
"presentment": "app",
"terminal": null,
"sub_merchant": null,
"return_url": null,
"claimed_at": null,
"min_age": null,
"capture_within": null
}
}
}
payment_intent.failed¶
Төлбөрийн оролдлого амжилтгүй — failure_code шалтгааныг хэлнэ (Алдааны код).
failed intent дахин төлөгдөж болно (OMAS): хүн өөр данс сонгоод дахин баталгаажуулж болно.
failure_code |
Хэзээ |
|---|---|
insufficient_funds, limit_exceeded, hold_mismatch, payment_failed |
Хүн баталгаажуулахад ledger барьцааг татгалзсан |
merchant_unavailable |
Барьцаа тавигдсан ч апп хооронд нь түдгэлзсэн эсвэл payout данс алга болсон — барьцаа суллагдсан |
capture_failed |
Capture-ийг ledger татгалзсан — барьцаа суллагдсан |
test_declined |
Test горим, дүн …01 |
Хүн өөрийнх биш / хаалттай данс сонгосон (account_unavailable) үед event илгээгдэхгүй: intent
requires_confirmation хэвээр үлдэж, хүн өөр данс сонгоно.
Жишээ (дүрслэл — test горимд дүн …01):
{
"id": "evt_Lq2Wm8Zr5Tn0Xs3Kd7Pb9Vc1F",
"type": "payment_intent.failed",
"created_at": "2026-10-22T09:01:00Z",
"livemode": false,
"app": "esim",
"data": {
"object": {
"id": "pi_Tz6Hc1Nq8Lv3Rw0Km5Xb2Pd9G",
"object": "payment_intent",
"status": "failed",
"amount": 45001,
"currency": "MNT",
"description": "Japan 5GB · 15 хоног",
"reference": "order_test_1",
"user_ref": "u_9f2c4e7a1b3d5f60718293a4b5c6d7e8",
"livemode": false,
"capture_method": "automatic",
"refunded_amount": 0,
"fee": null,
"net": null,
"failure_code": "test_declined",
"expires_at": "2026-10-22T09:10:00Z",
"capture_before": null,
"confirmed_at": null,
"created_at": "2026-10-22T09:00:00Z",
"metadata": {},
"presentment": "app",
"terminal": null,
"sub_merchant": null,
"return_url": null,
"claimed_at": null,
"min_age": null,
"capture_within": null
}
}
}
payment_intent.claimed¶
Хүн claimless (qr, redirect, terminal) intent-ийг Gerege App-д өөртөө авсан — QR уншуулсан, холбоос нээсэн,
sticker-т утсаа хүргэсэн. Дэлгэц, машин дээрээ «Утсаараа баталгаажуулна уу» гэж харуулах дохио; мөнгө хараахан
хөдлөөгүй (status: "requires_confirmation").
user_ref(энэ төлбөрт зориулсан санамсаргүйu_…) баclaimed_atбөглөгдсөн.- Баталгаажуулаагүй claim 90 секундэд хүчингүй болдог: тэр хугацааны дараа өөр хүн claim хийвэл шинэ
user_ref-тэй дахинpayment_intent.claimedирнэ. Нэг хүн дахин claim хийх нь шинэ event үүсгэхгүй. - Таны backend
POST …/unclaimхийхэд event илгээгдэхгүй (intent дахинuser_ref: null).
{
"id": "evt_Vec0000000000000000000008",
"type": "payment_intent.claimed",
"created_at": "2026-10-22T09:00:00Z",
"livemode": true,
"app": "vending",
"data": {
"object": {
"id": "pi_T3rm1nalVend0000000000Ab",
"object": "payment_intent",
"status": "requires_confirmation",
"amount": 3500,
"currency": "MNT",
"description": "Ус 0.5л",
"reference": "basket_77120",
"user_ref": "u_6b1e0c9d2f4a8e7c3b5d1f0a9e8c7b6d",
"livemode": true,
"capture_method": "manual",
"refunded_amount": 0,
"fee": null,
"net": null,
"failure_code": null,
"expires_at": "2026-10-22T09:05:00Z",
"capture_before": null,
"confirmed_at": null,
"created_at": "2026-10-22T08:59:00Z",
"metadata": {},
"presentment": "terminal",
"terminal": "4b8e6c1a-2d3f-4a5b-9c7d-0e1f2a3b4c5d",
"sub_merchant": "9a7c5e3b-1d2f-4e6a-8b0c-7d9e1f3a5b7c",
"return_url": null,
"claimed_at": "2026-10-22T08:59:40Z",
"min_age": null,
"capture_within": 120
}
}
}
payment_intent.expired¶
Claimless (qr, redirect, terminal) intent-ийн хугацаа дууссан — claim хийгдсэн ч, хийгдээгүй ч:
expires_atхүртэл хэн ч төлөөгүй (failure_code: null; баталгаажуулалт явж байхад дууссан бол"expired").- Manual барьцааны
capture_beforeөнгөрсөн, эсвэл capture хийхэд барьцаа аль хэдийн дууссан — мөнгө хүнд буцсан (failure_code: "authorization_expired").
Хугацааг минут тутмын job, эсвэл тэр intent-ийг уншсан хүсэлт (GET) бүртгэнэ — expires_at-аас хойш хамгийн
ихдээ нэг минутын дотор. app intent-д энэ event биш, payment_intent.canceled (status: "expired").
{
"id": "evt_Vec0000000000000000000009",
"type": "payment_intent.expired",
"created_at": "2026-10-22T09:00:00Z",
"livemode": true,
"app": "kiosk",
"data": {
"object": {
"id": "pi_QrK1osk00000000000000Cd2",
"object": "payment_intent",
"status": "expired",
"amount": 12000,
"currency": "MNT",
"description": "Хураамж",
"reference": null,
"user_ref": null,
"livemode": true,
"capture_method": "automatic",
"refunded_amount": 0,
"fee": null,
"net": null,
"failure_code": null,
"expires_at": "2026-10-22T08:59:59Z",
"capture_before": null,
"confirmed_at": null,
"created_at": "2026-10-22T08:50:00Z",
"metadata": {},
"presentment": "qr",
"terminal": null,
"sub_merchant": null,
"return_url": null,
"claimed_at": null,
"min_age": null,
"capture_within": null
}
}
}
refund.succeeded¶
Буцаалт хүний дансанд орсон. POST …/refunds нь ихэвчлэн тэр дор нь succeeded-ээр хариулна (event ч тэр
агшинд); ledger хариу өгөөгүй бол 202 pending, дараа нь job дуусгаж event илгээнэ. Test горимд шууд.
Intent-ийн refunded_amount ба status (partially_refunded / refunded) өөрчлөгдөнө, гэхдээ
payment_intent.* event илгээгдэхгүй — зөвхөн refund.*. Intent-ийн шинэ төлөвийг GET-ээр уншина.
{
"id": "evt_Vec0000000000000000000003",
"type": "refund.succeeded",
"created_at": "2026-10-22T09:00:00Z",
"livemode": true,
"app": "esim",
"data": {
"object": {
"id": "re_2kLm9Pq4Rs6Tu8Vw0Xy1Za3B",
"object": "refund",
"intent_id": "pi_4fQ2mX8kLp0aZr7TbN3cVw9E",
"amount": 10000,
"status": "succeeded",
"reason": "supplier_failed",
"failure_code": null,
"livemode": true,
"created_at": "2026-10-22T09:00:00Z"
}
}
}
refund.failed¶
Буцаалтыг татгалзсан — мөнгө хөдлөөгүй, intent-ийн refunded_amount өөрчлөгдөөгүй. failure_code:
failure_code |
Утга, юу хийх |
|---|---|
insufficient_funds |
Таны payout дансанд төлбөрийн мөнгө үлдээгүй (зарцуулсан) — дансаа цэнэглээд шинэ Idempotency-Key-ээр дахин |
settlement_shortfall |
Тооцоо асаалттай апп: клирингийн үлдэгдэл буцаалтад хүрэлцэхгүй (мөнгө аль хэдийн тооцоогоор шилжсэн) — оператортой холбогд |
refund_rejected |
Ledger бусад шалтгаанаар татгалзсан |
Жишээ (дүрслэл):
{
"id": "evt_Pn4Xq9Lr2Wm7Tz0Kc5Hb8Vd3J",
"type": "refund.failed",
"created_at": "2026-10-22T11:20:00Z",
"livemode": true,
"app": "shop",
"data": {
"object": {
"id": "re_Hk7Ns2Qw9Lm4Xt0Rz5Pc8Vb1D",
"object": "refund",
"intent_id": "pi_Mx3Lq8Tn1Wz6Rk0Pc4Hs9Vb2F",
"amount": 45000,
"status": "failed",
"reason": "customer_request",
"failure_code": "settlement_shortfall",
"livemode": true,
"created_at": "2026-10-22T11:20:00Z"
}
}
}
app.status_changed¶
Апп-ын status эсвэл review_status өөрчлөгдсөн: review-д илгээх, батлах, татгалзах, түдгэлзүүлэх, maintenance
асаах/унтраах. Өөрчлөлттэй нэг transaction-д бичигдэнэ.
livemode: true— апп өөрчлөлтийн өмнө эсвэл дараа нийтлэгдсэн (батлагдсан,active/maintenance) байсан — олон нийтэд нөлөөлнө;false— зөвхөн хөгжүүлэгч, тест хэрэглэгчдэд хамаатай (жишээ нь ноорог review-д орсон).review_note— татгалзах, түдгэлзүүлэх үеийн операторын тэмдэглэл; бусад үедnullбайж болно.- Шууд оролдлогогүй — job-оор 1 минутын дотор (Хүргэлт).
{
"id": "evt_Vec0000000000000000000004",
"type": "app.status_changed",
"created_at": "2026-10-22T09:00:00Z",
"livemode": true,
"app": "esim",
"data": {
"object": {
"object": "app",
"slug": "esim",
"status": "active",
"review_status": "suspended",
"previous_status": "active",
"previous_review_status": "approved",
"review_note": "Хэрэглэгчийн гомдол шалгагдаж байна"
}
}
}
consent.revoked¶
Хүн Gerege App-д таны mini app-ыг салгасан (зөвшөөрлөө цуцалсан). Тэр хүний бүх access / refresh token аль хэдийн
хүчингүй — тэдгээрээр хийх дуудлага 401 invalid_token. Таны талд тэр хүний session-ийг хааж, user_ref-ээр
холбосон мэдээллийг өөрийн нууцлалын бодлогоор устга.
user_ref— тэр хүний pairwisesub(апп-д тогтмол нэр).livemode: true— апп нийтлэгдсэн;false— зөвхөн хөгжүүлэгч, тест хэрэглэгчид нээж чаддаг үед.- Хамгийн боломжоор (best effort): салгалт өөрөө үргэлж хэрэгжинэ; event бичигдэж чадаагүй ховор тохиолдолд ирэхгүй байж болно. Шууд оролдлогогүй — job-оор.
{
"id": "evt_Vec0000000000000000000005",
"type": "consent.revoked",
"created_at": "2026-10-22T09:00:00Z",
"livemode": true,
"app": "esim",
"data": {
"object": {
"object": "consent",
"revoked_at": "2026-10-22T09:00:00Z",
"user_ref": "u_78a08c3afb344c1b4b765aead45e1693"
}
}
}
settlement.paid¶
Тооцоо таны payout данс руу шилжсэн (Тооцоо). Оператор таны апп-д тооцоог асаасан үед л илгээгдэнэ; асаагаагүй апп-д capture шууд payout данс руу ордог тул энэ event ирэхгүй.
- Үргэлж
livemode: true. paid0 байж болно (бүгд шимтгэл, эсвэл дараагийн тооцоо руу шилжсэн).sub_merchant— дэд худалдагчийн тооцоо бол түүний id, апп-ын өөрийнх болnull.- Тэнцэтгэл:
net = gross − refunds − fee,paid = net − holdback + released − reserve_refunds − recovered.
{
"id": "evt_Vec0000000000000000000007",
"type": "settlement.paid",
"created_at": "2026-10-22T09:00:00Z",
"livemode": true,
"app": "esim",
"data": {
"object": {
"id": "st_7QwErTy2UiOp4AsDf6GhJk8L",
"object": "settlement",
"status": "paid",
"cutoff": "2026-10-21T16:00:00Z",
"gross": 100000,
"refunds": 5000,
"reserve_refunds": 0,
"fee": 2000,
"net": 93000,
"holdback": 0,
"released": 0,
"recovered": 0,
"paid": 93000,
"intent_count": 3,
"destination_masked": "******0042",
"holdback_release_at": null,
"livemode": true,
"created_at": "2026-10-22T08:58:00Z",
"paid_at": "2026-10-22T08:59:00Z",
"refund_count": 1,
"fee_assessed": 2000,
"fee_returned": 0,
"fee_carried_in": 0,
"fee_carried_out": 0,
"clearing_carried_in": 0,
"clearing_carried_out": 0,
"failure_code": null,
"sub_merchant": null
}
}
}
webhook.test¶
Хөгжүүлэгчийн консолын «Тест илгээх» товч (POST /dev/apps/{id}/webhook/test, …/webhook-endpoints/{eid}/test) эсвэл
таны серверийн POST /webhook-endpoints/{eid}/test — бодит event-ийн замаар (ижил гарын үсэг, outbox, retry), гэхдээ
зөвхөн сонгосон нэг endpoint руу (шүүлтүүр, төлөвөөс үл хамааран), live endpoint руу ч үргэлж livemode: false.
event_types шүүлтүүрт нэрлэж болохгүй, Events API-д гарахгүй. OMAS-ын ping-ийн оронд. Үүгээр юу ч
бүү хий — 2xx буцаахад л хангалттай.
{
"id": "evt_Vec0000000000000000000006",
"type": "webhook.test",
"created_at": "2026-10-22T09:00:00Z",
"livemode": false,
"app": "esim",
"data": {
"object": {
"message": "Gerege App webhook test",
"object": "webhook_test"
}
}
}
Нийцэл¶
- Нэмэгдэх, хасагдахгүй: minor хувилбар шинэ event төрөл, объектод шинэ талбар нэмж болно. Мэдэхгүй
type-д2xx, мэдэхгүй талбарыг үл тоомсорло (JSON-оо strict schema-аар бүү татгалз). payment_intent.*event бүрийнdata.object-дpresentment,terminal,sub_merchantбайна (appintent-д"app",null,null).- OMAS-д бичсэн backend:
OpenPay-Signature/OpenPay-Event-Idтолгой ижил утгатай ирнэ;appintent-ийн хугацаа дуусахад OMAS-ын адилpayment_intent.canceled.