From f625ef7efccbd3f7959eec338669dec032c03cad Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Wed, 19 Jan 2022 19:43:29 +0100 Subject: [PATCH] Implement addinvoice call --- controllers/addinvoice.ctrl.go | 34 +++++++++--------- db/models/invoice.go | 28 +++++++-------- lib/service/invoices.go | 66 ++++++++++++++++++++++++++-------- 3 files changed, 82 insertions(+), 46 deletions(-) diff --git a/controllers/addinvoice.ctrl.go b/controllers/addinvoice.ctrl.go index b3d9ce9..c1867d0 100644 --- a/controllers/addinvoice.ctrl.go +++ b/controllers/addinvoice.ctrl.go @@ -1,12 +1,10 @@ package controllers import ( - "math/rand" "net/http" "github.com/getAlby/lndhub.go/lib/service" "github.com/labstack/echo/v4" - "github.com/labstack/gommon/random" ) // AddInvoiceController : Add invoice controller struct @@ -22,7 +20,7 @@ func NewAddInvoiceController(svc *service.LndhubService) *AddInvoiceController { func (controller *AddInvoiceController) AddInvoice(c echo.Context) error { userID := c.Get("UserID").(int64) type RequestBody struct { - Amt uint `json:"amt" validate:"required"` + Amt int64 `json:"amt" validate:"required,gte=0"` // amount in Satoshi Memo string `json:"memo"` DescriptionHash string `json:"description_hash"` } @@ -30,41 +28,41 @@ func (controller *AddInvoiceController) AddInvoice(c echo.Context) error { var body RequestBody if err := c.Bind(&body); err != nil { + c.Logger().Errorf("Failed to load addinvoice request body: %v", err) return c.JSON(http.StatusBadRequest, echo.Map{ - "message": "failed to bind json, amt field with positive value is required", + "error": true, + "code": 8, + "message": "Bad arguments", }) } if err := c.Validate(&body); err != nil { + c.Logger().Errorf("Invalid addinvoice request body: %v", err) return c.JSON(http.StatusBadRequest, echo.Map{ - "message": "amt with positive value is required", + "error": true, + "code": 8, + "message": "Bad arguments", }) } + c.Logger().Infof("Adding invoice: user_id=%v memo=%s value=%v description_hash=%s", userID, body.Memo, body.Amt, body.DescriptionHash) + invoice, err := controller.svc.AddInvoice(userID, body.Amt, body.Memo, body.DescriptionHash) if err != nil { - c.Logger().Errorf("error saving an invoice: %v", err) + c.Logger().Errorf("Error creating invoice: %v", err) + // TODO: sentry notification return c.JSON(http.StatusInternalServerError, nil) } + var responseBody struct { RHash string `json:"r_hash"` PaymentRequest string `json:"payment_request"` PayReq string `json:"pay_req"` } - //TODO - responseBody.PayReq = makePreimageHex() + responseBody.RHash = invoice.RHash responseBody.PaymentRequest = invoice.PaymentRequest + responseBody.PayReq = invoice.PaymentRequest return c.JSON(http.StatusOK, &responseBody) } - -const hexBytes = random.Hex - -func makePreimageHex() string { - b := make([]byte, 32) - for i := range b { - b[i] = hexBytes[rand.Intn(len(hexBytes))] - } - return string(b) -} diff --git a/db/models/invoice.go b/db/models/invoice.go index 391fec8..90f7fbd 100644 --- a/db/models/invoice.go +++ b/db/models/invoice.go @@ -9,20 +9,20 @@ import ( // Invoice : Invoice Model type Invoice struct { - ID uint `json:"id" bun:",pk,autoincrement"` - Type string `json:"type"` - UserID int64 `json:"user_id"` - User *User `bun:"rel:belongs-to,join:user_id=id"` - TransactionEntryID uint `json:"transaction_entry_id"` - Amount uint `json:"amount"` - Memo string `json:"memo"` - DescriptionHash string `json:"description_hash"` - PaymentRequest string `json:"payment_request"` - RHash string `json:"r_hash"` - State string `json:"state"` - CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` - UpdatedAt bun.NullTime `json:"updated_at"` - SettledAt bun.NullTime `json:"settled_at"` + ID uint `json:"id" bun:",pk,autoincrement"` + Type string `json:"type"` + UserID int64 `json:"user_id"` + User *User `bun:"rel:belongs-to,join:user_id=id"` + Amount int64 `json:"amount"` + Memo string `json:"memo"` + DescriptionHash string `json:"description_hash"` + PaymentRequest string `json:"payment_request"` + RHash string `json:"r_hash"` + State string `json:"state"` + AddIndex uint64 `json:"add_index"` + CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` + UpdatedAt bun.NullTime `json:"updated_at"` + SettledAt bun.NullTime `json:"settled_at"` } func (i *Invoice) BeforeAppendModel(ctx context.Context, query bun.Query) error { diff --git a/lib/service/invoices.go b/lib/service/invoices.go index f8ad360..d4232a8 100644 --- a/lib/service/invoices.go +++ b/lib/service/invoices.go @@ -2,8 +2,12 @@ package service import ( "context" + "encoding/hex" + "math/rand" "github.com/getAlby/lndhub.go/db/models" + "github.com/labstack/gommon/random" + "github.com/lightningnetwork/lnd/lnrpc" ) func (svc *LndhubService) Payinvoice(userId int64, invoice string) error { @@ -27,23 +31,57 @@ func (svc *LndhubService) Payinvoice(userId int64, invoice string) error { } -func (svc *LndhubService) AddInvoice(userID int64, amount uint, memo, descriptionHash string) (*models.Invoice, error) { - invoice := &models.Invoice{ - Type: "", - UserID: userID, - TransactionEntryID: 0, - Amount: amount, - Memo: memo, - DescriptionHash: descriptionHash, - PaymentRequest: "", - RHash: "", - State: "", +func (svc *LndhubService) AddInvoice(userID int64, amount int64, memo, descriptionHash string) (*models.Invoice, error) { + // Initialize new DB invoice + invoice := models.Invoice{ + Type: "incoming", + UserID: userID, + Amount: amount, + Memo: memo, + DescriptionHash: descriptionHash, + State: "initialized", } - // TODO: move this to a service layer and call a method - _, err := svc.DB.NewInsert().Model(invoice).Exec(context.TODO()) + // Save invoice - we save the invoice early to have a record in case the LN call fails + _, err := svc.DB.NewInsert().Model(&invoice).Exec(context.TODO()) if err != nil { return nil, err } - return invoice, nil + + // Initialize lnrpc invoice + lnInvoice := lnrpc.Invoice{ + Memo: memo, + Value: amount, + RPreimage: makePreimageHex(), + Expiry: 3600 * 24, // 24h + } + lndClient := *svc.LndClient + // Call LND + lnInvoiceResult, err := lndClient.AddInvoice(context.TODO(), &lnInvoice) + if err != nil { + return nil, err + } + + // Update the DB invoice with the data from the LND gRPC call + invoice.PaymentRequest = lnInvoiceResult.PaymentRequest + invoice.RHash = hex.EncodeToString(lnInvoiceResult.RHash) + invoice.AddIndex = lnInvoiceResult.AddIndex + invoice.State = "created" + + _, err = svc.DB.NewUpdate().Model(&invoice).WherePK().Exec(context.TODO()) + if err != nil { + return nil, err + } + + return &invoice, nil +} + +const hexBytes = random.Hex + +func makePreimageHex() []byte { + b := make([]byte, 32) + for i := range b { + b[i] = hexBytes[rand.Intn(len(hexBytes))] + } + return b }