> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bafanglaicai88.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Describe only the public 123hub merchant API documented on this site. The primary endpoint is POST /public/api/multihub/v1.
> Preserve API method names, field names, header names, and error codes exactly as documented. Do not invent endpoints or parameters.
> Treat every amount as an integer in minor units unless a page explicitly states otherwise.
> Never expose, request, or fabricate a merchant secret key. The SDK pages contain reference implementations, not official SDK packages.

# Go Examples

> Integrate with the API using Go

# Go Examples

Complete Go example client for integrating with the API using the standard library (`crypto/sha512`, `net/http`, `encoding/json`).

<Info>
  This is a reference implementation, not an official SDK package. It uses only
  Go standard library modules with no external dependencies required.
</Info>

## Configuration

Store your credentials in environment variables:

```bash theme={null}
export PAYMENT_API_APP_ID="1"
export PAYMENT_API_VERSION="2"
export PAYMENT_API_SECRET_KEY="your_secret_key"
```

## Client Package

```go theme={null}
package pay

import (
	"bytes"
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"crypto/sha512"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"
	"time"
)

const DefaultBaseURL = "https://api.bafanglaicai88.com"

// Client is the API client.
type Client struct {
	AppID      string
	SecretKey  string
	APIVersion int
	BaseURL    string
	HTTPClient *http.Client
}

// Response is the standard API response envelope.
type Response struct {
	Success        bool            `json:"success"`
	Result         json.RawMessage `json:"result,omitempty"`
	Error          *ErrorDetail    `json:"error,omitempty"`
	RequestID      string          `json:"request_id"`
	ProcessingTime float64         `json:"processing_time"`
}

// ErrorDetail contains error information when success is false.
type ErrorDetail struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

// PaymentIdentifiers contains identifiers for looking up a payment.
type PaymentIdentifiers struct {
	CID *string `json:"c_id,omitempty"`
	HID *string `json:"h_id,omitempty"`
}

// Amount represents a monetary amount with currency.
type Amount struct {
	Value    int    `json:"value"`
	Currency string  `json:"currency"`
}

// Payer contains payer information for a deposit.
type Payer struct {
	Email  string  `json:"email,omitempty"`
	Person *Person `json:"person,omitempty"`
}

// Person contains a person's name.
type Person struct {
	FirstName string `json:"first_name"`
	LastName  string `json:"last_name"`
}

// BankAccount contains the bank account identifier.
type BankAccount struct {
	ID string `json:"id"`
}

// BankDetails contains bank information for a receiver.
type BankDetails struct {
	Account BankAccount `json:"account"`
	IFSC    string      `json:"ifsc,omitempty"`
}

// Receiver contains receiver information for a withdrawal.
type Receiver struct {
	Bank   *BankDetails `json:"bank,omitempty"`
	Email  string       `json:"email,omitempty"`
	Phone  string       `json:"phone,omitempty"`
	Person *Person      `json:"person,omitempty"`
}

// BalanceEntry represents a single balance entry in the balance response.
type BalanceEntry struct {
	Value         int    `json:"value"`
	ValueFreezing int    `json:"value_freezing"`
	ValueBlocking int    `json:"value_blocking"`
	Currency      string `json:"currency"`
	Enabled       bool   `json:"enabled"`
}

// BalanceResult is the parsed result from a balance.get call.
type BalanceResult struct {
	Balance struct {
		ID      int            `json:"id"`
		Amounts []BalanceEntry `json:"amounts"`
		Enabled bool           `json:"enabled"`
	} `json:"balance"`
}

// PaymentStatusResult is the parsed result from a payment.status call.
type PaymentStatusResult struct {
	Payment struct {
		Status struct {
			Status string `json:"status"`
		} `json:"status"`
	} `json:"payment"`
}

// NewClient creates a new 123hub API client for one explicit appID/API version pair.
func NewClient(appID string, apiVersion int, secretKey string) *Client {
	if appID == "" {
		appID = os.Getenv("PAYMENT_API_APP_ID")
	}
	if secretKey == "" {
		secretKey = os.Getenv("PAYMENT_API_SECRET_KEY")
	}
	if apiVersion == 0 {
		apiVersion, _ = strconv.Atoi(os.Getenv("PAYMENT_API_VERSION"))
	}
	if apiVersion != 1 && apiVersion != 2 {
		panic("PAYMENT_API_VERSION must be 1 or 2")
	}
	return &Client{
		AppID:      appID,
		SecretKey:  secretKey,
		APIVersion: apiVersion,
		BaseURL:    DefaultBaseURL,
		HTTPClient: &http.Client{
			Timeout: 30 * time.Second,
		},
	}
}

func replayNonce() (string, error) {
	var nonce [16]byte
	if _, err := rand.Read(nonce[:]); err != nil {
		return "", fmt.Errorf("failed to generate replay nonce: %w", err)
	}
	return hex.EncodeToString(nonce[:]), nil
}

// send signs and sends a request body to the API, returning the response.
func (c *Client) send(body interface{}) (*Response, error) {
	bodyBytes, err := json.Marshal(body)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal body: %w", err)
	}

	path := fmt.Sprintf("/public/api/multihub/v%d", c.APIVersion)
	req, err := http.NewRequest("POST", c.BaseURL+path, bytes.NewReader(bodyBytes))
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-Data-Application-Id", c.AppID)
	var requestNonce string
	if c.APIVersion == 1 {
		hasher := sha512.New()
		hasher.Write(bodyBytes)
		hasher.Write([]byte(c.SecretKey))
		req.Header.Set("X-Data-Hash", hex.EncodeToString(hasher.Sum(nil)))
	} else {
		timestamp := fmt.Sprintf("%d", time.Now().Unix())
		nonce, err := replayNonce()
		if err != nil {
			return nil, err
		}
		requestNonce = nonce
		bodyHash := sha256.Sum256(bodyBytes)
		canonical := fmt.Sprintf(
			"quadpay-multihub-request-v2\n%s\nPOST\n/public/api/multihub/v2\n%s\n%s\n%s",
			c.AppID, timestamp, nonce, hex.EncodeToString(bodyHash[:]),
		)
		mac := hmac.New(sha512.New, []byte(c.SecretKey))
		mac.Write([]byte(canonical))
		req.Header.Set("X-Data-Timestamp", timestamp)
		req.Header.Set("X-Data-Nonce", nonce)
		req.Header.Set("X-Data-Signature", hex.EncodeToString(mac.Sum(nil)))
	}

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("request failed: %w", err)
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read response: %w", err)
	}

	var result Response
	if err := json.Unmarshal(respBody, &result); err != nil {
		return nil, fmt.Errorf("failed to parse response: %w", err)
	}
	if c.APIVersion == 2 {
		requestID := resp.Header.Get("X-Request-ID")
		if requestID == "" {
			requestID = result.RequestID
		}
		responseHash := sha256.Sum256(respBody)
		canonicalResponse := fmt.Sprintf(
			"quadpay-multihub-response-v2\n%s\n%s\n%s\n%d\n%s",
			c.AppID, requestNonce, requestID, resp.StatusCode,
			hex.EncodeToString(responseHash[:]),
		)
		mac := hmac.New(sha512.New, []byte(c.SecretKey))
		mac.Write([]byte(canonicalResponse))
		received, err := hex.DecodeString(resp.Header.Get("X-Data-Signature"))
		if err != nil || requestID == "" || !hmac.Equal(received, mac.Sum(nil)) {
			return nil, fmt.Errorf("Merchant API v2 response signature verification failed")
		}
	}

	return &result, nil
}

// Ping performs a gateway health check.
func (c *Client) Ping() (*Response, error) {
	return c.send(map[string]interface{}{
		"method": "gateway.ping",
		"params": map[string]interface{}{},
	})
}

// CreateDeposit creates a deposit (pay-in) payment.
func (c *Client) CreateDeposit(serviceID int, amount int, currency string, cID string, extra map[string]interface{}) (*Response, error) {
	payment := map[string]interface{}{
		"identifiers": map[string]interface{}{"c_id": cID},
		"amount":      map[string]interface{}{"value": amount, "currency": currency},
	}
	for k, v := range extra {
		payment[k] = v
	}

	return c.send(map[string]interface{}{
		"method":     "payment.in",
		"service_id": serviceID,
		"params": map[string]interface{}{
			"payment": payment,
		},
	})
}

// CreateWithdrawal creates a withdrawal (payout) payment.
func (c *Client) CreateWithdrawal(serviceID int, amount int, currency string, cID string, receiver Receiver, extra map[string]interface{}) (*Response, error) {
	payment := map[string]interface{}{
		"identifiers": map[string]interface{}{"c_id": cID},
		"amount":      map[string]interface{}{"value": amount, "currency": currency},
		"receiver":    receiver,
	}
	for k, v := range extra {
		payment[k] = v
	}

	return c.send(map[string]interface{}{
		"method":     "payment.out",
		"service_id": serviceID,
		"params": map[string]interface{}{
			"payment": payment,
		},
	})
}

// GetStatus checks payment status by identifiers.
func (c *Client) GetStatus(cID *string, hID *string) (*Response, error) {
	identifiers := map[string]interface{}{}
	if cID != nil {
		identifiers["c_id"] = *cID
	}
	if hID != nil {
		identifiers["h_id"] = *hID
	}

	return c.send(map[string]interface{}{
		"method": "payment.status",
		"params": map[string]interface{}{
			"payment": map[string]interface{}{
				"identifiers": identifiers,
			},
		},
	})
}

// GetBalance retrieves account balances.
func (c *Client) GetBalance() (*Response, error) {
	return c.send(map[string]interface{}{
		"method": "balance.get",
		"params": map[string]interface{}{},
	})
}

// ParseBalanceResult parses the Result field of a successful balance.get response.
func ParseBalanceResult(resp *Response) (*BalanceResult, error) {
	var result BalanceResult
	if err := json.Unmarshal(resp.Result, &result); err != nil {
		return nil, err
	}
	return &result, nil
}

// ParsePaymentStatusResult parses the Result field of a successful payment.status response.
func ParsePaymentStatusResult(resp *Response) (*PaymentStatusResult, error) {
	var result PaymentStatusResult
	if err := json.Unmarshal(resp.Result, &result); err != nil {
		return nil, err
	}
	return &result, nil
}
```

