Update template email
This commit is contained in:
@@ -112,7 +112,6 @@ func (s *BalanceService) WithdrawExecute(ctx mycontext.Context, req *entity.Wall
|
||||
transaction := &entity.Transaction{
|
||||
PartnerID: wallet.PartnerID,
|
||||
TransactionType: "WITHDRAW",
|
||||
ReferenceID: "",
|
||||
Status: "WAITING_APPROVAL",
|
||||
CreatedBy: ctx.RequestedBy(),
|
||||
Amount: totalAmount,
|
||||
|
||||
@@ -76,11 +76,9 @@ func (s *OrderService) CreateOrder(ctx mycontext.Context, req *entity.OrderReque
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var siteID int64
|
||||
productMap := make(map[int64]*entity.ProductDB)
|
||||
for _, product := range products {
|
||||
productMap[product.ID] = product
|
||||
siteID = product.SiteID
|
||||
}
|
||||
|
||||
totalAmount := 0.0
|
||||
@@ -93,32 +91,16 @@ func (s *OrderService) CreateOrder(ctx mycontext.Context, req *entity.OrderReque
|
||||
totalAmount += product.Price * float64(item.Quantity)
|
||||
}
|
||||
|
||||
parsedTime, err := time.Parse("2006-01-02", req.VisitDate)
|
||||
if err != nil {
|
||||
fmt.Println("Error parsing date:", err)
|
||||
return nil, errors.New("visit date not defined")
|
||||
}
|
||||
|
||||
metadata, err := json.Marshal(map[string]string{
|
||||
"bank_code": req.BankCode,
|
||||
"bank_name": req.BankName,
|
||||
})
|
||||
|
||||
order := &entity.Order{
|
||||
PartnerID: req.PartnerID,
|
||||
RefID: generator.GenerateUUID(),
|
||||
Status: order2.New.String(),
|
||||
Amount: totalAmount,
|
||||
Total: totalAmount + s.cfg.GetOrderFee(req.Source),
|
||||
Fee: s.cfg.GetOrderFee(req.Source),
|
||||
PaymentType: req.PaymentMethod,
|
||||
SiteID: &siteID,
|
||||
CreatedBy: req.CreatedBy,
|
||||
OrderItems: []entity.OrderItem{},
|
||||
Source: req.Source,
|
||||
VisitDate: parsedTime,
|
||||
TicketStatus: "UNUSED",
|
||||
Metadata: metadata,
|
||||
PartnerID: req.PartnerID,
|
||||
Status: order2.New.String(),
|
||||
Amount: totalAmount,
|
||||
Total: totalAmount + s.cfg.GetOrderFee(req.Source),
|
||||
Fee: s.cfg.GetOrderFee(req.Source),
|
||||
PaymentType: req.PaymentMethod,
|
||||
CreatedBy: req.CreatedBy,
|
||||
OrderItems: []entity.OrderItem{},
|
||||
Source: req.Source,
|
||||
}
|
||||
|
||||
for _, item := range req.OrderItems {
|
||||
@@ -126,7 +108,7 @@ func (s *OrderService) CreateOrder(ctx mycontext.Context, req *entity.OrderReque
|
||||
ItemID: item.ProductID,
|
||||
ItemType: productMap[item.ProductID].Type,
|
||||
Price: productMap[item.ProductID].Price,
|
||||
Quantity: item.Quantity,
|
||||
Quantity: int(item.Quantity),
|
||||
CreatedBy: req.CreatedBy,
|
||||
Product: productMap[item.ProductID].ToProduct(),
|
||||
})
|
||||
@@ -138,12 +120,6 @@ func (s *OrderService) CreateOrder(ctx mycontext.Context, req *entity.OrderReque
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token, err := s.crypt.GenerateJWTOrder(order)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when create token", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
order, err = s.repo.FindByID(ctx, order.ID)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when creating order", zap.Error(err))
|
||||
@@ -152,7 +128,6 @@ func (s *OrderService) CreateOrder(ctx mycontext.Context, req *entity.OrderReque
|
||||
|
||||
return &entity.OrderResponse{
|
||||
Order: order,
|
||||
Token: token,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -187,26 +162,6 @@ func (s *OrderService) CheckInInquiry(ctx mycontext.Context, qrCode string, part
|
||||
return nil, errors2.ErrorInvalidRequest
|
||||
}
|
||||
|
||||
location, _ := time.LoadLocation("Asia/Jakarta")
|
||||
today := time.Now().In(location).Format("2006-01-02")
|
||||
visitDate := time.Date(
|
||||
order.VisitDate.Year(),
|
||||
order.VisitDate.Month(),
|
||||
order.VisitDate.Day(),
|
||||
0, 0, 0, 0,
|
||||
location,
|
||||
).Format("2006-01-02")
|
||||
|
||||
if order.TicketStatus == "USED" || visitDate < today {
|
||||
return nil, errors2.NewErrorMessage(errors2.ErrorTicketInvalidOrAlreadyUsed,
|
||||
"Maaf! Tiket ini tidak valid karena sudah terpakai atau sudah kadaluwarsa")
|
||||
}
|
||||
|
||||
if visitDate != today {
|
||||
return nil, errors2.NewErrorMessage(errors2.ErrorTicketInvalidOrAlreadyUsed,
|
||||
"Maaf Tiket ini tidak valid karena tidak sesuai dengan tanggal tiket")
|
||||
}
|
||||
|
||||
token, err := s.crypt.GenerateJWTOrder(order)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when generate checkin token", zap.Error(err))
|
||||
@@ -242,17 +197,6 @@ func (s *OrderService) CheckInExecute(ctx mycontext.Context,
|
||||
Order: order,
|
||||
}
|
||||
|
||||
if order.TicketStatus != "UNUSED" {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
order.TicketStatus = "USED"
|
||||
order, err = s.repo.Update(ctx, order)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when updating order status", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -446,11 +390,6 @@ func (s *OrderService) processQRPayment(ctx mycontext.Context, order *entity.Ord
|
||||
}
|
||||
|
||||
func (s *OrderService) processVAPayment(ctx mycontext.Context, order *entity.Order, partnerID, createdBy int64) (*entity.PaymentResponse, error) {
|
||||
metadata := map[string]string{}
|
||||
if err := json.Unmarshal(order.Metadata, &metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
paymentRequest := entity.PaymentRequest{
|
||||
PaymentReferenceID: generator.GenerateUUIDV4(),
|
||||
TotalAmount: int64(order.Total),
|
||||
@@ -458,7 +397,6 @@ func (s *OrderService) processVAPayment(ctx mycontext.Context, order *entity.Ord
|
||||
CustomerID: strconv.FormatInt(order.User.ID, 10),
|
||||
CustomerName: order.User.Name,
|
||||
CustomerEmail: order.User.Email,
|
||||
BankCode: metadata["bank_code"],
|
||||
}
|
||||
|
||||
paymentResponse, err := s.pg.CreatePaymentVA(paymentRequest)
|
||||
@@ -544,13 +482,11 @@ func (s *OrderService) processPayment(ctx context.Context, tx *gorm.DB, req *ent
|
||||
transaction := &entity.Transaction{
|
||||
PartnerID: existingPayment.PartnerID,
|
||||
TransactionType: "PAYMENT_RECEIVED",
|
||||
ReferenceID: existingPayment.ReferenceID,
|
||||
Status: "SUCCESS",
|
||||
CreatedBy: 0,
|
||||
Amount: existingPayment.Amount,
|
||||
Fee: order.Fee,
|
||||
Total: order.Total,
|
||||
SiteID: order.SiteID,
|
||||
}
|
||||
if _, err = s.transaction.Create(ctx, tx, transaction); err != nil {
|
||||
return fmt.Errorf("failed to update transaction: %w", err)
|
||||
|
||||
@@ -14,6 +14,9 @@ import (
|
||||
"enaklo-pos-be/internal/services/studio"
|
||||
"enaklo-pos-be/internal/services/transaction"
|
||||
"enaklo-pos-be/internal/services/users"
|
||||
customerSvc "enaklo-pos-be/internal/services/v2/customer"
|
||||
orderSvc "enaklo-pos-be/internal/services/v2/order"
|
||||
productSvc "enaklo-pos-be/internal/services/v2/product"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
@@ -38,9 +41,17 @@ type ServiceManagerImpl struct {
|
||||
Transaction Transaction
|
||||
Balance Balance
|
||||
DiscoverService DiscoverService
|
||||
|
||||
OrderV2Svc orderSvc.Service
|
||||
CustomerV2Svc customerSvc.Service
|
||||
ProductV2Svc productSvc.Service
|
||||
}
|
||||
|
||||
func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl) *ServiceManagerImpl {
|
||||
|
||||
custSvcV2 := customerSvc.New(repo.CustomerRepo)
|
||||
productSvcV2 := productSvc.New(repo.ProductRepo)
|
||||
|
||||
return &ServiceManagerImpl{
|
||||
AuthSvc: auth.New(repo.Auth, repo.Crypto, repo.User, repo.EmailService, cfg.Email, repo.Trx, repo.License),
|
||||
EventSvc: event.NewEventService(repo.Event),
|
||||
@@ -56,6 +67,7 @@ func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl)
|
||||
Transaction: transaction.New(repo.Transaction, repo.Wallet, repo.Trx),
|
||||
Balance: balance.NewBalanceService(repo.Wallet, repo.Trx, repo.Crypto, &cfg.Withdraw, repo.Transaction),
|
||||
DiscoverService: discovery.NewDiscoveryService(repo.Site, cfg.Discovery, repo.Product),
|
||||
OrderV2Svc: orderSvc.New(repo.OrderRepo, productSvcV2, custSvcV2, repo.TransactionRepo, repo.Crypto, &cfg.Order, repo.EmailService),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
package customer
|
||||
|
||||
import (
|
||||
"enaklo-pos-be/internal/common/logger"
|
||||
"enaklo-pos-be/internal/common/mycontext"
|
||||
"enaklo-pos-be/internal/constants"
|
||||
"enaklo-pos-be/internal/entity"
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Repository interface {
|
||||
Create(ctx mycontext.Context, customer *entity.Customer) (*entity.Customer, error)
|
||||
FindByID(ctx mycontext.Context, id int64) (*entity.Customer, error)
|
||||
FindByPhone(ctx mycontext.Context, phone string) (*entity.Customer, error)
|
||||
FindByEmail(ctx mycontext.Context, email string) (*entity.Customer, error)
|
||||
AddPoints(ctx mycontext.Context, id int64, points int) error
|
||||
}
|
||||
|
||||
type Service interface {
|
||||
ResolveCustomer(ctx mycontext.Context, req *entity.CustomerResolutionRequest) (int64, error)
|
||||
AddPoints(ctx mycontext.Context, customerID int64, points int) error
|
||||
GetCustomer(ctx mycontext.Context, id int64) (*entity.Customer, error)
|
||||
}
|
||||
|
||||
type customerSvc struct {
|
||||
repo Repository
|
||||
}
|
||||
|
||||
func New(repo Repository) Service {
|
||||
return &customerSvc{
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *customerSvc) ResolveCustomer(ctx mycontext.Context, req *entity.CustomerResolutionRequest) (int64, error) {
|
||||
if req.Email == "" && req.PhoneNumber == "" {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if req.ID != nil && *req.ID > 0 {
|
||||
customer, err := s.repo.FindByID(ctx, *req.ID)
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), "not found") {
|
||||
return 0, errors.Wrap(err, "failed to find customer by ID")
|
||||
}
|
||||
} else {
|
||||
return customer.ID, nil
|
||||
}
|
||||
}
|
||||
|
||||
if req.PhoneNumber != "" {
|
||||
customer, err := s.repo.FindByPhone(ctx, req.PhoneNumber)
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), "not found") {
|
||||
return 0, errors.Wrap(err, "failed to find customer by phone")
|
||||
}
|
||||
} else {
|
||||
return customer.ID, nil
|
||||
}
|
||||
}
|
||||
|
||||
if req.Email != "" {
|
||||
customer, err := s.repo.FindByEmail(ctx, req.Email)
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), "not found") {
|
||||
return 0, errors.Wrap(err, "failed to find customer by email")
|
||||
}
|
||||
} else {
|
||||
return customer.ID, nil
|
||||
}
|
||||
}
|
||||
|
||||
if req.Name == "" {
|
||||
return 0, errors.New("customer name is required to create a new customer")
|
||||
}
|
||||
|
||||
newCustomer := &entity.Customer{
|
||||
Name: req.Name,
|
||||
Email: req.Email,
|
||||
Phone: req.PhoneNumber,
|
||||
Points: 0,
|
||||
CreatedAt: constants.TimeNow(),
|
||||
UpdatedAt: constants.TimeNow(),
|
||||
}
|
||||
|
||||
customer, err := s.repo.Create(ctx, newCustomer)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("failed to create customer", zap.Error(err))
|
||||
return 0, errors.Wrap(err, "failed to create customer")
|
||||
}
|
||||
|
||||
return customer.ID, nil
|
||||
}
|
||||
|
||||
func (s *customerSvc) AddPoints(ctx mycontext.Context, customerID int64, points int) error {
|
||||
if points <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := s.repo.AddPoints(ctx, customerID, points)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to add points to customer")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *customerSvc) GetCustomer(ctx mycontext.Context, id int64) (*entity.Customer, error) {
|
||||
customer, err := s.repo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get customer")
|
||||
}
|
||||
|
||||
return customer, nil
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package order
|
||||
|
||||
import (
|
||||
"enaklo-pos-be/internal/common/errors"
|
||||
"enaklo-pos-be/internal/common/logger"
|
||||
"enaklo-pos-be/internal/common/mycontext"
|
||||
"enaklo-pos-be/internal/constants"
|
||||
"enaklo-pos-be/internal/entity"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (s *orderSvc) CreateOrderInquiry(ctx mycontext.Context,
|
||||
req *entity.OrderRequest) (*entity.OrderInquiryResponse, error) {
|
||||
productIDs, filteredItems, err := s.validateOrderItems(ctx, req.OrderItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.OrderItems = filteredItems
|
||||
|
||||
productDetails, err := s.product.GetProductDetails(ctx, productIDs, req.PartnerID)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("failed to get product details", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderCalculation, err := s.calculateOrderTotals(ctx, req.OrderItems, productDetails, req.Source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
customerID, err := s.customer.ResolveCustomer(ctx, &entity.CustomerResolutionRequest{
|
||||
ID: req.CustomerID,
|
||||
Name: req.CustomerName,
|
||||
Email: req.CustomerEmail,
|
||||
PhoneNumber: req.CustomerPhoneNumber,
|
||||
})
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("failed to resolve customer", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inquiry := entity.NewOrderInquiry(
|
||||
req.PartnerID,
|
||||
customerID,
|
||||
orderCalculation.Subtotal,
|
||||
orderCalculation.Fee,
|
||||
orderCalculation.Total,
|
||||
req.PaymentMethod,
|
||||
req.Source,
|
||||
req.CreatedBy,
|
||||
req.CustomerName,
|
||||
req.CustomerPhoneNumber,
|
||||
req.CustomerEmail,
|
||||
)
|
||||
|
||||
for _, item := range req.OrderItems {
|
||||
product := productDetails.Products[item.ProductID]
|
||||
inquiry.AddOrderItem(item, product)
|
||||
}
|
||||
|
||||
savedInquiry, err := s.repo.CreateInquiry(ctx, inquiry)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("failed to create order inquiry", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token, err := s.crypt.GenerateJWTOrderInquiry(savedInquiry)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("failed to generate token", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &entity.OrderInquiryResponse{
|
||||
OrderInquiry: savedInquiry,
|
||||
Token: token,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *orderSvc) validateOrderItems(ctx mycontext.Context, items []entity.OrderItemRequest) ([]int64, []entity.OrderItemRequest, error) {
|
||||
var productIDs []int64
|
||||
var filteredItems []entity.OrderItemRequest
|
||||
|
||||
for _, item := range items {
|
||||
if item.Quantity <= 0 {
|
||||
continue
|
||||
}
|
||||
productIDs = append(productIDs, item.ProductID)
|
||||
filteredItems = append(filteredItems, item)
|
||||
}
|
||||
|
||||
if len(productIDs) == 0 {
|
||||
return nil, nil, errors.ErrorBadRequest
|
||||
}
|
||||
|
||||
return productIDs, filteredItems, nil
|
||||
}
|
||||
|
||||
func (s *orderSvc) calculateOrderTotals(
|
||||
ctx mycontext.Context,
|
||||
items []entity.OrderItemRequest,
|
||||
productDetails *entity.ProductDetails,
|
||||
source string,
|
||||
) (*entity.OrderCalculation, error) {
|
||||
subtotal := 0.0
|
||||
|
||||
for _, item := range items {
|
||||
product, ok := productDetails.Products[item.ProductID]
|
||||
if !ok {
|
||||
return nil, errors.NewError(errors.ErrorInvalidRequest.ErrorType(), "product not found")
|
||||
}
|
||||
subtotal += product.Price * float64(item.Quantity)
|
||||
}
|
||||
|
||||
fee := s.cfg.GetOrderFee(source)
|
||||
|
||||
return &entity.OrderCalculation{
|
||||
Subtotal: subtotal,
|
||||
Fee: fee,
|
||||
Total: subtotal + fee,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *orderSvc) validateInquiry(ctx mycontext.Context, token string) (*entity.OrderInquiry, error) {
|
||||
partnerID, inquiryID, err := s.crypt.ValidateJWTOrderInquiry(token)
|
||||
if err != nil {
|
||||
return nil, errors.NewError(errors.ErrorInvalidRequest.ErrorType(), "inquiry is not valid or expired")
|
||||
}
|
||||
|
||||
if partnerID != *ctx.GetPartnerID() {
|
||||
return nil, errors.NewError(errors.ErrorInvalidRequest.ErrorType(), "invalid request")
|
||||
}
|
||||
|
||||
inquiry, err := s.repo.FindInquiryByID(ctx, inquiryID)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when finding inquiry", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if inquiry.Status != constants.StatusPending {
|
||||
return nil, errors.NewError(errors.ErrorInvalidRequest.ErrorType(), "inquiry is no longer pending")
|
||||
}
|
||||
|
||||
return inquiry, nil
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package order
|
||||
|
||||
import (
|
||||
"enaklo-pos-be/internal/common/logger"
|
||||
"enaklo-pos-be/internal/common/mycontext"
|
||||
"enaklo-pos-be/internal/constants"
|
||||
"enaklo-pos-be/internal/entity"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (s *orderSvc) ExecuteOrderInquiry(ctx mycontext.Context,
|
||||
token string, paymentMethod string) (*entity.OrderResponse, error) {
|
||||
inquiry, err := s.validateInquiry(ctx, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
order := inquiry.ToOrder(paymentMethod)
|
||||
|
||||
savedOrder, err := s.repo.Create(ctx, order)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("failed to create order", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.processPostOrderActions(ctx, savedOrder, inquiry.ID, paymentMethod)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Warn("some post-order actions failed", zap.Error(err))
|
||||
}
|
||||
|
||||
return &entity.OrderResponse{
|
||||
Order: savedOrder,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *orderSvc) processPostOrderActions(
|
||||
ctx mycontext.Context,
|
||||
order *entity.Order,
|
||||
inquiryID string,
|
||||
paymentMethod string,
|
||||
) error {
|
||||
err := s.repo.UpdateInquiryStatus(ctx, inquiryID, constants.StatusExecuted)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when updating inquiry status", zap.Error(err))
|
||||
}
|
||||
|
||||
trx, err := s.createTransaction(ctx, order, paymentMethod)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when creating transaction", zap.Error(err))
|
||||
}
|
||||
|
||||
if order.CustomerID != nil && *order.CustomerID > 0 {
|
||||
err = s.addCustomerPoints(ctx, *order.CustomerID, int(order.Total))
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when adding points", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
s.sendTransactionReceipt(ctx, order, trx, "CASH")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *orderSvc) createTransaction(ctx mycontext.Context, order *entity.Order, paymentMethod string) (*entity.Transaction, error) {
|
||||
transaction := &entity.Transaction{
|
||||
ID: constants.GenerateUUID(),
|
||||
OrderID: order.ID,
|
||||
Amount: order.Total,
|
||||
PaymentMethod: paymentMethod,
|
||||
Status: "SUCCESS",
|
||||
CreatedAt: constants.TimeNow(),
|
||||
PartnerID: order.PartnerID,
|
||||
TransactionType: "TRANSACTION",
|
||||
}
|
||||
|
||||
_, err := s.transaction.Create(ctx, transaction)
|
||||
|
||||
return transaction, err
|
||||
}
|
||||
|
||||
func (s *orderSvc) addCustomerPoints(ctx mycontext.Context, customerID int64, points int) error {
|
||||
return s.customer.AddPoints(ctx, customerID, points)
|
||||
}
|
||||
|
||||
func (s *orderSvc) sendTransactionReceipt(ctx mycontext.Context, order *entity.Order, transaction *entity.Transaction, paymentMethod string) error {
|
||||
if order.CustomerID == nil || *order.CustomerID == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
customer, err := s.customer.GetCustomer(ctx, *order.CustomerID)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error getting customer details", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
branchName := "Bakso 343 Rawamangun"
|
||||
|
||||
var productIDs []int64
|
||||
productIDMap := make(map[int64]bool)
|
||||
for _, item := range order.OrderItems {
|
||||
if item.ItemID > 0 && !productIDMap[item.ItemID] {
|
||||
productIDs = append(productIDs, item.ItemID)
|
||||
productIDMap[item.ItemID] = true
|
||||
}
|
||||
}
|
||||
|
||||
productMap := make(map[int64]*entity.Product)
|
||||
if len(productIDs) > 0 {
|
||||
products, err := s.product.GetProductsByIDs(ctx, productIDs, order.PartnerID)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error fetching products", zap.Error(err))
|
||||
} else {
|
||||
for _, product := range products {
|
||||
productMap[product.ID] = product
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var itemsData []map[string]string
|
||||
for _, item := range order.OrderItems {
|
||||
itemName := "Item"
|
||||
|
||||
if product, exists := productMap[item.ItemID]; exists {
|
||||
itemName = product.Name
|
||||
}
|
||||
|
||||
itemsData = append(itemsData, map[string]string{
|
||||
"ItemName": itemName,
|
||||
"Quantity": fmt.Sprintf("%d", item.Quantity),
|
||||
"Price": fmt.Sprintf("Rp %s", formatCurrency(item.Price)),
|
||||
})
|
||||
}
|
||||
|
||||
transactionDate := transaction.CreatedAt.Format("02 January 2006 15:04")
|
||||
viewTransactionLink := fmt.Sprintf("https://enaklo.co.id/transaction/%s", transaction.ID)
|
||||
|
||||
emailData := map[string]interface{}{
|
||||
"UserName": customer.Name,
|
||||
"BranchName": branchName,
|
||||
"TransactionNumber": order.ID,
|
||||
"TransactionDate": transactionDate,
|
||||
"PaymentMethod": formatPaymentMethod(paymentMethod),
|
||||
"Items": itemsData,
|
||||
"TotalPayment": fmt.Sprintf("Rp %s", formatCurrency(order.Total)),
|
||||
"ViewTransactionLink": viewTransactionLink,
|
||||
}
|
||||
|
||||
return s.notification.SendEmailTransactional(ctx, entity.SendEmailNotificationParam{
|
||||
Sender: "noreply@enaklo.co.id",
|
||||
Recipient: customer.Email,
|
||||
Subject: "Enaklo - Resi Pembelian",
|
||||
TemplateName: "transaction_receipt",
|
||||
TemplatePath: "templates/transaction_receipt.html",
|
||||
Data: emailData,
|
||||
})
|
||||
}
|
||||
|
||||
func formatCurrency(amount float64) string {
|
||||
return fmt.Sprintf("%.2f", amount)
|
||||
}
|
||||
|
||||
func formatPaymentMethod(method string) string {
|
||||
methodMap := map[string]string{
|
||||
"CASH": "Tunai",
|
||||
"QRIS": "QRIS",
|
||||
"CARD": "Kartu Kredit/Debit",
|
||||
}
|
||||
|
||||
if displayName, exists := methodMap[method]; exists {
|
||||
return displayName
|
||||
}
|
||||
return method
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package order
|
||||
|
||||
import (
|
||||
"context"
|
||||
"enaklo-pos-be/internal/common/mycontext"
|
||||
"enaklo-pos-be/internal/entity"
|
||||
)
|
||||
|
||||
type Repository interface {
|
||||
Create(ctx mycontext.Context, order *entity.Order) (*entity.Order, error)
|
||||
FindByID(ctx mycontext.Context, id int64) (*entity.Order, error)
|
||||
CreateInquiry(ctx mycontext.Context, inquiry *entity.OrderInquiry) (*entity.OrderInquiry, error)
|
||||
FindInquiryByID(ctx mycontext.Context, id string) (*entity.OrderInquiry, error)
|
||||
UpdateInquiryStatus(ctx mycontext.Context, id string, status string) error
|
||||
}
|
||||
|
||||
type ProductService interface {
|
||||
GetProductDetails(ctx mycontext.Context, productIDs []int64, partnerID int64) (*entity.ProductDetails, error)
|
||||
GetProductsByIDs(ctx mycontext.Context, ids []int64, partnerID int64) ([]*entity.Product, error)
|
||||
}
|
||||
|
||||
type CustomerService interface {
|
||||
ResolveCustomer(ctx mycontext.Context, req *entity.CustomerResolutionRequest) (int64, error)
|
||||
AddPoints(ctx mycontext.Context, customerID int64, points int) error
|
||||
GetCustomer(ctx mycontext.Context, id int64) (*entity.Customer, error)
|
||||
}
|
||||
|
||||
type TransactionService interface {
|
||||
Create(ctx mycontext.Context, transaction *entity.Transaction) (*entity.Transaction, error)
|
||||
}
|
||||
|
||||
type CryptService interface {
|
||||
GenerateJWTOrderInquiry(inquiry *entity.OrderInquiry) (string, error)
|
||||
ValidateJWTOrderInquiry(tokenString string) (int64, string, error)
|
||||
}
|
||||
|
||||
type NotificationService interface {
|
||||
SendEmailTransactional(ctx context.Context, param entity.SendEmailNotificationParam) error
|
||||
}
|
||||
|
||||
type Service interface {
|
||||
CreateOrderInquiry(ctx mycontext.Context,
|
||||
req *entity.OrderRequest) (*entity.OrderInquiryResponse, error)
|
||||
ExecuteOrderInquiry(ctx mycontext.Context,
|
||||
token string, paymentMethod string) (*entity.OrderResponse, error)
|
||||
}
|
||||
|
||||
type Config interface {
|
||||
GetOrderFee(source string) float64
|
||||
}
|
||||
|
||||
type orderSvc struct {
|
||||
repo Repository
|
||||
product ProductService
|
||||
customer CustomerService
|
||||
transaction TransactionService
|
||||
crypt CryptService
|
||||
cfg Config
|
||||
notification NotificationService
|
||||
}
|
||||
|
||||
func New(
|
||||
repo Repository,
|
||||
product ProductService,
|
||||
customer CustomerService,
|
||||
transaction TransactionService,
|
||||
crypt CryptService,
|
||||
cfg Config,
|
||||
notification NotificationService,
|
||||
) Service {
|
||||
return &orderSvc{
|
||||
repo: repo,
|
||||
product: product,
|
||||
customer: customer,
|
||||
transaction: transaction,
|
||||
crypt: crypt,
|
||||
cfg: cfg,
|
||||
notification: notification,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package product
|
||||
|
||||
import (
|
||||
"enaklo-pos-be/internal/common/logger"
|
||||
"enaklo-pos-be/internal/common/mycontext"
|
||||
"enaklo-pos-be/internal/entity"
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (s *productSvc) GetProductsByIDs(ctx mycontext.Context, ids []int64, partnerID int64) ([]*entity.Product, error) {
|
||||
if len(ids) == 0 {
|
||||
return []*entity.Product{}, nil
|
||||
}
|
||||
|
||||
products, err := s.repo.GetProductsByIDs(ctx, ids, partnerID)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("failed to get products by IDs",
|
||||
zap.Int64s("productIDs", ids),
|
||||
zap.Int64("partnerID", partnerID),
|
||||
zap.Error(err))
|
||||
return nil, errors.Wrap(err, "failed to get products by IDs")
|
||||
}
|
||||
|
||||
// Validate that we found all requested products
|
||||
if len(products) != len(ids) {
|
||||
logger.ContextLogger(ctx).Warn("some products not found",
|
||||
zap.Int("requestedCount", len(ids)),
|
||||
zap.Int("foundCount", len(products)))
|
||||
}
|
||||
|
||||
return products, nil
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package product
|
||||
|
||||
import (
|
||||
"enaklo-pos-be/internal/common/logger"
|
||||
"enaklo-pos-be/internal/common/mycontext"
|
||||
"enaklo-pos-be/internal/entity"
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (s *productSvc) GetProductDetails(ctx mycontext.Context, productIDs []int64, partnerID int64) (*entity.ProductDetails, error) {
|
||||
if len(productIDs) == 0 {
|
||||
return &entity.ProductDetails{
|
||||
Products: make(map[int64]*entity.Product),
|
||||
}, nil
|
||||
}
|
||||
|
||||
productDetails, err := s.repo.GetProductDetails(ctx, productIDs, partnerID)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("failed to get product details",
|
||||
zap.Int64s("productIDs", productIDs),
|
||||
zap.Int64("partnerID", partnerID),
|
||||
zap.Error(err))
|
||||
return nil, errors.Wrap(err, "failed to get product details")
|
||||
}
|
||||
|
||||
if len(productDetails.Products) != len(productIDs) {
|
||||
missingIDs := findMissingProductIDs(productIDs, productDetails.Products)
|
||||
logger.ContextLogger(ctx).Warn("some products not found",
|
||||
zap.Int("requestedCount", len(productIDs)),
|
||||
zap.Int("foundCount", len(productDetails.Products)),
|
||||
zap.Int64s("missingIDs", missingIDs))
|
||||
|
||||
if len(productDetails.Products) == 0 {
|
||||
return nil, errors.New("no products found")
|
||||
}
|
||||
}
|
||||
|
||||
return productDetails, nil
|
||||
}
|
||||
|
||||
func findMissingProductIDs(requestedIDs []int64, foundProducts map[int64]*entity.Product) []int64 {
|
||||
var missingIDs []int64
|
||||
|
||||
for _, id := range requestedIDs {
|
||||
if _, exists := foundProducts[id]; !exists {
|
||||
missingIDs = append(missingIDs, id)
|
||||
}
|
||||
}
|
||||
|
||||
return missingIDs
|
||||
}
|
||||
|
||||
func (s *productSvc) IsProductAvailable(product *entity.Product) bool {
|
||||
return product.Status == "ACTIVE"
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package product
|
||||
|
||||
import (
|
||||
"enaklo-pos-be/internal/common/mycontext"
|
||||
"enaklo-pos-be/internal/entity"
|
||||
)
|
||||
|
||||
type Repository interface {
|
||||
GetProductsByIDs(ctx mycontext.Context, ids []int64, partnerID int64) ([]*entity.Product, error)
|
||||
GetProductDetails(ctx mycontext.Context, productIDs []int64, partnerID int64) (*entity.ProductDetails, error)
|
||||
}
|
||||
|
||||
type Service interface {
|
||||
GetProductsByIDs(ctx mycontext.Context, ids []int64, partnerID int64) ([]*entity.Product, error)
|
||||
GetProductDetails(ctx mycontext.Context, productIDs []int64, partnerID int64) (*entity.ProductDetails, error)
|
||||
}
|
||||
|
||||
type productSvc struct {
|
||||
repo Repository
|
||||
}
|
||||
|
||||
func New(repo Repository) Service {
|
||||
return &productSvc{
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user