2025-03-15 15:51:18 +08:00
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 ,
2025-05-03 10:50:41 +07:00
ExpiresAt : constants . TimeNow ( ) . Add ( 10 * time . Minute ) ,
2025-03-15 15:51:18 +08:00
CreatedAt : constants . TimeNow ( ) ,
UpdatedAt : constants . TimeNow ( ) ,
BranchID : request . BranchID ,
CashierID : request . CashierID ,
2025-05-03 10:50:41 +07:00
Password : request . GetHashPassword ( ) ,
2025-03-15 15:51:18 +08:00
}
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 ,
2025-06-04 23:45:25 +07:00
Password : registration . Password ,
2025-03-15 15:51:18 +08:00
}
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 ) )
}
2025-05-03 10:50:41 +07:00
signedToken , err := s . crypt . GenerateJWTCustomer ( customer )
if err != nil {
return nil , err
}
2025-03-15 15:51:18 +08:00
return & entity . MemberVerificationResponse {
2025-05-03 10:50:41 +07:00
Auth : customer . ToUserAuthenticate ( signedToken ) ,
2025-03-15 15:51:18 +08:00
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 {
2025-05-03 10:51:59 +07:00
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
}
2025-03-15 15:51:18 +08:00
//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 ,
2025-03-16 11:01:58 +08:00
"PointsName" : "ELP" ,
2025-03-15 15:51:18 +08:00
"PointsBalance" : customer . Points ,
"RedeemLink" : "https://enaklo.co.id/redeem" ,
2025-03-16 23:54:53 +07:00
"CurrentDate" : time . Now ( ) . Format ( "01-2006" ) ,
2025-03-15 15:51:18 +08:00
}
return s . notification . SendEmailTransactional ( ctx , entity . SendEmailNotificationParam {
Sender : "noreply@enaklo.co.id" ,
Recipient : customer . Email ,
Subject : "Welcome to Enaklo Membership Program" ,
TemplateName : "welcome_member" ,
2025-03-16 23:42:42 +07:00
TemplatePath : "/templates/welcome_member.html" ,
2025-03-15 15:51:18 +08:00
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
}