You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

111 lines
3.3 KiB
Go

package telegram
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
// httpClient is a shared client with sensible timeouts for Telegram Bot API calls.
var httpClient = &http.Client{
Timeout: 10 * time.Second,
}
// apiResponse is the generic envelope returned by every Bot API method.
type apiResponse struct {
OK bool `json:"ok"`
Result json.RawMessage `json:"result,omitempty"`
Description string `json:"description,omitempty"`
ErrorCode int `json:"error_code,omitempty"`
}
// CallBotAPI sends a JSON request to the Telegram Bot API and returns the result field.
func CallBotAPI(botToken, method string, payload any) (json.RawMessage, error) {
url := fmt.Sprintf("https://api.telegram.org/bot%s/%s", botToken, method)
body, err := json.Marshal(payload)
if err != nil {
return nil, fmt.Errorf("telegram: marshal payload: %w", err)
}
resp, err := httpClient.Post(url, "application/json", bytes.NewReader(body))
if err != nil {
return nil, fmt.Errorf("telegram: POST %s: %w", method, err)
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("telegram: read response: %w", err)
}
var apiResp apiResponse
if err := json.Unmarshal(respBody, &apiResp); err != nil {
return nil, fmt.Errorf("telegram: unmarshal response: %w", err)
}
if !apiResp.OK {
return nil, fmt.Errorf("telegram: %s failed (%d): %s", method, apiResp.ErrorCode, apiResp.Description)
}
return apiResp.Result, nil
}
// LabeledAmount represents one price component in a Telegram invoice.
type LabeledAmount struct {
Label string `json:"label"`
Amount int `json:"amount"` // smallest currency unit (kopecks for RUB)
}
// InvoiceLinkParams holds the parameters for createInvoiceLink.
type InvoiceLinkParams struct {
Title string `json:"title"`
Description string `json:"description"`
Payload string `json:"payload"`
ProviderToken string `json:"provider_token"`
Currency string `json:"currency"`
Prices []LabeledAmount `json:"prices"`
}
// CreateInvoiceLink calls the Telegram Bot API createInvoiceLink method
// and returns the HTTPS invoice URL the client can pass to openInvoice().
func CreateInvoiceLink(botToken string, params InvoiceLinkParams) (string, error) {
raw, err := CallBotAPI(botToken, "createInvoiceLink", params)
if err != nil {
return "", err
}
var link string
if err := json.Unmarshal(raw, &link); err != nil {
return "", fmt.Errorf("telegram: unmarshal invoice link: %w", err)
}
return link, nil
}
// AnswerPreCheckoutQuery responds to a Telegram pre_checkout_query.
// ok=true approves; ok=false + errorMsg declines.
func AnswerPreCheckoutQuery(botToken, queryID string, ok bool, errorMsg string) error {
payload := map[string]any{
"pre_checkout_query_id": queryID,
"ok": ok,
}
if !ok && errorMsg != "" {
payload["error_message"] = errorMsg
}
_, err := CallBotAPI(botToken, "answerPreCheckoutQuery", payload)
return err
}
// SetWebhook configures the Telegram webhook URL for payment callbacks.
func SetWebhook(botToken, webhookURL string) error {
payload := map[string]string{
"url": webhookURL,
}
_, err := CallBotAPI(botToken, "setWebhook", payload)
return err
}