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/go
import 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 үүсгэх үед нэг л удаа харагдана.

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;
  }
}