263 lines
7.6 KiB
Go
263 lines
7.6 KiB
Go
package member
|
|
|
|
import (
|
|
"enaklo-pos-be/internal/common/logger"
|
|
"enaklo-pos-be/internal/common/mycontext"
|
|
"enaklo-pos-be/internal/constants"
|
|
"enaklo-pos-be/internal/entity"
|
|
"errors"
|
|
"go.uber.org/zap"
|
|
"golang.org/x/exp/rand"
|
|
"time"
|
|
)
|
|
|
|
func (s *memberSvc) InitiateRegistration(
|
|
ctx mycontext.Context,
|
|
request *entity.MemberRegistrationRequest,
|
|
) (*entity.MemberRegistrationResponse, error) {
|
|
customerResolution := &entity.CustomerResolutionRequest{
|
|
Email: request.Email,
|
|
PhoneNumber: request.Phone,
|
|
}
|
|
|
|
checkResult, err := s.customerSvc.CustomerCheck(ctx, customerResolution)
|
|
if checkResult.Exists {
|
|
return nil, errors.New(checkResult.Message)
|
|
}
|
|
|
|
otp := generateOTP(6)
|
|
|
|
token := constants.GenerateUUID()
|
|
|
|
registration := &entity.MemberRegistration{
|
|
ID: constants.GenerateUUID(),
|
|
Token: token,
|
|
Name: request.Name,
|
|
Email: request.Email,
|
|
Phone: request.Phone,
|
|
BirthDate: request.BirthDate,
|
|
OTP: otp,
|
|
Status: constants.RegistrationPending,
|
|
ExpiresAt: constants.TimeNow().Add(10 * time.Minute), // OTP expires in 10 minutes
|
|
CreatedAt: constants.TimeNow(),
|
|
UpdatedAt: constants.TimeNow(),
|
|
BranchID: request.BranchID,
|
|
CashierID: request.CashierID,
|
|
}
|
|
|
|
savedRegistration, err := s.repo.CreateRegistration(ctx, registration)
|
|
if err != nil {
|
|
logger.ContextLogger(ctx).Error("failed to create member registration", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
err = s.sendRegistrationOTP(ctx, savedRegistration)
|
|
if err != nil {
|
|
logger.ContextLogger(ctx).Warn("failed to send OTP", zap.Error(err))
|
|
}
|
|
|
|
return &entity.MemberRegistrationResponse{
|
|
Token: token,
|
|
Status: savedRegistration.Status.String(),
|
|
ExpiresAt: savedRegistration.ExpiresAt,
|
|
Message: "Registration initiated. Please verify with OTP sent to your email.",
|
|
}, nil
|
|
}
|
|
|
|
func (s *memberSvc) VerifyOTP(
|
|
ctx mycontext.Context,
|
|
token string,
|
|
otp string,
|
|
) (*entity.MemberVerificationResponse, error) {
|
|
logger.ContextLogger(ctx).Info("verifying OTP for member registration", zap.String("token", token))
|
|
|
|
registration, err := s.repo.GetRegistrationByToken(ctx, token)
|
|
if err != nil {
|
|
logger.ContextLogger(ctx).Error("failed to get registration", zap.Error(err))
|
|
return nil, errors.New("invalid registration token")
|
|
}
|
|
|
|
if registration.Status == constants.RegistrationSuccess {
|
|
return nil, errors.New("registration already completed")
|
|
}
|
|
|
|
if registration.ExpiresAt.Before(constants.TimeNow()) {
|
|
return nil, errors.New("registration expired")
|
|
}
|
|
|
|
if registration.OTP != otp {
|
|
return nil, errors.New("invalid OTP")
|
|
}
|
|
|
|
customerResolution := &entity.CustomerResolutionRequest{
|
|
Name: registration.Name,
|
|
Email: registration.Email,
|
|
PhoneNumber: registration.Phone,
|
|
BirthDate: registration.BirthDate,
|
|
}
|
|
|
|
customerID, err := s.customerSvc.ResolveCustomer(ctx, customerResolution)
|
|
if err != nil {
|
|
logger.ContextLogger(ctx).Error("failed to create customer", zap.Error(err))
|
|
return nil, errors.New("failed to create member record")
|
|
}
|
|
|
|
err = s.repo.UpdateRegistrationStatus(ctx, token, constants.RegistrationSuccess)
|
|
if err != nil {
|
|
logger.ContextLogger(ctx).Warn("failed to update registration status", zap.Error(err))
|
|
}
|
|
|
|
customer, err := s.customerSvc.GetCustomer(ctx, customerID)
|
|
if err != nil {
|
|
logger.ContextLogger(ctx).Warn("failed to get created customer", zap.Error(err))
|
|
|
|
return &entity.MemberVerificationResponse{
|
|
CustomerID: customerID,
|
|
Name: registration.Name,
|
|
Email: registration.Email,
|
|
Phone: registration.Phone,
|
|
Status: "Registration completed successfully",
|
|
}, nil
|
|
}
|
|
|
|
err = s.sendWelcomeEmail(ctx, customer)
|
|
if err != nil {
|
|
logger.ContextLogger(ctx).Warn("failed to send welcome email", zap.Error(err))
|
|
}
|
|
|
|
return &entity.MemberVerificationResponse{
|
|
CustomerID: customer.ID,
|
|
Name: customer.Name,
|
|
Email: customer.Email,
|
|
Phone: customer.Phone,
|
|
Points: customer.Points,
|
|
Status: "Registration completed successfully",
|
|
}, nil
|
|
}
|
|
|
|
func (s *memberSvc) GetRegistrationStatus(
|
|
ctx mycontext.Context,
|
|
token string,
|
|
) (*entity.MemberRegistrationStatus, error) {
|
|
logger.ContextLogger(ctx).Info("checking registration status", zap.String("token", token))
|
|
|
|
registration, err := s.repo.GetRegistrationByToken(ctx, token)
|
|
if err != nil {
|
|
logger.ContextLogger(ctx).Error("failed to get registration", zap.Error(err))
|
|
return nil, errors.New("invalid registration token")
|
|
}
|
|
|
|
return &entity.MemberRegistrationStatus{
|
|
Token: registration.Token,
|
|
Status: registration.Status.String(),
|
|
ExpiresAt: registration.ExpiresAt,
|
|
IsExpired: registration.ExpiresAt.Before(constants.TimeNow()),
|
|
CreatedAt: registration.CreatedAt,
|
|
}, nil
|
|
}
|
|
|
|
func (s *memberSvc) ResendOTP(
|
|
ctx mycontext.Context,
|
|
token string,
|
|
) (*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))
|
|
return nil, errors.New("invalid registration token")
|
|
}
|
|
|
|
if registration.Status == constants.RegistrationSuccess {
|
|
return nil, errors.New("registration already completed")
|
|
}
|
|
|
|
newOTP := generateOTP(6)
|
|
newExpiresAt := constants.TimeNow().Add(10 * time.Minute)
|
|
|
|
err = s.repo.UpdateRegistrationOTP(ctx, token, newOTP, newExpiresAt)
|
|
if err != nil {
|
|
logger.ContextLogger(ctx).Error("failed to update OTP", zap.Error(err))
|
|
return nil, errors.New("failed to generate new OTP")
|
|
}
|
|
|
|
registration.OTP = newOTP
|
|
registration.ExpiresAt = newExpiresAt
|
|
|
|
err = s.sendRegistrationOTP(ctx, registration)
|
|
if err != nil {
|
|
logger.ContextLogger(ctx).Warn("failed to send OTP", zap.Error(err))
|
|
}
|
|
|
|
return &entity.ResendOTPResponse{
|
|
Token: token,
|
|
ExpiresAt: newExpiresAt,
|
|
Message: "OTP has been resent to your email and phone",
|
|
}, nil
|
|
}
|
|
|
|
func (s *memberSvc) 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
|
|
}
|
|
|
|
//if registration.Phone != "" {
|
|
// smsMessage := fmt.Sprintf("Your Enaklo registration code is: %s. Please provide this code to our staff to complete your registration.", registration.OTP)
|
|
// _ = s.notification.SendSMS(ctx, registration.Phone, smsMessage)
|
|
//}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *memberSvc) sendWelcomeEmail(
|
|
ctx mycontext.Context,
|
|
customer *entity.Customer,
|
|
) error {
|
|
|
|
welcomeData := map[string]interface{}{
|
|
"UserName": customer.Name,
|
|
"MemberID": customer.CustomerID,
|
|
"PointsName": "ELP",
|
|
"PointsBalance": customer.Points,
|
|
"RedeemLink": "https://enaklo.co.id/redeem",
|
|
"CurrentDate": time.Now().Format("01-20006"),
|
|
}
|
|
|
|
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,
|
|
})
|
|
}
|
|
|
|
func generateOTP(length int) string {
|
|
rand.Seed(uint64(time.Now().Nanosecond()))
|
|
digits := "0123456789"
|
|
otp := ""
|
|
for i := 0; i < length; i++ {
|
|
otp += string(digits[rand.Intn(len(digits))])
|
|
}
|
|
return otp
|
|
}
|