added sms integration

This commit is contained in:
Prince
2026-06-12 11:56:25 +02:00
parent 8a9d58fe05
commit c6ce11026c
10 changed files with 279 additions and 12 deletions

View File

@@ -10,7 +10,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import zw.qantra.tm.domain.enums.RequestType;
import zw.qantra.tm.domain.models.Transaction;
import zw.qantra.tm.domain.services.EmailService;
import zw.qantra.tm.domain.services.NotificationService;
import zw.qantra.tm.domain.services.TransactionService;
@RestController
@@ -18,7 +18,7 @@ import zw.qantra.tm.domain.services.TransactionService;
@CrossOrigin(origins = "*")
@RequiredArgsConstructor
public class TestController {
private final EmailService emailService;
private final NotificationService notificationService;
private final TransactionService transactionService;
private static final Logger logger = LoggerFactory.getLogger(TestController.class);
@@ -44,7 +44,7 @@ public class TestController {
@GetMapping("/hello/{email}")
public ResponseEntity<String> helloEndpoint(@PathVariable String email) {
emailService.sendSimpleMessage(email, "Hello", "Hello World!");
notificationService.sendEmail(email, "Hello", "Hello World!");
return ResponseEntity.ok("Hello World!");
}

View File

@@ -0,0 +1,14 @@
package zw.qantra.tm.domain.dtos.sms;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SmsApiResponse {
private int status;
private String result;
}

View File

@@ -0,0 +1,18 @@
package zw.qantra.tm.domain.dtos.sms;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SmsBatchRequest {
private String batchReference;
private List<SmsMessage> smsList;
}

View File

@@ -0,0 +1,17 @@
package zw.qantra.tm.domain.dtos.sms;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SmsMessage {
private String reference;
private String message;
private String msisdn;
}

View File

@@ -1,19 +1,31 @@
package zw.qantra.tm.domain.services;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
* @deprecated Use {@link NotificationService} instead for all email sending.
* Email functionality has been moved to NotificationService which supports
* pretty HTML templates.
*/
@Deprecated
@Component
public class EmailService {
private static final Logger log = LoggerFactory.getLogger(EmailService.class);
@Autowired
private JavaMailSender emailSender;
@Async
@Deprecated
public void sendSimpleMessage(
String to, String subject, String text) {
log.warn("EmailService.sendSimpleMessage() is deprecated. Use NotificationService instead.");
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom("noreply@qantra.co.zw");
message.setTo(to);

View File

@@ -0,0 +1,137 @@
package zw.qantra.tm.domain.services;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpHeaders;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import jakarta.mail.internet.MimeMessage;
import zw.qantra.tm.domain.dtos.sms.SmsApiResponse;
import zw.qantra.tm.domain.dtos.sms.SmsBatchRequest;
import zw.qantra.tm.domain.dtos.sms.SmsMessage;
import zw.qantra.tm.rest.RestService;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class NotificationService {
private static final Logger logger = LoggerFactory.getLogger(NotificationService.class);
private final RestService restService;
private final JavaMailSender emailSender;
@Value("${zss.sms.api.url}")
private String smsApiUrl;
@Value("${zss.sms.api.auth}")
private String smsApiAuth;
@Value("${sms.debug}")
private Boolean smsDebug;
@Async
public void sendEmail(String to, String subject, String text) {
sendEmailWithHtml(to, subject, text, null);
}
@Async
public void sendEmailWithHtml(String to, String subject, String htmlContent) {
sendEmailWithHtml(to, subject, null, htmlContent);
}
@Async
public void sendEmailWithHtml(String to, String subject, String text, String htmlContent) {
try {
MimeMessage message = emailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setFrom("noreply@qantra.co.zw");
helper.setTo(to);
helper.setSubject(subject);
if (htmlContent != null) {
helper.setText(text != null ? text : "", htmlContent);
} else if (text != null) {
helper.setText(text);
}
emailSender.send(message);
logger.info("Email sent to {} - Subject: {}", to, subject);
} catch (Exception e) {
logger.error("Failed to send email to {}: {}", to, e.getMessage(), e);
}
}
@Async
public void sendOtpEmail(String to, String username, String otpCode) {
try {
// Load and populate the HTML template
ClassPathResource resource = new ClassPathResource("templates/email-otp.html");
String htmlTemplate;
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))) {
htmlTemplate = reader.lines().collect(Collectors.joining("\n"));
}
// Simple placeholder replacement
String htmlContent = htmlTemplate
.replace("{{username}}", username != null ? username : "")
.replace("{{otpCode}}", otpCode != null ? otpCode : "");
MimeMessage message = emailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setFrom("noreply@qantra.co.zw");
helper.setTo(to);
helper.setSubject("Velocity Pay verification code");
helper.setText("Your Velocity Pay verification code is: " + otpCode, htmlContent);
emailSender.send(message);
logger.info("OTP email sent to {} for user: {}", to, username);
} catch (Exception e) {
logger.error("Failed to send OTP email to {}: {}", to, e.getMessage(), e);
}
}
@Async
public void sendSms(String to, String message) {
if(smsDebug) {
logger.info("Sending SMS to {}: {}", to, message);
return;
}
try {
SmsMessage smsMessage = SmsMessage.builder()
.reference(UUID.randomUUID().toString())
.message(message)
.msisdn(to)
.build();
SmsBatchRequest batchRequest = SmsBatchRequest.builder()
.batchReference(UUID.randomUUID().toString())
.smsList(Collections.singletonList(smsMessage))
.build();
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", smsApiAuth);
SmsApiResponse response = restService.postWithExternalAuth(
smsApiUrl,
batchRequest,
headers,
SmsApiResponse.class
);
logger.info("SMS sent to {} - Status: {}, Result: {}", to, response.getStatus(), response.getResult());
} catch (Exception e) {
logger.error("Failed to send SMS to {}: {}", to, e.getMessage(), e);
}
}
}

View File

@@ -18,7 +18,7 @@ import zw.qantra.tm.domain.dtos.*;
import zw.qantra.tm.domain.dtos.erp.VelocityCustomerResponseDto;
import zw.qantra.tm.domain.models.Otp;
import zw.qantra.tm.domain.models.User;
import zw.qantra.tm.domain.services.EmailService;
import zw.qantra.tm.domain.services.NotificationService;
import zw.qantra.tm.domain.services.OtpService;
import zw.qantra.tm.domain.services.UserService;
import io.nflow.engine.workflow.curated.State;
@@ -48,7 +48,7 @@ public class RegistrationWorkflow extends WorkflowDefinition {
@Autowired
private RestService restService;
@Autowired
private EmailService emailService;
private NotificationService notificationService;
@Autowired
private VelocityPaymentService velocityPaymentService;
@@ -117,9 +117,9 @@ public class RegistrationWorkflow extends WorkflowDefinition {
// Generate OTP
Otp otp = otpService.generateOtp(username, "REGISTRATION");
String string = String.format("Your Velocity Pay verification code is: %s", otp.getOtp());
emailService.sendSimpleMessage(email, "Velocity Pay verification code", string);
// LinkedHashMap response = restService.postAs(infobipUrl, string, headers, LinkedHashMap.class);
notificationService.sendOtpEmail(email, username, otp.getOtp());
notificationService.sendSms(registerRequest.getPhone(),
"Your Velocity Pay verification code is: " + otp.getOtp());
log.info("otp generated successfully: {}", otp.getOtp());
} catch (Exception e) {
@@ -142,10 +142,12 @@ public class RegistrationWorkflow extends WorkflowDefinition {
public void resendOtp(StateExecution execution) {
OtpRequest otpRequest = execution.getVariable("resend", OtpRequest.class);
Otp otp = otpService.generateOtp(otpRequest.getUsername(), "REGISTRATION");
String string = String.format("Your Velocity Pay verification code is: %s", otp.getOtp());
emailService.sendSimpleMessage(otpRequest.getUsername(), "Velocity Pay verification code", string);
// The username field in OtpRequest may actually contain the email; use it directly
notificationService.sendOtpEmail(otpRequest.getUsername(), otpRequest.getUsername(), otp.getOtp());
notificationService.sendSms(otpRequest.getPhone(),
"Your Velocity Pay verification code is: " + otp.getOtp());
log.info("otp generated successfully: {}", otp.getOtp());
log.info("otp resent successfully: {}", otp.getOtp());
}
public NextAction otpSuccess(StateExecution execution) {