Skip to main content

Go Integration

This guide shows how to integrate TxnCheck API into your Go application.

Installation

No external dependencies required. Uses standard library net/http. For structured JSON handling:
go get github.com/json-iterator/go  # Optional, for faster JSON

Basic Client Setup

package txncheck

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

// Config holds client configuration
type Config struct {
	APIKey    string
	SecretKey string // Optional, for request signing
	BaseURL   string
	Timeout   time.Duration
}

// DefaultConfig returns config with default values
func DefaultConfig(apiKey string) Config {
	return Config{
		APIKey:  apiKey,
		BaseURL: "https://api.txncheck.in/api/v1",
		Timeout: 30 * time.Second,
	}
}

// Client is the TxnCheck API client
type Client struct {
	config     Config
	httpClient *http.Client
}

// NewClient creates a new TxnCheck client
func NewClient(config Config) *Client {
	if config.BaseURL == "" {
		config.BaseURL = "https://api.txncheck.in/api/v1"
	}
	if config.Timeout == 0 {
		config.Timeout = 30 * time.Second
	}

	return &Client{
		config: config,
		httpClient: &http.Client{
			Timeout: config.Timeout,
		},
	}
}

// RequestStatus represents the status of a verification request
type RequestStatus string

const (
	StatusQueued     RequestStatus = "QUEUED"
	StatusProcessing RequestStatus = "PROCESSING"
	StatusCompleted  RequestStatus = "COMPLETED"
	StatusFailed     RequestStatus = "FAILED"
	StatusPartial    RequestStatus = "PARTIAL"
)

// AcceptedResponse is returned for async requests
type AcceptedResponse struct {
	StatusCode int           `json:"statusCode"`
	Message    string        `json:"message"`
	RequestID  string        `json:"requestId"`
	Status     RequestStatus `json:"status"`
}

// VerificationResult is returned for sync requests or status polling
type VerificationResult struct {
	StatusCode   int                    `json:"statusCode,omitempty"`
	RequestID    string                 `json:"requestId"`
	Method       string                 `json:"method,omitempty"`
	Status       RequestStatus          `json:"status"`
	Result       map[string]interface{} `json:"result,omitempty"`
	Error        map[string]interface{} `json:"error,omitempty"`
	StepStatuses map[string]string      `json:"stepStatuses,omitempty"`
	CreatedAt    string                 `json:"createdAt,omitempty"`
	CompletedAt  string                 `json:"completedAt,omitempty"`
}

// APIError represents an API error response
type APIError struct {
	StatusCode int    `json:"statusCode"`
	Message    string `json:"message"`
	Error      string `json:"error"`
}

func (e *APIError) Error() string {
	return fmt.Sprintf("%d %s: %s", e.StatusCode, e.Error, e.Message)
}

// signRequest generates HMAC-SHA256 signature
func (c *Client) signRequest(method, path string, body []byte) (timestamp, signature string) {
	if c.config.SecretKey == "" {
		return "", ""
	}

	timestamp = strconv.FormatInt(time.Now().UnixMilli(), 10)
	message := fmt.Sprintf("%s.%s.%s.%s", timestamp, method, path, string(body))

	h := hmac.New(sha256.New, []byte(c.config.SecretKey))
	h.Write([]byte(message))
	signature = hex.EncodeToString(h.Sum(nil))

	return timestamp, signature
}

// request makes an HTTP request to the API
func (c *Client) request(ctx context.Context, method, endpoint string, body interface{}) ([]byte, error) {
	var bodyBytes []byte
	var err error

	if body != nil {
		bodyBytes, err = json.Marshal(body)
		if err != nil {
			return nil, fmt.Errorf("failed to marshal body: %w", err)
		}
	}

	url := c.config.BaseURL + endpoint
	req, err := http.NewRequestWithContext(ctx, method, url, bytes.NewReader(bodyBytes))
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	req.Header.Set("X-API-Key", c.config.APIKey)
	req.Header.Set("Content-Type", "application/json")

	// Sign request if secret key is configured
	if timestamp, signature := c.signRequest(method, endpoint, bodyBytes); signature != "" {
		req.Header.Set("X-Timestamp", timestamp)
		req.Header.Set("X-Signature", signature)
	}

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

	if resp.StatusCode >= 400 {
		var apiErr APIError
		if json.Unmarshal(respBody, &apiErr) == nil {
			apiErr.StatusCode = resp.StatusCode
			return nil, &apiErr
		}
		return nil, &APIError{
			StatusCode: resp.StatusCode,
			Message:    string(respBody),
			Error:      "Unknown Error",
		}
	}

	return respBody, nil
}

