feat: notify supplier and client after payment is done
This commit is contained in:
parent
b426a36570
commit
e80e75cb8c
|
@ -29,6 +29,7 @@ type orderReq struct {
|
|||
ClientID string `json:"client_id"`
|
||||
Amount float64 `json:"amount"`
|
||||
Currency domain.FiatCurrency `json:"currency"`
|
||||
ClientEmail string `json:"client_email"`
|
||||
}
|
||||
|
||||
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), "")
|
||||
}
|
||||
|
||||
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 {
|
||||
return RenderError(c, fmt.Errorf("hdl.orderSrv.NewOrder %w", err), "")
|
||||
}
|
||||
|
|
|
@ -102,18 +102,22 @@ func (s *RestServer) onNotification(ctx context.Context, notifChan chan domain.N
|
|||
|
||||
order.Block = notif.BlockHash
|
||||
order.Tx = notif.Tx
|
||||
|
||||
if err := s.orderSrv.OrderCompleted(ctx, order); err != nil {
|
||||
order, err = s.orderSrv.OrderCompleted(ctx, order)
|
||||
if err != nil {
|
||||
log.Println("OrderCompleted:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Send email to client and provider
|
||||
if err := s.mailSrv.SendProviderConfirm(mail.SendOK{
|
||||
TxID: order.Tx,
|
||||
BlockHash: order.Block,
|
||||
DocHash: "",
|
||||
To: "doc.Email",
|
||||
Tx: order.Tx,
|
||||
Block: order.Block,
|
||||
Amount: order.Amount,
|
||||
ExplorerUrl: s.btcService.Explorer(order.Tx),
|
||||
CustomerID: order.ClientID,
|
||||
OrderID: order.OrderID,
|
||||
Timestamp: order.PaidAt,
|
||||
To: order.Email,
|
||||
}); err != nil {
|
||||
log.Println("error while send confirm email:", err)
|
||||
continue
|
||||
|
|
|
@ -10,6 +10,7 @@ type Order struct {
|
|||
ID primitive.ObjectID `bson:"_id" json:"_id"`
|
||||
OrderID string `json:"order_id"`
|
||||
ClientID string `json:"client_id"`
|
||||
Email string `json:"email"`
|
||||
Amount float64 `bson:"amount" json:"amount"`
|
||||
Tx string `bson:"tx" json:"tx"`
|
||||
Block string `bson:"block" json:"block"`
|
||||
|
|
|
@ -64,7 +64,7 @@ func (repo *Repo) FromAmount(ctx context.Context, amount float64, timestamp time
|
|||
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},
|
||||
bson.M{
|
||||
"tx": order.Tx,
|
||||
|
@ -73,8 +73,8 @@ func (repo *Repo) OrderCompleted(ctx context.Context, order *domain.Order) error
|
|||
},
|
||||
)
|
||||
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)
|
||||
return nil
|
||||
return order, nil
|
||||
}
|
||||
|
|
|
@ -31,11 +31,13 @@ type MailService struct {
|
|||
}
|
||||
|
||||
type SendOK struct {
|
||||
Price float64
|
||||
Amount float64
|
||||
ExplorerUrl string
|
||||
TxID string
|
||||
BlockHash string
|
||||
DocHash string
|
||||
Tx string
|
||||
CustomerID string
|
||||
OrderID string
|
||||
Block string
|
||||
Timestamp time.Time
|
||||
To string
|
||||
}
|
||||
|
||||
|
@ -115,10 +117,11 @@ func (m *MailService) SendProviderConfirm(data SendOK) error {
|
|||
return fmt.Errorf("os.ReadFile: %s", err)
|
||||
}
|
||||
template := strings.Replace(string(bts), "{{explorer_url}}", data.ExplorerUrl, -1)
|
||||
template = strings.Replace(template, "{{tx_id}}", data.TxID, -1)
|
||||
template = strings.Replace(template, "{{block_hash}}", data.BlockHash, -1)
|
||||
template = strings.Replace(template, "{{doc_hash}}", data.DocHash, -1)
|
||||
template = strings.Replace(template, "{{support_email}}", m.from, -1)
|
||||
template = strings.Replace(template, "{{customer_id}}", data.CustomerID, -1)
|
||||
template = strings.Replace(template, "{{order_id}}", data.OrderID, -1)
|
||||
template = strings.Replace(template, "{{tx}}", data.Tx, -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))
|
||||
return m.send(data.To, msg)
|
||||
}
|
||||
|
@ -129,10 +132,11 @@ func (m *MailService) SendClientConfirm(data SendOK) error {
|
|||
return fmt.Errorf("os.ReadFile: %s", err)
|
||||
}
|
||||
template := strings.Replace(string(bts), "{{explorer_url}}", data.ExplorerUrl, -1)
|
||||
template = strings.Replace(template, "{{tx_id}}", data.TxID, -1)
|
||||
template = strings.Replace(template, "{{block_hash}}", data.BlockHash, -1)
|
||||
template = strings.Replace(template, "{{doc_hash}}", data.DocHash, -1)
|
||||
template = strings.Replace(template, "{{support_email}}", m.from, -1)
|
||||
template = strings.Replace(template, "{{customer_id}}", data.CustomerID, -1)
|
||||
template = strings.Replace(template, "{{order_id}}", data.OrderID, -1)
|
||||
template = strings.Replace(template, "{{tx}}", data.Tx, -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))
|
||||
return m.send(data.To, msg)
|
||||
}
|
||||
|
@ -144,9 +148,8 @@ func (m *MailService) SendFail(data SendOK) error {
|
|||
return fmt.Errorf("os.ReadFile: %s", err)
|
||||
}
|
||||
template := strings.Replace(string(bts), "{{explorer_url}}", data.ExplorerUrl, -1)
|
||||
template = strings.Replace(template, "{{tx_id}}", data.TxID, -1)
|
||||
template = strings.Replace(template, "{{block_hash}}", data.BlockHash, -1)
|
||||
template = strings.Replace(template, "{{doc_hash}}", data.DocHash, -1)
|
||||
template = strings.Replace(template, "{{tx_id}}", data.Tx, -1)
|
||||
template = strings.Replace(template, "{{block_hash}}", data.Block, -1)
|
||||
template = strings.Replace(template, "{{support_email}}", m.from, -1)
|
||||
// TODO: Alert client too
|
||||
msg := []byte(m.messageWithHeaders(okSubject, data.To, template))
|
||||
|
|
|
@ -3,6 +3,7 @@ package mail
|
|||
import (
|
||||
"net/smtp"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.urkob.com/urko/btc-pay-checker/kit"
|
||||
"gitea.urkob.com/urko/btc-pay-checker/kit/cfg"
|
||||
|
@ -29,9 +30,13 @@ func init() {
|
|||
|
||||
func Test_mailService_SendOK(t *testing.T) {
|
||||
dto := SendOK{
|
||||
Price: 10.2,
|
||||
Amount: 12.0,
|
||||
ExplorerUrl: "test",
|
||||
TxID: "test-hash",
|
||||
Tx: "test-hash",
|
||||
CustomerID: "client",
|
||||
OrderID: "order",
|
||||
Block: "block",
|
||||
Timestamp: time.Now(),
|
||||
To: config.MailTo,
|
||||
}
|
||||
|
||||
|
@ -41,9 +46,13 @@ func Test_mailService_SendOK(t *testing.T) {
|
|||
|
||||
func Test_mailService_SendConfirm(t *testing.T) {
|
||||
dto := SendOK{
|
||||
Amount: 12.0,
|
||||
ExplorerUrl: "test",
|
||||
TxID: "test-hash",
|
||||
BlockHash: "block-hash",
|
||||
Tx: "test-hash",
|
||||
CustomerID: "client",
|
||||
OrderID: "order",
|
||||
Block: "block",
|
||||
Timestamp: time.Now(),
|
||||
To: config.MailTo,
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -28,16 +28,18 @@ func (o *Order) WithExpiration(expiration time.Duration) *Order {
|
|||
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{
|
||||
ID: primitive.NewObjectID(),
|
||||
OrderID: OrderID,
|
||||
ClientID: ClientID,
|
||||
OrderID: orderID,
|
||||
ClientID: clientID,
|
||||
Amount: amount,
|
||||
PaidAt: time.Time{},
|
||||
CreatedAt: time.Now(),
|
||||
ExpiresAt: time.Now().Add(o.expiration),
|
||||
Email: email,
|
||||
}
|
||||
|
||||
_, err := o.repo.Insert(ctx, &order)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue