Skip to Content

Go SDK

Go client library for the QPay V2 API. Thread-safe with automatic token management via mutex synchronization.

Go Reference GitHub

Installation

go get github.com/qpay-sdk/qpay-go

Requires Go 1.21 or later.

Configuration

Environment Variables

Set these variables and call LoadConfigFromEnv():

VariableDescription
QPAY_BASE_URLQPay API base URL (e.g., https://merchant.qpay.mn)
QPAY_USERNAMEQPay merchant username
QPAY_PASSWORDQPay merchant password
QPAY_INVOICE_CODEDefault invoice code
QPAY_CALLBACK_URLPayment callback URL
cfg, err := qpay.LoadConfigFromEnv() if err != nil { log.Fatal(err) } client := qpay.NewClient(cfg)

Manual Configuration

client := qpay.NewClient(&qpay.Config{ BaseURL: "https://merchant.qpay.mn", Username: "your_username", Password: "your_password", InvoiceCode: "YOUR_INVOICE_CODE", CallbackURL: "https://yoursite.com/qpay/callback", })

Custom HTTP Client

httpClient := &http.Client{ Timeout: 60 * time.Second, } client := qpay.NewClientWithHTTPClient(cfg, httpClient)

Quick Start

package main import ( "context" "fmt" "log" qpay "github.com/qpay-sdk/qpay-go" ) func main() { cfg, err := qpay.LoadConfigFromEnv() if err != nil { log.Fatal(err) } client := qpay.NewClient(cfg) invoice, err := client.CreateSimpleInvoice(context.Background(), &qpay.CreateSimpleInvoiceRequest{ InvoiceCode: cfg.InvoiceCode, SenderInvoiceNo: "ORDER-001", InvoiceReceiverCode: "terminal", InvoiceDescription: "Payment for Order #001", Amount: 50000, CallbackURL: cfg.CallbackURL, }) if err != nil { log.Fatal(err) } fmt.Printf("Invoice ID: %s\n", invoice.InvoiceID) fmt.Printf("QR Image: %s\n", invoice.QRImage) fmt.Printf("Short URL: %s\n", invoice.QPay_ShortURL) for _, url := range invoice.URLs { fmt.Printf(" %s: %s\n", url.Name, url.Link) } }

Create Invoice

invoice, err := client.CreateSimpleInvoice(ctx, &qpay.CreateSimpleInvoiceRequest{ InvoiceCode: "YOUR_CODE", SenderInvoiceNo: "ORDER-001", InvoiceReceiverCode: "terminal", InvoiceDescription: "Payment for order", SenderBranchCode: "BRANCH01", // optional Amount: 50000, CallbackURL: "https://yoursite.com/callback", }) if err != nil { log.Fatal(err) } fmt.Println("Invoice ID:", invoice.InvoiceID) fmt.Println("QR Text:", invoice.QRText) fmt.Println("QR Image (base64):", invoice.QRImage) fmt.Println("Short URL:", invoice.QPay_ShortURL)

For a full invoice with all options (lines, transactions, receiver data, etc.):

invoice, err := client.CreateInvoice(ctx, &qpay.CreateInvoiceRequest{ InvoiceCode: "YOUR_CODE", SenderInvoiceNo: "ORDER-002", InvoiceReceiverCode: "terminal", InvoiceDescription: "Detailed invoice", Amount: 100000, CallbackURL: "https://yoursite.com/callback", Lines: []qpay.InvoiceLine{ { LineDescription: "Product A", LineQuantity: "2", LineUnitPrice: "50000", }, }, })

Check Payment

result, err := client.CheckPayment(ctx, &qpay.PaymentCheckRequest{ ObjectType: "INVOICE", ObjectID: invoice.InvoiceID, Offset: &qpay.Offset{ PageNumber: 1, PageLimit: 10, }, }) if err != nil { log.Fatal(err) } if result.Count > 0 { fmt.Printf("Payment received! Paid amount: %.0f\n", result.PaidAmount) for _, row := range result.Rows { fmt.Printf(" Payment %s: status=%s amount=%s wallet=%s\n", row.PaymentID, row.PaymentStatus, row.PaymentAmount, row.PaymentWallet) } } else { fmt.Println("No payment yet") }

List Invoices / Payments

payments, err := client.ListPayments(ctx, &qpay.PaymentListRequest{ ObjectType: "INVOICE", ObjectID: invoice.InvoiceID, StartDate: "2024-01-01", EndDate: "2024-12-31", Offset: qpay.Offset{ PageNumber: 1, PageLimit: 20, }, }) if err != nil { log.Fatal(err) } fmt.Printf("Total payments: %d\n", payments.Count) for _, p := range payments.Rows { fmt.Printf(" %s | %s | %s MNT | %s\n", p.PaymentDate, p.PaymentStatus, p.PaymentAmount, p.PaymentWallet) }

Get details for a single payment:

detail, err := client.GetPayment(ctx, "payment-id-here") if err != nil { log.Fatal(err) } fmt.Printf("Payment %s: %s (%s MNT)\n", detail.PaymentID, detail.PaymentStatus, detail.PaymentAmount)

Webhook Handling

QPay sends a POST request to your callback_url when a payment is completed. Verify and process it in your HTTP handler:

func callbackHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } // Read the callback body (contains payment information) body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "Bad request", http.StatusBadRequest) return } defer r.Body.Close() // Parse the callback data var callback struct { PaymentID string `json:"payment_id"` } if err := json.Unmarshal(body, &callback); err != nil { http.Error(w, "Bad request", http.StatusBadRequest) return } // Verify the payment by calling QPay API result, err := client.CheckPayment(r.Context(), &qpay.PaymentCheckRequest{ ObjectType: "INVOICE", ObjectID: callback.PaymentID, }) if err != nil { http.Error(w, "Internal error", http.StatusInternalServerError) return } if result.Count > 0 { // Payment verified -- update your order status log.Printf("Payment confirmed: %s (%.0f MNT)", callback.PaymentID, result.PaidAmount) } w.WriteHeader(http.StatusOK) }

Error Handling

All API errors are returned as *qpay.Error, which includes the HTTP status code, QPay error code, and message.

invoice, err := client.CreateSimpleInvoice(ctx, req) if err != nil { if qErr, ok := qpay.IsQPayError(err); ok { fmt.Printf("QPay error: %s (status %d)\n", qErr.Code, qErr.StatusCode) fmt.Printf("Message: %s\n", qErr.Message) fmt.Printf("Raw body: %s\n", qErr.RawBody) switch qErr.Code { case qpay.ErrInvoiceCodeInvalid: // Handle invalid invoice code case qpay.ErrInvalidAmount: // Handle invalid amount case qpay.ErrAuthenticationFailed: // Handle auth failure case qpay.ErrInvoiceNotFound: // Handle missing invoice case qpay.ErrInvoicePaid: // Handle already-paid invoice case qpay.ErrPermissionDenied: // Handle permission denied default: // Handle other API errors } } else { // Non-API error (network, JSON parsing, etc.) fmt.Printf("Error: %v\n", err) } }

Available Error Code Constants

The SDK exports all QPay error codes as constants:

qpay.ErrAuthenticationFailed // "AUTHENTICATION_FAILED" qpay.ErrInvoiceNotFound // "INVOICE_NOTFOUND" qpay.ErrInvoicePaid // "INVOICE_PAID" qpay.ErrInvoiceAlreadyCanceled // "INVOICE_ALREADY_CANCELED" qpay.ErrInvoiceCodeInvalid // "INVOICE_CODE_INVALID" qpay.ErrInvalidAmount // "INVALID_AMOUNT" qpay.ErrPaymentNotFound // "PAYMENT_NOTFOUND" qpay.ErrPaymentAlreadyCanceled // "PAYMENT_ALREADY_CANCELED" qpay.ErrPermissionDenied // "PERMISSION_DENIED" // ... and many more

API Reference

MethodDescriptionReturns
NewClient(cfg)Create client with default HTTP settings*Client
NewClientWithHTTPClient(cfg, http)Create client with custom HTTP client*Client
LoadConfigFromEnv()Load config from env vars*Config, error
IsQPayError(err)Check if error is a QPay API error*Error, bool
GetToken(ctx)Authenticate and get token pair*TokenResponse, error
RefreshToken(ctx)Refresh the current access token*TokenResponse, error
CreateInvoice(ctx, req)Create detailed invoice with all options*InvoiceResponse, error
CreateSimpleInvoice(ctx, req)Create simple invoice with minimal fields*InvoiceResponse, error
CreateEbarimtInvoice(ctx, req)Create invoice with ebarimt (tax) data*InvoiceResponse, error
CancelInvoice(ctx, id)Cancel an invoice by IDerror
GetPayment(ctx, id)Get payment details by ID*PaymentDetail, error
CheckPayment(ctx, req)Check if payment has been made*PaymentCheckResponse, error
ListPayments(ctx, req)List payments with filters*PaymentListResponse, error
CancelPayment(ctx, id, req)Cancel a card paymenterror
RefundPayment(ctx, id, req)Refund a card paymenterror
CreateEbarimt(ctx, req)Create electronic tax receipt*EbarimtResponse, error
CancelEbarimt(ctx, id)Cancel electronic tax receipt*EbarimtResponse, error
Last updated on