// UPIByMobileRequest is the request body for UPI by mobile
type UPIByMobileRequest struct {
	Mobile string `json:"mobile"`
	Async  *bool  `json:"async,omitempty"`
}

// UPIByMobile gets UPI VPAs linked to a mobile number
func (c *Client) UPIByMobile(ctx context.Context, mobile string, sync bool) (*VerificationResult, error) {
	asyncVal := !sync
	body := UPIByMobileRequest{
		Mobile: mobile,
		Async:  &asyncVal,
	}

	respBody, err := c.request(ctx, "POST", "/upi-by-mobile", body)
	if err != nil {
		return nil, err
	}

	var result VerificationResult
	if err := json.Unmarshal(respBody, &result); err != nil {
		return nil, fmt.Errorf("failed to unmarshal response: %w", err)
	}

	return &result, nil
}

// KYCByMobile gets KYC data by mobile number
func (c *Client) KYCByMobile(ctx context.Context, mobile string, sync bool) (*VerificationResult, error) {
	asyncVal := !sync
	body := map[string]interface{}{
		"mobile": mobile,
		"async":  asyncVal,
	}

	respBody, err := c.request(ctx, "POST", "/kyc-by-mobile", body)
	if err != nil {
		return nil, err
	}

	var result VerificationResult
	if err := json.Unmarshal(respBody, &result); err != nil {
		return nil, fmt.Errorf("failed to unmarshal response: %w", err)
	}

	return &result, nil
}

// VPAChargebackCheck checks VPAs against blocklist
func (c *Client) VPAChargebackCheck(ctx context.Context, vpas []string, sync bool) (*VerificationResult, error) {
	asyncVal := !sync
	body := map[string]interface{}{
		"vpas":  vpas,
		"async": asyncVal,
	}

	respBody, err := c.request(ctx, "POST", "/vpa-chargeback-check", body)
	if err != nil {
		return nil, err
	}

	var result VerificationResult
	if err := json.Unmarshal(respBody, &result); err != nil {
		return nil, fmt.Errorf("failed to unmarshal response: %w", err)
	}

	return &result, nil
}

// FullCheck performs full verification: UPI + KYC + Chargeback check
func (c *Client) FullCheck(ctx context.Context, mobile string, sync bool) (*VerificationResult, error) {
	asyncVal := !sync
	body := map[string]interface{}{
		"mobile": mobile,
		"async":  asyncVal,
	}

	respBody, err := c.request(ctx, "POST", "/full-check", body)
	if err != nil {
		return nil, err
	}

	var result VerificationResult
	if err := json.Unmarshal(respBody, &result); err != nil {
		return nil, fmt.Errorf("failed to unmarshal response: %w", err)
	}

	return &result, nil
}

// BulkVPACheck checks multiple VPAs against blocklist
func (c *Client) BulkVPACheck(ctx context.Context, vpas []string, sync bool) (*VerificationResult, error) {
	asyncVal := !sync
	body := map[string]interface{}{
		"vpas":  vpas,
		"async": asyncVal,
	}

	respBody, err := c.request(ctx, "POST", "/bulk/vpa-chargeback-check", body)
	if err != nil {
		return nil, err
	}

	var result VerificationResult
	if err := json.Unmarshal(respBody, &result); err != nil {
		return nil, fmt.Errorf("failed to unmarshal response: %w", err)
	}

	return &result, nil
}

// GetRequestStatus gets the status of a request
func (c *Client) GetRequestStatus(ctx context.Context, requestID string) (*VerificationResult, error) {
	respBody, err := c.request(ctx, "GET", "/requests/"+requestID, nil)
	if err != nil {
		return nil, err
	}

	var result VerificationResult
	if err := json.Unmarshal(respBody, &result); err != nil {
		return nil, fmt.Errorf("failed to unmarshal response: %w", err)
	}

	return &result, nil
}

