Go Examples
Complete Go example client for integrating with the Multihub API using the standard library (crypto/sha512, net/http, encoding/json).
This is a reference implementation, not an official SDK package. It uses only Go standard library modules with no external dependencies required.
Configuration
Store your credentials in environment variables:Copy
export MULTIHUB_APP_ID="1"
export MULTIHUB_SECRET_KEY="your_secret_key"
MultihubClient Package
Copy
package multihub
import (
"bytes"
"crypto/sha512"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
)
const DefaultBaseURL = "https://api.123hub.pro/public/api/multihub/v1"
// Client is the Multihub API client.
type Client struct {
AppID string
SecretKey string
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"`
Next interface{} `json:"next"`
RequestID string `json:"request_id"`
ProcessingTime float64 `json:"processing_time"`
}
// ErrorDetail contains error information when success is false.
type ErrorDetail struct {
Code string `json:"code"`
Message string `json:"message"`
}
// PaymentIdentifiers contains identifiers for looking up a payment.
type PaymentIdentifiers struct {
CID *int `json:"c_id,omitempty"`
HID *int `json:"h_id,omitempty"`
}
// Amount represents a monetary amount with currency.
type Amount struct {
Value float64 `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"`
}
// Receiver contains receiver information for a withdrawal.
type Receiver struct {
BankAccount string `json:"bank_account,omitempty"`
IFSCCode string `json:"ifsc_code,omitempty"`
Person *Person `json:"person,omitempty"`
}
// BalanceEntry represents a single balance entry in the balance response.
type BalanceEntry struct {
Currency string `json:"currency"`
Available string `json:"available"`
}
// BalanceResult is the parsed result from a balance.get call.
type BalanceResult struct {
Balances []BalanceEntry `json:"balances"`
}
// 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 Multihub API client.
// If appID or secretKey are empty, they are read from environment variables.
func NewClient(appID, secretKey string) *Client {
if appID == "" {
appID = os.Getenv("MULTIHUB_APP_ID")
}
if secretKey == "" {
secretKey = os.Getenv("MULTIHUB_SECRET_KEY")
}
return &Client{
AppID: appID,
SecretKey: secretKey,
BaseURL: DefaultBaseURL,
HTTPClient: &http.Client{
Timeout: 30 * time.Second,
},
}
}
// 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)
}
// Compute SHA-512 hash of body + secret key
hasher := sha512.New()
hasher.Write(bodyBytes)
hasher.Write([]byte(c.SecretKey))
hash := hex.EncodeToString(hasher.Sum(nil))
req, err := http.NewRequest("POST", c.BaseURL, 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)
req.Header.Set("X-Data-Hash", hash)
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)
}
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 float64, currency string, cID int, 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 float64, currency string, cID int, 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 *int, hID *int) (*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)
Copy
package main
import (
"fmt"
"log"
"your-project/multihub"
)
func main() {
client := multihub.NewClient("1", "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: %s - %s\n", resp.Error.Code, resp.Error.Message)
}
}
Create a Deposit Payment
Copy
resp, err := client.CreateDeposit(
14701, // serviceID
10000, // amount
"INR", // currency
12345, // cID (your client-side identifier)
map[string]interface{}{
"description": "Order #12345",
"payer": multihub.Payer{
Email: "[email protected]",
Person: &multihub.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: %s - %s\n", resp.Error.Code, resp.Error.Message)
}
Create a Withdrawal (Payout)
Copy
resp, err := client.CreateWithdrawal(
14701, // serviceID
5000, // amount
"INR", // currency
67890, // cID
multihub.Receiver{
BankAccount: "1234567890",
IFSCCode: "SBIN0001234",
Person: &multihub.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: %s - %s\n", resp.Error.Code, resp.Error.Message)
}
Check Payment Status
Copy
// 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 := multihub.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: %s - %s\n", resp.Error.Code, resp.Error.Message)
}
// Look up by hub-side ID
hID := 999001
resp, err = client.GetStatus(nil, &hID)
Get Balance
Copy
resp, err := client.GetBalance()
if err != nil {
log.Fatalf("Network error: %v", err)
}
if resp.Success {
balances, err := multihub.ParseBalanceResult(resp)
if err != nil {
log.Fatalf("Failed to parse result: %v", err)
}
for _, b := range balances.Balances {
fmt.Printf("%s: %s\n", b.Currency, b.Available)
}
} else {
fmt.Printf("Error: %s - %s\n", resp.Error.Code, resp.Error.Message)
}
Error Handling
Successful responses return HTTP 200, errors return HTTP 400. Always use theSuccess field to determine the outcome:
Copy
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 "INVALID_HASH":
log.Println("Authentication failed - check your secret key")
case "INVALID_PARAMS":
log.Printf("Invalid parameters: %s", resp.Error.Message)
case "SERVICE_UNAVAILABLE":
log.Println("Service temporarily unavailable - retry later")
default:
log.Printf("API error: %s - %s", resp.Error.Code, resp.Error.Message)
}
}
Complete Example
Copy
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"time"
"your-project/multihub"
)
func main() {
appID := os.Getenv("MULTIHUB_APP_ID")
secretKey := os.Getenv("MULTIHUB_SECRET_KEY")
if appID == "" {
appID = "1"
}
if secretKey == "" {
secretKey = "your_secret_key"
}
client := multihub.NewClient(appID, 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, _ := multihub.ParseBalanceResult(balanceResp)
if balances != nil {
for _, b := range balances.Balances {
fmt.Printf(" %s: %s\n", b.Currency, b.Available)
}
}
} else {
log.Printf("Balance check failed: %s", balanceResp.Error.Message)
}
// 3. Create a deposit
orderID := int(time.Now().Unix())
depositResp, err := client.CreateDeposit(
14701,
10000,
"INR",
orderID,
map[string]interface{}{
"description": fmt.Sprintf("Order #%d", orderID),
"payer": map[string]interface{}{
"email": "[email protected]",
"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 := multihub.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 == "completed" || status == "failed" || status == "cancelled" {
break
}
}
}
// Helper to pretty-print JSON for debugging
func prettyJSON(v interface{}) string {
b, _ := json.MarshalIndent(v, "", " ")
return string(b)
}
