192 lines
4.2 KiB
Go
192 lines
4.2 KiB
Go
package phoenixd
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/ekzyis/zaply/lightning"
|
|
"github.com/labstack/echo/v4"
|
|
)
|
|
|
|
type Phoenixd struct {
|
|
url *url.URL
|
|
accessToken string
|
|
limitedAccessToken string
|
|
webhookUrl string
|
|
|
|
paymentsChan chan *lightning.Invoice
|
|
}
|
|
|
|
func NewPhoenixd(opts ...func(*Phoenixd) *Phoenixd) *Phoenixd {
|
|
ln := &Phoenixd{
|
|
paymentsChan: make(chan *lightning.Invoice),
|
|
}
|
|
for _, opt := range opts {
|
|
opt(ln)
|
|
}
|
|
return ln
|
|
}
|
|
|
|
func WithPhoenixdURL(u string) func(*Phoenixd) *Phoenixd {
|
|
return func(p *Phoenixd) *Phoenixd {
|
|
u, err := url.Parse(u)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
p.url = u
|
|
return p
|
|
}
|
|
}
|
|
|
|
func WithPhoenixdLimitedAccessToken(limitedAccessToken string) func(*Phoenixd) *Phoenixd {
|
|
return func(p *Phoenixd) *Phoenixd {
|
|
p.limitedAccessToken = limitedAccessToken
|
|
return p
|
|
}
|
|
}
|
|
|
|
func WithPhoenixdWebhookUrl(webhookUrl string) func(*Phoenixd) *Phoenixd {
|
|
return func(p *Phoenixd) *Phoenixd {
|
|
p.webhookUrl = webhookUrl
|
|
return p
|
|
}
|
|
}
|
|
|
|
func (p *Phoenixd) CreateInvoice(msats int64, description string) (lightning.PaymentRequest, error) {
|
|
values := url.Values{}
|
|
values.Add("amountSat", strconv.FormatInt(msats/1000, 10))
|
|
values.Add("description", description)
|
|
if p.webhookUrl != "" {
|
|
values.Add("webhookUrl", p.webhookUrl)
|
|
}
|
|
|
|
endpoint := p.url.JoinPath("createinvoice")
|
|
|
|
req, err := http.NewRequest("POST", endpoint.String(), strings.NewReader(values.Encode()))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
if p.limitedAccessToken != "" {
|
|
req.SetBasicAuth("", p.limitedAccessToken)
|
|
}
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return "", fmt.Errorf("phoenixd %s: %s", resp.Status, string(body))
|
|
}
|
|
|
|
var response struct {
|
|
Serialized string `json:"serialized"`
|
|
}
|
|
if err := json.Unmarshal(body, &response); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return lightning.PaymentRequest(response.Serialized), nil
|
|
}
|
|
|
|
func (p *Phoenixd) GetInvoice(paymentHash string) (*lightning.Invoice, error) {
|
|
endpoint := p.url.JoinPath("payments/incoming", paymentHash)
|
|
|
|
req, err := http.NewRequest("GET", endpoint.String(), nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if p.limitedAccessToken != "" {
|
|
req.SetBasicAuth("", p.limitedAccessToken)
|
|
}
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("phoenixd %s: %s", resp.Status, string(body))
|
|
}
|
|
|
|
var response struct {
|
|
PaymentHash string `json:"paymentHash"`
|
|
Preimage string `json:"preimage"`
|
|
Sats int64 `json:"receivedSat"`
|
|
Description string `json:"description"`
|
|
CreatedAt int64 `json:"createdAt"`
|
|
ConfirmedAt int64 `json:"completedAt"`
|
|
}
|
|
if err := json.Unmarshal(body, &response); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
createdAt := time.Unix(response.CreatedAt/1000, 0)
|
|
var confirmedAt time.Time
|
|
if response.ConfirmedAt != 0 {
|
|
confirmedAt = time.Unix(response.ConfirmedAt/1000, 0)
|
|
}
|
|
|
|
return &lightning.Invoice{
|
|
PaymentHash: response.PaymentHash,
|
|
Preimage: response.Preimage,
|
|
Msats: response.Sats * 1_000,
|
|
Description: response.Description,
|
|
CreatedAt: createdAt,
|
|
ConfirmedAt: confirmedAt,
|
|
}, nil
|
|
}
|
|
|
|
func (p *Phoenixd) IncomingPayments() chan *lightning.Invoice {
|
|
return p.paymentsChan
|
|
}
|
|
|
|
func (p *Phoenixd) WebhookHandler(c echo.Context) error {
|
|
var webhook struct {
|
|
Type string `json:"type"`
|
|
AmountSat int64 `json:"amountSat"`
|
|
PaymentHash string `json:"paymentHash"`
|
|
}
|
|
if err := c.Bind(&webhook); err != nil {
|
|
return err
|
|
}
|
|
|
|
go func() {
|
|
inv, err := p.GetInvoice(webhook.PaymentHash)
|
|
if err != nil {
|
|
c.Logger().Error(err)
|
|
return
|
|
}
|
|
|
|
log.Printf(
|
|
"payment received: %s | %d msats | %s | %s | %s",
|
|
inv.PaymentHash, inv.Msats, inv.Description,
|
|
inv.CreatedAt.Format(time.RFC3339), inv.ConfirmedAt.Format(time.RFC3339),
|
|
)
|
|
|
|
p.paymentsChan <- inv
|
|
}()
|
|
|
|
return c.NoContent(http.StatusOK)
|
|
}
|