gov-pay
Webhook гарын авлага

Найдвартай хүлээн авах

Webhook event-ийг алдагдалгүй, давхардалгүй боловсруулах зөвлөмжүүд.

Webhook бол сүлжээгээр дамждаг тул хоцролт, давталт, түр алдаа гарах нь хэвийн зүйл. Доорх дөрвөн зарчмыг баримталснаар та event-ийг алдалгүй, давхардуулалгүй найдвартай хүлээн авах боломжтой.

1. 5 секундын дотор хурдан 2xx буцаа

gov-pay таны endpoint руу хүсэлт явуулахдаа 5 секундын timeout тогтоодог. Хэрэв та энэ хугацаанд 2xx хариу буцаахгүй бол хүргэлт амжилтгүй гэж тооцогдоно.

Тиймээс хариу буцаахдаа хүнд ажлыг битгий хий. Зөв загвар нь:

  1. Event-ийг хүлээн авч, гарын үсгийг шалга (Гарын үсэг шалгах).
  2. Payload-ийг queue / background job руу хий (эсвэл DB-д түр хадгал).
  3. Шууд 2xx буцаа.
  4. Бодит боловсруулалтыг (захиалга шинэчлэх, имэйл явуулах гэх мэт) async ажиллуул.

Гарт орж ирмэгц synchronous байдлаар DB бичих, гуравдагч системд хүсэлт явуулах зэрэг удаан ажил хийвэл timeout-д орж, gov-pay event-ийг "хүргэгдээгүй" гэж үзнэ. Хүнд ажлыг үргэлж async/queue руу шилжүүл.

app.post('/govpay/webhook', async (req, res) => {
  // 1. Гарын үсэг шалгах (доорх линкийг үз)
  if (!verifySignature(req)) {
    return res.status(401).send('invalid signature');
  }

  // 2. Queue-д хий, бодит ажлыг async хий
  await queue.add('govpay-event', {
    event: req.header('X-GovPay-Event'),
    body: req.body,
  });

  // 3. Хурдан 2xx
  res.status(200).send('ok');
});

2. Idempotent consumer бай — ижил event давтагдаж болно

Сүлжээний түр алдаа, timeout-ийн дараа дахин оролдлого зэргээс шалтгаалж нэг event олон удаа хүргэгдэх боломжтой. Тиймээс таны consumer idempotent байх ёстой: ижил event-ийг хоёр удаа боловсруулсан ч үр дүн нэг л байх ёстой.

Давхардлыг шүүх найдвартай түлхүүр нь payload доторх payment id. Энэ id-г аль хэдийн боловсруулсан эсэхээ шалгаж, давхардвал алгасаад 2xx буцаа.

async function handleEvent(event, payload) {
  // payment id-аар давхардлыг шүү
  const already = await db.processedEvents.findByPaymentId(payload.id);
  if (already) {
    return; // аль хэдийн боловсруулсан — алгас
  }

  // ... бодит боловсруулалт ...

  await db.processedEvents.insert({ paymentId: payload.id, event });
}

Зөвлөмж: processedEvents хүснэгтэд payment id-г unique constraint болгож тавь. Тэгвэл өрсөлдөгч (race) хүсэлт орж ирсэн ч өгөгдлийн сан давхар бичилтийг таслан зогсооно.

3. Гарын үсгийг үргэлж шалга

Хүсэлт бүр дээр дараах header ирнэ:

HeaderУтга
X-GovPay-EventEvent-ийн төрөл (payment.submitted, payment.confirmed)
X-GovPay-SignatureХүсэлтийн биеийн HMAC-SHA256 гарын үсэг (таны secret-ээр)

Гарын үсгийг шалгаагүй endpoint бол хэн ч хуурамч event илгээх боломжтой. Боловсруулалт хийхээсээ өмнө үргэлж гарын үсгийг баталгаажуул. Тооцоолох болон харьцуулах дэлгэрэнгүйг Гарын үсэг шалгах хуудаснаас үзнэ үү.

4. Аль болох хурдан хариул

Удаан хариу буцаах тусам timeout-д орох эрсдэл нэмэгдэнэ. Иймд:

  • Гарын үсэг шалгах болон queue-д хийхээс өөр ажлыг synchronous хэсэгт бүү хий.
  • Гуравдагч системийн дуудлага, имэйл, push notification зэргийг async хэсэгт шилжүүл.
  • Endpoint-аа боломжийн бага latency-тэй (CDN-ийн ард биш, шууд) байлга.

Event төрлүүд ба payload

gov-pay одоогоор хоёр төрлийн event илгээдэг. Webhook бүртгэхдээ events[] дотор сонгож авна.

EventХэзээ илгээгдэхPayload
payment.submittedТөлбөр төрийн системд илгээгдсэн{ id, invoiceId }
payment.confirmedMoF баталсан (төлбөр төлөгдсөн){ id, amount }

payment.submitted event:

{
  "id": "b4f1c2d8-1234-4a56-9bcd-0123456789ab",
  "invoiceId": "a1e2c3d4-5678-4b90-8cde-fedcba987654"
}

payment.confirmed event:

{
  "id": "b4f1c2d8-1234-4a56-9bcd-0123456789ab",
  "amount": 150000
}

Хоёр event дээр ч payment id нэг хэвээр байна. Энэ id-г idempotency болон төлбөрийн төлөв хянахдаа гол түлхүүр болгон ашигла. Төлбөрийн төлөвийн дэлгэрэнгүйг Төлбөр хуудаснаас үзнэ үү.

Хэрэв таны endpoint түр унтарсан үед event ирвэл та төлбөрийн төлвийг GET /payments/{id}-аар хүссэн үедээ дахин асуун шалгах боломжтой. Webhook бол хурдан мэдэгдэх хэрэгсэл; эцсийн үнэн нь үргэлж API-аас авсан төлөв.

Холбоотой хуудаснууд