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"`
|
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), "")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"`
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
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)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue