velocity integration complete
This commit is contained in:
@@ -1,6 +1,11 @@
|
|||||||
package zw.qantra.tm.domain.controllers;
|
package zw.qantra.tm.domain.controllers;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
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.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import zw.qantra.tm.domain.models.Provider;
|
import zw.qantra.tm.domain.models.Provider;
|
||||||
@@ -16,8 +21,11 @@ public class ProviderController {
|
|||||||
private final ProviderService providerService;
|
private final ProviderService providerService;
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity getProviders(@RequestParam(required = false) String category) {
|
public ResponseEntity getProviders(
|
||||||
return ResponseEntity.ok(providerService.getProvidersByCategory(category));
|
@And({
|
||||||
|
@Spec(path = "category", spec = Equal.class)
|
||||||
|
}) Specification<Provider> spec, Pageable pageable) {
|
||||||
|
return ResponseEntity.ok(providerService.findAll(spec, pageable));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}/products")
|
@GetMapping("/{id}/products")
|
||||||
|
|||||||
@@ -30,4 +30,5 @@ public class Provider extends BaseEntity {
|
|||||||
private double percentageCommission;
|
private double percentageCommission;
|
||||||
@Column(columnDefinition = "TEXT")
|
@Column(columnDefinition = "TEXT")
|
||||||
private String meta;
|
private String meta;
|
||||||
|
private Integer priority;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package zw.qantra.tm.domain.repositories;
|
package zw.qantra.tm.domain.repositories;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
import zw.qantra.tm.domain.models.Provider;
|
import zw.qantra.tm.domain.models.Provider;
|
||||||
|
|
||||||
@@ -8,7 +9,7 @@ import java.util.List;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface ProviderRepository extends JpaRepository<Provider, UUID> {
|
public interface ProviderRepository extends JpaRepository<Provider, UUID>, JpaSpecificationExecutor<Provider> {
|
||||||
List<Provider> findByCategory(String category);
|
List<Provider> findByCategory(String category);
|
||||||
Provider findByClientId(String clientId);
|
Provider findByClientId(String clientId);
|
||||||
boolean existsByClientId(String clientId);
|
boolean existsByClientId(String clientId);
|
||||||
|
|||||||
@@ -26,31 +26,62 @@ public class HotRechargeTokenService {
|
|||||||
private String hotAccessCode;
|
private String hotAccessCode;
|
||||||
@Value("${hot.password}")
|
@Value("${hot.password}")
|
||||||
private String hotPassword;
|
private String hotPassword;
|
||||||
@Value("${hot.token.expiry.minutes:25}")
|
@Value("${hot.cache.duration.minutes:5}")
|
||||||
private int tokenExpiryMinutes;
|
private int cacheDurationMinutes;
|
||||||
|
|
||||||
private String cachedToken;
|
private String cachedToken;
|
||||||
private long tokenExpiryTime;
|
private String cachedRefreshToken;
|
||||||
|
private long cacheExpiryTime;
|
||||||
|
|
||||||
public synchronized String getToken() {
|
public synchronized String getToken() {
|
||||||
if (isTokenValid()) {
|
if (isCacheValid()) {
|
||||||
logger.info("Returning cached HotRecharge token");
|
logger.info("Cache is valid, attempting token refresh");
|
||||||
return cachedToken;
|
return refreshCachedToken();
|
||||||
}
|
}
|
||||||
return fetchNewToken();
|
return fetchNewToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isTokenValid() {
|
private boolean isCacheValid() {
|
||||||
return cachedToken != null && System.currentTimeMillis() < tokenExpiryTime;
|
return cachedToken != null && cachedRefreshToken != null && System.currentTimeMillis() < cacheExpiryTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized String fetchNewToken() {
|
private synchronized String refreshCachedToken() {
|
||||||
if (isTokenValid()) {
|
if (!isCacheValid()) {
|
||||||
return cachedToken;
|
return fetchNewToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||||
headers.setAccept(Collections.singletonList(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);
|
LinkedHashMap<String, Object> response = restService.postAs(hotAuthUrl + "/identity/login", payload, headers, LinkedHashMap.class);
|
||||||
cachedToken = (String) response.get("token");
|
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;
|
return cachedToken;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Failed to fetch HotRecharge token: " + e.getMessage(), e);
|
logger.error("Failed to fetch HotRecharge token: " + e.getMessage(), e);
|
||||||
cachedToken = null;
|
cachedToken = null;
|
||||||
|
cachedRefreshToken = null;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,7 +109,7 @@ public class HotRechargeTokenService {
|
|||||||
public synchronized void invalidateToken() {
|
public synchronized void invalidateToken() {
|
||||||
logger.info("Invalidating cached HotRecharge token");
|
logger.info("Invalidating cached HotRecharge token");
|
||||||
cachedToken = null;
|
cachedToken = null;
|
||||||
tokenExpiryTime = 0;
|
cachedRefreshToken = null;
|
||||||
|
cacheExpiryTime = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.cache.annotation.Cacheable;
|
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.HttpHeaders;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -164,16 +166,8 @@ public class ProviderService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object getProvidersByCategory(String category) {
|
public Object findAll(Specification<Provider> specification, Pageable pageable) {
|
||||||
|
return getProviderRepository().findAll(specification, pageable);
|
||||||
List<Provider> existingProviders;
|
|
||||||
if (category != null && !category.isEmpty()) {
|
|
||||||
existingProviders = getProviderRepository().findByCategory(category);
|
|
||||||
} else {
|
|
||||||
existingProviders = getProviderRepository().findAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
return existingProviders;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object getProviders(List<Provider> existingProviders) {
|
public Object getProviders(List<Provider> existingProviders) {
|
||||||
|
|||||||
@@ -181,7 +181,10 @@ public class VelocityPaymentService {
|
|||||||
transaction.setTargetUrl(redirectUrl);
|
transaction.setTargetUrl(redirectUrl);
|
||||||
return transaction;
|
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) {
|
} catch (Exception e) {
|
||||||
log.error("Error initiating payment: {}", e.getMessage(), 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.AdditionalDataService;
|
||||||
import zw.qantra.tm.domain.services.HotRechargeBalanceService;
|
import zw.qantra.tm.domain.services.HotRechargeBalanceService;
|
||||||
import zw.qantra.tm.domain.services.HotRechargeTokenService;
|
import zw.qantra.tm.domain.services.HotRechargeTokenService;
|
||||||
|
import zw.qantra.tm.domain.services.SettingService;
|
||||||
import zw.qantra.tm.rest.RestService;
|
import zw.qantra.tm.rest.RestService;
|
||||||
|
|
||||||
|
|
||||||
@@ -13,7 +14,8 @@ public class EconetBundlesConfirmationHotProcessor extends NetoneConfirmationHot
|
|||||||
AdditionalDataService additionalDataService,
|
AdditionalDataService additionalDataService,
|
||||||
HotRechargeTokenService hotRechargeTokenService,
|
HotRechargeTokenService hotRechargeTokenService,
|
||||||
HotRechargeBalanceService hotRechargeBalanceService,
|
HotRechargeBalanceService hotRechargeBalanceService,
|
||||||
RestService restService) {
|
RestService restService,
|
||||||
super(additionalDataService, hotRechargeTokenService, hotRechargeBalanceService, 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.AdditionalDataService;
|
||||||
import zw.qantra.tm.domain.services.HotRechargeBalanceService;
|
import zw.qantra.tm.domain.services.HotRechargeBalanceService;
|
||||||
import zw.qantra.tm.domain.services.HotRechargeTokenService;
|
import zw.qantra.tm.domain.services.HotRechargeTokenService;
|
||||||
|
import zw.qantra.tm.domain.services.SettingService;
|
||||||
import zw.qantra.tm.rest.RestService;
|
import zw.qantra.tm.rest.RestService;
|
||||||
|
|
||||||
|
|
||||||
@@ -13,7 +14,8 @@ public class EconetConfirmationHotProcessor extends NetoneConfirmationHotProcess
|
|||||||
AdditionalDataService additionalDataService,
|
AdditionalDataService additionalDataService,
|
||||||
HotRechargeTokenService hotRechargeTokenService,
|
HotRechargeTokenService hotRechargeTokenService,
|
||||||
HotRechargeBalanceService hotRechargeBalanceService,
|
HotRechargeBalanceService hotRechargeBalanceService,
|
||||||
RestService restService) {
|
RestService restService,
|
||||||
super(additionalDataService, hotRechargeTokenService, hotRechargeBalanceService, 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.AdditionalDataService;
|
||||||
import zw.qantra.tm.domain.services.HotRechargeBalanceService;
|
import zw.qantra.tm.domain.services.HotRechargeBalanceService;
|
||||||
import zw.qantra.tm.domain.services.HotRechargeTokenService;
|
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.domain.services.processors.TransactionProcessorInterface;
|
||||||
import zw.qantra.tm.rest.RestService;
|
import zw.qantra.tm.rest.RestService;
|
||||||
import zw.qantra.tm.utils.Utils;
|
import zw.qantra.tm.utils.Utils;
|
||||||
@@ -28,6 +29,7 @@ public class NetoneConfirmationHotProcessor implements TransactionProcessorInter
|
|||||||
protected final HotRechargeTokenService hotRechargeTokenService;
|
protected final HotRechargeTokenService hotRechargeTokenService;
|
||||||
protected final HotRechargeBalanceService hotRechargeBalanceService;
|
protected final HotRechargeBalanceService hotRechargeBalanceService;
|
||||||
protected final RestService restService;
|
protected final RestService restService;
|
||||||
|
protected final SettingService settingService;
|
||||||
|
|
||||||
@Value("${hot.api.url:https://ssl.hot.co.zw/api/v3}")
|
@Value("${hot.api.url:https://ssl.hot.co.zw/api/v3}")
|
||||||
private String hotApiUrl;
|
private String hotApiUrl;
|
||||||
@@ -36,6 +38,8 @@ public class NetoneConfirmationHotProcessor implements TransactionProcessorInter
|
|||||||
@Override
|
@Override
|
||||||
public Object process(Transaction transaction) throws Exception {
|
public Object process(Transaction transaction) throws Exception {
|
||||||
|
|
||||||
|
// 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();
|
String token = hotRechargeTokenService.getToken();
|
||||||
if (!hotRechargeBalanceService.checkBalance(token, transaction.getAmount())) {
|
if (!hotRechargeBalanceService.checkBalance(token, transaction.getAmount())) {
|
||||||
transaction.setStatus(Status.FAILED);
|
transaction.setStatus(Status.FAILED);
|
||||||
@@ -43,7 +47,9 @@ public class NetoneConfirmationHotProcessor implements TransactionProcessorInter
|
|||||||
transaction.setErrorMessage("There's a technical issue on our end. Please try again later.");
|
transaction.setErrorMessage("There's a technical issue on our end. Please try again later.");
|
||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.setCreditAccount(Utils.formatPhoneNumber(transaction.getCreditAccount(), "ZW"));
|
||||||
transaction.setStatus(Status.SUCCESS);
|
transaction.setStatus(Status.SUCCESS);
|
||||||
transaction.setResponseCode("00");
|
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.AdditionalData;
|
||||||
import zw.qantra.tm.domain.models.Provider;
|
import zw.qantra.tm.domain.models.Provider;
|
||||||
import zw.qantra.tm.domain.models.Transaction;
|
import zw.qantra.tm.domain.models.Transaction;
|
||||||
import zw.qantra.tm.domain.services.AdditionalDataService;
|
import zw.qantra.tm.domain.services.*;
|
||||||
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.processors.TransactionProcessorInterface;
|
import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface;
|
||||||
import zw.qantra.tm.exceptions.ApiException;
|
import zw.qantra.tm.exceptions.ApiException;
|
||||||
import zw.qantra.tm.rest.RestService;
|
import zw.qantra.tm.rest.RestService;
|
||||||
@@ -35,6 +32,7 @@ public class ZesaConfirmationHotProcessor implements TransactionProcessorInterfa
|
|||||||
private final ProviderService providerService;
|
private final ProviderService providerService;
|
||||||
private final HotRechargeTokenService hotRechargeTokenService;
|
private final HotRechargeTokenService hotRechargeTokenService;
|
||||||
private final HotRechargeBalanceService hotRechargeBalanceService;
|
private final HotRechargeBalanceService hotRechargeBalanceService;
|
||||||
|
private final SettingService settingService;
|
||||||
|
|
||||||
@Value("${hot.api.url:https://ssl.hot.co.zw/api/v3}")
|
@Value("${hot.api.url:https://ssl.hot.co.zw/api/v3}")
|
||||||
private String hotApiUrl;
|
private String hotApiUrl;
|
||||||
@@ -44,6 +42,13 @@ public class ZesaConfirmationHotProcessor implements TransactionProcessorInterfa
|
|||||||
logger.info("Processing ZESA confirmation from HotRecharge");
|
logger.info("Processing ZESA confirmation from HotRecharge");
|
||||||
|
|
||||||
try {
|
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();
|
String token = hotRechargeTokenService.getToken();
|
||||||
if (token == null || token.isEmpty()) {
|
if (token == null || token.isEmpty()) {
|
||||||
transaction.setStatus(Status.FAILED);
|
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;
|
package zw.qantra.tm.domain.services.processors.payments;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.RandomStringUtils;
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import zw.qantra.tm.domain.dtos.SdkActionDto;
|
|
||||||
import zw.qantra.tm.domain.enums.Status;
|
import zw.qantra.tm.domain.enums.Status;
|
||||||
import zw.qantra.tm.domain.models.Transaction;
|
import zw.qantra.tm.domain.models.Transaction;
|
||||||
import zw.qantra.tm.domain.services.SettingService;
|
import zw.qantra.tm.domain.services.SettingService;
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import zw.qantra.tm.exceptions.AlreadyExistsException;
|
|||||||
import zw.qantra.tm.rest.RestService;
|
import zw.qantra.tm.rest.RestService;
|
||||||
|
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import zw.qantra.tm.utils.Utils;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -99,16 +100,7 @@ public class RegistrationWorkflow extends WorkflowDefinition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// update phone number format
|
// update phone number format
|
||||||
PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
|
payload.setPhone(Utils.formatPhoneNumber(payload.getPhone(), "ZW"));
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
execution.setVariable("registerRequest", payload);
|
execution.setVariable("registerRequest", payload);
|
||||||
execution.setVariable("body", payload);
|
execution.setVariable("body", payload);
|
||||||
|
|||||||
@@ -77,24 +77,11 @@ public class CleanupHandler implements HandlerInterface {
|
|||||||
PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
|
PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
|
||||||
|
|
||||||
if(debitPhoneNumber != null && !debitPhoneNumber.isEmpty()){
|
if(debitPhoneNumber != null && !debitPhoneNumber.isEmpty()){
|
||||||
try {
|
transaction.setDebitPhone(Utils.formatPhoneNumber(debitPhoneNumber, ""));
|
||||||
// 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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(creditPhoneNumber != null && !creditPhoneNumber.isEmpty()){
|
if(creditPhoneNumber != null && !creditPhoneNumber.isEmpty()){
|
||||||
try {
|
transaction.setDebitPhone(Utils.formatPhoneNumber(creditPhoneNumber, "ZW"));
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return transaction;
|
return transaction;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public class RecipientsUpdateHandler implements HandlerInterface {
|
|||||||
List<Recipient> recipients = recipientService.getRecipient(transaction.getCreditAccount(), transaction.getUserId());
|
List<Recipient> recipients = recipientService.getRecipient(transaction.getCreditAccount(), transaction.getUserId());
|
||||||
|
|
||||||
Recipient recipient = null;
|
Recipient recipient = null;
|
||||||
if(recipients.size() > 0){
|
if(!recipients.isEmpty()){
|
||||||
recipient = recipients.get(0);
|
recipient = recipients.get(0);
|
||||||
}else{
|
}else{
|
||||||
recipient = new Recipient();
|
recipient = new Recipient();
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
package zw.qantra.tm.rest;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.GsonBuilder;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.json.JSONArray;
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.HttpRequest;
|
|
||||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
|
||||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
|
||||||
import org.springframework.http.client.ClientHttpResponse;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class RestTemplateInterceptor
|
|
||||||
implements ClientHttpRequestInterceptor {
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(getClass().getName());
|
|
||||||
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ClientHttpResponse intercept(
|
|
||||||
HttpRequest request,
|
|
||||||
byte[] reqBody,
|
|
||||||
ClientHttpRequestExecution execution) throws IOException {
|
|
||||||
String requestBody = new String(reqBody, StandardCharsets.UTF_8);
|
|
||||||
if(!isJSONValid(requestBody)) requestBody = toBasicJson(requestBody);
|
|
||||||
|
|
||||||
logger.info("Request Headers: {}", request.getHeaders().toString());
|
|
||||||
|
|
||||||
// we don't log file data
|
|
||||||
if(request.getHeaders().getContentLength() < 10000){
|
|
||||||
logger.info("Request: {}: {}\n{}",
|
|
||||||
request.getMethod(),
|
|
||||||
request.getURI(),
|
|
||||||
gson.toJson(gson.fromJson(requestBody, HashMap.class)));
|
|
||||||
}
|
|
||||||
ClientHttpResponse response = execution.execute(request, reqBody);
|
|
||||||
InputStreamReader isr = new InputStreamReader(response.getBody());
|
|
||||||
String body = new BufferedReader(isr).lines()
|
|
||||||
.collect(Collectors.joining("\n"));
|
|
||||||
try{
|
|
||||||
logger.info("Response body:\n{}", gson.toJson(gson.fromJson(body, HashMap.class)));
|
|
||||||
}catch (Exception exception){
|
|
||||||
logger.info("Problem serializing resp: {}", exception.getMessage());
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toBasicJson(String string){
|
|
||||||
HashMap map = new HashMap();
|
|
||||||
map.put("log", string);
|
|
||||||
return gson.toJson(map);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isJSONValid(String test) {
|
|
||||||
try {
|
|
||||||
new JSONObject(test);
|
|
||||||
} catch (JSONException ex) {
|
|
||||||
try {
|
|
||||||
new JSONArray(test);
|
|
||||||
} catch (JSONException ex1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -36,9 +36,11 @@ public class ProviderSeeder implements Seeder {
|
|||||||
.integrationProcessorLabel("HOT")
|
.integrationProcessorLabel("HOT")
|
||||||
.accountFieldName("Phone Number")
|
.accountFieldName("Phone Number")
|
||||||
.percentageCommission(5)
|
.percentageCommission(5)
|
||||||
.requiresAccount(true)
|
.requiresAccount(false)
|
||||||
.requiresAmount(true)
|
.requiresAmount(true)
|
||||||
.requiresPhone(true)
|
.requiresPhone(true)
|
||||||
|
.priority(2)
|
||||||
|
.meta("{\"hotProductId\": \"101\"}")
|
||||||
.uid(Utils.getUid())
|
.uid(Utils.getUid())
|
||||||
.build(),
|
.build(),
|
||||||
Provider.builder()
|
Provider.builder()
|
||||||
@@ -52,10 +54,12 @@ public class ProviderSeeder implements Seeder {
|
|||||||
.integrationProcessorLabel("HOT")
|
.integrationProcessorLabel("HOT")
|
||||||
.accountFieldName("Phone Number")
|
.accountFieldName("Phone Number")
|
||||||
.percentageCommission(5)
|
.percentageCommission(5)
|
||||||
.requiresAccount(true)
|
.requiresAccount(false)
|
||||||
.requiresAmount(true)
|
.requiresAmount(true)
|
||||||
.requiresPhone(true)
|
.requiresPhone(true)
|
||||||
.uid(Utils.getUid())
|
.uid(Utils.getUid())
|
||||||
|
.priority(3)
|
||||||
|
.meta("{\"hotProductId\": \"35\"}")
|
||||||
.build(),
|
.build(),
|
||||||
Provider.builder()
|
Provider.builder()
|
||||||
.name("Zesa Prepaid")
|
.name("Zesa Prepaid")
|
||||||
@@ -71,6 +75,8 @@ public class ProviderSeeder implements Seeder {
|
|||||||
.requiresAccount(true)
|
.requiresAccount(true)
|
||||||
.requiresAmount(true)
|
.requiresAmount(true)
|
||||||
.requiresPhone(true)
|
.requiresPhone(true)
|
||||||
|
.priority(1)
|
||||||
|
.meta("{\"hotProductId\": \"41\"}")
|
||||||
.uid(Utils.getUid())
|
.uid(Utils.getUid())
|
||||||
.build(),
|
.build(),
|
||||||
Provider.builder()
|
Provider.builder()
|
||||||
@@ -84,9 +90,11 @@ public class ProviderSeeder implements Seeder {
|
|||||||
.integrationProcessorLabel("HOT")
|
.integrationProcessorLabel("HOT")
|
||||||
.accountFieldName("Phone Number")
|
.accountFieldName("Phone Number")
|
||||||
.percentageCommission(5)
|
.percentageCommission(5)
|
||||||
.requiresAccount(true)
|
.requiresAccount(false)
|
||||||
.requiresAmount(false)
|
.requiresAmount(false)
|
||||||
.requiresPhone(true)
|
.requiresPhone(true)
|
||||||
|
.priority(4)
|
||||||
|
.meta("{\"hotProductId\": \"111\"}")
|
||||||
.uid(Utils.getUid())
|
.uid(Utils.getUid())
|
||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,12 +7,31 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
import com.fasterxml.jackson.databind.util.StdDateFormat;
|
import com.fasterxml.jackson.databind.util.StdDateFormat;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
|
import com.google.i18n.phonenumbers.NumberParseException;
|
||||||
|
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||||
|
import com.google.i18n.phonenumbers.Phonenumber;
|
||||||
|
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class Utils {
|
public class Utils {
|
||||||
|
|
||||||
|
public static String formatPhoneNumber(String phoneNumber, String region) {
|
||||||
|
if (phoneNumber == null || phoneNumber.isEmpty()) {
|
||||||
|
return phoneNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
|
||||||
|
try {
|
||||||
|
Phonenumber.PhoneNumber numberProto = phoneUtil.parse(phoneNumber, region);
|
||||||
|
return phoneUtil.format(numberProto, PhoneNumberUtil.PhoneNumberFormat.E164)
|
||||||
|
.replaceAll("[^0-9]", "");
|
||||||
|
} catch (NumberParseException e) {
|
||||||
|
return phoneNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static <T> T deepCopy(Object object, Class<T> clazz) {
|
public static <T> T deepCopy(Object object, Class<T> clazz) {
|
||||||
String json = toJson(object);
|
String json = toJson(object);
|
||||||
return fromJson(json, clazz);
|
return fromJson(json, clazz);
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ jwt.secret=your-super-secret-jwt-key-here-make-it-very-long-and-secure-in-produc
|
|||||||
jwt.expiration=86400000
|
jwt.expiration=86400000
|
||||||
|
|
||||||
logging.level.root=WARN
|
logging.level.root=WARN
|
||||||
spring.datasource.nflow.url=jdbc:postgresql://62.171.179.108:5432/nflow
|
spring.datasource.nflow.url=jdbc:postgresql://62.171.179.108:5432/qpay-nflow
|
||||||
spring.datasource.nflow.username=postgres
|
spring.datasource.nflow.username=postgres
|
||||||
spring.datasource.nflow.password=zdDZMzq6F4B4L1IUla
|
spring.datasource.nflow.password=zdDZMzq6F4B4L1IUla
|
||||||
spring.main.allow-bean-definition-overriding=true
|
spring.main.allow-bean-definition-overriding=true
|
||||||
@@ -64,7 +64,7 @@ hot.api.url=https://ssl.hot.co.zw/api/v3
|
|||||||
hot.auth.url=https://ssl.hot.co.zw/api/v3/identity/login
|
hot.auth.url=https://ssl.hot.co.zw/api/v3/identity/login
|
||||||
hot.access.code=vusumuzi@qantra.co.zw
|
hot.access.code=vusumuzi@qantra.co.zw
|
||||||
hot.password=Stikbanch@50
|
hot.password=Stikbanch@50
|
||||||
hot.token.expiry.minutes=25
|
hot.cache.duration.minutes=5
|
||||||
|
|
||||||
velocity.api.base-url=https://api.velocityafrica.net
|
velocity.api.base-url=https://api.velocityafrica.net
|
||||||
velocity.api.api-key=cHE08ADmiATtf8VYgA59-wKVKVKj2WusKDAaaRLDsG8
|
velocity.api.api-key=cHE08ADmiATtf8VYgA59-wKVKVKj2WusKDAaaRLDsG8
|
||||||
|
|||||||
@@ -66,4 +66,15 @@
|
|||||||
<changeSet author="khoza (generated)" id="1780061546055-16">
|
<changeSet author="khoza (generated)" id="1780061546055-16">
|
||||||
<addUniqueConstraint columnNames="username" constraintName="UC_USERSUSERNAME_COL" tableName="users"/>
|
<addUniqueConstraint columnNames="username" constraintName="UC_USERSUSERNAME_COL" tableName="users"/>
|
||||||
</changeSet>
|
</changeSet>
|
||||||
|
<changeSet author="khoza (generated)" id="1780062553770-3">
|
||||||
|
<addColumn tableName="provider">
|
||||||
|
<column name="priority" type="integer"/>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
<changeSet author="khoza (generated)" id="1780062553770-1">
|
||||||
|
<dropUniqueConstraint constraintName="UC_USERSUSERNAME_COL" tableName="users"/>
|
||||||
|
</changeSet>
|
||||||
|
<changeSet author="khoza (generated)" id="1780062553770-2">
|
||||||
|
<addUniqueConstraint columnNames="username" constraintName="UC_USERSUSERNAME_COL" tableName="users"/>
|
||||||
|
</changeSet>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|||||||
Reference in New Issue
Block a user