Payments — payment intent¶
Mini app-д мөнгө авах цорын ганц арга. Гадны төлбөрийн хэрэгсэл (карт форм, QR, банк шилжүүлэг) mini app дотор ХОРИГЛОНО. Мөнгө нь Gerege Wallet-ийн ledger (core banking)-д хөдөлнө; хүн өөрийн хэтэвчний дансаар төлнө.
1. Урсгал¶
- Хүн mini app-д «Төлөх» дарна → frontend → өөрийн backend.
- Backend →
POST /api/v1/miniapp/payment-intents(Bearer хүний access token, scopepayments,Idempotency-Key) → intent (requires_confirmation). Дүнг backend өөрийн каталогоос тооцно. - Frontend → bridge
requestPayment({intentId})→ host өөрийн UI-д баталгаажуулна (§3). - Backend төлбөрийг webhook
payment_intent.succeededба/эсвэлGET /payment-intents/{id}-ээр шалгасны дараа л бараа/үйлчилгээ олгоно. Bridge-ийнsucceeded-ээр олгох ХОРИГЛОНО.
2. API (https://geregeapp.mn/api/v1/miniapp)¶
Auth. Mini app-ын intent үүсгэх — зөвхөн хүний Bearer access token (scope payments): intent үргэлж хэн
нэгнийх, token нь хэнийх болохыг хэлнэ. (Mini app биш гадны систем — вэб дэлгүүр, kiosk, вендинг — client credential-аар
presentment: "qr" | "redirect" | "terminal" intent үүсгэнэ: pay-with-gerege.md.) Унших, жагсаах, capture, цуцлах, буцаах — Bearer эсвэл апп-ын
client credential (Authorization: Basic base64(client_id:client_secret)) — webhook handler, reconciler, нийлүүлэгчийн
алдааны дараах буцаалт зэрэг хүнгүй сервер талын үйлдэлд. Аль ч тохиолдолд зөвхөн өөрийн апп-ын, credential-ийн
орчны (test/live) intent-д хүрнэ. Rate limit: минутад 600, burst 120.
Үүсгэх — POST /payment-intents¶
Idempotency-Key толгой ЗААВАЛ: 1–128 хэвлэгдэх ASCII, зайгүй. Нэг апп × орчинд key үүрд өвөрмөц:
ижил key + ижил body (ба ижил хүн) → тэр intent 200; ижил key + өөр body/хүн → 409 idempotency_conflict.
{ "amount": 45000, "currency": "MNT", "description": "Japan 5GB 15 хоног", "reference": "order_8812",
"expires_in": 600, "capture_method": "automatic", "metadata": { "package": "JC071" } }
| Талбар | Дүрэм |
|---|---|
amount |
MNT бүхэл тоо, 100 ≤ amount ≤ 50 000 000, мөн апп-ын max_amount-аас ихгүй (доор) |
currency |
MNT (анхдагч) |
description |
1–80 тэмдэгт, хүнд харагдана |
reference |
≤ 64, мөр шилжилтгүй; таны захиалгын id — апп дотор өвөрмөц ЗӨВЛӨЖ БАЙНА |
expires_in |
60–1800 секунд (анхдагч 600) |
capture_method |
automatic (анхдагч) эсвэл manual (§4) |
metadata |
≤ 20 түлхүүр; түлхүүр ≤ 40, утга ≤ 500 тэмдэгт, string |
presentment |
app (анхдагч — хүний token-д цорын ганц зөвшөөрөгдөх утга). qr/redirect/terminal — pay-with-gerege.md |
min_age |
13–21, апп-ынхаас багагүй — баталгаажуулах үед хүний насыг шалгана (403 age_restricted) |
capture_within |
зөвхөн manual: 60–604 800 секунд (анхдагч 7 хоног) — барьцааны хугацаа (§4) |
max_amount — нэг төлбөрийн дээд дүнг оператор апп тус бүрт тогтооно (PUT /api/v1/miniapp/review/apps/{id}/limits
{"max_amount": 500000}, 100–50 000 000, null = арилгах); хөгжүүлэгч өөрөө өөрчлөхгүй, консолын апп-ын
мэдээлэлд max_amount гэж харагдана. Хэтэрвэл 400 validation_error (details[0].path = "amount").
Тогтоогоогүй бол дээд хязгаар нь 50 000 000.
Мэдэхгүй талбар → 400. Live intent-ийг дараах үед үүсгэх үедээ татгалзана: core banking тохируулагдаагүй
(503 payments_unavailable), оператор payout данс тохируулаагүй (409 payouts_not_configured).
Хариу 201 (шинэ) / 200 (idempotent давталт):
{ "id": "pi_4fQ2mX8kLp0aZr7TbN3cVw9E", "object": "payment_intent", "status": "requires_confirmation",
"amount": 45000, "currency": "MNT", "description": "Japan 5GB 15 хоног", "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": null, "created_at": "2026-10-22T09:00:00Z", "metadata": { "package": "JC071" },
"presentment": "app", "terminal": null, "sub_merchant": null, "return_url": null, "claimed_at": null,
"min_age": null, "capture_within": null }
1.7.0-аас нэмэгдсэн талбарууд (бүх intent-д): presentment, terminal (терминалын id), sub_merchant (дэд
худалдагчийн id), return_url, claimed_at, min_age, capture_within (manual-д тоо, бусад null) — app intent-д
presentment: "app", бусад нь null (min_age, capture_within-ийг өгсөн бол тэр утга). Худалдагчийн claimless
intent-д user_ref claim хүртэл null (pay-with-gerege.md).
fee, net (OMAS) — оператор апп-ын тооцоог асаасан үед capture хийгдсэн live төлбөрт шимтгэл ба amount − fee;
хуучин зам (тооцоо унтраалттай), test горим, capture-ээс өмнө null (§7). OMAS-аас нэмэгдсэн: object,
capture_method, capture_before.
Бусад¶
| Method | Зам | Тайлбар |
|---|---|---|
| GET | /payment-intents/{id} |
Төлөв. Хугацаа нь дууссан бол тэр дор нь expired болж хариулна |
| GET | /payment-intents?reference=&status=&limit=&after= |
Шинэ нь эхэнд. limit 1–100 (анхдагч 20); дараагийн хуудас after=<өмнөх хуудасны сүүлийн id>. Хариу {data: [...], has_more} |
| POST | /payment-intents/{id}/capture |
Manual capture (§4). Аль хэдийн capture хийгдсэнийг байгаагаар нь 200 |
| POST | /payment-intents/{id}/cancel |
Төлөгдөөгүйг (requires_confirmation, failed) цуцлах, эсвэл manual-ын барьцааг суллах (requires_capture). Аль хэдийн canceled → 200 |
| POST | /payment-intents/{id}/refunds |
{amount?, reason?} + Idempotency-Key (§5) |
Төлөв¶
requires_confirmation ──хүн баталгаажуулна──▶ processing ──▶ succeeded ──▶ partially_refunded ──▶ refunded
│ ▲ │
│ └──────── failed ◀── татгалзсан ──────┤ (manual) ──▶ requires_capture ──capture──▶ succeeded
├──cancel──▶ canceled └──cancel / capture_before──▶ canceled / expired
└──expires_at──▶ expired
processing— ledger-ийн хариу хүлээгдэж байна. Алдаа ч биш, амжилт ч биш: дахин бүү төлүүл, дараа нь дахин унш (платформ өөрөө ижил idempotency key-ээр тулгаж дуусгана).failed— дахин төлөх боломжтой (хүн өөр данс сонгож болно);failure_codeшалтгааныг хэлнэ.requires_capture— manual intent-ийн мөнгө хүний дансанд барьцаалагдсан.
3. Баталгаажуулалт (host)¶
- Host intent-ийг өөрийн session-ээр татаж mini app-ын нэр/icon (registry-ээс), дүн, тайлбар-ыг өөрийн UI-д харуулна; хүн хэтэвчний дансаа сонгоно. Баталгаажуулалт: Face ID / нууц код.
- Үлдэгдэл хүрэлцэхгүй (
insufficient_funds) бол host өөр данс эсвэл цэнэглэлт санал болгоно. livemode: falseintent нь «ТЕСТ» шошготой.- Хүний дансны KYC хязгаар (нэг удаагийн / өдрийн / сарын, хүний KYC түвшнээр) барьцаа (hold)-нд хэрэглэгдэнэ:
хэтэрвэл баталгаажуулалт
403 limit_exceeded, intentfailed(failure_code: limit_exceeded,payment_intent.failed) — төлөгдөх боломжтой хэвээр (өөр данс, эсвэл дараа). Барьцаа хязгаарт нэг удаа тоологдоно; цуцлагдсан/хугацаа дууссан барьцаа хязгаарыг чөлөөлнө. Апп тус бүрийн өдрийн хязгаар — «удахгүй».
4. Manual capture¶
capture_method: "manual" — бараа/үйлчилгээг олгож чадах нь тодорхой болсон үед л мөнгө авах (eSIM
идэвхжих, суудал баталгаажих). Хүн баталгаажуулахад мөнгө барьцаалагдана → requires_capture +
payment_intent.requires_capture webhook. Дараа нь backend:
POST …/capture→succeeded(202болprocessing, дахин унш);- эсвэл
POST …/cancel→ барьцаа суллагдана (canceled; буцаалт биш). capture_before(баталгаажуулснаас 7 хоног, эсвэлcapture_withinсекунд) өнгөрвөл барьцаа автоматаар суллагдана →expired(failure_code: "authorization_expired"). Хугацаа өнгөрсний дараа capture →409 expired.- Capture үргэлж бүтэн дүнгээр. Дүнтэй хэсэгчилсэн capture (
{amount}) — «удахгүй»: бүтэн capture хийгээд үлдсэнийг буцаана (§5).
5. Буцаалт — POST /payment-intents/{id}/refunds¶
{"amount": 10000, "reason": "supplier_failed"} + Idempotency-Key (intent тус бүрт өвөрмөц).
amountорхивол үлдсэнийг бүгдийг; нийлбэр ≤ төлсөн дүн.reason≤ 255.- Зөвхөн
succeeded/partially_refundedintent-ийг, төлбөрөөс хойш 90 хоногийн дотор. - Хариу:
201(шинэ),200(давталт),202(pending— ledger-ийн хариу хүлээгдэж байна;refund.succeeded/refund.failedwebhook ирнэ).
{ "id": "re_2kLm9Pq4Rs6Tu8Vw0Xy1Za3B", "object": "refund", "intent_id": "pi_…", "amount": 10000,
"status": "succeeded", "reason": "supplier_failed", "failure_code": null, "livemode": true,
"created_at": "2026-10-22T09:00:00Z" }
Алдаа: 409 not_refundable, 409 refund_window_expired, 400 validation_error (дүн хэтэрсэн),
409 idempotency_conflict.
6. Test горим¶
- Credential хоёр хос:
test(mapp_test_…) баlive(mapp_…). Test credential-ээр үүссэн intentlivemode:false— мөнгө хөдлөхгүй, ledger-т хүрэхгүй. - Test intent-ийн үр дүнг
amount-ын сүүлийн хоёр орон шийднэ (хүн host-д баталгаажуулсны дараа):
amount төгсгөл |
Үр дүн |
|---|---|
…01 (жишээ 10001) |
failed, failure_code: "test_declined", payment_intent.failed |
…02 (жишээ 10002) |
canceled, payment_intent.canceled |
бусад, automatic |
succeeded, payment_intent.succeeded |
бусад, manual |
requires_capture, дараа нь capture → succeeded |
- Webhook test горимд ч илгээгдэнэ (
livemode:false). - Test credential зөвхөн бүртгэсэн гишүүн ба тест хэрэглэгчдийн нээлтэд ажиллана (auth.md §2).
- Апп-ыг огт нээлгүйгээр: консолоос test intent үүсгэж, host-ийн баталгаажуулалтыг тоглуулж болно
(
/dev/apps/{id}/test-intents,…/{pi}/confirm— console.md §3); дээрх дүнгийн дүрэм, webhook нь ижил.
7. Тооцоо (settlement)¶
Payout дансыг developer өөрөө өөрчилж чадахгүй — оператор тохируулна (PUT /api/v1/miniapp/review/apps/{id}/payout);
developer масктай дугаарыг л харна. Мөнгө хоёр замын аль нэгээр явна:
- Тооцоогүй (анхдагч): capture хийгдмэгц бүтэн дүн payout данс руу шууд орно; буцаалт тэр данснаас гарна;
fee/netньnull. - Тооцоотой (оператор апп-ыг асаасан): capture апп-ын клиринг данс руу (core banking-ийн GL) орж, хуваарийн дагуу тооцоогоор шимтгэлийг хасаад payout данс руу шилжинэ. Асаахаас өмнө capture хийгдсэн төлбөр хуучин замаараа үлдэнэ.
Шимтгэл ба хуваарь — апп бүрт оператор тогтооно (төлбөрийн дүрэм, хувилбартай; хүчин төгөлдөр болсон дүрмийг
хэзээ ч засахгүй, шинэ хувилбар үүсгэнэ). Консолд GET /api/v1/miniapp/dev/apps/{id}/payment-rule-ээр харна.
- Төлбөр бүрийн шимтгэл capture-ийн мөчид хүчинтэй дүрмээр:
round_half_up(amount × fee_bps / 10000) + fee_fixed, дараа ньfee_min-ээс багагүй,fee_max-аас ихгүй, төлбөрөөс ихгүй. Хагас нь дээш тойрно: 1.5% × 10 034 = 150.51 → 151, 1.5% × 100 = 1.5 → 2. - Буцаалт шимтгэлийг буцаахгүй, дүрэм
refund_fee: trueгэвэл буцаалтын хувиар буцаана (хэсэгчилсэн буцаалтуудын нийлбэр бүтэн буцаалтад шимтгэлтэй яг тэнцүү). - Хуваарь:
daily/weekdays/weekly:<mon…sun>/manual, УлаанбаатарынHH:MM-д, T+N: Улаанбаатарын d өдрийн төлбөр d+N өдрийн ажиллалтаас хойш. Дүрэмгүй бол шимтгэл 0, ажлын өдөр 10:00, T+1. - Holdback (нөөц) байхгүй.
Settle хийгдсэн төлбөрийн буцаалт клирингээс (тухайн апп-ын settle хийгдээгүй мөнгөнөөс) гарна. Клиринг
хүрэлцэхгүй бол буцаалт failed, failure_code: "settlement_shortfall" (refund.failed webhook) — дараа
дахин оролдоно уу эсвэл оператортой холбогдоно уу. Тооцооны дүнгээс хэтэрсэн буцаалт ба авах мөнгөгүй шимтгэл
дараагийн тооцоонд шилжинэ (clearing_carried_*, fee_carried_*).
API (OMAS 1.0.2) — Bearer access_token эсвэл client Basic, зөвхөн өөрийн апп, зөвхөн live (test credential
хоосон жагсаалт / 404):
| Method | Зам | Хариу |
|---|---|---|
| GET | /settlements?status=pending\|paid\|failed&limit=1–100&cursor= |
{items: Settlement[], next_cursor, has_more}, шинэ нь эхэнд |
| GET | /settlements/{id} |
Settlement + intents[] + refund_items[] + truncated (> 1000 мөр) |
{ "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": "<id>" | null — дэд худалдагчтай худалдагч апп-д тооцоо (апп, дэд худалдагч) тус бүрт тусдаа
(pay-with-gerege.md §8); null = апп-ын өөрийн урсгал.
net = gross − refunds − fee, paid = net − recovered (holdback байхгүй). fee — энэ тооцоонд үнэхээр авсан
шимтгэл (буцаасан нь их бол сөрөг); recovered — өмнөх тооцооноос хэтэрсэн буцаалтыг одоо суутгасан (сөрөг бол
дараагийнх руу шилжсэн). Тооцоо төлөгдмөгц settlement.paid webhook (webhooks.md); paid 0 байж болно.