package ln import ( "encoding/hex" "encoding/json" "log" "strconv" "strings" "time" "github.com/lightningnetwork/lnd/lnrpc" ) type Currency int const ( CurMSat Currency = iota CurSat CurBTC CurUSD CurEur ) const ( Paid int = iota UnPaid Expired ) var InvoiceStatus = map[int]string{ UnPaid: "unpaid", Paid: "paid", Expired: "expired", } const ( InfoEndpoint = "info" InvoiceEndpoint = "invoice" ) var ( CurrencyString = map[Currency]string{ CurUSD: "USD", CurSat: "SAT", CurBTC: "BTC", CurMSat: "MSAT", CurEur: "EUR", } CurrencyID = map[string]Currency{ "USD": CurUSD, "SAT": CurSat, "MSAT": CurMSat, "BTC": CurBTC, "EUR": CurEur, } ) type timestamp time.Time func (t *timestamp) UnmarshalJSON(in []byte) error { if string(in) == "null" { *t = timestamp(time.Unix(0, 0)) return nil } // Try to first parse in RFC3339 var val time.Time var err error // put the input in string mode strIn := string(in) // clean strIn = strings.Replace(strIn, "\"", "", -1) if val, err = time.Parse(time.RFC3339, strIn); err != nil { val, err := strconv.Atoi(strIn) if err != nil { log.Printf("error unmarshal time %s", in) return err } parsedTime := time.Unix(int64(val), 0) *t = timestamp(parsedTime) return nil } *t = timestamp(val) return nil } func (t timestamp) MarshalJSON() ([]byte, error) { str := strconv.Itoa(int(time.Time(t).Unix())) return []byte(str), nil } func (t timestamp) String() string { return time.Time(t).Format(time.RFC3339) } type Invoice struct { AddIndex uint64 `json:"-"` SettleIndex uint64 `json:"-"` Description string `json:"description"` Msatoshi float64 `json:"msatoshi"` Payreq string `json:"payreq"` RHash string `json:"rhash"` PreImage string `json:"-"` // used as secret admin token Status string `json:"status"` Settled bool `json:"settled"` PaidAt timestamp `json:"paid_at"` CreatedAt timestamp `json:"created_at"` ExpiresAt timestamp `json:"expires_at"` Expired bool `json:"-"` QuotedCurrency string `json:"-"` QuotedAmount float64 `json:"-"` } func (i Invoice) MarshalBinary() ([]byte, error) { return json.Marshal(i) } func (i *Invoice) UnmarshalBinary(b []byte) error { return json.Unmarshal(b, &i) } // Create new Invoice from lnrpc.Invoice func InvoiceFromLndIn(lndInvoice *lnrpc.Invoice) *Invoice { //log.Printf("new invoice form lnd with value %d", lndInvoice.Value) //log.Printf("setting msat to %f", float64(lndInvoice.Value*1000)) invoice := Invoice{ AddIndex: lndInvoice.AddIndex, Description: lndInvoice.GetMemo(), Msatoshi: float64(lndInvoice.Value * 1000), Payreq: lndInvoice.PaymentRequest, RHash: hex.EncodeToString(lndInvoice.RHash), PreImage: hex.EncodeToString(lndInvoice.RPreimage), CreatedAt: timestamp(time.Unix(lndInvoice.CreationDate, 0)), PaidAt: timestamp(time.Unix(lndInvoice.SettleDate, 0)), Status: InvoiceStatus[UnPaid], } invoice.ExpiresAt = timestamp(time.Time(invoice.CreatedAt).Add(time.Second * time.Duration(lndInvoice.Expiry))) // Calculate status if lndInvoice.Settled { invoice.Settled = true invoice.Status = InvoiceStatus[Paid] } if time.Now().After(time.Time(invoice.ExpiresAt)) { invoice.Status = InvoiceStatus[Expired] invoice.Expired = true } return &invoice } // Update stored invoice from lnd invoice func UpdateInvoiceFromLnd(storedIn *Invoice, newIn *lnrpc.Invoice) *Invoice { storedIn.SettleIndex = newIn.SettleIndex storedIn.PaidAt = timestamp(time.Unix(newIn.SettleDate, 0)) // Calculate status if newIn.Settled { storedIn.Status = InvoiceStatus[Paid] storedIn.Settled = true } // Handle expired status if time.Now().After(time.Time(storedIn.ExpiresAt)) { storedIn.Status = InvoiceStatus[Expired] storedIn.Expired = true } return storedIn } func IsExpiredInvoice(creation, expiry int64) bool { createdAt := time.Unix(int64(creation), 0) expiresAt := createdAt.Add(time.Second * time.Duration(expiry)) return time.Now().After(expiresAt) }