15 минутад анхны test төлбөр¶
Юу хийх: test түлхүүрээр
qrтөлбөр (payment intent) үүсгэж, хүний талыг тоглуулж, webhook хүлээн авч, мөнгийг capture хийнэ. Хугацаа: ~15 минут. Мөнгө хөдлөхгүй — бүгд test горимд (livemode: false). Сервер тал:modules/miniapp1.9.0.
Энэ хуудсыг дуусгахад танд: худалдагч апп (kind: "merchant"), scope-той test API түлхүүр, гарын үсэг шалгадаг
webhook endpoint ба хүлээн авагч, requires_capture → succeeded болсон нэг test intent байна. Ойлголтуудыг
Төлбөрийн амьдралын мөчлөг,
Төлбөрийн урсгалууд ба claim-аас уншина.
0. Юу хэрэгтэй¶
| Gerege App-ын бүртгэл | geregeapp.mn-д eID-ээр нэвтэрсэн |
| Байгууллага | Таны админ эсвэл менежер байдаг байгууллага (workspace) — §1 |
| Хэрэгсэл | curl, jq, Python 3 (webhook хүлээн авагчид), cloudflared эсвэл ngrok (tunnel) |
Хуулийн этгээдгүй бол
Хувь хүнд зориулсан sandbox байгууллага (нэг товчоор, eID-ийн төлөөлөлгүй) — «удахгүй». Одоогоор
байгууллагатай хамт ажиллагчаасаа таныг гишүүнээр нэмүүлэх хүсэлт гаргана (/join/<slug> холбоос → админ
батална), эсвэл өөрийн компанийн нэрийн өмнөөс байгууллагаа нээнэ (eID-д байгууллагын ADMIN төлөөлөгчөөр
бүртгэлтэй хүн: профайл → Байгууллагууд).
1. «Mini апп-ууд»-ыг байгууллагадаа суулгах¶
Байгууллагын админ geregeapp.mn → байгууллагаа сонгоод → App Store (/apps) → «Mini апп-ууд»
(mn.gerege.miniapp) → Суулгах. Энэ нь хөгжүүлэгчийн консол ба Gerege App-аар төлөх API-г байгууллагад нээнэ.
Нэг удаа л хийнэ; суулгасан мөчөөс test горим бүхэлдээ ажиллана — review хэрэггүй.
2. Консолын API-д нэвтрэх¶
Худалдагч апп бүртгэх (§3) нь одоогоор консолын API-аар л (консолын бүртгэх цонхны «Худалдагч» сонголт — «удахгүй»);
доорх §4–§5-ыг ч энэ хуудас API-аар хийнэ. Консолын API нь таны хүний session-ээр (Authorization: Bearer)
ажиллана:
- geregeapp.mn-д байгууллагаа идэвхтэй болгоно (толгойн байгууллага солигч).
- Хөтчийн DevTools → Application (Storage) → Cookies →
https://geregeapp.mn→session_token-ийн утгыг хуулна.
Session бол таны бүрэн нэвтрэлт
Хэнд ч бүү өг, repo, лог, чатад бүү хуул. Хамгийн ихдээ 12 цаг амьдарна; гарахад (sign out) үхнэ. Зөвхөн
консолын (/dev/…) ба хүний талын тоглуулгад (§7) хэрэглэнэ — таны сервер API түлхүүрээр (§4) ярина.
3. Худалдагч апп бүртгэх¶
curl -sS "$GEREGE/dev/apps" \
-H "Authorization: Bearer $GEREGE_SESSION" -H 'Content-Type: application/json' \
-d '{"kind": "merchant", "slug": "my-shop-test", "name": "Миний дэлгүүр"}' | tee app.json | jq
export APP_ID=$(jq -r .id app.json)
Хүлээгдэх хариу 201 (товчилсон):
{
"id": "00000000-0000-4000-8000-000000000001",
"slug": "my-shop-test", "name": "Миний дэлгүүр", "kind": "merchant",
"scopes": ["profile.basic", "payments"], "category": "services",
"status": "draft", "review_status": "draft",
"live_client_id": "mapp_…", "test_client_id": "mapp_test_…",
"has_live_secret": false, "has_test_secret": false,
"webhook_url": null, "return_hosts": [], "payouts_configured": false, "version": 1
}
slug— 2–32 тэмдэгт,a-z,0-9,-, платформд нэг л:409 slug_takenбол өөр нэр. Хувийн sandbox байгууллагад slugsbx--ээр эхэлнэ (sbx-my-shop; Sandbox).- Худалдагч Gerege App-ын launcher-т хэзээ ч гарахгүй, manifest шаардахгүй (Худалдагч ба mini app).
4. Test API түлхүүр авах¶
Preset «Checkout сервер» (checkout_server) = payments:read, payments:write, refunds:write, events:read — энэ
хуудсанд хэрэгтэй бүх эрх:
curl -sS "$GEREGE/dev/apps/$APP_ID/keys" \
-H "Authorization: Bearer $GEREGE_SESSION" -H 'Content-Type: application/json' \
-d '{"name": "quickstart", "environment": "test", "preset": "checkout_server"}' | tee test-key.json | jq
export GEREGE_CLIENT_ID=$(jq -r .client_id test-key.json)
export GEREGE_CLIENT_SECRET=$(jq -r .client_secret test-key.json)
{ "id": "…", "object": "api_key", "name": "quickstart", "environment": "test",
"client_id": "gk_test_…", "secret_hint": "…", "status": "active", "legacy": false,
"scopes": ["payments:read", "payments:write", "refunds:write", "events:read"], "allowed_ips": [],
"client_secret": "gks_test_…", "notice": "Энэ secret-ийг дахин харуулахгүй — одоо хадгална уу" }
Secret нэг удаа харагдана (сервер зөвхөн SHA-256-ийг нь хадгална; secret_hint нь сүүлийн 4 тэмдэгт — аль нь
болохыг танихад). Үүнээс хойшх бүх худалдагчийн дуудлага Authorization: Basic base64(client_id:client_secret) — curl-д
-u. Scope дутвал 403 insufficient_scope + required_scope. Солих (…/keys/{kid}/roll) ба хүчингүй болгох
(…/keys/{kid}/revoke) — консолоос. Хуучин POST /dev/apps/{id}/credentials (mapp_test_… / gma_cs_…, бүх эрхтэй)
ажилласаар; live түлхүүр одоогоор тэр замаар л (шинэ API-д дахин нэвтрэлт шаардана — «удахгүй»).
Secret зөвхөн таны сервер дээр: браузер, мобайл апп, терминал, машинд хэзээ ч (Аюулгүй байдлын загвар).
5. Webhook хүлээн авагч¶
Gerege webhook-ийг зөвхөн нийтийн https хаяг руу илгээнэ — localhost руу шууд биш. Тиймээс локал серверээ
tunnel-ээр гаргана.
5.1 Tunnel (тусдаа терминалд) — хэвлэсэн https://….trycloudflare.com хаягийг тэмдэглэнэ:
5.2 Test орчны webhook endpoint үүсгэж, secret авна (event_types хоосон = бүх event; апп-д орчин бүрт ≤ 5
endpoint, тус бүр өөрийн secret-тэй):
export TUNNEL=https://<таны-tunnel>.trycloudflare.com
curl -sS "$GEREGE/dev/apps/$APP_ID/webhook-endpoints" \
-H "Authorization: Bearer $GEREGE_SESSION" -H 'Content-Type: application/json' \
-d "{\"url\": \"$TUNNEL/gerege\", \"environment\": \"test\", \"event_types\": []}" | tee webhook.json | jq
export ENDPOINT_ID=$(jq -r .endpoint.id webhook.json)
export GEREGE_WEBHOOK_SECRET=$(jq -r .webhook_secret webhook.json)
{ "endpoint": { "id": "we_…", "object": "webhook_endpoint", "environment": "test", "livemode": false,
"url": "https://<таны-tunnel>.trycloudflare.com/gerege", "event_types": [], "status": "active" },
"webhook_secret": "whsec_…", "notice": "Энэ secret-ийг дахин харуулахгүй — одоо хадгална уу" }
5.3 Хүлээн авагч — webhook.py (Python-ы стандарт сан л):
import hashlib, hmac, json, os, time
from http.server import BaseHTTPRequestHandler, HTTPServer
SECRET = os.environ["GEREGE_WEBHOOK_SECRET"].encode()
def verify(header: str, body: bytes) -> bool:
t, sigs = None, []
for part in header.split(","):
key, _, value = part.strip().partition("=")
if key == "t" and value.isdigit():
t = int(value)
elif key == "v1":
sigs.append(value)
if t is None or abs(time.time() - t) > 300: # 5 минутаас хуучин бол replay
return False
want = hmac.new(SECRET, str(t).encode() + b"." + body, hashlib.sha256).hexdigest()
return any(hmac.compare_digest(want, s) for s in sigs)
class Hook(BaseHTTPRequestHandler):
def do_POST(self):
body = self.rfile.read(int(self.headers.get("Content-Length", 0))) # түүхий byte — parse-ээс өмнө
if not verify(self.headers.get("Gerege-Signature", ""), body):
self.send_response(400); self.end_headers(); return
event = json.loads(body)
obj = event["data"]["object"]
print(event["id"], event["type"], obj.get("id"), obj.get("status"), flush=True)
self.send_response(204); self.end_headers() # 2xx-ыг 10 с дотор
HTTPServer(("127.0.0.1", 8787), Hook).serve_forever()
5.4 Шалгах — гарын үсэгтэй webhook.test-ийг одоо илгээнэ:
curl -sS -X POST "$GEREGE/dev/apps/$APP_ID/webhook-endpoints/$ENDPOINT_ID/test" \
-H "Authorization: Bearer $GEREGE_SESSION" | jq '{ok, status_code}'
{"ok": true, "status_code": 204} гарч, хүлээн авагчийн терминалд evt_… webhook.test None None
хэвлэгдэнэ. Гарын үсгийн бүх дүрэм: Webhook.
6. qr intent үүсгэх¶
Таны сервер сагс бүрт нэг intent үүсгэнэ. Энэ жишээ manual capture — мөнгийг эхлээд барьцаалж (hold), бараа өгсний дараа л авна (Барьцаа ба capture).
curl -sS "$GEREGE/payment-intents" -u "$GEREGE_CLIENT_ID:$GEREGE_CLIENT_SECRET" \
-H 'Content-Type: application/json' -H 'Idempotency-Key: order-1001' \
-d '{"amount": 3500, "description": "Захиалга №1001", "reference": "order-1001",
"presentment": "qr", "expires_in": 600,
"capture_method": "manual", "capture_within": 300}' | tee pi.json | jq
export PI=$(jq -r .id pi.json)
export PAY_TOKEN=$(jq -r '.pay_url | split("/") | last' pi.json)
Хүлээгдэх хариу 201:
{
"id": "pi_8Hn2Wq4Zc6Xv1Bm3Np5Rt7Ly",
"object": "payment_intent",
"status": "requires_confirmation",
"amount": 3500,
"currency": "MNT",
"description": "Захиалга №1001",
"reference": "order-1001",
"user_ref": null,
"livemode": false,
"capture_method": "manual",
"refunded_amount": 0,
"fee": null,
"net": null,
"failure_code": null,
"expires_at": "2026-10-27T03:10:00Z",
"capture_before": null,
"confirmed_at": null,
"created_at": "2026-10-27T03:00:00Z",
"metadata": {},
"presentment": "qr",
"terminal": null,
"sub_merchant": null,
"return_url": null,
"claimed_at": null,
"min_age": null,
"capture_within": 300,
"pay_url": "https://geregeapp.mn/pay/Qk3vX9aB2cD4eF6gH8jK0mNp"
}
- Ижил
Idempotency-Key+ ижил body-оор дахин илгээвэл200— ижил intent, ижилpay_url; өөр body →409 idempotency_conflict(Давталт). pay_urlнь зөвхөн энэ хариунд (ба давталтад) байна —GET, жагсаалт, webhook-д хэзээ ч. Түүнд дүн, хүлээн авагч байхгүй; лог-д бүү бич.- Бодит касс энэ
pay_url-ийг дэлгэцэндээ QR болгон зурна (qrencode -t ansiutf8 "$(jq -r .pay_url pi.json)").
7. Хүний тал — claim ба баталгаажуулалт¶
Бодит амьдралд хүн QR-ийг Gerege App-аар уншуулж, төлөх цонхонд Face ID дарна. Апп ард нь хоёр дуудлага хийдэг: claim (intent-ийг өөртөө авах) ба confirm (данс сонгож төлөх).
Утсаар уншуулах
Gerege App (iOS)-ын апп доторх QR товч ба NFC уншигч нь pay_url-ийг claim хийж төлөх цонхыг нээнэ — TestFlight
121-ээс хойшх build-д. Утасны камераар уншуулах (Universal Link) ба компьютер дээрх /pay хуудас — «удахгүй».
Таны утсанд тэр build байхгүй бол апп-ын хийдэг хоёр дуудлагыг өөрийн session-ээр доорх шиг хийнэ — сервер
дээрх зам яг ижил. Test intent-ийг зөвхөн апп-ыг бүртгэсэн хүн (та) ба тест хэрэглэгчид claim хийнэ; бусдад тэр
intent байхгүй мэт (404 unknown_payment).
7.1 Claim:
curl -sS "$GEREGE/payments/claim" -H "Authorization: Bearer $GEREGE_SESSION" \
-H 'Content-Type: application/json' -d "{\"token\": \"$PAY_TOKEN\"}" | jq
{
"id": "pi_8Hn2Wq4Zc6Xv1Bm3Np5Rt7Ly", "status": "requires_confirmation",
"amount": 3500, "currency": "MNT", "description": "Захиалга №1001",
"livemode": false, "capture_method": "manual",
"merchant": {"slug": "my-shop-test", "name": "Миний дэлгүүр", "icon_url": ""},
"account_id": null, "failure_code": null,
"expires_at": "2026-10-27T03:10:00Z", "confirmed_at": null, "created_at": "2026-10-27T03:00:00Z",
"presentment": "qr", "terminal": null, "sub_merchant": null, "return_url": null
}
Хүлээн авагчид: evt_… payment_intent.claimed pi_… requires_confirmation. Баталгаажаагүй claim 90 секундэд
дуусна — дараа нь өөр хүн авч болно; тэр хүртэл дахин claim хийх нь танд idempotent.
7.2 Confirm — төлөх данс (Gerege Wallet) сонгоно:
export ACCOUNT=$(curl -sS https://geregeapp.mn/api/v1/bank/me/accounts \
-H "Authorization: Bearer $GEREGE_SESSION" | jq -r '.accounts[0].id')
curl -sS -X POST "$GEREGE/payments/$PI/confirm" -H "Authorization: Bearer $GEREGE_SESSION" \
-H 'Content-Type: application/json' -H "Idempotency-Key: confirm-$PI" \
-d "{\"account_id\": \"$ACCOUNT\"}" | jq '{id, status, account_id}'
Хүлээн авагчид: evt_… payment_intent.requires_capture pi_… requires_capture. Test горимд мөнгө хөдлөхгүй — дүнгийн
сүүлийн хоёр орон үр дүнг шийднэ: …01 → failed (test_declined), …02 → canceled, бусад → энд шиг
(Test ба live).
8. Capture¶
Бараагаа өгсөн (эсвэл үйлчилгээгээ үзүүлсэн) бол мөнгийг авна — capture_within (энд 300 с) дотор:
curl -sS -X POST "$GEREGE/payment-intents/$PI/capture" -u "$GEREGE_CLIENT_ID:$GEREGE_CLIENT_SECRET" \
| jq '{id, status, capture_before, confirmed_at}'
{ "id": "pi_8Hn2Wq4Zc6Xv1Bm3Np5Rt7Ly", "status": "succeeded",
"capture_before": "2026-10-27T03:06:12Z", "confirmed_at": "2026-10-27T03:01:40Z" }
Хүлээн авагчид: evt_… payment_intent.succeeded pi_… succeeded. Бараа гараагүй бол capture-ийн оронд
POST /payment-intents/$PI/cancel → canceled, барьцаа хүнд буцна. Юу ч хийгээгүй бол capture_before
өнгөрөхөд барьцаа өөрөө суллагдаж payment_intent.expired (failure_code: "authorization_expired") ирнэ.
9. Шалгах ба тулгах¶
Webhook-д сохроор бүү итгэ — чухал алхмын өмнө төлвийг өөрийн түлхүүрээр дахин уншина:
curl -sS "$GEREGE/payment-intents/$PI" -u "$GEREGE_CLIENT_ID:$GEREGE_CLIENT_SECRET" | jq '{status, amount, reference, user_ref}'
curl -sS "$GEREGE/events?limit=20" \
-u "$GEREGE_CLIENT_ID:$GEREGE_CLIENT_SECRET" | jq -r '.data[] | "\(.id) \(.type)"'
user_ref нь энэ нэг төлбөрийн санамсаргүй нэр (u_<32 hex>) — хүний нэр, регистр, данс танд хэзээ ч ирэхгүй
(Хүний мэдээлэл). Events API (GET /events, events:read) нь webhook-ийн яг тэр body-г
түлхүүрийн орчноор, хуучнаас нь 30 хоног буцаана — webhook алдсан (сервер унтарсан, машин offline) үед энэ жагсаалтаар
тулгана. Хүргэлтийн лог ба дахин илгээх — консолд (Хөгжүүлэгчийн sandbox).
10. Алдаа гарвал¶
| Хариу | Шалтгаан | Засах |
|---|---|---|
401 {"error": "invalid_client"} |
client id/secret буруу, хүчингүй/хугацаа дууссан түлхүүр; эсвэл live түлхүүрийг апп батлагдахаас өмнө | §4-ийн test түлхүүрийг ашигла |
403 insufficient_scope (required_scope) |
түлхүүрт тэр scope алга | шинэ түлхүүр (scope өөрчлөгдөхгүй) |
400 idempotency_key_required |
Idempotency-Key толгой алга (1–128 тэмдэгт) |
захиалгын дугаараа өг |
400 presentment_required |
Basic-аар presentment-гүй intent |
qr / redirect / terminal |
400 validation_error |
details[].path талбар буруу (дүн 100–50 000 000, тайлбар 1–80, expires_in 60–1800, capture_within 60–604 800) |
талбараа засна |
404 unknown_payment (claim) |
token буруу, эсвэл test intent-ийг апп-ын эзэн/тест хэрэглэгч биш хүн | өөрийн session-ээ шалга |
409 already_claimed (retry_after: 90) |
өөр хүн claim хийсэн | 90 с хүлээ, эсвэл худалдагч unclaim |
410 expired |
expires_at өнгөрсөн |
шинэ intent |
409 not_capturable |
manual биш, эсвэл хараахан requires_capture биш |
төлвийг GET-ээр шалга |
401 unauthorized (консол) |
session дууссан эсвэл буруу | §2-ыг давт |
503 no_secret_key · pay_url_unavailable |
суулгацын тохиргоо | бидэнтэй холбогдоно уу |
Бүх код: Алдааны код.
11. Дараа нь¶
- Байнгын sticker / NFC / вендинг: Терминалаар test төлбөр.
- Вэб checkout (
redirect), олон эзэмшигч, тооцоо: Gerege App-аар төлөх. - Аль урсгал танд тохирох: Урсгалаа сонгох.
- Live болох: байгууллагын гэрээ, бизнесийн профайл (KYB), апп-ын review, live түлхүүр — Test ба live.