// WaitForResult polls for request completion
func (c *Client) WaitForResult(ctx context.Context, requestID string, pollInterval, maxWait time.Duration) (*VerificationResult, error) {
	if pollInterval == 0 {
		pollInterval = 2 * time.Second
	}
	if maxWait == 0 {
		maxWait = 60 * time.Second
	}

	terminalStatuses := map[RequestStatus]bool{
		StatusCompleted: true,
		StatusFailed:    true,
		StatusPartial:   true,
	}

	deadline := time.Now().Add(maxWait)
	ticker := time.NewTicker(pollInterval)
	defer ticker.Stop()

	for {
		result, err := c.GetRequestStatus(ctx, requestID)
		if err != nil {
			return nil, err
		}

		if terminalStatuses[result.Status] {
			return result, nil
		}

		select {
		case <-ctx.Done():
			return nil, ctx.Err()
		case <-ticker.C:
			if time.Now().After(deadline) {
				return nil, fmt.Errorf("request %s did not complete within %v", requestID, maxWait)
			}
		}
	}
}

Usage Examples

Async Mode (Default)

package main

import (
	"context"
	"fmt"
	"log"

	"yourproject/txncheck"
)

func main() {
	client := txncheck.NewClient(txncheck.DefaultConfig("fb_your_api_key_here"))
	ctx := context.Background()

	// Submit request (async mode)
	result, err := client.UPIByMobile(ctx, "+919876543210", false)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Request ID: %s\n", result.RequestID)

	// Poll for result
	finalResult, err := client.WaitForResult(ctx, result.RequestID, 0, 0)
	if err != nil {
		log.Fatal(err)
	}
	
	fmt.Printf("Status: %s\n", finalResult.Status)
	if upi, ok := finalResult.Result["upi"].([]interface{}); ok {
		fmt.Printf("UPI addresses: %v\n", upi)
	}
}

Sync Mode

package main

import (
	"context"
	"fmt"
	"log"

	"yourproject/txncheck"
)

func main() {
	client := txncheck.NewClient(txncheck.DefaultConfig("fb_your_api_key_here"))
	ctx := context.Background()

	// Get result immediately (sync mode)
	result, err := client.UPIByMobile(ctx, "+919876543210", true)
	if err != nil {
		log.Fatal(err)
	}

	if result.Status == txncheck.StatusCompleted {
		fmt.Printf("Name: %v\n", result.Result["name"])
		if upi, ok := result.Result["upi"].([]interface{}); ok {
			fmt.Printf("UPI addresses: %v\n", upi)
		}
	}
}

Full Check

result, err := client.FullCheck(ctx, "+919876543210", true)
if err != nil {
	log.Fatal(err)
}

if result.Status == txncheck.StatusCompleted {
	// UPI data
	if upiData, ok := result.Result["upiByMobile"].(map[string]interface{}); ok {
		fmt.Printf("Name: %v\n", upiData["name"])
		fmt.Printf("VPAs: %v\n", upiData["upi"])
	}

	// KYC data
	if kycData, ok := result.Result["kycByMobile"].(map[string]interface{}); ok {
		fmt.Printf("PAN: %v\n", kycData["pan"])
		fmt.Printf("DOB: %v\n", kycData["dob"])
	}

	// Blocklist
	if blocklist, ok := result.Result["vpaChargebackCheck"].(map[string]interface{}); ok {
		if summary, ok := blocklist["summary"].(map[string]interface{}); ok {
			fmt.Printf("Blocklisted: %v\n", summary["blocklisted"])
		}
	}
}

VPA Blocklist Check

vpas := []string{"user1@upi", "user2@paytm", "suspicious@ybl"}
result, err := client.VPAChargebackCheck(ctx, vpas, true)
if err != nil {
	log.Fatal(err)
}

