feat: notify supplier and client after payment is done

This commit is contained in:
Urko 2023-07-19 21:01:35 +02:00
parent b426a36570
commit e80e75cb8c
9 changed files with 161 additions and 39 deletions

View File

@ -29,6 +29,7 @@ type orderReq struct {
ClientID string `json:"client_id"` ClientID string `json:"client_id"`
Amount float64 `json:"amount"` Amount float64 `json:"amount"`
Currency domain.FiatCurrency `json:"currency"` Currency domain.FiatCurrency `json:"currency"`
ClientEmail string `json:"client_email"`
} }
func (hdl *OrderHandler) Post(c *fiber.Ctx) error { func (hdl *OrderHandler) Post(c *fiber.Ctx) error {
@ -42,7 +43,7 @@ func (hdl *OrderHandler) Post(c *fiber.Ctx) error {
return RenderError(c, fmt.Errorf("hdl.conversor.UsdToBtc %w", err), "") return RenderError(c, fmt.Errorf("hdl.conversor.UsdToBtc %w", err), "")
} }
order, err := hdl.orderSrv.NewOrder(c.Context(), req.OrderID, req.ClientID, btcAmount) order, err := hdl.orderSrv.NewOrder(c.Context(), req.OrderID, req.ClientID, req.ClientEmail, btcAmount)
if err != nil { if err != nil {
return RenderError(c, fmt.Errorf("hdl.orderSrv.NewOrder %w", err), "") return RenderError(c, fmt.Errorf("hdl.orderSrv.NewOrder %w", err), "")
} }

View File

@ -102,18 +102,22 @@ func (s *RestServer) onNotification(ctx context.Context, notifChan chan domain.N
order.Block = notif.BlockHash order.Block = notif.BlockHash
order.Tx = notif.Tx order.Tx = notif.Tx
order, err = s.orderSrv.OrderCompleted(ctx, order)
if err := s.orderSrv.OrderCompleted(ctx, order); err != nil { if err != nil {
log.Println("OrderCompleted:", err) log.Println("OrderCompleted:", err)
continue continue
} }
// Send email to client and provider // Send email to client and provider
if err := s.mailSrv.SendProviderConfirm(mail.SendOK{ if err := s.mailSrv.SendProviderConfirm(mail.SendOK{
TxID: order.Tx, Tx: order.Tx,
BlockHash: order.Block, Block: order.Block,
DocHash: "", Amount: order.Amount,
To: "doc.Email", ExplorerUrl: s.btcService.Explorer(order.Tx),
CustomerID: order.ClientID,
OrderID: order.OrderID,
Timestamp: order.PaidAt,
To: order.Email,
}); err != nil { }); err != nil {
log.Println("error while send confirm email:", err) log.Println("error while send confirm email:", err)
continue continue

View File

@ -10,6 +10,7 @@ type Order struct {
ID primitive.ObjectID `bson:"_id" json:"_id"` ID primitive.ObjectID `bson:"_id" json:"_id"`
OrderID string `json:"order_id"` OrderID string `json:"order_id"`
ClientID string `json:"client_id"` ClientID string `json:"client_id"`
Email string `json:"email"`
Amount float64 `bson:"amount" json:"amount"` Amount float64 `bson:"amount" json:"amount"`
Tx string `bson:"tx" json:"tx"` Tx string `bson:"tx" json:"tx"`
Block string `bson:"block" json:"block"` Block string `bson:"block" json:"block"`

View File

@ -64,7 +64,7 @@ func (repo *Repo) FromAmount(ctx context.Context, amount float64, timestamp time
return order, nil return order, nil
} }
func (repo *Repo) OrderCompleted(ctx context.Context, order *domain.Order) error { func (repo *Repo) OrderCompleted(ctx context.Context, order *domain.Order) (*domain.Order, error) {
updateOpts, err := repo.collection.UpdateOne(ctx, bson.M{"_id": order.ID}, updateOpts, err := repo.collection.UpdateOne(ctx, bson.M{"_id": order.ID},
bson.M{ bson.M{
"tx": order.Tx, "tx": order.Tx,
@ -73,8 +73,8 @@ func (repo *Repo) OrderCompleted(ctx context.Context, order *domain.Order) error
}, },
) )
if err != nil { if err != nil {
return fmt.Errorf("collection.UpdateOne: %s", err) return nil, fmt.Errorf("collection.UpdateOne: %s", err)
} }
log.Printf("OrderCompleted update %+v\n", updateOpts) log.Printf("OrderCompleted update %+v\n", updateOpts)
return nil return order, nil
} }

View File

@ -31,11 +31,13 @@ type MailService struct {
} }
type SendOK struct { type SendOK struct {
Price float64 Amount float64
ExplorerUrl string ExplorerUrl string
TxID string Tx string
BlockHash string CustomerID string
DocHash string OrderID string
Block string
Timestamp time.Time
To string To string
} }
@ -115,10 +117,11 @@ func (m *MailService) SendProviderConfirm(data SendOK) error {
return fmt.Errorf("os.ReadFile: %s", err) return fmt.Errorf("os.ReadFile: %s", err)
} }
template := strings.Replace(string(bts), "{{explorer_url}}", data.ExplorerUrl, -1) template := strings.Replace(string(bts), "{{explorer_url}}", data.ExplorerUrl, -1)
template = strings.Replace(template, "{{tx_id}}", data.TxID, -1) template = strings.Replace(template, "{{customer_id}}", data.CustomerID, -1)
template = strings.Replace(template, "{{block_hash}}", data.BlockHash, -1) template = strings.Replace(template, "{{order_id}}", data.OrderID, -1)
template = strings.Replace(template, "{{doc_hash}}", data.DocHash, -1) template = strings.Replace(template, "{{tx}}", data.Tx, -1)
template = strings.Replace(template, "{{support_email}}", m.from, -1) template = strings.Replace(template, "{{block}}", data.Block, -1)
template = strings.Replace(template, "{{timestamp}}", data.Timestamp.Format(time.RFC3339), -1)
msg := []byte(m.messageWithHeaders(okSubject, data.To, template)) msg := []byte(m.messageWithHeaders(okSubject, data.To, template))
return m.send(data.To, msg) return m.send(data.To, msg)
} }
@ -129,10 +132,11 @@ func (m *MailService) SendClientConfirm(data SendOK) error {
return fmt.Errorf("os.ReadFile: %s", err) return fmt.Errorf("os.ReadFile: %s", err)
} }
template := strings.Replace(string(bts), "{{explorer_url}}", data.ExplorerUrl, -1) template := strings.Replace(string(bts), "{{explorer_url}}", data.ExplorerUrl, -1)
template = strings.Replace(template, "{{tx_id}}", data.TxID, -1) template = strings.Replace(template, "{{customer_id}}", data.CustomerID, -1)
template = strings.Replace(template, "{{block_hash}}", data.BlockHash, -1) template = strings.Replace(template, "{{order_id}}", data.OrderID, -1)
template = strings.Replace(template, "{{doc_hash}}", data.DocHash, -1) template = strings.Replace(template, "{{tx}}", data.Tx, -1)
template = strings.Replace(template, "{{support_email}}", m.from, -1) template = strings.Replace(template, "{{block}}", data.Block, -1)
template = strings.Replace(template, "{{timestamp}}", data.Timestamp.Format(time.RFC3339), -1)
msg := []byte(m.messageWithHeaders(okSubject, data.To, template)) msg := []byte(m.messageWithHeaders(okSubject, data.To, template))
return m.send(data.To, msg) return m.send(data.To, msg)
} }
@ -144,9 +148,8 @@ func (m *MailService) SendFail(data SendOK) error {
return fmt.Errorf("os.ReadFile: %s", err) return fmt.Errorf("os.ReadFile: %s", err)
} }
template := strings.Replace(string(bts), "{{explorer_url}}", data.ExplorerUrl, -1) template := strings.Replace(string(bts), "{{explorer_url}}", data.ExplorerUrl, -1)
template = strings.Replace(template, "{{tx_id}}", data.TxID, -1) template = strings.Replace(template, "{{tx_id}}", data.Tx, -1)
template = strings.Replace(template, "{{block_hash}}", data.BlockHash, -1) template = strings.Replace(template, "{{block_hash}}", data.Block, -1)
template = strings.Replace(template, "{{doc_hash}}", data.DocHash, -1)
template = strings.Replace(template, "{{support_email}}", m.from, -1) template = strings.Replace(template, "{{support_email}}", m.from, -1)
// TODO: Alert client too // TODO: Alert client too
msg := []byte(m.messageWithHeaders(okSubject, data.To, template)) msg := []byte(m.messageWithHeaders(okSubject, data.To, template))

