From 44e9315706f54f4776a1fb576c3023c69ac5f409 Mon Sep 17 00:00:00 2001 From: Urko Date: Wed, 19 Jul 2023 21:20:59 +0200 Subject: [PATCH] feat: configure webhook --- README.md | 3 +++ cmd/http/server/main.go | 1 - internal/api/server.go | 9 +++++++ internal/domain/order.go | 8 +++--- internal/services/btc/btc.go | 3 --- internal/services/order.go | 49 +++++++++++++++++++++++++++++++++++- kit/cfg/config.go | 1 + 7 files changed, 65 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 51452c9..e1b95d5 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,9 @@ The application uses the following environment variables: MAIL_USER, MAIL_PASSWORD, MAIL_HOST, MAIL_PORT, MAIL_FROM, MAIL_TEMPLATES_DIR: Configuration for the mail service. ``` +### Webhook configuration +If you want some http endpoint to be called by **POST** you have to set up `WEBHOOK_URL` as environment variable with absolute URL of your endpoint. After payment is completed by client, order will be send through http `POST`. + ## Dependencies The application uses several external packages: diff --git a/cmd/http/server/main.go b/cmd/http/server/main.go index 883e318..1ddd9dc 100644 --- a/cmd/http/server/main.go +++ b/cmd/http/server/main.go @@ -80,7 +80,6 @@ func main() { <-ctx.Done() - log.Println("on shutdown") if restServer != nil { if err := restServer.Shutdown(); err != nil { panic(fmt.Errorf("restServer.Shutdown: %w", err)) diff --git a/internal/api/server.go b/internal/api/server.go index 05f4cae..e44a8f3 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -108,6 +108,15 @@ func (s *RestServer) onNotification(ctx context.Context, notifChan chan domain.N continue } + // notify + if s.config.WebhookUrl != "" { + go func() { + if err := s.orderSrv.Webhook(ctx, s.config.WebhookUrl, order); err != nil { + log.Println("orderSrv.Webhook:", err) + } + }() + } + // Send email to client and provider if err := s.mailSrv.SendProviderConfirm(mail.SendOK{ Tx: order.Tx, diff --git a/internal/domain/order.go b/internal/domain/order.go index 19cafee..b4ed798 100644 --- a/internal/domain/order.go +++ b/internal/domain/order.go @@ -7,10 +7,10 @@ import ( ) type Order struct { - ID primitive.ObjectID `bson:"_id" json:"_id"` - OrderID string `json:"order_id"` - ClientID string `json:"client_id"` - Email string `json:"email"` + ID primitive.ObjectID `bson:"_id" json:"-"` + OrderID string `bson:"order_id" json:"order_id"` + ClientID string `bson:"client_id" json:"client_id"` + Email string `bson:"email" json:"email"` Amount float64 `bson:"amount" json:"amount"` Tx string `bson:"tx" json:"tx"` Block string `bson:"block" json:"block"` diff --git a/internal/services/btc/btc.go b/internal/services/btc/btc.go index 2c58d77..9ab1521 100644 --- a/internal/services/btc/btc.go +++ b/internal/services/btc/btc.go @@ -2,7 +2,6 @@ package btc import ( "fmt" - "net/http" ) type BitcoinService struct { @@ -10,7 +9,6 @@ type BitcoinService struct { auth string zmqAddress string walletAddress string - client *http.Client testNet bool } @@ -20,7 +18,6 @@ func NewBitcoinService(host, auth, zmqAddress, walletAddress string) *BitcoinSer auth: auth, zmqAddress: zmqAddress, walletAddress: walletAddress, - client: &http.Client{}, } from, err := bs.getAddressGroupings(false) diff --git a/internal/services/order.go b/internal/services/order.go index 4bb2b53..9c88a16 100644 --- a/internal/services/order.go +++ b/internal/services/order.go @@ -1,7 +1,13 @@ package services import ( + "bytes" "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" "time" "gitea.urkob.com/urko/btc-pay-checker/internal/domain" @@ -14,12 +20,14 @@ const defaultExpirationTime = time.Minute * 30 type Order struct { repo *order.Repo expiration time.Duration + client *http.Client } func NewOrder(repo *order.Repo) *Order { return &Order{ repo: repo, expiration: defaultExpirationTime, + client: &http.Client{}, } } @@ -27,7 +35,6 @@ func (o *Order) WithExpiration(expiration time.Duration) *Order { o.expiration = expiration return o } - func (o *Order) NewOrder(ctx context.Context, orderID, clientID, email string, amount float64) (*domain.Order, error) { order := domain.Order{ ID: primitive.NewObjectID(), @@ -54,3 +61,43 @@ func (o *Order) FromAmount(ctx context.Context, amount float64, timestamp time.T func (o *Order) OrderCompleted(ctx context.Context, order *domain.Order) (*domain.Order, error) { return o.repo.OrderCompleted(ctx, order) } + +func (o *Order) Webhook(ctx context.Context, webhookUrl string, order *domain.Order) error { + if webhookUrl == "" { + return nil + } + + reqBody, err := json.Marshal(order) + if err != nil { + fmt.Println(err) + return err + } + + payload := bytes.NewReader(reqBody) + client := &http.Client{} + req, err := http.NewRequest(http.MethodPost, webhookUrl, payload) + if err != nil { + fmt.Println(err) + return err + } + req.Header.Add("Accept", "application/json") + req.Header.Add("Content-Type", "application/json") + + res, err := client.Do(req) + if err != nil { + return fmt.Errorf("client.Do: %w", err) + } + if res.StatusCode != http.StatusOK { + defer res.Body.Close() + + body, err := io.ReadAll(res.Body) + if err != nil { + return errors.Join(fmt.Errorf("response status code is: %d", res.StatusCode), err) + } + + return fmt.Errorf("response status code is: %d | response body %s body", res.StatusCode, string(body)) + } + + return nil + +} diff --git a/kit/cfg/config.go b/kit/cfg/config.go index 1baaca6..f7f7e83 100644 --- a/kit/cfg/config.go +++ b/kit/cfg/config.go @@ -17,6 +17,7 @@ type Config struct { RpcAuth string `required:"true" split_words:"true"` RpcHost string `required:"true" split_words:"true"` WalletAddress string `required:"true" split_words:"true"` + WebhookUrl string `required:"false" split_words:"true"` ApiPort string `required:"true" split_words:"true"` Views string `required:"true" split_words:"true"` ConversorApi string `required:"true" split_words:"true"`