if result.Status == txncheck.StatusCompleted {
	// Blocklisted VPAs
	if blocklisted, ok := result.Result["blocklisted"].([]interface{}); ok {
		for _, v := range blocklisted {
			if vpa, ok := v.(map[string]interface{}); ok {
				fmt.Printf("⚠️ BLOCKED: %v\n", vpa["vpa"])
			}
		}
	}

	// Clean VPAs
	if clean, ok := result.Result["clean"].([]interface{}); ok {
		for _, v := range clean {
			if vpa, ok := v.(map[string]interface{}); ok {
				fmt.Printf("✓ Clean: %v\n", vpa["vpa"])
			}
		}
	}
}

Error Handling

package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"time"

	"yourproject/txncheck"
)

func verifyWithRetry(client *txncheck.Client, mobile string, maxRetries int) (*txncheck.VerificationResult, error) {
	ctx := context.Background()
	var lastErr error

	for attempt := 1; attempt <= maxRetries; attempt++ {
		result, err := client.UPIByMobile(ctx, mobile, true)
		if err == nil {
			return result, nil
		}

		lastErr = err

		// Check if error is retryable
		var apiErr *txncheck.APIError
		if errors.As(err, &apiErr) {
			// Don't retry client errors (except rate limit)
			if apiErr.StatusCode >= 400 && apiErr.StatusCode < 500 && apiErr.StatusCode != 429 {
				return nil, err
			}
		}

		// Exponential backoff
		delay := time.Duration(1<<(attempt-1)) * time.Second
		if delay > 10*time.Second {
			delay = 10 * time.Second
		}
		
		log.Printf("Retry %d/%d in %v...", attempt, maxRetries, delay)
		time.Sleep(delay)
	}

	return nil, lastErr
}

func main() {
	client := txncheck.NewClient(txncheck.DefaultConfig("fb_your_api_key_here"))

	result, err := verifyWithRetry(client, "+919876543210", 3)
	if err != nil {
		var apiErr *txncheck.APIError
		if errors.As(err, &apiErr) {
			fmt.Printf("API Error (%d): %s\n", apiErr.StatusCode, apiErr.Message)
		} else {
			fmt.Printf("Error: %v\n", err)
		}
		return
	}

	fmt.Printf("Verified: %v\n", result.Result["name"])
}

Webhook Signature Verification

package webhooks

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"io"
	"net/http"
	"strconv"
	"time"
)

// VerifyWebhookSignature verifies the webhook signature
func VerifyWebhookSignature(payload []byte, signature, timestamp, secretKey string, maxAgeSeconds int64) bool {
	// Check timestamp freshness
	webhookTime, err := strconv.ParseInt(timestamp, 10, 64)
	if err != nil {
		return false
	}

	currentTime := time.Now().UnixMilli()
	ageMs := currentTime - webhookTime

	if ageMs > maxAgeSeconds*1000 {
		return false // Webhook too old
	}

	// Compute expected signature
	message := timestamp + "." + string(payload)
	h := hmac.New(sha256.New, []byte(secretKey))
	h.Write([]byte(message))
	expected := hex.EncodeToString(h.Sum(nil))

	// Constant-time comparison
	return hmac.Equal([]byte(signature), []byte(expected))
}

// WebhookPayload represents the webhook payload
type WebhookPayload struct {
	Event     string                 `json:"event"`
	Timestamp string                 `json:"timestamp"`
	Data      map[string]interface{} `json:"data"`
}

// WebhookHandler handles incoming webhooks
func WebhookHandler(secretKey string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		signature := r.Header.Get("X-Webhook-Signature")
		timestamp := r.Header.Get("X-Webhook-Timestamp")

		if signature == "" || timestamp == "" {
			http.Error(w, "Missing signature headers", http.StatusUnauthorized)
			return
		}

		body, err := io.ReadAll(r.Body)
		if err != nil {
			http.Error(w, "Failed to read body", http.StatusBadRequest)
			return
		}

		if !VerifyWebhookSignature(body, signature, timestamp, secretKey, 300) {
			http.Error(w, "Invalid signature", http.StatusUnauthorized)
			return
		}

		var payload WebhookPayload
		if err := json.Unmarshal(body, &payload); err != nil {
			http.Error(w, "Invalid JSON", http.StatusBadRequest)
			return
		}

		switch payload.Event {
		case "request.completed":
			// Handle completed request
			requestID := payload.Data["requestId"].(string)
			log.Printf("Request completed: %s", requestID)

		case "request.failed":
			// Handle failed request
			requestID := payload.Data["requestId"].(string)
			log.Printf("Request failed: %s", requestID)
		}

		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
	}
}

