package hyperswitch import ( "bytes" "context" "encoding/json" "fmt" "net/http" "time" ) // Client is the Hyperswitch API client for payment operations. type Client struct { baseURL string apiKey string httpClient *http.Client } // NewClient creates a new Hyperswitch client. func NewClient(baseURL, apiKey string) *Client { return &Client{ baseURL: baseURL, apiKey: apiKey, httpClient: &http.Client{ Timeout: 30 * time.Second, }, } } // CreatePaymentRequest is the request body for POST /payments. type CreatePaymentRequest struct { Amount int64 `json:"amount"` // Amount in minor units (e.g. centimes for EUR) Currency string `json:"currency"` // e.g. "EUR" ReturnURL string `json:"return_url,omitempty"` Metadata map[string]string `json:"metadata,omitempty"` } // PaymentResponse is the response from POST /payments. type PaymentResponse struct { PaymentID string `json:"payment_id"` ClientSecret string `json:"client_secret"` Status string `json:"status"` Amount int64 `json:"amount"` Currency string `json:"currency"` } // PaymentStatus is the response from GET /payments/{payment_id}. type PaymentStatus struct { PaymentID string `json:"payment_id"` Status string `json:"status"` } // CreatePayment creates a payment in Hyperswitch and returns client_secret for frontend. func (c *Client) CreatePayment(ctx context.Context, amount int64, currency, orderID, returnURL string, metadata map[string]string) (*PaymentResponse, error) { if metadata == nil { metadata = make(map[string]string) } if orderID != "" { metadata["order_id"] = orderID } reqBody := CreatePaymentRequest{ Amount: amount, Currency: currency, ReturnURL: returnURL, Metadata: metadata, } body, err := json.Marshal(reqBody) if err != nil { return nil, fmt.Errorf("marshal create payment request: %w", err) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/payments", bytes.NewReader(body)) if err != nil { return nil, fmt.Errorf("create request: %w", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("api-key", c.apiKey) resp, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("http request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("hyperswitch create payment failed: status %d", resp.StatusCode) } var out PaymentResponse if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { return nil, fmt.Errorf("decode response: %w", err) } return &out, nil } // CreatePaymentSimple creates a payment and returns paymentID and clientSecret. // Convenience wrapper for PaymentProvider interface. func (c *Client) CreatePaymentSimple(ctx context.Context, amount int64, currency, orderID, returnURL string, metadata map[string]string) (paymentID, clientSecret string, err error) { resp, err := c.CreatePayment(ctx, amount, currency, orderID, returnURL, metadata) if err != nil { return "", "", err } return resp.PaymentID, resp.ClientSecret, nil } // GetPaymentStatus retrieves payment status string from Hyperswitch. func (c *Client) GetPaymentStatus(ctx context.Context, paymentID string) (string, error) { status, err := c.GetPayment(ctx, paymentID) if err != nil { return "", err } return status.Status, nil } // GetPayment retrieves payment status from Hyperswitch. func (c *Client) GetPayment(ctx context.Context, paymentID string) (*PaymentStatus, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/payments/"+paymentID, nil) if err != nil { return nil, fmt.Errorf("create request: %w", err) } req.Header.Set("api-key", c.apiKey) resp, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("http request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("hyperswitch get payment failed: status %d", resp.StatusCode) } var out PaymentStatus if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { return nil, fmt.Errorf("decode response: %w", err) } return &out, nil } // CreateRefundRequest is the request body for POST /refunds (v0.403 R2) type CreateRefundRequest struct { PaymentID string `json:"payment_id"` Amount *int64 `json:"amount,omitempty"` // nil = full refund Reason string `json:"reason,omitempty"` RefundType string `json:"refund_type,omitempty"` // "instant" or "scheduled" } // RefundResponse is the response from POST /refunds type RefundResponse struct { RefundID string `json:"refund_id"` PaymentID string `json:"payment_id"` Amount int64 `json:"amount"` Currency string `json:"currency"` Status string `json:"status"` } // CreateRefund creates a refund against a payment (v0.403 R2) func (c *Client) CreateRefund(ctx context.Context, paymentID string, amount *int64, reason string) (*RefundResponse, error) { reqBody := CreateRefundRequest{ PaymentID: paymentID, Amount: amount, Reason: reason, RefundType: "instant", } body, err := json.Marshal(reqBody) if err != nil { return nil, fmt.Errorf("marshal refund request: %w", err) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/refunds", bytes.NewReader(body)) if err != nil { return nil, fmt.Errorf("create request: %w", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("api-key", c.apiKey) resp, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("http request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("hyperswitch create refund failed: status %d", resp.StatusCode) } var out RefundResponse if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { return nil, fmt.Errorf("decode response: %w", err) } return &out, nil }