SDK ба webhook
Хоёр SDK хоёулаа гадны хамааралгүй. GET, Idempotency-Key-тэй хүсэлт болон event batch-ийг 429, 5xx, сүлжээний алдаан дээр exponential backoff-оор (250 мс-ээс, дээд 8 с) өөрсдөө давтана; түлхүүргүй POST/PUT/DELETE-ийг хэзээ ч давтахгүй. API түлхүүрээ browser-т бүү байршуул — серверээсээ дууд.
Go
Go 1.25+, зөвхөн стандарт сан.
go get gitlab.gerege.mn/products/core-services/billing/sdk/goimport billing "gitlab.gerege.mn/products/core-services/billing/sdk/go"
c := &billing.Client{BaseURL: "https://api.billing.gerege.mn", APIKey: os.Getenv("BILLING_KEY")}
// Харилцагч (external_id = таны системийн id)
cust, err := c.CreateCustomer(ctx, billing.CustomerInput{ExternalID: "org-42", Name: "ХХК Жишээ"})
// Event илгээх (events:write түлхүүр). 500-аас олон бол SDK өөрөө хуваана.
res, err := c.SendEvents(ctx, []billing.Event{
{IdempotencyKey: "req-8f2c", ExternalCustomerID: "org-42", Type: "api.call"},
})
for _, r := range res.Rejected() {
log.Printf("event %d татгалзсан: %s", r.Index, r.ErrorCode)
}
// Хэтэвчээс хасах (wallet:charge түлхүүр, Idempotency-Key заавал)
mv, err := c.Charge(ctx, cust.ID, billing.ChargeInput{AmountMinor: 250_000, Description: "Гарын үсэг 1ш"}, "charge-req-8f2c")
if billing.IsCode(err, "insufficient_funds") { /* үлдэгдэл хүрэлцэхгүй */ }Алдаа *billing.APIError{StatusCode, Code, Message}; логикоо billing.IsCode(err, "…")-аар code дээр бич.
TypeScript
Node 18+ ба fetch-тэй орчин. ESM, төрлүүдтэй.
# @gerege/billing npm-д нийтлэгдээгүй: репогийн sdk/ts-ээс багц үүсгэж суулгана
cd sdk/ts && npm ci && npm run build && npm pack
npm install ./gerege-billing-0.1.0.tgz # өөрийн төсөл дотроосimport { BillingClient, isCode } from "@gerege/billing";
const c = new BillingClient({ baseURL: "https://api.billing.gerege.mn", apiKey: process.env.BILLING_KEY! });
const { results } = await c.sendEvents([
{ idempotency_key: "req-8f2c", external_customer_id: "org-42", type: "api.call" },
]);
try {
await c.charge(customerId, { amount_minor: 250000 }, "charge-req-8f2c");
} catch (err) {
if (isCode(err, "insufficient_funds")) { /* үлдэгдэл хүрэлцэхгүй */ }
else throw err;
}Алдаа APIError {status, code}; isCode(err, "…"). Мөнгө үргэлж бүхэл минор нэгжээр (amount_minor).
Webhook шалгах
Хүргэлт бүр Billing-Signature: t=<unix>,v1=<hex> толгойтой ирнэ: v1 = hex(HMAC-SHA256(secret, "<t>.<түүхий бие>")). Secret (whsec_…) endpoint үүсгэх үед нэг л удаа харагдана.
- Түүхий биеийг шалга — parse хийж дахин serialize хийсэн JSON гарын үсгийг эвдэнэ.
- Харьцуулалт тогтмол хугацааных;
t±5 минутаас хэтэрвэл (replay) татгалзана. - Нэг event хэд хэдэн удаа ирж болно —
Billing-Event-Id-аар давхардлыг шүү. - 10 секундэд 2xx буцаа; үгүй бол 1м, 5м, 30м, 2ц, 6ц, 12ц, 24ц-ийн дараа дахин оролдоно.
Go
func handler(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(http.MaxBytesReader(w, r.Body, 1<<20))
ev, err := billing.ParseWebhook(secret, r.Header.Get(billing.HeaderSignature), body, 0) // 0 = 5 мин
if err != nil {
http.Error(w, "bad signature", http.StatusBadRequest)
return
}
// ev.ID-аар давхардлыг шүүгээд ev.Type, ev.Data-г боловсруул
w.WriteHeader(http.StatusNoContent)
}TypeScript
import { parseWebhook, HEADER_SIGNATURE, SignatureError } from "@gerege/billing";
export async function POST(request: Request) {
const raw = await request.text(); // түүхий бие — parse хийгээд дахин serialize хийхгүй
try {
const ev = await parseWebhook(secret, request.headers.get(HEADER_SIGNATURE), raw);
// ev.id-аар давхардлыг шүүгээд ev.type, ev.data-г боловсруул
return new Response(null, { status: 204 });
} catch (err) {
if (err instanceof SignatureError) return new Response("bad signature", { status: 400 });
throw err;
}
}