## Usage Examples

### Ping (Health Check)

```go theme={null}
package main

import (
	"fmt"
	"log"

	"your-project/paymentapi"
)

func main() {
	client := paymentapi.NewClient("1", 2, "your_secret_key")

	resp, err := client.Ping()
	if err != nil {
		log.Fatalf("Network error: %v", err)
	}

	if resp.Success {
		fmt.Println("Gateway is up")
	} else {
		fmt.Printf("Error: %d - %s\n", resp.Error.Code, resp.Error.Message)
	}
}
```

### Create a Deposit Payment

The examples below use INR (India), but the same client works for all supported currencies (MXN, ARS, TRY, AUD, LKR, UYU) -- just change the `serviceID`, `currency`, and `country` values.

```go theme={null}
resp, err := client.CreateDeposit(
	14701,   // serviceID
	10000,   // amount
	"INR",   // currency
	"12345", // cID (your client-side identifier)
	map[string]interface{}{
		"description": "Order #12345",
		"payer": paymentapi.Payer{
			Email: "customer@example.com",
			Person: &paymentapi.Person{
				FirstName: "John",
				LastName:  "Doe",
			},
		},
	},
)
if err != nil {
	log.Fatalf("Network error: %v", err)
}

if resp.Success {
	fmt.Printf("Payment created (request_id=%s)\n", resp.RequestID)
} else {
	fmt.Printf("Error: %d - %s\n", resp.Error.Code, resp.Error.Message)
}
```