// Usage
func main() {
	webhookSecret := "your_webhook_secret"
	
	http.HandleFunc("/webhook/fraud-buster", WebhookHandler(webhookSecret))
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Complete Integration Example

package main

import (
	"context"
	"fmt"
	"log"
	"strings"

	"yourproject/txncheck"
)

// VerificationOutcome represents the verification result
type VerificationOutcome struct {
	Verified  bool
	RiskScore int
	Details   struct {
		Name        string
		PANVerified bool
		VPALinked   bool
		VPACount    int
		Blocklisted bool
	}
	Error string
}

// VerifyCustomerForCheckout verifies a customer before payment
func VerifyCustomerForCheckout(client *txncheck.Client, mobile, paymentVPA string) VerificationOutcome {
	ctx := context.Background()
	outcome := VerificationOutcome{}

	// Step 1: Full verification
	log.Printf("Starting verification for %s", mobile)
	verification, err := client.FullCheck(ctx, mobile, true)
	if err != nil {
		log.Printf("Verification failed: %v", err)
		outcome.RiskScore = 100
		outcome.Error = err.Error()
		return outcome
	}

	if verification.Status != txncheck.StatusCompleted {
		log.Printf("Verification incomplete: %s", verification.Status)
		outcome.RiskScore = 100
		return outcome
	}

	// Step 2: Extract data
	upiData, _ := verification.Result["upiByMobile"].(map[string]interface{})
	kycData, _ := verification.Result["kycByMobile"].(map[string]interface{})
	blocklist, _ := verification.Result["vpaChargebackCheck"].(map[string]interface{})

	// Step 3: Check if payment VPA belongs to user
	var userVPAs []string
	if upi, ok := upiData["upi"].([]interface{}); ok {
		for _, v := range upi {
			if vpa, ok := v.(string); ok {
				userVPAs = append(userVPAs, strings.ToLower(vpa))
			}
		}
	}

	vpaLinked := false
	paymentVPALower := strings.ToLower(paymentVPA)
	for _, vpa := range userVPAs {
		if vpa == paymentVPALower {
			vpaLinked = true
			break
		}
	}

	if !vpaLinked {
		log.Printf("Payment VPA %s not linked to %s", paymentVPA, mobile)
		outcome.RiskScore += 50
	}

	// Step 4: Check blocklist
	if blocklistedList, ok := blocklist["blocklisted"].([]interface{}); ok {
		for _, v := range blocklistedList {
			if item, ok := v.(map[string]interface{}); ok {
				if vpa, ok := item["vpa"].(string); ok {
					if strings.ToLower(vpa) == paymentVPALower {
						log.Printf("Payment VPA %s is BLOCKLISTED!", paymentVPA)
						outcome.RiskScore = 100
						outcome.Details.Blocklisted = true
						return outcome
					}
				}
			}
		}
	}

	// Step 5: Build result
	outcome.Verified = outcome.RiskScore < 50

	if name, ok := kycData["fullName"].(string); ok {
		outcome.Details.Name = name
	} else if name, ok := upiData["name"].(string); ok {
		outcome.Details.Name = name
	}

	if pan, ok := kycData["pan"].(string); ok {
		outcome.Details.PANVerified = pan != ""
	}

	outcome.Details.VPALinked = vpaLinked
	outcome.Details.VPACount = len(userVPAs)

	log.Printf("Verification complete. Risk score: %d", outcome.RiskScore)
	return outcome
}

func main() {
	client := txncheck.NewClient(txncheck.DefaultConfig("fb_your_api_key_here"))

	result := VerifyCustomerForCheckout(
		client,
		"+919876543210",
		"user@upi",
	)

	if result.Verified {
		fmt.Println("✓ Customer verified, proceed with payment")
		fmt.Printf("  Name: %s\n", result.Details.Name)
	} else {
		fmt.Printf("⚠️ Verification failed. Risk score: %d\n", result.RiskScore)
		if result.Details.Blocklisted {
			fmt.Println("  Reason: VPA is blocklisted")
		}
	}
}

Next Steps