package heleket import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "strconv" "time" ) const apiBase = "https://api.heleket.com" type Client struct { merchantID string apiKey string http *http.Client } type InvoiceRequest struct { Amount string `json:"amount"` Currency string `json:"currency"` OrderID string `json:"order_id"` URLReturn string `json:"url_return,omitempty"` URLSuccess string `json:"url_success,omitempty"` URLCallback string `json:"url_callback,omitempty"` PayerEmail string `json:"payer_email,omitempty"` Lifetime int `json:"lifetime,omitempty"` AdditionalData string `json:"additional_data,omitempty"` } type Invoice struct { UUID string `json:"uuid"` OrderID string `json:"order_id"` Amount string `json:"amount"` PaymentStatus string `json:"payment_status"` URL string `json:"url"` ExpiredAt int64 `json:"expired_at"` IsFinal bool `json:"is_final"` } type apiResponse struct { State int `json:"state"` Result json.RawMessage `json:"result"` Errors json.RawMessage `json:"errors"` } func NewClient(merchantID, apiKey string) *Client { return &Client{ merchantID: merchantID, apiKey: apiKey, http: &http.Client{Timeout: 30 * time.Second}, } } func (c *Client) Enabled() bool { return c.merchantID != "" && c.apiKey != "" } func OrderExternalID(orderID int) string { return fmt.Sprintf("shop-order-%d", orderID) } func ParseOrderExternalID(orderID string) (int, bool) { const prefix = "shop-order-" if len(orderID) <= len(prefix) || orderID[:len(prefix)] != prefix { return 0, false } id, err := strconv.Atoi(orderID[len(prefix):]) return id, err == nil && id > 0 } func FormatAmount(amount float64) string { return strconv.FormatFloat(amount, 'f', 2, 64) } func (c *Client) CreateInvoice(ctx context.Context, req InvoiceRequest) (Invoice, error) { body, err := encodeBody(req) if err != nil { return Invoice{}, err } httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, apiBase+"/v1/payment", bytes.NewReader(body)) if err != nil { return Invoice{}, err } httpReq.Header.Set("Content-Type", "application/json") httpReq.Header.Set("merchant", c.merchantID) httpReq.Header.Set("sign", SignBody(body, c.apiKey)) resp, err := c.http.Do(httpReq) if err != nil { return Invoice{}, err } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { return Invoice{}, err } if resp.StatusCode < 200 || resp.StatusCode >= 300 { return Invoice{}, fmt.Errorf("heleket http %d: %s", resp.StatusCode, string(respBody)) } var wrapped apiResponse if err := json.Unmarshal(respBody, &wrapped); err != nil { return Invoice{}, err } if wrapped.State != 0 { return Invoice{}, fmt.Errorf("heleket api state %d: %s", wrapped.State, string(wrapped.Errors)) } var invoice Invoice if err := json.Unmarshal(wrapped.Result, &invoice); err != nil { return Invoice{}, err } return invoice, nil } // MapPaymentStatus maps Heleket payment_status to shop order status. func MapPaymentStatus(paymentStatus string) string { switch paymentStatus { case "paid", "paid_over": return "paid" case "cancel", "fail", "system_fail": return "cancelled" case "refund_paid": return "refunded" default: return "unpaid" } } func IsPaid(paymentStatus string) bool { return paymentStatus == "paid" || paymentStatus == "paid_over" }