View File

@ -3,6 +3,7 @@ package mail
import ( import (
"net/smtp" "net/smtp"
"testing" "testing"
"time"
"gitea.urkob.com/urko/btc-pay-checker/kit" "gitea.urkob.com/urko/btc-pay-checker/kit"
"gitea.urkob.com/urko/btc-pay-checker/kit/cfg" "gitea.urkob.com/urko/btc-pay-checker/kit/cfg"
@ -29,9 +30,13 @@ func init() {
func Test_mailService_SendOK(t *testing.T) { func Test_mailService_SendOK(t *testing.T) {
dto := SendOK{ dto := SendOK{
Price: 10.2, Amount: 12.0,
ExplorerUrl: "test", ExplorerUrl: "test",
TxID: "test-hash", Tx: "test-hash",
CustomerID: "client",
OrderID: "order",
Block: "block",
Timestamp: time.Now(),
To: config.MailTo, To: config.MailTo,
} }
@ -41,9 +46,13 @@ func Test_mailService_SendOK(t *testing.T) {
func Test_mailService_SendConfirm(t *testing.T) { func Test_mailService_SendConfirm(t *testing.T) {
dto := SendOK{ dto := SendOK{
Amount: 12.0,
ExplorerUrl: "test", ExplorerUrl: "test",
TxID: "test-hash", Tx: "test-hash",
BlockHash: "block-hash", CustomerID: "client",
OrderID: "order",
Block: "block",
Timestamp: time.Now(),
To: config.MailTo, To: config.MailTo,
} }

View File

@ -1 +1,51 @@
TODO: <!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
color: #333;
}
.container {
border-radius: 5px;
background-color: #f2f2f2;
padding: 20px;
}
.header {
text-align: center;
padding: 10px;
color: white;
background-color: #4CAF50;
}
.content {
margin: 20px 0;
}
.footer {
text-align: center;
padding: 10px;
color: white;
background-color: #333;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h2>Payment Confirmation</h2>
</div>
<div class="content">
<p>Dear Customer,</p>
<p>We are pleased to inform you that we have received your Bitcoin payment. Here are the details of the transaction:</p>
<p><strong>Transaction ID:</strong> {{tx}}</p>
<p><strong>Block ID:</strong> {{block}}</p>
<p><strong>Timestamp:</strong> {{timestamp}}</p>
<p>You can view the transaction details at the following URL: <a href="{{explorer_url}}">{{explorer_url}}</a></p>
<p>Thank you for your payment!</p>
</div>
<div class="footer">
<p>© 2023 Bitcoin Payment Checker. All rights reserved.</p>
</div>
</div>
</body>
</html>

View File

@ -1 +1,53 @@
TODO: <!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
color: #333;
}
.container {
border-radius: 5px;
background-color: #f2f2f2;
padding: 20px;
}
.header {
text-align: center;
padding: 10px;
color: white;
background-color: #4CAF50;
}
.content {
margin: 20px 0;
}
.footer {
text-align: center;
padding: 10px;
color: white;
background-color: #333;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h2>Payment Confirmation</h2>
</div>
<div class="content">
<p>Dear Supplier,</p>
<p>We are pleased to inform you that we have received a Bitcoin payment for a recent order. Here are the details of the transaction:</p>
<p><strong>Customer ID:</strong> {{customer_id}}</p>
<p><strong>Order ID:</strong> {{order_id}}</p>
<p><strong>Transaction ID:</strong> {{tx}}</p>
<p><strong>Block ID:</strong> {{block}}</p>
<p><strong>Timestamp:</strong> {{timestamp}}</p>
<p>You can view the transaction details at the following URL: <a href="{{explorer_url}}">{{explorer_url}}</a></p>
<p>We will proceed with the order fulfillment as per our agreement. Thank you for your cooperation!</p>
</div>
<div class="footer">
<p>© 2023 Bitcoin Payment Checker. All rights reserved.</p>
</div>
</div>
</body>
</html>

View File

@ -28,16 +28,18 @@ func (o *Order) WithExpiration(expiration time.Duration) *Order {
return o return o
} }
func (o *Order) NewOrder(ctx context.Context, OrderID string, ClientID string, amount float64) (*domain.Order, error) { func (o *Order) NewOrder(ctx context.Context, orderID, clientID, email string, amount float64) (*domain.Order, error) {
order := domain.Order{ order := domain.Order{
ID: primitive.NewObjectID(), ID: primitive.NewObjectID(),
OrderID: OrderID, OrderID: orderID,
ClientID: ClientID, ClientID: clientID,
Amount: amount, Amount: amount,
PaidAt: time.Time{}, PaidAt: time.Time{},
CreatedAt: time.Now(), CreatedAt: time.Now(),
ExpiresAt: time.Now().Add(o.expiration), ExpiresAt: time.Now().Add(o.expiration),
Email: email,
} }
_, err := o.repo.Insert(ctx, &order) _, err := o.repo.Insert(ctx, &order)
if err != nil { if err != nil {
return nil, err return nil, err
@ -49,6 +51,6 @@ func (o *Order) FromAmount(ctx context.Context, amount float64, timestamp time.T
return o.repo.FromAmount(ctx, amount, timestamp) return o.repo.FromAmount(ctx, amount, timestamp)
} }
func (o *Order) OrderCompleted(ctx context.Context, order *domain.Order) error { func (o *Order) OrderCompleted(ctx context.Context, order *domain.Order) (*domain.Order, error) {
return o.repo.OrderCompleted(ctx, order) return o.repo.OrderCompleted(ctx, order)
} }