// Package qbirr is a thin Go client for the qbirr payment verification API. // // qb := qbirr.New("your_api_key") // r, err := qb.Verify(context.Background(), qbirr.VerifyRequest{ // Provider: "cbe", // Ref: "FT23001234ABC", // Amount: 500, // ReceiverName: "YOUR NAME", // ReceiverAccount: "1000017692643", // }) // if err != nil { return err } // if r.Verified { fmt.Println("Paid by", r.Payer) } package qbirr import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "net/http" "time" ) const ( Version = "1.0.0" DefaultBaseURL = "https://verify.qbirr.com" DefaultTimeout = 30 * time.Second ) // Provider identifies which bank or wallet the reference belongs to. type Provider = string const ( ProviderCBE Provider = "cbe" ProviderTelebirr Provider = "telebirr" ProviderAbyssinia Provider = "abyssinia" ProviderDashen Provider = "dashen" ProviderMpesa Provider = "mpesa" ProviderAwash Provider = "awash" ProviderEbirr Provider = "ebirr" ProviderZamZam Provider = "zamzam" ) // VerifyRequest is the payload for /api/v1/verify. type VerifyRequest struct { Provider Provider `json:"provider"` Ref string `json:"ref"` Amount float64 `json:"amount"` ReceiverName string `json:"receiver_name"` ReceiverAccount string `json:"receiver_account,omitempty"` // required for CBE & Abyssinia } // VerifyResult is the API response. Verification failures arrive here (Verified=false + Error set), // NOT as Go errors. Errors are reserved for network/HTTP problems. type VerifyResult struct { Verified bool `json:"verified"` Payer string `json:"payer"` Amount float64 `json:"amount"` Error string `json:"error"` } // UsageStatus is the response from /api/v1/usage. type UsageStatus struct { Used int `json:"used"` Limit int `json:"limit"` Remaining int `json:"remaining"` Plan string `json:"plan"` PeriodStart string `json:"period_start"` PeriodEnd string `json:"period_end"` } // APIError wraps a non-2xx HTTP response. type APIError struct { Status int Message string } func (e *APIError) Error() string { return fmt.Sprintf("qbirr: HTTP %d: %s", e.Status, e.Message) } // Client talks to the qbirr API. type Client struct { APIKey string BaseURL string HTTPClient *http.Client } // New returns a client with sensible defaults. func New(apiKey string) *Client { return &Client{ APIKey: apiKey, BaseURL: DefaultBaseURL, HTTPClient: &http.Client{Timeout: DefaultTimeout}, } } // Verify performs a verification. Returns (result, nil) for both verified=true and verified=false // (real verification failures). Returns (nil, error) only for transport/HTTP issues. func (c *Client) Verify(ctx context.Context, req VerifyRequest) (*VerifyResult, error) { if c.APIKey == "" { return nil, errors.New("qbirr: APIKey is empty") } var out VerifyResult if err := c.do(ctx, "POST", "/api/v1/verify", req, &out); err != nil { return nil, err } return &out, nil } // Usage returns the current calendar-month quota status. func (c *Client) Usage(ctx context.Context) (*UsageStatus, error) { var out UsageStatus if err := c.do(ctx, "GET", "/api/v1/usage", nil, &out); err != nil { return nil, err } return &out, nil } func (c *Client) do(ctx context.Context, method, path string, body any, out any) error { var rdr io.Reader if body != nil { buf, err := json.Marshal(body) if err != nil { return fmt.Errorf("qbirr: marshal: %w", err) } rdr = bytes.NewReader(buf) } req, err := http.NewRequestWithContext(ctx, method, c.BaseURL+path, rdr) if err != nil { return err } req.Header.Set("X-API-Key", c.APIKey) req.Header.Set("Accept", "application/json") if body != nil { req.Header.Set("Content-Type", "application/json") } req.Header.Set("User-Agent", "qbirr-go/"+Version) resp, err := c.HTTPClient.Do(req) if err != nil { return fmt.Errorf("qbirr: network: %w", err) } defer resp.Body.Close() raw, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("qbirr: read body: %w", err) } if resp.StatusCode >= 400 { // Try to extract { error: "..." } var apiErr struct{ Error string `json:"error"` } _ = json.Unmarshal(raw, &apiErr) msg := apiErr.Error if msg == "" { msg = string(raw) } return &APIError{Status: resp.StatusCode, Message: msg} } if err := json.Unmarshal(raw, out); err != nil { return fmt.Errorf("qbirr: decode (HTTP %d): %w", resp.StatusCode, err) } return nil }