Агуулгыг алгасах

API-ийн нийтлэг дүрэм

Энэ хуудсаар: бүх server API-д нийтлэг дүрмийг — хаяг, нэвтрэлт, орчин, формат, id, idempotency, давталт, хуудаслалт — нэг удаа уншаад, endpoint бүрийн хуудсанд дахин хайхгүй болно. 10 минут. Хэрэгтэй: консолоос авсан test API түлхүүр (gk_test_… / gks_test_…).

Хаяг ба хувилбар

  • Бүх server route https://geregeapp.mn/api/v1/miniapp угтвартай (SDK-уудын baseUrl / WithBaseURL-ийн анхдагч https://geregeapp.mn).
  • Major хувилбар нь замд (/api/v1). Minor хувилбар (модулийн хувилбар, жишээ нь 1.7.0) зөвхөн нэмнэ: шинэ талбар, шинэ утга, шинэ event, шинэ алдааны код. Тиймээс таны код мэдэхгүй талбар, event, кодыг үл тоох ёстой — JSON-оо хатуу (additionalProperties: false, DisallowUnknownFields) задлахгүй.
  • Хувилбар бүрийн өөрчлөлт: changelog (баримтын «Лавлах» хэсэгт гарна).

Нэвтрэлт

Хоёр төрлийн credential. Аль route юуг авахыг доорх хүснэгт хэлнэ.

Credential Толгой Хэн, хэзээ
Апп-ын API түлхүүр (client credential) Authorization: Basic base64(client_id:client_secret) таны сервер өөрийнхөө өмнөөс: худалдагчийн claimless intent, intent-ийн уншилт/үйлдэл, тооцоо, тайлан, терминал, дэд худалдагч, webhook endpoint, event, token endpoint
Хүний access token Authorization: Bearer gma_at_… (payments consent scope) mini app-ын backend хүний өмнөөс: app intent үүсгэх, GET /me; мөн intent-ийн уншилт/үйлдэл, тооцоо
Route Basic (түлхүүрийн scope) Bearer
POST /oauth/token ✓ oauth:token (эсвэл body-д client_id/client_secret — хоёуланг нь биш) —
GET /me — ✓
POST /payment-intents ✓ payments:write — зөвхөн qr · redirect · terminal ✓ — зөвхөн app
GET /payment-intents[/{id}] ✓ payments:read ✓
…/{id}/capture, …/{id}/cancel, …/{id}/unclaim ✓ payments:write ✓
…/{id}/refunds ✓ refunds:write ✓
GET /settlements[/{id}] ✓ settlements:read ✓
GET /statements ✓ settlements:read (зөвхөн Basic) —
GET /terminals[/{id}], POST /terminals/print-sheet ✓ terminals:read (зөвхөн Basic) —
POST /terminals, PATCH /terminals/{id}, POST /terminals/batch ✓ terminals:write (live түлхүүр) —
GET /sub-merchants[/{id}], …/{id}/payout-links ✓ sub_merchants:read —
POST /sub-merchants, PATCH /sub-merchants/{id} ✓ sub_merchants:write (live түлхүүр) —
POST /sub-merchants/{id}/payout-link ✓ payout_links:write (live түлхүүр) —
/webhook-endpoints… ✓ webhooks:write (live түлхүүр) —
GET /events[/{id}] ✓ events:read —
GET /pay/{token}, GET /pay/t/{code} нэвтрэлтгүй (нийтийн)

