package ln import ( "bytes" "encoding/json" "fmt" "log" "net/http" "net/url" "strconv" "strings" "git.sp4ke.com/sp4ke/bit4sat/btc" "git.sp4ke.com/sp4ke/bit4sat/bus" "git.sp4ke.com/sp4ke/bit4sat/config" "git.sp4ke.com/sp4ke/bit4sat/db" "git.sp4ke.com/sp4ke/bit4sat/lndrpc" "git.sp4ke.com/sp4ke/bit4sat/utils" "github.com/gin-gonic/gin" "github.com/mediocregopher/radix/v3" ) var ( Client *http.Client ) func Info() (gin.H, error) { result := make(gin.H) resp, err := Client.Get(getUrl(InfoEndpoint)) if err != nil { return nil, err } jsonDec := json.NewDecoder(resp.Body) jsonDec.Decode(&result) return result, nil } // Quick check if invoice was paid func CheckInvoice(id string) (*Invoice, error) { lnInvoice, err := lndrpc.LookupInvoiceRhashStr(id) if err != nil { return nil, err } invoice := InvoiceFromLndIn(lnInvoice) return invoice, nil } // This will watch on the pubsub for paid invoices given an invoice id func PollPaidInvoice(invoiceId string, invoicePaidChan chan<- *Invoice, errorChan chan<- error) { watchPaidChannel := make(chan radix.PubSubMessage) busName := fmt.Sprintf("%s:*", bus.InvoicePaidChannelPrefix) err := db.DB.RedisPubSub.PSubscribe(watchPaidChannel, busName) if err != nil { errorChan <- err return } for { msg := <-watchPaidChannel //log.Printf("received message on %s", msg.Channel) split := strings.Split(msg.Channel, ":") if len(split) != 2 { errorChan <- fmt.Errorf("error in pubsub channel parsing %s", msg.Channel) } targetInvoiceId := strings.Split(msg.Channel, ":")[1] //log.Printf("target invoice is %s", targetInvoiceId) if targetInvoiceId == invoiceId { invoice := Invoice{} err := json.Unmarshal(msg.Message, &invoice) if err != nil { errorChan <- err return } invoicePaidChan <- &invoice return } } } func PollPaidInvoiceTMP(invoiceId string, invoiceChan chan<- *Invoice, errorChan chan<- error) { invoice := Invoice{} reqParams := url.Values{} // Timeout in seconds reqParams.Set("timeout", strconv.Itoa(60)) reqURI := fmt.Sprintf("%s/%s/wait?%s", getUrl(InvoiceEndpoint), invoiceId, reqParams.Encode()) log.Printf("polling to %s", reqURI) var err error var jsonDec *json.Decoder resp := &http.Response{} for { //log.Println("new poll") resp, err = http.Get(reqURI) if err != nil { log.Printf("Error: ", err) } jsonDec = json.NewDecoder(resp.Body) if resp.StatusCode == http.StatusPaymentRequired { log.Printf("invoice %s not yet paid", invoiceId) continue } if resp.StatusCode == http.StatusGone { log.Printf("invoice expired %s", invoiceId) invoice.Expired = true break } if resp.StatusCode == http.StatusOK { log.Printf("Invoice paid %s", invoice) break } else { log.Println("else !") // This section handles unknown answer from ln-charge log.Printf("InvoicePoll Error: unknown resopnse %s", resp.Status) jsonDec.Decode(&invoice) close(invoiceChan) errorChan <- fmt.Errorf("%s: %s", resp.Status, invoice) return } } jsonDec.Decode(&invoice) invoiceChan <- &invoice log.Printf("quit polling %s", invoiceId) } func NewInvoiceForUpload(amount float64, curr Currency, uploadId string) (*Invoice, error) { var err error var satValue int64 description := fmt.Sprintf("bit4sat upload: %s", uploadId) if curr == CurMSat { log.Println("cur is msat") // Convert to MSAT, meaning values under 1000 MSAT are ignored satValue = int64(amount / 1000) // Other supported currecies } if curr != CurSat { log.Println("cur is custom") // Get Sat satValue for this invoice msatVal, err := btc.ToMsat(CurrencyString[curr], amount) if err != nil { return nil, err } satValue = msatVal / 1000 } else { satValue = int64(amount) } lndInvoice, err := lndrpc.AddInvoiceSat(description, satValue) if err != nil { return nil, err } invoice := InvoiceFromLndIn(lndInvoice) invoice.QuotedCurrency = CurrencyString[curr] invoice.QuotedAmount = amount return invoice, nil } func NewInvoiceForUploadTMP(amount float32, curr Currency, uploadId string) (*Invoice, error) { webhookUrl := fmt.Sprintf("http://%s:%s/%s", config.ApiHost, config.ApiPort, config.ChargeCallbackEndpoint) reqData := gin.H{ "description": fmt.Sprintf("bit4sat upload: %s", uploadId), "webhook": webhookUrl, } // If Satoshi convert to millisat if curr == CurSat { reqData["msatoshi"] = amount * 1000 } else { reqData["currency"] = CurrencyString[curr] reqData["amount"] = amount } jsonEnc, err := json.Marshal(reqData) if err != nil { return nil, err } log.Printf("Asking for invoice with: %s", jsonEnc) resp, err := Client.Post(getUrl(InvoiceEndpoint), "application/json", bytes.NewReader(jsonEnc)) if err != nil { return nil, err } invoice := Invoice{} jsonDec := json.NewDecoder(resp.Body) jsonDec.Decode(&invoice) return &invoice, nil } func getUrl(endpoint string) string { url, err := url.Parse(fmt.Sprintf("http://api-token:%s@%s/%s", config.LnChargeToken, config.LnChargeApi, endpoint)) if err != nil { log.Fatal(err) } return url.String() } func init() { Client = utils.NewHttpClient() }