This commit is contained in:
aditya.siregar
2025-04-05 11:28:06 +08:00
parent 118ec58521
commit c642c5c61b
35 changed files with 1194 additions and 212 deletions
-1
View File
@@ -83,7 +83,6 @@ func (u *AuthServiceImpl) AuthenticateUser(ctx context.Context, email, password
}
func (u *AuthServiceImpl) SendPasswordResetLink(ctx context.Context, email string) error {
// Check if the user exists
user, err := u.authRepo.CheckExistsUserAccount(ctx, email)
if err != nil {
logger.ContextLogger(ctx).Error("error when getting user", zap.Error(err))
@@ -162,7 +162,6 @@ func (s *memberSvc) ResendOTP(
) (*entity.ResendOTPResponse, error) {
logger.ContextLogger(ctx).Info("resending OTP", zap.String("token", token))
// Get registration by token
registration, err := s.repo.GetRegistrationByToken(ctx, token)
if err != nil {
logger.ContextLogger(ctx).Error("failed to get registration", zap.Error(err))
@@ -211,7 +210,7 @@ func (s *memberSvc) sendRegistrationOTP(
Recipient: registration.Email,
Subject: "Enaklo - Registration Verification Code",
TemplateName: "member_registration_otp",
TemplatePath: "/templates/member_registration_otp.html",
TemplatePath: "templates/member_registration_otp.html",
Data: emailData,
})
-26
View File
@@ -240,32 +240,6 @@ func (s *OrderService) Execute(ctx mycontext.Context, req *entity.OrderExecuteRe
Order: order,
}
if order.PaymentType != "CASH" {
if order.PaymentType == "VA" {
paymentResponse, err := s.processVAPayment(ctx, order, partnerID, req.CreatedBy)
if err != nil {
return nil, err
}
resp.VirtualAccount = paymentResponse.VirtualAccountNumber
resp.BankName = paymentResponse.BankName
resp.BankCode = paymentResponse.BankCode
}
if order.PaymentType == "QRIS" {
paymentResponse, err := s.processQRPayment(ctx, order, partnerID, req.CreatedBy)
if err != nil {
return nil, err
}
resp.QRCode = paymentResponse.QRCodeURL
} else {
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)
if err != nil {
+5 -1
View File
@@ -16,6 +16,7 @@ import (
"enaklo-pos-be/internal/services/transaction"
"enaklo-pos-be/internal/services/users"
customerSvc "enaklo-pos-be/internal/services/v2/customer"
"enaklo-pos-be/internal/services/v2/inprogress_order"
orderSvc "enaklo-pos-be/internal/services/v2/order"
productSvc "enaklo-pos-be/internal/services/v2/product"
@@ -47,12 +48,14 @@ type ServiceManagerImpl struct {
CustomerV2Svc customerSvc.Service
ProductV2Svc productSvc.Service
MemberRegistrationSvc member.RegistrationService
InProgressSvc inprogress_order.InProgressOrderService
}
func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl) *ServiceManagerImpl {
custSvcV2 := customerSvc.New(repo.CustomerRepo)
custSvcV2 := customerSvc.New(repo.CustomerRepo, repo.EmailService)
productSvcV2 := productSvc.New(repo.ProductRepo)
inprogressOrder := inprogress_order.NewInProgressOrderService(repo.InProgressOrderRepo)
return &ServiceManagerImpl{
AuthSvc: auth.New(repo.Auth, repo.Crypto, repo.User, repo.EmailService, cfg.Email, repo.Trx, repo.License),
@@ -72,6 +75,7 @@ func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl)
OrderV2Svc: orderSvc.New(repo.OrderRepo, productSvcV2, custSvcV2, repo.TransactionRepo, repo.Crypto, &cfg.Order, repo.EmailService),
MemberRegistrationSvc: member.NewMemberRegistrationService(repo.MemberRepository, repo.EmailService, custSvcV2),
CustomerV2Svc: custSvcV2,
InProgressSvc: inprogressOrder,
}
}
+148 -7
View File
@@ -1,6 +1,8 @@
package customer
import (
"context"
errors2 "enaklo-pos-be/internal/common/errors"
"enaklo-pos-be/internal/common/logger"
"enaklo-pos-be/internal/common/mycontext"
"enaklo-pos-be/internal/constants"
@@ -8,7 +10,9 @@ import (
"enaklo-pos-be/internal/utils"
"github.com/pkg/errors"
"go.uber.org/zap"
"log"
"strings"
"time"
)
type Repository interface {
@@ -16,26 +20,35 @@ type Repository interface {
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
AddPoints(ctx mycontext.Context, id int64, points int, reference string) error
FindSequence(ctx mycontext.Context, partnerID int64) (int64, error)
GetAllCustomers(ctx mycontext.Context, req entity.MemberSearch) (entity.MemberList, int, error)
VerifyOTP(ctx mycontext.Context, verificationHash string, otpCode string) (int64, error)
}
type Service interface {
ResolveCustomer(ctx mycontext.Context, req *entity.CustomerResolutionRequest) (int64, error)
AddPoints(ctx mycontext.Context, customerID int64, points int) error
AddPoints(ctx mycontext.Context, customerID int64, points int, reference string) error
GetCustomer(ctx mycontext.Context, id int64) (*entity.Customer, error)
CustomerCheck(ctx mycontext.Context, req *entity.CustomerResolutionRequest) (*entity.CustomerCheckResponse, error)
GetAllCustomers(ctx mycontext.Context, req *entity.MemberSearch) (*entity.MemberList, int, error)
RegistrationMember(ctx mycontext.Context, req *entity.Customer) (*entity.Customer, error)
VerifyOTP(ctx mycontext.Context, verificationID, otpCode string) error
}
type EmailService interface {
SendEmailTransactional(ctx context.Context, param entity.SendEmailNotificationParam) error
}
type customerSvc struct {
repo Repository
repo Repository
notification EmailService
}
func New(repo Repository) Service {
func New(repo Repository, notification EmailService) Service {
return &customerSvc{
repo: repo,
repo: repo,
notification: notification,
}
}
@@ -106,12 +119,68 @@ func (s *customerSvc) ResolveCustomer(ctx mycontext.Context, req *entity.Custome
return customer.ID, nil
}
func (s *customerSvc) AddPoints(ctx mycontext.Context, customerID int64, points int) error {
func (s *customerSvc) RegistrationMember(ctx mycontext.Context, req *entity.Customer) (*entity.Customer, error) {
if req.Email == "" && req.PhoneNumber == "" {
return nil, errors2.ErrorPhoneNumberEmailIsRequired
}
if req.PhoneNumber != "" {
customer, err := s.repo.FindByPhone(ctx, req.PhoneNumber)
if err != nil && !strings.Contains(err.Error(), "not found") {
return nil, errors2.ErrorInternalServer
}
if customer != nil {
return nil, errors2.ErrorPhoneNumberIsAlreadyRegistered
}
}
if req.Email != "" {
customer, err := s.repo.FindByEmail(ctx, req.Email)
if err != nil && !strings.Contains(err.Error(), "not found") {
return nil, errors2.ErrorInternalServer
}
if customer != nil {
return nil, errors2.ErrorEmailIsAlreadyRegistered
}
}
newCustomer := &entity.Customer{
Name: req.Name,
Email: req.Email,
Phone: req.PhoneNumber,
CreatedAt: constants.TimeNow(),
UpdatedAt: constants.TimeNow(),
BirthDate: req.BirthDate,
Password: req.HashedPassword(),
}
customer, err := s.repo.Create(ctx, newCustomer)
if err != nil {
logger.ContextLogger(ctx).Error("failed to create customer", zap.Error(err))
return nil, errors2.ErrorInternalServer
}
errs := s.sendRegistrationOTP(ctx, &entity.MemberRegistration{
Name: customer.Name,
Email: customer.Email,
OTP: customer.OTP,
})
if err != nil {
logger.ContextLogger(ctx).Error("failed to send OTP", zap.Error(errs))
}
return customer, nil
}
func (s *customerSvc) AddPoints(ctx mycontext.Context, customerID int64, points int, reference string) error {
if points <= 0 {
return nil
}
err := s.repo.AddPoints(ctx, customerID, points)
err := s.repo.AddPoints(ctx, customerID, points, reference)
if err != nil {
return errors.Wrap(err, "failed to add points to customer")
}
@@ -202,3 +271,75 @@ func (s *customerSvc) GetAllCustomers(ctx mycontext.Context, req *entity.MemberS
return &customers, totalCount, nil
}
func (s *customerSvc) sendRegistrationOTP(
ctx mycontext.Context,
registration *entity.MemberRegistration,
) error {
emailData := map[string]interface{}{
"UserName": registration.Name,
"OTPCode": registration.OTP,
}
err := s.notification.SendEmailTransactional(ctx, entity.SendEmailNotificationParam{
Sender: "noreply@enaklo.co.id",
Recipient: registration.Email,
Subject: "Enaklo - Registration Verification Code",
TemplateName: "member_registration_otp",
TemplatePath: "templates/member_registration_otp.html",
Data: emailData,
})
if err != nil {
return err
}
return nil
}
func (s *customerSvc) VerifyOTP(ctx mycontext.Context, verificationID, otpCode string) error {
customerID, err := s.repo.VerifyOTP(ctx, verificationID, otpCode)
if err != nil {
return errors.Wrap(err, "verification failed")
}
customer, _ := s.repo.FindByID(ctx, customerID)
go func(customer *entity.Customer) {
newCtx := context.Background()
defer func() {
if r := recover(); r != nil {
log.Printf("Recovered from panic in sendWelcomeEmail: %v", r)
}
}()
s.sendWelcomeEmail(newCtx, customer)
}(customer)
return nil
}
func (s *customerSvc) sendWelcomeEmail(
ctx context.Context,
customer *entity.Customer,
) error {
welcomeData := map[string]interface{}{
"UserName": customer.Name,
"MemberID": customer.CustomerID,
"PointsName": "EnakPoint",
"PointsBalance": customer.Points,
"RedeemLink": "https://enaklo.co.id/redeem",
"CurrentDate": time.Now().Format("01-2006"),
}
return s.notification.SendEmailTransactional(ctx, entity.SendEmailNotificationParam{
Sender: "noreply@enaklo.co.id",
Recipient: customer.Email,
Subject: "Welcome to Enaklo Membership Program",
TemplateName: "welcome_member",
TemplatePath: "templates/welcome_member.html",
Data: welcomeData,
})
}
@@ -0,0 +1,51 @@
package inprogress_order
import (
"enaklo-pos-be/internal/common/logger"
"enaklo-pos-be/internal/common/mycontext"
"enaklo-pos-be/internal/entity"
"enaklo-pos-be/internal/repository"
"github.com/pkg/errors"
"go.uber.org/zap"
)
type InProgressOrderService interface {
Save(ctx mycontext.Context, order *entity.InProgressOrder) (*entity.InProgressOrder, error)
GetOrdersByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int) ([]*entity.InProgressOrder, error)
}
type inProgressOrderSvc struct {
repo repository.InProgressOrderRepository
}
func NewInProgressOrderService(repo repository.InProgressOrderRepository) InProgressOrderService {
return &inProgressOrderSvc{
repo: repo,
}
}
func (s *inProgressOrderSvc) Save(ctx mycontext.Context, order *entity.InProgressOrder) (*entity.InProgressOrder, error) {
createdOrder, err := s.repo.CreateOrUpdate(ctx, order)
if err != nil {
logger.ContextLogger(ctx).Error("failed to create in-progress order",
zap.Error(err),
zap.Int64("partnerID", order.PartnerID))
return nil, errors.Wrap(err, "failed to create in-progress order")
}
return createdOrder, nil
}
func (s *inProgressOrderSvc) GetOrdersByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int) ([]*entity.InProgressOrder, error) {
orders, err := s.repo.GetListByPartnerID(ctx, partnerID, limit, offset)
if err != nil {
logger.ContextLogger(ctx).Error("failed to get in-progress orders by partner ID",
zap.Error(err),
zap.Int64("partnerID", partnerID),
zap.Int("limit", limit),
zap.Int("offset", offset))
return nil, errors.Wrap(err, "failed to get in-progress orders")
}
return orders, nil
}
@@ -51,6 +51,9 @@ func (s *orderSvc) CreateOrderInquiry(ctx mycontext.Context,
req.CustomerName,
req.CustomerPhoneNumber,
req.CustomerEmail,
req.PaymentProvider,
req.TableNumber,
req.OrderType,
)
for _, item := range req.OrderItems {
+6 -5
View File
@@ -10,13 +10,14 @@ import (
)
func (s *orderSvc) ExecuteOrderInquiry(ctx mycontext.Context,
token string, paymentMethod string) (*entity.OrderResponse, error) {
token string, paymentMethod, paymentProvider, inprogressOrderID string) (*entity.OrderResponse, error) {
inquiry, err := s.validateInquiry(ctx, token)
if err != nil {
return nil, err
}
order := inquiry.ToOrder(paymentMethod)
order := inquiry.ToOrder(paymentMethod, paymentProvider)
order.InProgressOrderID = inprogressOrderID
savedOrder, err := s.repo.Create(ctx, order)
if err != nil {
@@ -51,7 +52,7 @@ func (s *orderSvc) processPostOrderActions(
}
if order.CustomerID != nil && *order.CustomerID > 0 {
err = s.addCustomerPoints(ctx, *order.CustomerID, int(order.Total/1000))
err = s.addCustomerPoints(ctx, *order.CustomerID, int(order.Total/1000), fmt.Sprintf("TRX #%s", trx.ID))
if err != nil {
logger.ContextLogger(ctx).Error("error when adding points", zap.Error(err))
}
@@ -78,8 +79,8 @@ func (s *orderSvc) createTransaction(ctx mycontext.Context, order *entity.Order,
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) addCustomerPoints(ctx mycontext.Context, customerID int64, points int, reference string) error {
return s.customer.AddPoints(ctx, customerID, points, reference)
}
func (s *orderSvc) sendTransactionReceipt(ctx mycontext.Context, order *entity.Order, transaction *entity.Transaction, paymentMethod string) error {
+2 -2
View File
@@ -21,7 +21,7 @@ type ProductService interface {
type CustomerService interface {
ResolveCustomer(ctx mycontext.Context, req *entity.CustomerResolutionRequest) (int64, error)
AddPoints(ctx mycontext.Context, customerID int64, points int) error
AddPoints(ctx mycontext.Context, customerID int64, points int, reference string) error
GetCustomer(ctx mycontext.Context, id int64) (*entity.Customer, error)
}
@@ -42,7 +42,7 @@ type Service interface {
CreateOrderInquiry(ctx mycontext.Context,
req *entity.OrderRequest) (*entity.OrderInquiryResponse, error)
ExecuteOrderInquiry(ctx mycontext.Context,
token string, paymentMethod string) (*entity.OrderResponse, error)
token string, paymentMethod, paymentProvider, inProgressOrderID string) (*entity.OrderResponse, error)
}
type Config interface {