API түлхүүр (1.8.0). Апп орчин бүрт олон нэртэй түлхүүртэй (≤ 20 идэвхтэй), тус бүр өөрийн scope, сонголтоор IP allowlist ба дуусах хугацаатай. Консолын апп-ын «Түлхүүр» (/dev/apps/{id}/keys)-ээс үүсгэж, солиж (roll — хуучин нь давхцлын турш ажилласаар, 0 – 7 хоног, анхдагч 24 цаг), хүчингүй болгоно.

  • Client id: gk_live_ / gk_test_ + 20 base62 (нууц биш, log-д бичиж болно). Secret: gks_live_ / gks_test_ + 43 тэмдэгт base64url + _ + 6 тэмдэгт base62 checksum (түүнээс өмнөх бүх хэсгийн CRC32) — консолд нэг удаа харагдана, платформ зөвхөн SHA-256-ийг нь хадгална. Checksum нууц биш: secret scanner мэдээллийн сангүйгээр жинхэнэ хэлбэрийг таниулна (Аюулгүй байдлын checklist).
  • Хуучин хос (mapp_… / mapp_test_… + gma_cs_…) нь «Анхдагч түлхүүр» болж ажилласаар — тухайн орчны бүх scope-той. POST /dev/apps/{id}/credentials түүнийг шууд (давхцалгүй) солино.
  • Scope дутуу бол 403 insufficient_scope + {"required_scope": "refunds:write"} ба WWW-Authenticate: Basic error="insufficient_scope", scope="refunds:write" (401 биш — secret зөв). Түлхүүрийн scope өөрчлөгдөхгүй — өөр scope-той шинэ түлхүүр үүсгэнэ. Preset: «Бүрэн эрх», «Checkout сервер», «Вендинг / kiosk backend», «Терминал суулгагч», «Зөвхөн унших», «Mini app backend».
  • terminals:write, sub_merchants:write, payout_links:write, webhooks:write нь test түлхүүрт олгогдохгүй: бүртгэл нь хоёр орчинд нэг тул test түлхүүр бичвэл 403 live_only (scope-оос өмнө шалгагдана).
  • Түлхүүрийн IP allowlist-д ороогүй хаягаас — 403 ip_not_allowed (ip — харсан хаяг). Хаягийг платформ өөрөө тодорхойлно (итгэмжлэгдсэн proxy-ийн ард) — X-Forwarded-For-оо бүү тааварла.
  • Live түлхүүрийг үүсгэх, солих, IP-г нь өөрчлөх нь miniapp.keys.live ба дахин нэвтрэлт шаардана (403 fresh_sign_in_required); суулгац шалгаж чадахгүй үед 503 fresh_sign_in_unavailable.
  • Хүчингүй болсон, хугацаа нь дууссан, солигдоод давхцал нь өнгөрсөн түлхүүр — 401 invalid_client, буруу secret-ээс ялгагдахгүй.
  • Live түлхүүр нь апп батлагдсаны дараа л ажиллана; апп түдгэлзсэн/идэвхгүй бол бүх түлхүүр 401 invalid_client.
  • Апп зөвхөн өөрийн объектыг харна; өөр апп-ынх 404.
  • Төлбөр, бүртгэл, тайлан, /me-ийн хариу Cache-Control: no-store — завсрын cache хадгалахгүй.
  • Secret, token-ыг зөвхөн сервер дээр — Аюулгүй байдлын checklist.
curl -s https://geregeapp.mn/api/v1/miniapp/payment-intents?limit=5 \
  -u "$GEREGE_CLIENT_ID:$GEREGE_CLIENT_SECRET"

Орчин

  • Орчныг credential шийднэ: gk_test_… (хуучин mapp_test_…) → test, gk_live_… (mapp_…) → live; хүний token нь нээлтийн орчноо авч явна.
  • Объект бүрт livemode (true = live). Test түлхүүр live объектыг хэзээ ч харахгүй, эсрэгээрээ ч (404).
  • Test горимд мөнгө хөдлөхгүй; тест дүн, тест хэрэглэгч — Sandbox.
  • Терминал, дэд худалдагч нь хоёр орчинд нэг бүртгэл: test түлхүүр уншина, бичих нь live түлхүүр эсвэл консол (403 live_only). Webhook endpoint нь орчин бүрт тусдаа, гэхдээ server API-аар (/webhook-endpoints) зөвхөн live түлхүүр удирдана — test endpoint-ийг консолоос. Тооцоо зөвхөн live (test-д жагсаалт хоосон); тайлан (/statements) ба /events түлхүүрийн орчныг уншина.

Хүсэлтийн формат

  • Body нь JSON (Content-Type: application/json); token endpoint нь application/x-www-form-urlencoded эсвэл JSON.
  • Route-ийн авдаггүй талбар, JSON биш эсвэл хэт том body → 400 invalid_request (хэмжээ: Хязгаар). Энэ нь санаатай: буруу бичсэн талбар чимээгүй үл тоогдохгүй.
  • Хоосон утгатай заавал биш талбарыг орхих нь анхдагч утгыг авна (currency: "MNT", expires_in: 600, capture_method: "automatic").
  • Хариу JSON; «null болно» гэсэн талбар үргэлж байна (Объектууд).

Мөнгө ба цаг

  • Мөнгө — бүхэл ₮ (3500 = 3 500 ₮), бутархайгүй; валют одоогоор зөвхөн MNT.
  • Цаг — RFC 3339, UTC (2026-10-22T09:00:00Z). Тооцооны хуваарь, «өдөр» нь Улаанбаатарын цагаар (Тооцоо).
  • Хугацааг секундээр өгнө (expires_in, capture_within).

Id ба угтвар

Объект Хэлбэр
Payment intent · Refund · Event · Settlement · Webhook endpoint pi_ · re_ · evt_ · st_ · we_ + 24 base62
Терминал, дэд худалдагч, payout холболт, API түлхүүр (консолын id) UUID (жижиг үсгээр)
Терминалын code 10 тэмдэгт, 23456789abcdefghjkmnpqrstuvwxyz (0/o, 1/l/i-гүй — нүдээр уншина)
Хүн user_ref / sub (u_ + 32 hex): mini app-д апп бүрт тогтмол pairwise, claimless төлбөрт төлбөр бүрт санамсаргүй
Credential gk_live_, gk_test_ (client id), gks_live_, gks_test_ (secret); хуучин mapp_, mapp_test_, gma_cs_; gml_ (launch code), gma_at_, gma_rt_, whsec_

