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

Вэб дэлгүүр — 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. Нэг удаагийн тохиргоо

  1. Худалдагч апп ба test түлхүүр (preset «Checkout сервер») — Gerege App-аар төлөх §2. Түлхүүрийг серверийн орчны хувьсагчид (GEREGE_CLIENT_ID, GEREGE_CLIENT_SECRET) хадгална — repo-д, браузерт, мобайл апп-д хэзээ ч.
  2. Буцах 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}):
{ "return_hosts": ["shop.mn", "www.shop.mn"] }

≤ 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-ийг хэн ч гараар бичиж болно — тиймээс юуг ч шийдэхгүй:

  1. Захиалгынхаа хадгалсан intent id-г ав (pi зөвхөн сануулга; таны захиалгын intent-тэй таарахгүй бол үл тоо).
  2. GET /payment-intents/{id} (Basic) — status, amount, reference-ийг захиалгатайгаа тулга.
  3. 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 үед дахин төлөх зам.