velocity integration complete
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
package zw.qantra.tm.domain.controllers;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.kaczmarzyk.spring.data.jpa.domain.Equal;
|
||||
import net.kaczmarzyk.spring.data.jpa.web.annotation.And;
|
||||
import net.kaczmarzyk.spring.data.jpa.web.annotation.Spec;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import zw.qantra.tm.domain.models.Provider;
|
||||
@@ -16,8 +21,11 @@ public class ProviderController {
|
||||
private final ProviderService providerService;
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity getProviders(@RequestParam(required = false) String category) {
|
||||
return ResponseEntity.ok(providerService.getProvidersByCategory(category));
|
||||
public ResponseEntity getProviders(
|
||||
@And({
|
||||
@Spec(path = "category", spec = Equal.class)
|
||||
}) Specification<Provider> spec, Pageable pageable) {
|
||||
return ResponseEntity.ok(providerService.findAll(spec, pageable));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/products")
|
||||
|
||||
@@ -30,4 +30,5 @@ public class Provider extends BaseEntity {
|
||||
private double percentageCommission;
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String meta;
|
||||
private Integer priority;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package zw.qantra.tm.domain.repositories;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import zw.qantra.tm.domain.models.Provider;
|
||||
|
||||
@@ -8,7 +9,7 @@ import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface ProviderRepository extends JpaRepository<Provider, UUID> {
|
||||
public interface ProviderRepository extends JpaRepository<Provider, UUID>, JpaSpecificationExecutor<Provider> {
|
||||
List<Provider> findByCategory(String category);
|
||||
Provider findByClientId(String clientId);
|
||||
boolean existsByClientId(String clientId);
|
||||
|
||||
@@ -26,31 +26,62 @@ public class HotRechargeTokenService {
|
||||
private String hotAccessCode;
|
||||
@Value("${hot.password}")
|
||||
private String hotPassword;
|
||||
@Value("${hot.token.expiry.minutes:25}")
|
||||
private int tokenExpiryMinutes;
|
||||
@Value("${hot.cache.duration.minutes:5}")
|
||||
private int cacheDurationMinutes;
|
||||
|
||||
private String cachedToken;
|
||||
private long tokenExpiryTime;
|
||||
private String cachedRefreshToken;
|
||||
private long cacheExpiryTime;
|
||||
|
||||
public synchronized String getToken() {
|
||||
if (isTokenValid()) {
|
||||
logger.info("Returning cached HotRecharge token");
|
||||
return cachedToken;
|
||||
if (isCacheValid()) {
|
||||
logger.info("Cache is valid, attempting token refresh");
|
||||
return refreshCachedToken();
|
||||
}
|
||||
return fetchNewToken();
|
||||
}
|
||||
|
||||
private boolean isTokenValid() {
|
||||
return cachedToken != null && System.currentTimeMillis() < tokenExpiryTime;
|
||||
private boolean isCacheValid() {
|
||||
return cachedToken != null && cachedRefreshToken != null && System.currentTimeMillis() < cacheExpiryTime;
|
||||
}
|
||||
|
||||
private synchronized String fetchNewToken() {
|
||||
if (isTokenValid()) {
|
||||
return cachedToken;
|
||||
private synchronized String refreshCachedToken() {
|
||||
if (!isCacheValid()) {
|
||||
return fetchNewToken();
|
||||
}
|
||||
|
||||
try {
|
||||
logger.info("Fetching new HotRecharge token");
|
||||
logger.info("Refreshing HotRecharge token via refresh endpoint");
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
|
||||
|
||||
Map<String, String> payload = Map.of(
|
||||
"token", cachedToken,
|
||||
"refreshToken", cachedRefreshToken
|
||||
);
|
||||
|
||||
LinkedHashMap<String, Object> response = restService.postAs(hotAuthUrl + "/identity/refresh", payload, headers, LinkedHashMap.class);
|
||||
cachedToken = (String) response.get("token");
|
||||
cachedRefreshToken = (String) response.get("refreshToken");
|
||||
cacheExpiryTime = System.currentTimeMillis() + (cacheDurationMinutes * 60 * 1000L);
|
||||
|
||||
logger.info("HotRecharge token refreshed via refresh endpoint, cache extended by {} minutes", cacheDurationMinutes);
|
||||
return cachedToken;
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to refresh HotRecharge token: " + e.getMessage(), e);
|
||||
// Fall back to login on refresh failure
|
||||
return fetchNewToken();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized String fetchNewToken() {
|
||||
if (isCacheValid()) {
|
||||
return refreshCachedToken();
|
||||
}
|
||||
|
||||
try {
|
||||
logger.info("Fetching new HotRecharge token via login");
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
|
||||
@@ -62,13 +93,15 @@ public class HotRechargeTokenService {
|
||||
|
||||
LinkedHashMap<String, Object> response = restService.postAs(hotAuthUrl + "/identity/login", payload, headers, LinkedHashMap.class);
|
||||
cachedToken = (String) response.get("token");
|
||||
tokenExpiryTime = System.currentTimeMillis() + (tokenExpiryMinutes * 60 * 1000L);
|
||||
cachedRefreshToken = (String) response.get("refreshToken");
|
||||
cacheExpiryTime = System.currentTimeMillis() + (cacheDurationMinutes * 60 * 1000L);
|
||||
|
||||
logger.info("New HotRecharge token cached, expires in {} minutes", tokenExpiryMinutes);
|
||||
logger.info("New HotRecharge token cached, cache expires in {} minutes", cacheDurationMinutes);
|
||||
return cachedToken;
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to fetch HotRecharge token: " + e.getMessage(), e);
|
||||
cachedToken = null;
|
||||
cachedRefreshToken = null;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -76,7 +109,7 @@ public class HotRechargeTokenService {
|
||||
public synchronized void invalidateToken() {
|
||||
logger.info("Invalidating cached HotRecharge token");
|
||||
cachedToken = null;
|
||||
tokenExpiryTime = 0;
|
||||
cachedRefreshToken = null;
|
||||
cacheExpiryTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -164,16 +166,8 @@ public class ProviderService {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Object getProvidersByCategory(String category) {
|
||||
|
||||
List<Provider> existingProviders;
|
||||
if (category != null && !category.isEmpty()) {
|
||||
existingProviders = getProviderRepository().findByCategory(category);
|
||||
} else {
|
||||
existingProviders = getProviderRepository().findAll();
|
||||
}
|
||||
|
||||
return existingProviders;
|
||||
public Object findAll(Specification<Provider> specification, Pageable pageable) {
|
||||
return getProviderRepository().findAll(specification, pageable);
|
||||
}
|
||||
|
||||
public Object getProviders(List<Provider> existingProviders) {
|
||||
|
||||
@@ -181,7 +181,10 @@ public class VelocityPaymentService {
|
||||
transaction.setTargetUrl(redirectUrl);
|
||||
return transaction;
|
||||
}
|
||||
return null;
|
||||
|
||||
String errorMessage = (String) body.get("errorMessage");
|
||||
log.error("Unexpected transaction response from Velocity: {}", response);
|
||||
throw new IllegalStateException(errorMessage);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error initiating payment: {}", e.getMessage(), e);
|
||||
|
||||
@@ -4,6 +4,7 @@ import org.springframework.stereotype.Service;
|
||||
import zw.qantra.tm.domain.services.AdditionalDataService;
|
||||
import zw.qantra.tm.domain.services.HotRechargeBalanceService;
|
||||
import zw.qantra.tm.domain.services.HotRechargeTokenService;
|
||||
import zw.qantra.tm.domain.services.SettingService;
|
||||
import zw.qantra.tm.rest.RestService;
|
||||
|
||||
|
||||
@@ -13,7 +14,8 @@ public class EconetBundlesConfirmationHotProcessor extends NetoneConfirmationHot
|
||||
AdditionalDataService additionalDataService,
|
||||
HotRechargeTokenService hotRechargeTokenService,
|
||||
HotRechargeBalanceService hotRechargeBalanceService,
|
||||
RestService restService) {
|
||||
super(additionalDataService, hotRechargeTokenService, hotRechargeBalanceService, restService);
|
||||
RestService restService,
|
||||
SettingService settingService) {
|
||||
super(additionalDataService, hotRechargeTokenService, hotRechargeBalanceService, restService, settingService);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import org.springframework.stereotype.Service;
|
||||
import zw.qantra.tm.domain.services.AdditionalDataService;
|
||||
import zw.qantra.tm.domain.services.HotRechargeBalanceService;
|
||||
import zw.qantra.tm.domain.services.HotRechargeTokenService;
|
||||
import zw.qantra.tm.domain.services.SettingService;
|
||||
import zw.qantra.tm.rest.RestService;
|
||||
|
||||
|
||||
@@ -13,7 +14,8 @@ public class EconetConfirmationHotProcessor extends NetoneConfirmationHotProcess
|
||||
AdditionalDataService additionalDataService,
|
||||
HotRechargeTokenService hotRechargeTokenService,
|
||||
HotRechargeBalanceService hotRechargeBalanceService,
|
||||
RestService restService) {
|
||||
super(additionalDataService, hotRechargeTokenService, hotRechargeBalanceService, restService);
|
||||
RestService restService,
|
||||
SettingService settingService) {
|
||||
super(additionalDataService, hotRechargeTokenService, hotRechargeBalanceService, restService, settingService);
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import zw.qantra.tm.domain.models.Transaction;
|
||||
import zw.qantra.tm.domain.services.AdditionalDataService;
|
||||
import zw.qantra.tm.domain.services.HotRechargeBalanceService;
|
||||
import zw.qantra.tm.domain.services.HotRechargeTokenService;
|
||||
import zw.qantra.tm.domain.services.SettingService;
|
||||
import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface;
|
||||
import zw.qantra.tm.rest.RestService;
|
||||
import zw.qantra.tm.utils.Utils;
|
||||
@@ -28,6 +29,7 @@ public class NetoneConfirmationHotProcessor implements TransactionProcessorInter
|
||||
protected final HotRechargeTokenService hotRechargeTokenService;
|
||||
protected final HotRechargeBalanceService hotRechargeBalanceService;
|
||||
protected final RestService restService;
|
||||
protected final SettingService settingService;
|
||||
|
||||
@Value("${hot.api.url:https://ssl.hot.co.zw/api/v3}")
|
||||
private String hotApiUrl;
|
||||
@@ -36,14 +38,18 @@ public class NetoneConfirmationHotProcessor implements TransactionProcessorInter
|
||||
@Override
|
||||
public Object process(Transaction transaction) throws Exception {
|
||||
|
||||
String token = hotRechargeTokenService.getToken();
|
||||
if (!hotRechargeBalanceService.checkBalance(token, transaction.getAmount())) {
|
||||
transaction.setStatus(Status.FAILED);
|
||||
transaction.setResponseCode("92");
|
||||
transaction.setErrorMessage("There's a technical issue on our end. Please try again later.");
|
||||
return transaction;
|
||||
// do this if we not simulating success, if simulating success, we skip balance check and just return success
|
||||
if(!settingService.getBooleanSetting("SIMULATE_HOT_SUCCESS")){
|
||||
String token = hotRechargeTokenService.getToken();
|
||||
if (!hotRechargeBalanceService.checkBalance(token, transaction.getAmount())) {
|
||||
transaction.setStatus(Status.FAILED);
|
||||
transaction.setResponseCode("92");
|
||||
transaction.setErrorMessage("There's a technical issue on our end. Please try again later.");
|
||||
return transaction;
|
||||
}
|
||||
}
|
||||
|
||||
transaction.setCreditAccount(Utils.formatPhoneNumber(transaction.getCreditAccount(), "ZW"));
|
||||
transaction.setStatus(Status.SUCCESS);
|
||||
transaction.setResponseCode("00");
|
||||
|
||||
|
||||
@@ -13,10 +13,7 @@ import zw.qantra.tm.domain.enums.Status;
|
||||
import zw.qantra.tm.domain.models.AdditionalData;
|
||||
import zw.qantra.tm.domain.models.Provider;
|
||||
import zw.qantra.tm.domain.models.Transaction;
|
||||
import zw.qantra.tm.domain.services.AdditionalDataService;
|
||||
import zw.qantra.tm.domain.services.HotRechargeBalanceService;
|
||||
import zw.qantra.tm.domain.services.HotRechargeTokenService;
|
||||
import zw.qantra.tm.domain.services.ProviderService;
|
||||
import zw.qantra.tm.domain.services.*;
|
||||
import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface;
|
||||
import zw.qantra.tm.exceptions.ApiException;
|
||||
import zw.qantra.tm.rest.RestService;
|
||||
@@ -35,6 +32,7 @@ public class ZesaConfirmationHotProcessor implements TransactionProcessorInterfa
|
||||
private final ProviderService providerService;
|
||||
private final HotRechargeTokenService hotRechargeTokenService;
|
||||
private final HotRechargeBalanceService hotRechargeBalanceService;
|
||||
private final SettingService settingService;
|
||||
|
||||
@Value("${hot.api.url:https://ssl.hot.co.zw/api/v3}")
|
||||
private String hotApiUrl;
|
||||
@@ -44,6 +42,13 @@ public class ZesaConfirmationHotProcessor implements TransactionProcessorInterfa
|
||||
logger.info("Processing ZESA confirmation from HotRecharge");
|
||||
|
||||
try {
|
||||
// do this if we not simulating success, if simulating success, we skip balance check and just return success
|
||||
if(settingService.getBooleanSetting("SIMULATE_HOT_SUCCESS")){
|
||||
transaction.setStatus(Status.SUCCESS);
|
||||
transaction.setResponseCode("00");
|
||||
return transaction;
|
||||
}
|
||||
|
||||
String token = hotRechargeTokenService.getToken();
|
||||
if (token == null || token.isEmpty()) {
|
||||
transaction.setStatus(Status.FAILED);
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package zw.qantra.tm.domain.services.processors.handlers;
|
||||
|
||||
public interface LogicHandler<I, O> {
|
||||
O process(I input);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package zw.qantra.tm.domain.services.processors.handlers;
|
||||
|
||||
public class LogicPipeline<I, O> {
|
||||
|
||||
private final LogicHandler<I, O> currentHandler;
|
||||
|
||||
public LogicPipeline(LogicHandler<I, O> currentHandler) {
|
||||
this.currentHandler = currentHandler;
|
||||
}
|
||||
|
||||
public <K> LogicPipeline<I, K> addHandler(LogicHandler<O, K> newHandler) {
|
||||
return new LogicPipeline<>(input -> newHandler.process(currentHandler.process(input)));
|
||||
}
|
||||
|
||||
public O execute(I input) {
|
||||
return currentHandler.process(input);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,16 @@
|
||||
package zw.qantra.tm.domain.services.processors.payments;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import zw.qantra.tm.domain.dtos.SdkActionDto;
|
||||
import zw.qantra.tm.domain.enums.Status;
|
||||
import zw.qantra.tm.domain.models.Transaction;
|
||||
import zw.qantra.tm.domain.services.SettingService;
|
||||
|
||||
@@ -30,6 +30,7 @@ import zw.qantra.tm.exceptions.AlreadyExistsException;
|
||||
import zw.qantra.tm.rest.RestService;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import zw.qantra.tm.utils.Utils;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
@@ -99,16 +100,7 @@ public class RegistrationWorkflow extends WorkflowDefinition {
|
||||
}
|
||||
|
||||
// update phone number format
|
||||
PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
|
||||
try {
|
||||
String phone = payload.getPhone();
|
||||
Phonenumber.PhoneNumber zimProto = phoneUtil.parse(phone, "ZW");
|
||||
phone = zimProto.getCountryCode() + String.valueOf(zimProto.getNationalNumber());
|
||||
payload.setPhone(phone.replaceAll("[^0-9]", ""));
|
||||
} catch (NumberParseException e) {
|
||||
System.err.println("NumberParseException was thrown: " + e.toString());
|
||||
return NextAction.moveToState(FAILED, "Invalid phone number");
|
||||
}
|
||||
payload.setPhone(Utils.formatPhoneNumber(payload.getPhone(), "ZW"));
|
||||
|
||||
execution.setVariable("registerRequest", payload);
|
||||
execution.setVariable("body", payload);
|
||||
|
||||
@@ -77,24 +77,11 @@ public class CleanupHandler implements HandlerInterface {
|
||||
PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
|
||||
|
||||
if(debitPhoneNumber != null && !debitPhoneNumber.isEmpty()){
|
||||
try {
|
||||
// phone must begin with '+'
|
||||
Phonenumber.PhoneNumber zimPhoneNumber = phoneUtil.parse(debitPhoneNumber, "");
|
||||
String formattedPhoneNumber = phoneUtil.format(zimPhoneNumber, PhoneNumberUtil.PhoneNumberFormat.E164);
|
||||
transaction.setDebitPhone(formattedPhoneNumber.replaceAll("[^0-9]", ""));
|
||||
} catch (NumberParseException e) {
|
||||
System.err.println("NumberParseException was thrown: " + e.toString());
|
||||
}
|
||||
transaction.setDebitPhone(Utils.formatPhoneNumber(debitPhoneNumber, ""));
|
||||
}
|
||||
|
||||
if(creditPhoneNumber != null && !creditPhoneNumber.isEmpty()){
|
||||
try {
|
||||
Phonenumber.PhoneNumber zimPhoneNumber = phoneUtil.parse(creditPhoneNumber, "ZW");
|
||||
String formattedPhoneNumber = phoneUtil.format(zimPhoneNumber, PhoneNumberUtil.PhoneNumberFormat.E164);
|
||||
transaction.setCreditPhone(formattedPhoneNumber.replaceAll("[^0-9]", ""));
|
||||
} catch (NumberParseException e) {
|
||||
System.err.println("NumberParseException was thrown: " + e.toString());
|
||||
}
|
||||
transaction.setDebitPhone(Utils.formatPhoneNumber(creditPhoneNumber, "ZW"));
|
||||
}
|
||||
|
||||
return transaction;
|
||||
|
||||
@@ -24,7 +24,7 @@ public class RecipientsUpdateHandler implements HandlerInterface {
|
||||
List<Recipient> recipients = recipientService.getRecipient(transaction.getCreditAccount(), transaction.getUserId());
|
||||
|
||||
Recipient recipient = null;
|
||||
if(recipients.size() > 0){
|
||||
if(!recipients.isEmpty()){
|
||||
recipient = recipients.get(0);
|
||||
}else{
|
||||
recipient = new Recipient();
|
||||
|
||||
Reference in New Issue
Block a user