package invoice import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "strings" "time" ) type LNbitsClient struct { baseURL string invoiceKey string hc *http.Client } func NewLNbits(baseURL, invoiceKey string) *LNbitsClient { return &LNbitsClient{ baseURL: strings.TrimRight(baseURL, "/"), invoiceKey: invoiceKey, hc: &http.Client{Timeout: 15 * time.Second}, } } type createReq struct { Out bool `json:"out"` Amount int64 `json:"amount"` Memo string `json:"memo"` Expiry int `json:"expiry,omitempty"` } type createResp struct { PaymentHash string `json:"payment_hash"` PaymentRequest string `json:"payment_request"` BOLT11 string `json:"bolt11"` } type statusResp struct { Paid bool `json:"paid"` Pending bool `json:"pending"` Details *struct { Pending bool `json:"pending"` Status string `json:"status"` } `json:"details"` } func (c *LNbitsClient) Create(ctx context.Context, amountSats int64, memo string, expirySecs int) (string, string, error) { body, err := json.Marshal(createReq{Out: false, Amount: amountSats, Memo: memo, Expiry: expirySecs}) if err != nil { return "", "", err } req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/api/v1/payments", bytes.NewReader(body)) if err != nil { return "", "", err } req.Header.Set("X-Api-Key", c.invoiceKey) req.Header.Set("Content-Type", "application/json") resp, err := c.hc.Do(req) if err != nil { return "", "", fmt.Errorf("%w: %v", ErrLNbits, err) } defer resp.Body.Close() b, _ := io.ReadAll(resp.Body) if resp.StatusCode/100 != 2 { return "", "", fmt.Errorf("%w: %s", ErrLNbits, string(b)) } var cr createResp if err := json.Unmarshal(b, &cr); err != nil { return "", "", fmt.Errorf("%w: decode: %v", ErrLNbits, err) } pr := cr.PaymentRequest if pr == "" { pr = cr.BOLT11 } return cr.PaymentHash, pr, nil } func (c *LNbitsClient) Status(ctx context.Context, hash string) (bool, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/api/v1/payments/"+hash, nil) if err != nil { return false, err } req.Header.Set("X-Api-Key", c.invoiceKey) resp, err := c.hc.Do(req) if err != nil { return false, fmt.Errorf("%w: %v", ErrLNbits, err) } defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { return false, nil } if resp.StatusCode/100 != 2 { b, _ := io.ReadAll(resp.Body) return false, fmt.Errorf("%w: %s", ErrLNbits, string(b)) } var sr statusResp if err := json.NewDecoder(resp.Body).Decode(&sr); err != nil { return false, err } return sr.Paid, nil }