### Create a Withdrawal (Payout)

```go theme={null}
resp, err := client.CreateWithdrawal(
	14701,  // serviceID
	5000,   // amount
	"INR",  // currency
	"67890", // cID
	paymentapi.Receiver{
		Bank: &paymentapi.BankDetails{
			Account: paymentapi.BankAccount{ID: "1234567890"},
			IFSC:    "SBIN0001234",
		},
		Person: &paymentapi.Person{
			FirstName: "Jane",
			LastName:  "Doe",
		},
	},
	map[string]interface{}{
		"description": "Payout #67890",
	},
)
if err != nil {
	log.Fatalf("Network error: %v", err)
}

if resp.Success {
	fmt.Println("Withdrawal created successfully")
} else {
	fmt.Printf("Error: %d - %s\n", resp.Error.Code, resp.Error.Message)
}
```

### Check Payment Status

```go theme={null}
// Look up by client-side ID
cID := "12345"
resp, err := client.GetStatus(&cID, nil)
if err != nil {
	log.Fatalf("Network error: %v", err)
}

if resp.Success {
	status, err := paymentapi.ParsePaymentStatusResult(resp)
	if err != nil {
		log.Fatalf("Failed to parse result: %v", err)
	}
	fmt.Printf("Payment status: %s\n", status.Payment.Status.Status)
} else {
	fmt.Printf("Error: %d - %s\n", resp.Error.Code, resp.Error.Message)
}

// Look up by hub-side ID
hID := "999001"
resp, err = client.GetStatus(nil, &hID)
```

### Get Balance

