Вэб дэлгүүр — redirect checkout¶
Энэ хуудсаар
Юу хийх: вэб дэлгүүрийнхээ «Gerege App-аар төлөх» товчийг холбож, захиалгыг зөвхөн серверээрээ шалгасны
дараа биелүүлэх. Хугацаа: ~1 цаг (test түлхүүрээр). Хэрэгтэй: худалдагч апп (kind: "merchant") ба
түүний test credential (бүртгэл), HTTPS-тэй backend, буцах хуудсын host.
Хүн таны сайтад «Gerege App-аар төлөх» дарна → таны backend redirect intent үүсгэнэ → браузер pay_url руу
очно → хүн Gerege App-аар төлнө → апп таны return_url-ийг ?pi=<intent id>-тэй нээнэ → таны backend intent-ийг
Gerege-ээс дахин уншиж захиалгаа биелүүлнэ.
1. Урсгал¶
Браузер Таны backend Gerege Хүний утас (Gerege App)
│ «Gerege App-аар төлөх» │ │ │
│────────────────────────▶│ POST /payment-intents │ │
│ │ {presentment:"redirect", …} │ │
│ │─────────────────────────────────▶│ │
│ │◀──── 201 {id, pay_url, …} ───────│ │
│◀── 303 Location: pay_url│ │ │
│──── GET pay_url ────────────────────────────────────────────▶ утсанд: Universal Link → апп ──────▶│
│ │ │ компьютерт: QR хуудас → утсаар ──▶│
│ │ │◀── claim → Face ID → төлөгдөнө ───│
│ │◀── webhook payment_intent.succeeded │
│◀──────────── апп return_url?pi=pi_… нээнэ ─────────────────────────────────────────────────────│
│── GET return_url?pi= ──▶│ GET /payment-intents/{id} │ │
│ │─────────────────────────────────▶│ status, amount, reference │
│◀── «Төлөгдлөө» ─────────│ (таарвал биелүүлнэ) │ │
- Утсан дээрх браузер:
pay_urlньhttps://geregeapp.mn/pay/<token>— Universal Link тул Gerege App шууд нээгдэнэ (цөмийн AASA/pay/*production-д гармагц — §9; тэр болтол Safari хуудас нээгдэж болно). - Компьютер: тэр хуудас QR харуулна; хүн утасныхаа Gerege App-аар уншуулна. Хуудас төлөвөө
GET /api/v1/miniapp/pay/{token}-оор (session-гүй, нийтийн) шалгаж «Утсаараа баталгаажуулна уу» → «Төлөгдлөө» гэж солигдоно («удахгүй» — цөмийн/payхуудас). - Хэзээ ч:
pay_url-ийг iframe-д ачаалахгүй, popup-д нээхгүй — нэг таб, ердийн навигаци.
2. Нэг удаагийн тохиргоо¶
- Худалдагч апп ба test түлхүүр (preset «Checkout сервер») — Gerege App-аар төлөх §2.
Түлхүүрийг серверийн орчны хувьсагчид (
GEREGE_CLIENT_ID,GEREGE_CLIENT_SECRET) хадгална — repo-д, браузерт, мобайл апп-д хэзээ ч. - Буцах host-ууд (
return_hosts) —return_url-ийн host нь энд байх ёстой, үгүй бол intent үүсгэх нь400 validation_error(details[0].path = "return_url"). Хөгжүүлэгчийн консолоос (workspace-ийн session; API ньPATCH /api/v1/miniapp/dev/apps/{id}):
≤ 5, яг host (scheme, port, path, *.-гүй; www.shop.mn тусдаа host). Ноорог эсвэл татгалзсан үед л
өөрчлөгдөнө (бусад үед 409 needs_review) — төлсний дараа хүнийг хаашаа
илгээхийг review харсан байх ёстой. Host-ын эзэмшлийг /.well-known/gerege-merchant.txt-ээр баталгаажуулах —
«удахгүй».
3. Webhook URL — POST …/dev/apps/{id}/webhook {"url": "https://shop.mn/webhooks/gerege"} → whsec_… нэг
удаа (Webhooks). Review-д илгээхэд идэвхтэй live endpoint заавал — энэ (№1), эсвэл
endpoint API-аар нэмсэн аль нэг.
4. return_url нь https:// (production-аас бусад суулгацад http://localhost болно), ≤ 2048 тэмдэгт, userinfo-гүй.
3. Захиалга бүрт intent үүсгэх¶
Дүнг өөрийн каталогоос тооцно — браузерийн илгээсэн тоог хэзээ ч биш. Idempotency-Key заавал: захиалгын
дугаар (дахин оролдлого бүрт order-8812-1, order-8812-2) — сүлжээ тасарч дахин илгээвэл тэр intent, тэр
pay_url буцна (200), хоёр дахь intent үүсэхгүй.
curl https://geregeapp.mn/api/v1/miniapp/payment-intents \
-u "$GEREGE_CLIENT_ID:$GEREGE_CLIENT_SECRET" \
-H 'Content-Type: application/json' -H 'Idempotency-Key: ord_8812-1' \
-d '{"amount": 45000, "description": "Захиалга ord_8812", "reference": "ord_8812", "expires_in": 900,
"presentment": "redirect", "return_url": "https://shop.mn/orders/ord_8812/done"}'
{
"id": "pi_3kQ9vR2mXn7LpT4wYb8cZd1e", "object": "payment_intent", "status": "requires_confirmation",
"amount": 45000, "currency": "MNT", "description": "Захиалга ord_8812", "reference": "ord_8812",
"user_ref": null, "livemode": false, "capture_method": "automatic", "refunded_amount": 0,
"fee": null, "net": null, "failure_code": null, "expires_at": "2026-10-27T09:15:00Z", "capture_before": null,
"confirmed_at": null, "created_at": "2026-10-27T09:00:00Z", "metadata": {},
"presentment": "redirect", "terminal": null, "sub_merchant": null,
"return_url": "https://shop.mn/orders/ord_8812/done", "claimed_at": null, "min_age": null, "capture_within": null,
"pay_url": "https://geregeapp.mn/pay/Qk3vN8pLx2Rt7YwZb4Mc9Hs1"
}
| Талбар | Дүрэм |
|---|---|
amount |
бүхэл ₮, 100 – 50 000 000 (апп-ын операторын max_amount-аас ихгүй) |
description |
1–80 тэмдэгт, хүний хэтэвчний хуулгад харагдана (Gerege App-ын төлөх цонх худалдагчийн description-ийг харуулахгүй) — хувийн мэдээлэл бичихгүй |
reference |
≤ 64, таны захиалгын дугаар; GET /payment-intents?reference= хайна |
expires_in |
60–1800 с, анхдагч 600. Хүн энэ хугацаанд төлөөгүй бол expired |
return_url |
redirect-д заавал; host нь return_hosts-д |
pay_url нь claim-ийн bearer secret: зөвхөн энэ хариунд (ба ижил key-ийн давталтад) байна — GET, жагсаалт,
webhook-д хэзээ ч үгүй. Лог-д бичихгүй, хүнээс өөр хэнд ч харуулахгүй.
4. Хүнийг pay_url руу илгээх¶
Backend хариуд нь 303 See Other + Location: <pay_url> буцаана (эсвэл frontend нь backend-ээс pay_url-ийг
аваад window.location.assign). Товчийг дарсны дараа давхар даралтаас хамгаална — давхар хүсэлт ижил
Idempotency-Key-тэй бол ижил intent тул аюулгүй, гэхдээ товчийг идэвхгүй болгох нь хүнд ойлгомжтой.
«Gerege App-аар төлөх» товчны брэндийн дүрэм ба asset (лого, өнгө, хэмжээ) — «удахгүй». Тэр болтол: товчны текст яг «Gerege App-аар төлөх», өөр төлбөрийн аргатай ижил хэмжээтэй.
5. Буцаж ирэхэд серверээр шалгах¶
Gerege App төлсний дараа return_url-д pi параметр нэмж нээнэ (return_url-д байсан query хэвээр:
…/done?o=7 → …/done?o=7&pi=pi_…). Энэ URL-ийг хэн ч гараар бичиж болно — тиймээс юуг ч шийдэхгүй:
- Захиалгынхаа хадгалсан intent id-г ав (
piзөвхөн сануулга; таны захиалгын intent-тэй таарахгүй бол үл тоо). GET /payment-intents/{id}(Basic) —status,amount,reference-ийг захиалгатайгаа тулга.status→ дэлгэц:
status |
Хүнд | Таны backend |
|---|---|---|
succeeded |
«Төлөгдлөө» | захиалгыг нэг удаа биелүүл (webhook-оос ч ирж болно — идэвхтэй түгжээ эсвэл төлөвийн шалгалт) |
requires_confirmation, processing |
«Gerege App дээр баталгаажуулна уу…» (3 с тутам шинэчлэх) | хүлээнэ |
failed |
«Амжилтгүй — апп дотроо дахин оролдоно уу» | хүн хугацаа дуустал дахин төлж болно |
expired, canceled |
«Төлбөр хийгдээгүй» + «Дахин төлөх» | шинэ оролдлого = шинэ intent (ord_8812-2) |
6. Webhook¶
Хүн төлсөн ч таны хуудас руу буцаж ирэхгүй байж болно (апп хаасан, сүлжээ тасарсан). Тиймээс биелүүлэлтийн гол зам
нь webhook payment_intent.succeeded, return_url бол хүнд үр дүнг харуулах зам:
- Гарын үсгийг түүхий body-оор шалгана,
id-аар давхардлыг арилгана, 10 с дотор2xx(Webhooks). - Event-ийн
data.object-д итгэхийн оронд intent-ийгGET-ээр дахин уншаад шийдэх нь хамгийн найдвартай. payment_intent.expired— захиалгыг «төлөгдөөгүй» болгож, хүсвэл дахин санал болгоно.payment_intent.failed— эцсийн биш: хүн хугацаа дуустал дахин оролдож болно.
7. Цуцлах, хугацаа дуусах, дахин оролдох¶
- Хүн захиалгаа цуцалсан (таны сайт дээр) →
POST /payment-intents/{id}/cancel. Төлөгдсөн intent цуцлагдахгүй (409 not_cancelable) — тэр үед буцаалт:POST /payment-intents/{id}/refunds+Idempotency-Key. - Хугацаа дууссан → дахин оролдлого бүрт шинэ
Idempotency-Key(ord_8812-2),referenceхэвээр — нэг захиалгын бүх оролдлогыгGET /payment-intents?reference=ord_8812олно. - Хоёр оролдлого хоёулаа төлөгдсөн (хуучин таб дээр төлсөн) —
reference-ээр илрүүлж илүүг нь буцаана. - Сүлжээний алдаа,
429,5xx→ ижилIdempotency-Key-ээр дахин илгээнэ (алдаа).
8. Бүтэн жишээ¶
examples/node/src/checkout.ts ба examples/go/cmd/checkout/main.go (энэ репод) — каталог, захиалга, redirect,
return_url дээрх шалгалт, webhook. Test түлхүүрээр ажиллана:
export GEREGE_CLIENT_ID=gk_test_… GEREGE_CLIENT_SECRET=gks_test_… GEREGE_WEBHOOK_SECRET=whsec_…
export PUBLIC_URL=https://shop.example.mn # host нь return_hosts-д
cd examples/go && go run ./cmd/checkout # эсвэл: cd examples/node && npm run build && npm run checkout
Гол хэсэг нь (Node, @gerege/miniapp-node):
const pi = await gerege.paymentIntents.create(
{ presentment: "redirect", amount: order.amount, description: `Захиалга ${order.id}`, reference: order.id,
expires_in: 900, return_url: `${publicUrl}/orders/${order.id}/done` },
{ idempotencyKey: `${order.id}-${order.attempt}` },
);
res.writeHead(303, { Location: pi.pay_url }).end();
// return_url дээр: URL-д биш, Gerege-ийн хариунд итгэнэ.
const cur = await gerege.paymentIntents.retrieve(order.intent);
if (cur.status === "succeeded" && cur.amount === order.amount && cur.reference === order.id) fulfil(order);
Go (sdk/go, package miniapp):
pi, err := c.PaymentIntents.Create(ctx, key, miniapp.CreatePaymentIntentParams{
Amount: o.Amount, Description: "Захиалга " + o.ID, Reference: o.ID, ExpiresIn: 900,
Presentment: miniapp.PresentmentRedirect, ReturnURL: public + "/orders/" + o.ID + "/done",
})
if err == nil {
http.Redirect(w, r, pi.PayURL, http.StatusSeeOther)
}
9. Хэрэгжилтийн төлөв¶
| Хэсэг | Төлөв |
|---|---|
redirect intent, return_hosts, return_url?pi=, webhook, idempotency |
✓ (miniapp 1.7.0) |
Gerege App iOS: /pay/<token> → claim → төлөх цонх → төлсний дараа return_url?pi=-ийг системээр нээх |
✓ (Universal Link — цөмийн AASA /pay/* production-д гармагц) |
Компьютерт https://geregeapp.mn/pay/<token> QR хуудас (цөмийн shell) |
«удахгүй» (API нь бэлэн: GET /api/v1/miniapp/pay/{token}) |
/.well-known/gerege-merchant.txt-ээр host баталгаажуулах; товчны брэндийн asset |
«удахгүй» |
| Android Gerege App | түр зогссон — Android дээр pay_url вэб хуудсаа нээнэ |
Шалгах жагсаалт: дүн серверээс · Idempotency-Key = захиалга + оролдлого · pay_url лог-д алга · return_url дээр
GET-ээр шалгадаг · webhook гарын үсэг + dedupe · биелүүлэлт нэг удаа · expired/canceled үед дахин төлөх зам.