222 lines
6.5 KiB
Go
Raw Normal View History

2023-10-08 15:59:42 +07:00
package order
import (
2024-06-04 02:59:31 +07:00
"encoding/json"
"errors"
2023-10-08 15:59:42 +07:00
"furtuna-be/internal/common/logger"
2024-06-04 02:59:31 +07:00
order2 "furtuna-be/internal/constants/order"
2023-10-08 15:59:42 +07:00
"furtuna-be/internal/entity"
"furtuna-be/internal/repository"
2024-06-04 02:59:31 +07:00
"furtuna-be/internal/utils/generator"
2023-10-08 15:59:42 +07:00
"go.uber.org/zap"
2024-06-04 02:59:31 +07:00
"golang.org/x/net/context"
"gorm.io/gorm"
"strconv"
"time"
2023-10-08 15:59:42 +07:00
)
type OrderService struct {
2024-06-04 02:59:31 +07:00
repo repository.Order
crypt repository.Crypto
product repository.Product
midtrans repository.Midtrans
payment repository.Payment
2023-10-08 15:59:42 +07:00
}
2024-06-04 02:59:31 +07:00
func NewOrderService(repo repository.Order, product repository.Product, crypt repository.Crypto,
midtrans repository.Midtrans, payment repository.Payment) *OrderService {
2023-10-08 15:59:42 +07:00
return &OrderService{
2024-06-04 02:59:31 +07:00
repo: repo,
product: product,
crypt: crypt,
midtrans: midtrans,
payment: payment,
2023-10-08 15:59:42 +07:00
}
}
2024-06-04 02:59:31 +07:00
func (s *OrderService) CreateOrder(ctx context.Context, req *entity.OrderRequest) (*entity.OrderResponse, error) {
productIDs := make([]int64, len(req.OrderItems))
for i, item := range req.OrderItems {
productIDs[i] = item.ProductID
}
2023-10-08 15:59:42 +07:00
2024-06-04 02:59:31 +07:00
products, err := s.product.GetProductsByIDs(ctx, productIDs, req.PartnerID)
if err != nil {
logger.ContextLogger(ctx).Error("error when getting products by IDs", zap.Error(err))
return nil, err
2023-10-08 15:59:42 +07:00
}
2024-06-04 02:59:31 +07:00
productMap := make(map[int64]*entity.ProductDB)
for _, product := range products {
productMap[product.ID] = product
}
2023-10-08 15:59:42 +07:00
2024-06-04 02:59:31 +07:00
totalAmount := 0.0
for _, item := range req.OrderItems {
product, ok := productMap[item.ProductID]
if !ok {
logger.ContextLogger(ctx).Error("product not found", zap.Int64("productID", item.ProductID))
return nil, errors.New("product not found")
}
totalAmount += product.Price * float64(item.Quantity)
}
2023-10-08 15:59:42 +07:00
2024-06-04 02:59:31 +07:00
order := &entity.Order{
PartnerID: req.PartnerID,
RefID: generator.GenerateUUID(),
Status: order2.New.String(),
Amount: totalAmount,
PaymentType: req.PaymentMethod,
CreatedBy: req.CreatedBy,
OrderItems: []entity.OrderItem{},
2023-10-08 15:59:42 +07:00
}
2024-06-04 02:59:31 +07:00
for _, item := range req.OrderItems {
order.OrderItems = append(order.OrderItems, entity.OrderItem{
ItemID: item.ProductID,
ItemType: productMap[item.ProductID].Type,
Price: productMap[item.ProductID].Price,
Quantity: item.Quantity,
CreatedBy: req.CreatedBy,
})
}
2023-10-08 15:59:42 +07:00
2024-06-04 02:59:31 +07:00
order, err = s.repo.Create(ctx, order)
2023-10-08 15:59:42 +07:00
if err != nil {
2024-06-04 02:59:31 +07:00
logger.ContextLogger(ctx).Error("error when creating order", zap.Error(err))
2023-10-08 15:59:42 +07:00
return nil, err
}
2024-06-04 02:59:31 +07:00
token, err := s.crypt.GenerateJWTOrder(order)
2023-10-08 15:59:42 +07:00
if err != nil {
2024-06-04 02:59:31 +07:00
logger.ContextLogger(ctx).Error("error when create token", zap.Error(err))
return nil, err
2023-10-08 15:59:42 +07:00
}
2024-06-04 02:59:31 +07:00
return &entity.OrderResponse{
Order: order,
Token: token,
}, nil
2023-10-08 15:59:42 +07:00
}
2024-06-04 02:59:31 +07:00
func (s *OrderService) Execute(ctx context.Context, req *entity.OrderExecuteRequest) (*entity.ExecuteOrderResponse, error) {
partnerID, orderID, err := s.crypt.ValidateJWTOrder(req.Token)
2023-10-08 15:59:42 +07:00
if err != nil {
2024-06-04 02:59:31 +07:00
logger.ContextLogger(ctx).Error("error when validating JWT order", zap.Error(err))
return nil, err
2023-10-08 15:59:42 +07:00
}
2024-06-04 02:59:31 +07:00
order, err := s.repo.FindByID(ctx, orderID)
2023-10-08 15:59:42 +07:00
if err != nil {
2024-06-04 02:59:31 +07:00
if errors.Is(err, gorm.ErrRecordNotFound) {
logger.ContextLogger(ctx).Error("order not found", zap.Int64("orderID", orderID))
return nil, errors.New("order not found")
}
logger.ContextLogger(ctx).Error("error when finding order by ID", zap.Error(err))
2023-10-08 15:59:42 +07:00
return nil, err
}
2024-06-04 02:59:31 +07:00
// Check for existing payment to handle idempotency
payment, err := s.payment.FindByOrderAndPartnerID(ctx, orderID, partnerID)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
logger.ContextLogger(ctx).Error("error getting payment data from db", zap.Error(err))
return nil, err
}
2023-10-08 15:59:42 +07:00
2024-06-04 02:59:31 +07:00
if payment != nil {
return s.createExecuteOrderResponse(order, payment), nil
}
if order.PartnerID != partnerID {
logger.ContextLogger(ctx).Error("partner ID mismatch", zap.Int64("orderID", orderID), zap.Int64("tokenPartnerID", partnerID), zap.Int64("orderPartnerID", order.PartnerID))
return nil, errors.New("partner ID mismatch")
}
if order.Status != "NEW" {
return nil, errors.New("invalid state")
}
resp := &entity.ExecuteOrderResponse{
Order: order,
}
if order.PaymentType != "CASH" {
paymentResponse, err := s.processNonCashPayment(ctx, order, partnerID, req.CreatedBy)
if err != nil {
return nil, err
}
resp.PaymentToken = paymentResponse.Token
resp.RedirectURL = paymentResponse.RedirectURL
}
order.SetExecutePaymentStatus()
order, err = s.repo.Update(ctx, order)
2023-10-08 15:59:42 +07:00
if err != nil {
2024-06-04 02:59:31 +07:00
logger.ContextLogger(ctx).Error("error when updating order status", zap.Error(err))
2023-10-08 15:59:42 +07:00
return nil, err
}
2024-06-04 02:59:31 +07:00
return resp, nil
2023-10-08 15:59:42 +07:00
}
2024-06-04 02:59:31 +07:00
func (s *OrderService) createExecuteOrderResponse(order *entity.Order, payment *entity.Payment) *entity.ExecuteOrderResponse {
var metadata map[string]string
if err := json.Unmarshal(payment.RequestMetadata, &metadata); err != nil {
logger.ContextLogger(context.Background()).Error("error unmarshaling request metadata", zap.Error(err))
return &entity.ExecuteOrderResponse{
Order: order,
}
}
return &entity.ExecuteOrderResponse{
Order: order,
PaymentToken: metadata["payment_token"],
RedirectURL: metadata["payment_redirect_url"],
}
}
func (s *OrderService) processNonCashPayment(ctx context.Context, order *entity.Order, partnerID, createdBy int64) (*entity.MidtransResponse, error) {
paymentRequest := entity.MidtransRequest{
PaymentReferenceID: generator.GenerateUUIDV4(),
TotalAmount: int64(order.Amount),
OrderItems: order.OrderItems,
}
paymentResponse, err := s.midtrans.CreatePayment(paymentRequest)
2023-10-08 15:59:42 +07:00
if err != nil {
2024-06-04 02:59:31 +07:00
logger.ContextLogger(ctx).Error("error when creating payment", zap.Error(err))
2023-10-08 15:59:42 +07:00
return nil, err
}
2024-06-04 02:59:31 +07:00
requestMetadata, err := json.Marshal(map[string]string{
"partner_id": strconv.FormatInt(partnerID, 10),
"created_by": strconv.FormatInt(createdBy, 10),
"payment_token": paymentResponse.Token,
"payment_redirect_url": paymentResponse.RedirectURL,
})
if err != nil {
logger.ContextLogger(ctx).Error("error when marshaling request metadata", zap.Error(err))
return nil, err
2023-10-08 15:59:42 +07:00
}
2024-06-04 02:59:31 +07:00
payment := &entity.Payment{
PartnerID: strconv.FormatInt(partnerID, 10),
OrderID: strconv.FormatInt(order.ID, 10),
ReferenceID: paymentRequest.PaymentReferenceID,
Channel: "xendit",
PaymentType: order.PaymentType,
Amount: order.Amount,
State: "pending",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
RequestMetadata: requestMetadata,
2023-10-08 15:59:42 +07:00
}
2024-06-04 02:59:31 +07:00
_, err = s.payment.Create(ctx, payment)
2023-10-08 15:59:42 +07:00
if err != nil {
2024-06-04 02:59:31 +07:00
logger.ContextLogger(ctx).Error("error when creating payment record", zap.Error(err))
2023-10-08 15:59:42 +07:00
return nil, err
}
2024-06-04 02:59:31 +07:00
return paymentResponse, nil
2023-10-08 15:59:42 +07:00
}