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), CreatedAt: constants.TimeNow(), UpdatedAt: constants.TimeNow(), BranchID: request.BranchID, CashierID: request.CashierID, Password: request.GetHashPassword(), } 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, Password: registration.Password, } 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)) } signedToken, err := s.crypt.GenerateJWTCustomer(customer) if err != nil { return nil, err } return &entity.MemberVerificationResponse{ Auth: customer.ToUserAuthenticate(signedToken), 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)) 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-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, }) } 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 }