package user import ( "context" "errors" "fmt" "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/pkgs/helpers" afro "github.com/amanuelabay/afrosms-go" "github.com/resend/resend-go/v2" "github.com/twilio/twilio-go" twilioApi "github.com/twilio/twilio-go/rest/api/v2010" "golang.org/x/crypto/bcrypt" ) func (s *Service) SendOtp(ctx context.Context, sentTo string, otpFor domain.OtpFor, medium domain.OtpMedium, provider domain.OtpProvider) error { otpCode := helpers.GenerateOTP() message := fmt.Sprintf("Welcome to Fortune bets, your OTP is %s please don't share with anyone.", otpCode) switch medium { case domain.OtpMediumSms: switch provider { case "twilio": if err := s.SendTwilioSMSOTP(ctx, sentTo, message, provider); err != nil { return err } case "afromessage": if err := s.SendAfroMessageSMSOTP(ctx, sentTo, message, provider); err != nil { return err } default: return fmt.Errorf("invalid sms provider: %s", provider) } case domain.OtpMediumEmail: if err := s.SendEmailOTP(ctx, sentTo, message); err != nil { return err } } otp := domain.Otp{ SentTo: sentTo, Medium: medium, For: otpFor, Otp: otpCode, Used: false, CreatedAt: time.Now(), ExpiresAt: time.Now().Add(OtpExpiry), } return s.otpStore.CreateOtp(ctx, otp) } func hashPassword(plaintextPassword string) ([]byte, error) { hash, err := bcrypt.GenerateFromPassword([]byte(plaintextPassword), 12) if err != nil { return []byte{}, err } return hash, nil } func (s *Service) SendAfroMessageSMSOTP(ctx context.Context, receiverPhone, message string, provider domain.OtpProvider) error { apiKey := s.config.AFRO_SMS_API_KEY senderName := s.config.AFRO_SMS_SENDER_NAME hostURL := s.config.ADRO_SMS_HOST_URL endpoint := "/api/send" // API endpoint has been updated // TODO: no need for package for the afro message operations (pretty simple stuff) request := afro.GetRequest(apiKey, endpoint, hostURL) request.BaseURL = "https://api.afromessage.com/api/send" request.Method = "GET" request.Sender(senderName) request.To(receiverPhone, message) response, err := afro.MakeRequestWithContext(ctx, request) if err != nil { return err } if response["acknowledge"] == "success" { return nil } else { fmt.Println(response["response"].(map[string]interface{})) return errors.New("SMS delivery failed") } } func (s *Service) SendTwilioSMSOTP(ctx context.Context, receiverPhone, message string, provider domain.OtpProvider) error { accountSid := s.config.TwilioAccountSid authToken := s.config.TwilioAuthToken senderPhone := s.config.TwilioSenderPhoneNumber client := twilio.NewRestClientWithParams(twilio.ClientParams{ Username: accountSid, Password: authToken, }) params := &twilioApi.CreateMessageParams{} params.SetTo(receiverPhone) params.SetFrom(senderPhone) params.SetBody(message) _, err := client.Api.CreateMessage(params) if err != nil { return fmt.Errorf("Error sending SMS message: %s" + err.Error()) } return nil } func (s *Service) SendEmailOTP(ctx context.Context, receiverEmail, message string) error { apiKey := s.config.ResendApiKey client := resend.NewClient(apiKey) formattedSenderEmail := "FortuneBets <" + s.config.ResendSenderEmail + ">" params := &resend.SendEmailRequest{ From: formattedSenderEmail, To: []string{receiverEmail}, Subject: "FortuneBets - One Time Password", Text: message, } _, err := client.Emails.Send(params) if err != nil { return err } return nil }