```go theme={null}
resp, err := client.GetBalance()
if err != nil {
	log.Fatalf("Network error: %v", err)
}

if resp.Success {
	balances, err := paymentapi.ParseBalanceResult(resp)
	if err != nil {
		log.Fatalf("Failed to parse result: %v", err)
	}
	for _, b := range balances.Balance.Amounts {
		fmt.Printf("%s: %d\n", b.Currency, b.Value)
	}
} else {
	fmt.Printf("Error: %d - %s\n", resp.Error.Code, resp.Error.Message)
}
```

## Error Handling

Successful responses return HTTP 200, errors return HTTP 400. Always use the `Success` field to determine the outcome:

```go theme={null}
resp, err := client.CreateDeposit(14701, 10000, "INR", "12345", nil)
if err != nil {
	// Network-level error (DNS, timeout, connection refused, etc.)
	log.Printf("Network error: %v", err)
	return
}

if resp.Success {
	// Operation succeeded
	fmt.Printf("Request ID: %s\n", resp.RequestID)
	fmt.Printf("Processing time: %.3fs\n", resp.ProcessingTime)
} else {
	// Operation failed - check error code
	switch resp.Error.Code {
	case 3000:
		log.Println("Authentication failed - check your secret key")
	case 1005:
		log.Printf("Invalid parameters: %s", resp.Error.Message)
	case 7002:
		log.Println("Service temporarily unavailable - retry later")
	default:
		log.Printf("API error: %d - %s", resp.Error.Code, resp.Error.Message)
	}
}
```

## Complete Example

```go theme={null}
package main

import (
	"fmt"
	"log"
	"os"
	"strconv"
	"time"

	"your-project/paymentapi"
)

func main() {
	appID := os.Getenv("PAYMENT_API_APP_ID")
	apiVersion, _ := strconv.Atoi(os.Getenv("PAYMENT_API_VERSION"))
	secretKey := os.Getenv("PAYMENT_API_SECRET_KEY")
	if appID == "" {
		appID = "1"
	}
	if secretKey == "" {
		secretKey = "your_secret_key"
	}
	if apiVersion == 0 {
		apiVersion = 2
	}

	client := paymentapi.NewClient(appID, apiVersion, secretKey)

	// 1. Ping the gateway
	pingResp, err := client.Ping()
	if err != nil {
		log.Fatalf("Ping failed: %v", err)
	}
	if !pingResp.Success {
		log.Fatalf("Gateway unreachable: %s", pingResp.Error.Message)
	}
	fmt.Println("Gateway is up")

	// 2. Check balance
	balanceResp, err := client.GetBalance()
	if err != nil {
		log.Printf("Balance request failed: %v", err)
	} else if balanceResp.Success {
		balances, _ := paymentapi.ParseBalanceResult(balanceResp)
		if balances != nil {
			for _, b := range balances.Balance.Amounts {
				fmt.Printf("  %s: %d\n", b.Currency, b.Value)
			}
		}
	} else {
		log.Printf("Balance check failed: %s", balanceResp.Error.Message)
	}

	// 3. Create a deposit
	orderID := fmt.Sprintf("%d", time.Now().Unix())
	depositResp, err := client.CreateDeposit(
		14701,
		10000,
		"INR",
		orderID,
		map[string]interface{}{
			"description": fmt.Sprintf("Order #%s", orderID),
			"payer": map[string]interface{}{
				"email": "customer@example.com",
				"person": map[string]interface{}{
					"first_name": "John",
					"last_name":  "Doe",
				},
			},
		},
	)
	if err != nil {
		log.Fatalf("Deposit request failed: %v", err)
	}
	if !depositResp.Success {
		log.Fatalf("Deposit failed: %s", depositResp.Error.Message)
	}
	fmt.Printf("Deposit created (request_id=%s)\n", depositResp.RequestID)

	// 4. Poll for status
	for i := 0; i < 5; i++ {
		time.Sleep(3 * time.Second)

		statusResp, err := client.GetStatus(&orderID, nil)
		if err != nil {
			log.Printf("Status request failed: %v", err)
			break
		}
		if !statusResp.Success {
			log.Printf("Status check failed: %s", statusResp.Error.Message)
			break
		}

		parsed, err := paymentapi.ParsePaymentStatusResult(statusResp)
		if err != nil {
			log.Printf("Failed to parse status: %v", err)
			break
		}

		status := parsed.Payment.Status.Status
		fmt.Printf("Payment status: %s\n", status)
		if status == "success" || status == "error" || status == "canceled" || status == "declined" || status == "refunded" || status == "partially_refunded" {
			break
		}
	}
}
```