Буруу хэлбэрийн id нь 404 (400 биш). Угтвар нь secret scanner-т таних, буруу төрлийн утгыг эрт татгалзахад тусална.

Idempotency

Мөнгө хөдөлгөх хүсэлт Idempotency-Key толгой шаардана:

Хүсэлт Key-ийн хүрээ
POST /payment-intents апп + орчин бүрт давтагдашгүй
POST /payment-intents/{id}/refunds intent бүрт давтагдашгүй
Хүний POST /payments/{id}/confirm (Gerege App) intent бүрт
  • Утга: 1 – 128 хэвлэгдэх ASCII, зайгүй. Алга/буруу → 400 idempotency_key_required.
  • Ижил key + ижил body → 200 ба анх үүссэн объект (одоогийн төлвөөрөө; claimless intent-д ижил pay_url). Шинээр үүсвэл 201.
  • Ижил key + өөр body → 409 idempotency_conflict. app intent-д «body»-д хүн ч орно — өөр хүний token-оор ижил key бол зөрчил.
  • Зөвлөмж: нэг захиалга = нэг key — захиалга/сагсны дугаараа key (ба reference) болго (order:8812). Тэгвэл timeout-ийн дараах давталт хоёр дахь төлбөр үүсгэхгүй.
  • Бүртгэл нь өөрөөр idempotent: ижил reference-тэй терминал, ижил code-той дэд худалдагч дахин үүсгэвэл 200 ба байгаа мөр.
  • Capture, cancel, unclaim нь key-гүй ч дахин дуудахад аюулгүй: аль хэдийн capture хийгдсэнийг capture хийвэл байгаагаар нь, цуцлагдсаныг цуцалбал мөн.
curl -s https://geregeapp.mn/api/v1/miniapp/payment-intents \
  -u "$GEREGE_CLIENT_ID:$GEREGE_CLIENT_SECRET" \
  -H "Idempotency-Key: order:8812" -H "Content-Type: application/json" \
  -d '{"amount": 45000, "description": "Захиалга #8812", "reference": "order:8812", "presentment": "redirect", "return_url": "https://shop.mn/done"}'

Давталт

  • Сүлжээний алдаа, timeout, 429, 5xx → ижил key-ээр дахин (exponential backoff). SDK-д err.retryable / apiErr.Retryable().
  • 202 Accepted → ledger-ийн хариу хараахан тодорхойгүй (status: "processing" эсвэл буцаалт pending). Платформ өөрөө дуусгана; GET-ээр дахин унш эсвэл webhook хүлээ.
  • 409 in_progress → түр хүлээгээд дахин унш.
  • 4xx-ийг өөрчлөлтгүй давтахгүй — Алдааны код-ын «Давтах» багана.
  • SDK өөрөө давтахгүй — давталтын бодлого таных.

Хуудаслалт

Жагсаалт Хэлбэр Дараагийн хуудас
GET /payment-intents?reference=&status=&limit=&after= (шинэ нь эхэнд) {"data": [...], "has_more": bool} after = энэ хуудасны сүүлийн id
GET /settlements?status=&limit=&cursor= (шинэ нь эхэнд) {"items": [...], "next_cursor": string\|null, "has_more": bool} cursor = next_cursor
GET /events?type=&created_gte=&limit=&after= (хуучин нь эхэнд, 30 хоног) {"data": [...], "next_cursor": "evt_…"\|null} after = next_cursor; null — одоохондоо шинэ event алга, дараа нь сүүлийн id-аасаа дахин асуу
GET /terminals, GET /sub-merchants, …/payout-links, GET /webhook-endpoints {"data": [...]} хуудасгүй
GET /statements?from=&to=&format= нэг тайлан (data мөрүүд) хуудасгүй — хугацаагаар хуваана (≤ 100 000 мөр)

limit 1 – 100 (анхдагч 20; /events-д 50). Буруу утга → 400 invalid_limit / invalid_cursor / invalid_status. Node-д listAll() бүх хуудсыг дагана.

let after;
do {
  const page = await gerege.paymentIntents.list({ status: "succeeded", limit: 100, after });
  for (const pi of page.data) reconcile(pi);
  after = page.has_more ? page.data.at(-1).id : undefined;
} while (after);

Алдаа

  • Хоёр хэлбэр (OAuth ба модулийн), code-оор шийднэ — Алдааны код.
  • Хариунд X-Request-Id толгой байвал SDK түүнийг requestId / RequestID-д өгнө — тусламж хүсэхдээ хавсаргана (Тусламж). Secret-ийг хэзээ ч бүү хавсарга.