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

SDK-гүй хэлүүд

Node, Go-оос өөр хэлээр (PHP, Python, Java, C#, Ruby …) холбогдох нь энгийн: API нь ердийн HTTPS + JSON, нэвтрэлт нь HTTP Basic, webhook-ийн гарын үсэг нь нэг HMAC. Энэ хуудсаар та өөрийн хэлэндээ жижиг client ба webhook шалгагч бичнэ — 30–60 минут.

  • Хэрэгтэй: апп-ын API түлхүүр — client_id (gk_test_… / gk_live_…, эсвэл «Анхдагч түлхүүр»-ийн mapp_test_… / mapp_…) ба secret (gks_… / gma_cs_…), webhook endpoint-ийн secret (whsec_…), HTTP client, HMAC-SHA256 бүхий crypto сан.

1. HTTP API товчоор

Зүйл Дүрэм
Суурь URL https://geregeapp.mn/api/v1/miniapp
Нэвтрэлт Authorization: Basic base64(client_id:secret) — ганц зам. Түлхүүр бүр scope-той: route-ийн scope дутуу бол 403 insufficient_scope + required_scope («Анхдагч түлхүүр» тухайн орчны бүх scope-той). Test ба live түлхүүр тус тусдаа — түлхүүрийн орчин л харагдана (livemode)
Mini app-ын хүний token Authorization: Bearer gma_at_… (app intent үүсгэх, /me) — нээлт ба token
Body Content-Type: application/json, UTF-8. Мэдэхгүй талбар илгээвэл 400 invalid_request — зөвхөн баримтжуулсан талбарыг илгээ
Мөнгө Бүхэл төгрөг (3500 = 3 500₮), currency нь MNT
Цаг RFC 3339, UTC (2026-10-22T09:00:00Z)
Idempotency-Key Intent үүсгэх, буцаалтад заавал: 1–128 хэвлэгдэх ASCII, зайгүй. Сүлжээний алдаа, 429, 5xx-д ижил key-ээр дахин
Алдаа Модулийн {"error": "<монгол текст>", "code": "<код>", …} эсвэл OAuth-ын {"error": "<код>", "error_description": "…"} — code-оор салаал (Алдааны код)
Хуудаслалт, хязгаар API-ийн нийтлэг дүрэм, Хязгаар
curl -s -u "$GEREGE_CLIENT_ID:$GEREGE_CLIENT_SECRET" \
  -H "Content-Type: application/json" -H "Idempotency-Key: order_8812" \
  -d '{"amount": 3500, "description": "Ус 0.5л", "reference": "order_8812", "presentment": "qr"}' \
  https://geregeapp.mn/api/v1/miniapp/payment-intents

Client бичихдээ:

  • Алдааны хариуг уншихдаа: code талбар байвал тэр нь код, error нь хүнд харуулж болох мессеж; code байхгүй ч error нь [a-z_]+ хэлбэртэй бол (OAuth) error нь код; аль нь ч биш бол ({"error": "not found"}, 429, 500) кодыг HTTP статусаар (http_404 г.м.) нэрлэ — SDK-ууд яг ингэдэг.
  • X-Request-Id хариуны толгойг log-д хадгал — Тусламж-д хэрэгтэй.
  • Timeout 20–30 секунд; дахин оролдохдоо exponential backoff + ижил Idempotency-Key.
  • Client secret зөвхөн сервер дээр — браузер, мобайл апп, терминалд хэзээ ч (Аюулгүй байдал).

2. Webhook шалгах

Алгоритм, толгойнууд, нарийн зүйлс ба Node, Go, Python, PHP-ийн бэлэн функц — Гарын үсэг шалгах. Доор Java ба C# — ижил логик (|now − t| ≤ 300, аль нэг v1, constant-time, жижиг үсэгтэй hex):

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.HexFormat;
import java.util.List;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public final class GeregeWebhook {
    public static boolean verify(byte[] rawBody, String header, List<String> secrets, long now) throws Exception {
        long t = -1;
        List<byte[]> sigs = new ArrayList<>();
        for (String part : (header == null ? "" : header).split(",")) {
            String p = part.trim();
            int i = p.indexOf('=');
            String key = i < 0 ? p : p.substring(0, i);
            String value = i < 0 ? "" : p.substring(i + 1);
            if (key.equals("t") && value.matches("[0-9]{1,18}")) t = Long.parseLong(value);
            else if (key.equals("v1")) sigs.add(value.getBytes(StandardCharsets.US_ASCII));
        }
        if (t < 0 || sigs.isEmpty() || Math.abs(now - t) > 300) return false;
        boolean ok = false;
        for (String secret : secrets) {
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
            mac.update((t + ".").getBytes(StandardCharsets.UTF_8));
            byte[] want = HexFormat.of().formatHex(mac.doFinal(rawBody)).getBytes(StandardCharsets.US_ASCII);
            for (byte[] sig : sigs) ok = MessageDigest.isEqual(sig, want) | ok;
        }
        return ok;
    }
}
using System.Linq;
using System.Security.Cryptography;
using System.Text;

public static class GeregeWebhook
{
    public static bool Verify(byte[] rawBody, string? header, IEnumerable<string> secrets, long now, long tolerance = 300)
    {
        long t = -1;
        var sigs = new List<byte[]>();
        foreach (var part in (header ?? "").Split(','))
        {
            var p = part.Trim();
            var i = p.IndexOf('=');
            var key = i < 0 ? p : p[..i];
            var value = i < 0 ? "" : p[(i + 1)..];
            if (key == "t" && value.Length is > 0 and <= 18 && value.All(char.IsAsciiDigit)) t = long.Parse(value);
            else if (key == "v1") sigs.Add(Encoding.ASCII.GetBytes(value));
        }
        if (t < 0 || sigs.Count == 0 || Math.Abs(now - t) > tolerance) return false;
        var ok = false;
        foreach (var secret in secrets)
        {
            using var mac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
            var message = Encoding.UTF8.GetBytes($"{t}.").Concat(rawBody).ToArray();
            var want = Encoding.ASCII.GetBytes(Convert.ToHexString(mac.ComputeHash(message)).ToLowerInvariant());
            foreach (var sig in sigs) ok = CryptographicOperations.FixedTimeEquals(sig, want) | ok;
        }
        return ok;
    }
}

Body-г framework-ийн parse хийхээс өмнө byte-аар ав (Spring: @RequestBody byte[]; ASP.NET Core: Request.EnableBuffering() + Request.Body-г MemoryStream-д хуулах).

3. Өөрийн кодоо шалгах

  • Платформын өөрийн кодоос үүсгэсэн тест векторууд (vectors.json: гарын үсэг зурах ба шалгах 20 тохиолдол) SDK-уудын хамт түгээгддэг. SDK нийтлэгдэх хүртэл файлыг Тусламж-аас ав; түүнийг өөрийн unit test-д ачаалж webhook.verification[] бүрт valid-тай тэнцэх эсэхийг шалга (жишээ).
  • Векторгүйгээр: Гарын үсэг шалгах-ийн «Ажилласан жишээ»-ний secret, t, body-оор aab41441…c9821 гарч байгаа эсэх.
  • Бодит хүргэлтээр: консолын «Тест илгээх» (webhook.test) — tunnel-ээр локал машин руу (Локал webhook).