diff --git a/internal/api/handler/payment.go b/internal/api/handler/payment.go index 0516dec..a33b375 100644 --- a/internal/api/handler/payment.go +++ b/internal/api/handler/payment.go @@ -25,10 +25,11 @@ func NewOrderHandler(walletAddress string, orderSrv *services.Order, conversor * } type orderReq struct { - OrderID string `json:"order_id"` - ClientID string `json:"client_id"` - Amount float64 `json:"amount"` - Currency domain.FiatCurrency `json:"currency"` + OrderID string `json:"order_id"` + 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), "") } diff --git a/internal/api/server.go b/internal/api/server.go index 092cde6..05f4cae 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -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 diff --git a/internal/domain/order.go b/internal/domain/order.go index afc38db..19cafee 100644 --- a/internal/domain/order.go +++ b/internal/domain/order.go @@ -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"` diff --git a/internal/platform/mongodb/order/repository.go b/internal/platform/mongodb/order/repository.go index d9a2a55..0a76b4f 100644 --- a/internal/platform/mongodb/order/repository.go +++ b/internal/platform/mongodb/order/repository.go @@ -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 } diff --git a/internal/services/mail/mail.go b/internal/services/mail/mail.go index 03f05dc..112adda 100644 --- a/internal/services/mail/mail.go +++ b/internal/services/mail/mail.go @@ -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)) diff --git a/internal/services/mail/mail_test.go b/internal/services/mail/mail_test.go index 2fd0294..0ca8f69 100644 --- a/internal/services/mail/mail_test.go +++ b/internal/services/mail/mail_test.go @@ -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, } diff --git a/internal/services/mail/templates/client_confirm.html b/internal/services/mail/templates/client_confirm.html index 17498c8..eea6eb5 100644 --- a/internal/services/mail/templates/client_confirm.html +++ b/internal/services/mail/templates/client_confirm.html @@ -1 +1,51 @@ -TODO: \ No newline at end of file + + + + + + +
+
+

Payment Confirmation

+
+
+

Dear Customer,

+

We are pleased to inform you that we have received your Bitcoin payment. Here are the details of the transaction:

+

Transaction ID: {{tx}}

+

Block ID: {{block}}

+

Timestamp: {{timestamp}}

+

You can view the transaction details at the following URL: {{explorer_url}}

+

Thank you for your payment!

+
+ +
+ + diff --git a/internal/services/mail/templates/provider_confirm.html b/internal/services/mail/templates/provider_confirm.html index 17498c8..8c87818 100644 --- a/internal/services/mail/templates/provider_confirm.html +++ b/internal/services/mail/templates/provider_confirm.html @@ -1 +1,53 @@ -TODO: \ No newline at end of file + + + + + + +
+
+

Payment Confirmation

+
+
+

Dear Supplier,

+

We are pleased to inform you that we have received a Bitcoin payment for a recent order. Here are the details of the transaction:

+

Customer ID: {{customer_id}}

+

Order ID: {{order_id}}

+

Transaction ID: {{tx}}

+

Block ID: {{block}}

+

Timestamp: {{timestamp}}

+

You can view the transaction details at the following URL: {{explorer_url}}

+

We will proceed with the order fulfillment as per our agreement. Thank you for your cooperation!

+
+ +
+ + diff --git a/internal/services/order.go b/internal/services/order.go index 2ea86a1..4bb2b53 100644 --- a/internal/services/order.go +++ b/internal/services/order.go @@ -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) }