initial integration done
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package zw.qantra.tm.domain.models;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import lombok.*;
|
||||
import org.hibernate.annotations.SQLRestriction;
|
||||
@@ -22,4 +23,6 @@ public class Provider extends BaseEntity {
|
||||
private String accountFieldName;
|
||||
private String integrationProcessorLabel;
|
||||
private double percentageCommission;
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String meta;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
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.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Service;
|
||||
import zw.qantra.tm.rest.RestService;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class HotRechargeTokenService {
|
||||
Logger logger = LoggerFactory.getLogger(HotRechargeTokenService.class);
|
||||
|
||||
private final RestService restService;
|
||||
|
||||
@Value("${hot.api.url:https://ssl.hot.co.zw/api/v3}")
|
||||
private String hotAuthUrl;
|
||||
@Value("${hot.access.code}")
|
||||
private String hotAccessCode;
|
||||
@Value("${hot.password}")
|
||||
private String hotPassword;
|
||||
@Value("${hot.token.expiry.minutes:30}")
|
||||
private int tokenExpiryMinutes;
|
||||
|
||||
private String cachedToken;
|
||||
private long tokenExpiryTime;
|
||||
|
||||
public synchronized String getToken() {
|
||||
if (isTokenValid()) {
|
||||
logger.info("Returning cached HotRecharge token");
|
||||
return cachedToken;
|
||||
}
|
||||
return fetchNewToken();
|
||||
}
|
||||
|
||||
private boolean isTokenValid() {
|
||||
return cachedToken != null && System.currentTimeMillis() < tokenExpiryTime;
|
||||
}
|
||||
|
||||
private synchronized String fetchNewToken() {
|
||||
if (isTokenValid()) {
|
||||
return cachedToken;
|
||||
}
|
||||
|
||||
try {
|
||||
logger.info("Fetching new HotRecharge token");
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
|
||||
|
||||
Map<String, String> payload = Map.of(
|
||||
"AccessCode", hotAccessCode,
|
||||
"Password", hotPassword
|
||||
);
|
||||
|
||||
LinkedHashMap<String, Object> response = restService.postAs(hotAuthUrl + "/identity/login", payload, headers, LinkedHashMap.class);
|
||||
cachedToken = (String) response.get("token");
|
||||
tokenExpiryTime = System.currentTimeMillis() + (tokenExpiryMinutes * 60 * 1000L);
|
||||
|
||||
logger.info("New HotRecharge token cached, expires in {} minutes", tokenExpiryMinutes);
|
||||
return cachedToken;
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to fetch HotRecharge token: " + e.getMessage(), e);
|
||||
cachedToken = null;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void invalidateToken() {
|
||||
logger.info("Invalidating cached HotRecharge token");
|
||||
cachedToken = null;
|
||||
tokenExpiryTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
package zw.qantra.tm.domain.services.processors.confirmations;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import zw.qantra.tm.domain.dtos.BillPaymentDto;
|
||||
import zw.qantra.tm.domain.enums.RequestType;
|
||||
import zw.qantra.tm.domain.enums.Status;
|
||||
import zw.qantra.tm.domain.models.AdditionalData;
|
||||
import zw.qantra.tm.domain.models.Transaction;
|
||||
import zw.qantra.tm.domain.services.AdditionalDataService;
|
||||
import zw.qantra.tm.domain.services.TransactionService;
|
||||
import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface;
|
||||
import zw.qantra.tm.rest.RestService;
|
||||
import zw.qantra.tm.utils.Utils;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
@Service("CONFIRM_ECONET_BROADBAND")
|
||||
public class EconetBroadbandConfirmationProcessor extends EconetConfirmationProcessor{
|
||||
public EconetBroadbandConfirmationProcessor(RestService restService, TransactionService transactionService, AdditionalDataService additionalDataService) {
|
||||
super(restService, transactionService, additionalDataService);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package zw.qantra.tm.domain.services.processors.confirmations;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import zw.qantra.tm.domain.services.AdditionalDataService;
|
||||
import zw.qantra.tm.domain.services.TransactionService;
|
||||
import zw.qantra.tm.rest.RestService;
|
||||
|
||||
@Service("CONFIRM_NETONE")
|
||||
public class NetoneConfirmationProcessor extends EconetConfirmationProcessor{
|
||||
public NetoneConfirmationProcessor(RestService restService, TransactionService transactionService, AdditionalDataService additionalDataService) {
|
||||
super(restService, transactionService, additionalDataService);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package zw.qantra.tm.domain.services.processors.confirmations.hotrecharge;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import zw.qantra.tm.domain.enums.Status;
|
||||
import zw.qantra.tm.domain.models.Transaction;
|
||||
import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface;
|
||||
|
||||
|
||||
@Service("CONFIRM_ECONET_BUNDLES_HOT")
|
||||
@RequiredArgsConstructor
|
||||
public class EconetBundlesConfirmationHotProcessor implements TransactionProcessorInterface {
|
||||
@Override
|
||||
public Object process(Transaction transaction) throws Exception {
|
||||
transaction.setStatus(Status.SUCCESS);
|
||||
transaction.setResponseCode("00");
|
||||
return transaction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object reverse(Transaction transaction) {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object poll(Transaction transaction) throws Exception {
|
||||
return transaction;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package zw.qantra.tm.domain.services.processors.confirmations.hotrecharge;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import zw.qantra.tm.domain.enums.Status;
|
||||
import zw.qantra.tm.domain.models.Transaction;
|
||||
import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface;
|
||||
|
||||
|
||||
@Service("CONFIRM_ECONET_HOT")
|
||||
@RequiredArgsConstructor
|
||||
public class EconetConfirmationHotProcessor implements TransactionProcessorInterface {
|
||||
@Override
|
||||
public Object process(Transaction transaction) throws Exception {
|
||||
transaction.setStatus(Status.SUCCESS);
|
||||
transaction.setResponseCode("00");
|
||||
return transaction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object reverse(Transaction transaction) {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object poll(Transaction transaction) throws Exception {
|
||||
return transaction;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package zw.qantra.tm.domain.services.processors.confirmations.hotrecharge;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import zw.qantra.tm.domain.enums.Status;
|
||||
import zw.qantra.tm.domain.models.Transaction;
|
||||
import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface;
|
||||
|
||||
|
||||
@Service("CONFIRM_NETONE_HOT")
|
||||
@RequiredArgsConstructor
|
||||
public class NetoneConfirmationHotProcessor implements TransactionProcessorInterface {
|
||||
@Override
|
||||
public Object process(Transaction transaction) throws Exception {
|
||||
transaction.setStatus(Status.SUCCESS);
|
||||
transaction.setResponseCode("00");
|
||||
return transaction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object reverse(Transaction transaction) {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object poll(Transaction transaction) throws Exception {
|
||||
return transaction;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package zw.qantra.tm.domain.services.processors.confirmations.hotrecharge;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import zw.qantra.tm.domain.enums.RequestType;
|
||||
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.HotRechargeTokenService;
|
||||
import zw.qantra.tm.domain.services.ProviderService;
|
||||
import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface;
|
||||
import zw.qantra.tm.exceptions.ApiException;
|
||||
import zw.qantra.tm.rest.RestService;
|
||||
import zw.qantra.tm.utils.Utils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
@Service("CONFIRM_ZESA_HOT")
|
||||
@RequiredArgsConstructor
|
||||
public class ZesaConfirmationHotProcessor implements TransactionProcessorInterface {
|
||||
Logger logger = LoggerFactory.getLogger(ZesaConfirmationHotProcessor.class);
|
||||
|
||||
private final RestService restService;
|
||||
private final AdditionalDataService additionalDataService;
|
||||
private final ProviderService providerService;
|
||||
private final HotRechargeTokenService hotRechargeTokenService;
|
||||
|
||||
@Value("${hot.api.url:https://ssl.hot.co.zw/api/v3}")
|
||||
private String hotApiUrl;
|
||||
|
||||
@Override
|
||||
public Object process(Transaction transaction) throws Exception {
|
||||
logger.info("Processing ZESA confirmation from HotRecharge");
|
||||
|
||||
try {
|
||||
String token = hotRechargeTokenService.getToken();
|
||||
if (token == null || token.isEmpty()) {
|
||||
transaction.setStatus(Status.FAILED);
|
||||
transaction.setResponseCode("91");
|
||||
transaction.setErrorMessage("Failed to authenticate with HotRecharge");
|
||||
return transaction;
|
||||
}
|
||||
|
||||
if (!checkBalance(token, transaction.getAmount())) {
|
||||
transaction.setStatus(Status.FAILED);
|
||||
transaction.setResponseCode("91");
|
||||
transaction.setErrorMessage("Insufficient balance");
|
||||
return transaction;
|
||||
}
|
||||
|
||||
Map<String, Object> customerData = checkCustomer(token, transaction);
|
||||
if (customerData == null) {
|
||||
transaction.setStatus(Status.FAILED);
|
||||
transaction.setResponseCode("91");
|
||||
transaction.setErrorMessage("Customer not found or invalid");
|
||||
return transaction;
|
||||
}
|
||||
|
||||
transaction.setStatus(Status.SUCCESS);
|
||||
transaction.setResponseCode("00");
|
||||
|
||||
List<Map<String, String>> additionalDataList = buildAdditionalData(customerData);
|
||||
transaction.setAdditionalData(additionalDataList);
|
||||
|
||||
AdditionalData additionalData = AdditionalData.builder()
|
||||
.transactionId(transaction.getId().toString())
|
||||
.requestType(RequestType.CONFIRM)
|
||||
.jsonString(Utils.toJson(additionalDataList))
|
||||
.build();
|
||||
additionalDataService.getAdditionalDataRepository().save(additionalData);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error processing ZESA confirmation: " + e.getMessage(), e);
|
||||
transaction.setStatus(Status.FAILED);
|
||||
transaction.setResponseCode("99");
|
||||
transaction.setErrorMessage("Processing error: " + e.getMessage());
|
||||
}
|
||||
|
||||
return transaction;
|
||||
}
|
||||
|
||||
private List<Map<String, String>> buildAdditionalData(Map<String, Object> customerData) {
|
||||
List<Map<String, String>> additionalDataList = new ArrayList<>();
|
||||
if (customerData != null) {
|
||||
additionalDataList.add(Map.of("key", "accountNumber",
|
||||
"value", customerData.getOrDefault("accountNumber", "").toString()));
|
||||
Map<String, Object> details = (Map<String, Object>) customerData.get("details");
|
||||
if (details != null) {
|
||||
for (Map.Entry<String, Object> entry : details.entrySet()) {
|
||||
additionalDataList.add(Map.of("key", entry.getKey(), "value", entry.getValue().toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return additionalDataList;
|
||||
}
|
||||
|
||||
private boolean checkBalance(String token, BigDecimal amount) {
|
||||
try {
|
||||
String balanceUrl = hotApiUrl + "/account/balance/4";
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setBearerAuth(token);
|
||||
|
||||
LinkedList responseEntity = restService.getAs(balanceUrl, headers, LinkedList.class);
|
||||
LinkedHashMap response = (LinkedHashMap) responseEntity.get(0);
|
||||
logger.info("Response: " + response);
|
||||
|
||||
if (response != null && response.containsKey("balance")) {
|
||||
Object balance = response.get("balance");
|
||||
logger.info("Current balance: " + balance);
|
||||
return (new BigDecimal(balance.toString())).compareTo(amount) > 0;
|
||||
}
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to check balance: " + e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> checkCustomer(String token, Transaction transaction) {
|
||||
try {
|
||||
Provider provider = providerService.getProviderRepository()
|
||||
.findByClientId(transaction.getBillClientId());
|
||||
if(provider == null){
|
||||
throw new ApiException("Provider not found for billClientId: " + transaction.getBillClientId());
|
||||
}
|
||||
Map meta = Utils.fromJson(provider.getMeta(), Map.class);
|
||||
String hotProductId = (String) meta.get("hotProductId");
|
||||
|
||||
String customerUrl = hotApiUrl + "/query/customer/" + hotProductId + "/" + transaction.getCreditAccount();
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setBearerAuth(token);
|
||||
|
||||
LinkedHashMap<String, Object> response = restService.getAs(customerUrl, headers, LinkedHashMap.class);
|
||||
if (response != null && response.containsKey("details")) {
|
||||
logger.info("Customer found: " + response.get("accountNumber"));
|
||||
return response;
|
||||
}
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to check customer: " + e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object reverse(Transaction transaction) {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object poll(Transaction transaction) throws Exception {
|
||||
return transaction;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package zw.qantra.tm.domain.services.processors.confirmations.steward;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import zw.qantra.tm.domain.services.AdditionalDataService;
|
||||
import zw.qantra.tm.domain.services.TransactionService;
|
||||
import zw.qantra.tm.rest.RestService;
|
||||
|
||||
@Service("CONFIRM_ECONET_BUNDLES_STEWARD")
|
||||
public class EconetBroadbandConfirmationStewardProcessor extends EconetConfirmationStewardProcessor {
|
||||
public EconetBroadbandConfirmationStewardProcessor(RestService restService, TransactionService transactionService, AdditionalDataService additionalDataService) {
|
||||
super(restService, transactionService, additionalDataService);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package zw.qantra.tm.domain.services.processors.confirmations;
|
||||
package zw.qantra.tm.domain.services.processors.confirmations.steward;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@@ -18,12 +18,11 @@ import zw.qantra.tm.rest.RestService;
|
||||
import zw.qantra.tm.utils.Utils;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service("CONFIRM_ECONET")
|
||||
@Service("CONFIRM_ECONET_STEWARD")
|
||||
@RequiredArgsConstructor
|
||||
public class EconetConfirmationProcessor implements TransactionProcessorInterface {
|
||||
Logger logger = LoggerFactory.getLogger(EconetConfirmationProcessor.class);
|
||||
public class EconetConfirmationStewardProcessor implements TransactionProcessorInterface {
|
||||
Logger logger = LoggerFactory.getLogger(EconetConfirmationStewardProcessor.class);
|
||||
|
||||
private final RestService restService;
|
||||
private final TransactionService transactionService;
|
||||
@@ -0,0 +1,13 @@
|
||||
package zw.qantra.tm.domain.services.processors.confirmations.steward;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import zw.qantra.tm.domain.services.AdditionalDataService;
|
||||
import zw.qantra.tm.domain.services.TransactionService;
|
||||
import zw.qantra.tm.rest.RestService;
|
||||
|
||||
@Service("CONFIRM_NETONE_STEWARD")
|
||||
public class NetoneConfirmationStewardStewardProcessor extends EconetConfirmationStewardProcessor {
|
||||
public NetoneConfirmationStewardStewardProcessor(RestService restService, TransactionService transactionService, AdditionalDataService additionalDataService) {
|
||||
super(restService, transactionService, additionalDataService);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package zw.qantra.tm.domain.services.processors.confirmations;
|
||||
package zw.qantra.tm.domain.services.processors.confirmations.steward;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@@ -19,10 +19,10 @@ import zw.qantra.tm.utils.Utils;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
@Service("CONFIRM_ZESA")
|
||||
@Service("CONFIRM_ZESA_STEWARD")
|
||||
@RequiredArgsConstructor
|
||||
public class ZesaConfirmationProcessor implements TransactionProcessorInterface {
|
||||
Logger logger = LoggerFactory.getLogger(ZesaConfirmationProcessor.class);
|
||||
public class ZesaConfirmationStewardProcessor implements TransactionProcessorInterface {
|
||||
Logger logger = LoggerFactory.getLogger(ZesaConfirmationStewardProcessor.class);
|
||||
|
||||
private final RestService restService;
|
||||
private final TransactionService transactionService;
|
||||
@@ -0,0 +1,245 @@
|
||||
package zw.qantra.tm.domain.services.processors.integrations;
|
||||
|
||||
import jakarta.transaction.Transactional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.client.ResourceAccessException;
|
||||
import zw.qantra.tm.domain.enums.RequestType;
|
||||
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.*;
|
||||
import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface;
|
||||
import zw.qantra.tm.exceptions.ApiException;
|
||||
import zw.qantra.tm.rest.RestService;
|
||||
import zw.qantra.tm.utils.Utils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
@Service("INTEGRATION_HOT")
|
||||
@RequiredArgsConstructor
|
||||
public class HotRechargeIntegrationProcessor implements TransactionProcessorInterface {
|
||||
Logger logger = LoggerFactory.getLogger(HotRechargeIntegrationProcessor.class);
|
||||
private final RestService restService;
|
||||
private final AdditionalDataService additionalDataService;
|
||||
private final SettingService settingService;
|
||||
private final HotRechargeTokenService hotRechargeTokenService;
|
||||
private final ProviderService providerService;
|
||||
|
||||
@Value("${hot.api.url:https://ssl.hot.co.zw/api/v3}")
|
||||
private String hotApiUrl;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Object process(Transaction transaction) throws Exception {
|
||||
|
||||
if(settingService.getBooleanSetting("SIMULATE_INTEGRATION_DELAY")){
|
||||
Thread.sleep(5000);
|
||||
}
|
||||
|
||||
if(settingService.getBooleanSetting("SIMULATE_INTEGRATION_FAILURE")){
|
||||
transaction.setStatus(Status.FAILED);
|
||||
transaction.setResponseCode("92");
|
||||
transaction.setErrorMessage("Simulated failure");
|
||||
return transaction;
|
||||
}
|
||||
|
||||
if(settingService.getBooleanSetting("SIMULATE_HOT_SUCCESS")){
|
||||
transaction.setStatus(Status.SUCCESS);
|
||||
transaction.setResponseCode("responseCode");
|
||||
transaction.setCreditRef("creditRef");
|
||||
transaction.setErrorMessage(null);
|
||||
return transaction;
|
||||
}
|
||||
|
||||
try {
|
||||
logger.info("Processing HotRecharge integration");
|
||||
String token = hotRechargeTokenService.getToken();
|
||||
|
||||
if (token == null || token.isEmpty()) {
|
||||
transaction.setStatus(Status.FAILED);
|
||||
transaction.setResponseCode("91");
|
||||
transaction.setErrorMessage("Failed to authenticate with HotRecharge");
|
||||
return transaction;
|
||||
}
|
||||
|
||||
if (!checkBalance(token, transaction.getAmount())) {
|
||||
transaction.setStatus(Status.FAILED);
|
||||
transaction.setResponseCode("91");
|
||||
transaction.setErrorMessage("Insufficient balance");
|
||||
return transaction;
|
||||
}
|
||||
|
||||
LinkedHashMap<String, Object> rechargeResponse = performRecharge(token, transaction);
|
||||
if (rechargeResponse == null) {
|
||||
transaction.setStatus(Status.FAILED);
|
||||
transaction.setResponseCode("99");
|
||||
transaction.setErrorMessage("Recharge request failed");
|
||||
return transaction;
|
||||
}
|
||||
|
||||
Boolean successful = (Boolean) rechargeResponse.get("successful");
|
||||
if (successful != null && successful) {
|
||||
transaction.setStatus(Status.SUCCESS);
|
||||
transaction.setResponseCode("00");
|
||||
transaction.setCreditRef(rechargeResponse.get("rechargeId").toString());
|
||||
transaction.setErrorMessage(null);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> rechargeData = (Map<String, Object>) rechargeResponse.get("rechargeData");
|
||||
if (rechargeData != null) {
|
||||
List<Map<String, String>> additionalDataList = buildAdditionalDataList(rechargeData);
|
||||
transaction.setAdditionalData(additionalDataList);
|
||||
|
||||
AdditionalData additionalData = AdditionalData.builder()
|
||||
.transactionId(transaction.getId().toString())
|
||||
.requestType(RequestType.INTEGRATION)
|
||||
.jsonString(Utils.toJson(additionalDataList))
|
||||
.build();
|
||||
additionalDataService.getAdditionalDataRepository().save(additionalData);
|
||||
}
|
||||
} else {
|
||||
transaction.setStatus(Status.FAILED);
|
||||
transaction.setResponseCode((String) rechargeResponse.getOrDefault("responseCode", "99"));
|
||||
transaction.setErrorMessage((String) rechargeResponse.getOrDefault("message", "Recharge failed"));
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error processing HotRecharge integration: " + e.getMessage(), e);
|
||||
transaction.setStatus(Status.FAILED);
|
||||
transaction.setResponseCode("99");
|
||||
transaction.setErrorMessage("Processing error: " + e.getMessage());
|
||||
}
|
||||
|
||||
return transaction;
|
||||
|
||||
}
|
||||
|
||||
private boolean checkBalance(String token, BigDecimal amount) {
|
||||
try {
|
||||
String balanceUrl = hotApiUrl + "/account/balance/4";
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setBearerAuth(token);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
LinkedList<LinkedHashMap<String, Object>> responseEntity = restService.getAs(balanceUrl, headers, LinkedList.class);
|
||||
LinkedHashMap<String, Object> response = responseEntity.get(0);
|
||||
logger.info("Balance response: {}", response);
|
||||
|
||||
if (response != null && response.containsKey("balance")) {
|
||||
BigDecimal balance = new BigDecimal(response.get("balance").toString());
|
||||
logger.info("Current balance: {}", balance);
|
||||
return balance.compareTo(amount) > 0;
|
||||
}
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to check balance: {}", e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private LinkedHashMap<String, Object> performRecharge(String token, Transaction transaction) {
|
||||
try {
|
||||
String rechargeUrl = hotApiUrl + "/products/recharge";
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setBearerAuth(token);
|
||||
headers.setContentType(org.springframework.http.MediaType.APPLICATION_JSON);
|
||||
|
||||
Map<String, Object> request = new LinkedHashMap<>();
|
||||
request.put("AgentReference", UUID.randomUUID().toString());
|
||||
request.put("ProductId", getProductId(transaction.getBillClientId()));
|
||||
request.put("Target", transaction.getCreditAccount());
|
||||
request.put("Amount", transaction.getAmount());
|
||||
|
||||
if(getProductId(transaction.getBillClientId()).equals("41")) {
|
||||
List<Map<String, String>> rechargeOptions = new ArrayList<>();
|
||||
if (transaction.getCreditPhone() != null) {
|
||||
Map<String, String> option = new LinkedHashMap<>();
|
||||
option.put("Name", "NotifyNumber");
|
||||
option.put("ParameterType", "String");
|
||||
option.put("Value", transaction.getCreditPhone());
|
||||
rechargeOptions.add(option);
|
||||
}
|
||||
if (!rechargeOptions.isEmpty()) {
|
||||
request.put("RechargeOptions", rechargeOptions);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Sending recharge request: {}", request);
|
||||
ResponseEntity<?> response = restService.postAsEntity(rechargeUrl, request, headers, LinkedHashMap.class);
|
||||
logger.info("Recharge response: {}", response.getBody());
|
||||
|
||||
LinkedHashMap<String, Object> successResponse = (LinkedHashMap<String, Object>) response.getBody();
|
||||
return successResponse;
|
||||
} catch (HttpClientErrorException e) {
|
||||
logger.info("HTTP error response received: {}", e.getStatusCode());
|
||||
try {
|
||||
String responseBody = e.getResponseBodyAsString();
|
||||
logger.info("Error response body: {}", responseBody);
|
||||
LinkedHashMap<String, Object> errorResponseMap = Utils.fromJson(responseBody, LinkedHashMap.class);
|
||||
|
||||
List<Map<String, String>> errors = (List<Map<String, String>>) errorResponseMap.get("errors");
|
||||
if(errors != null && !errors.isEmpty()){
|
||||
String errorMessage = errors.get(0).get("message");
|
||||
errorResponseMap.put("message", errorMessage);
|
||||
logger.info("Extracted error message: {}", errorMessage);
|
||||
}
|
||||
return errorResponseMap;
|
||||
} catch (Exception parseException) {
|
||||
logger.error("Failed to parse error response: {}", parseException.getMessage());
|
||||
LinkedHashMap<String, Object> errorMap = new LinkedHashMap<>();
|
||||
errorMap.put("message", e.getMessage());
|
||||
return errorMap;
|
||||
}
|
||||
} catch (ResourceAccessException e) {
|
||||
logger.error("Network error during recharge: {}", e.getMessage(), e);
|
||||
LinkedHashMap<String, Object> errorMap = new LinkedHashMap<>();
|
||||
errorMap.put("message", "Network error: " + e.getMessage());
|
||||
return errorMap;
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to perform recharge: {}", e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private List<Map<String, String>> buildAdditionalDataList(Map<String, Object> rechargeData) {
|
||||
List<Map<String, String>> additionalDataList = new ArrayList<>();
|
||||
for (Map.Entry<String, Object> entry : rechargeData.entrySet()) {
|
||||
Map<String, String> dataItem = new LinkedHashMap<>();
|
||||
dataItem.put("key", entry.getKey());
|
||||
dataItem.put("value", entry.getValue().toString());
|
||||
additionalDataList.add(dataItem);
|
||||
}
|
||||
return additionalDataList;
|
||||
}
|
||||
|
||||
private String getProductId(String billClientId) {
|
||||
Provider provider = providerService.getProviderRepository()
|
||||
.findByClientId(billClientId);
|
||||
if(provider == null){
|
||||
throw new ApiException("Provider not found for billClientId: " + billClientId);
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> meta = Utils.fromJson(provider.getMeta(), Map.class);
|
||||
return meta.get("hotProductId");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object reverse(Transaction transaction) {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object poll(Transaction transaction) {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -140,6 +140,14 @@ public class EcocashRemotePaymentProcessor implements TransactionProcessorInterf
|
||||
@Override
|
||||
public Object poll(Transaction transaction) throws Exception {
|
||||
logger.info("polling transaction");
|
||||
|
||||
if(settingService.getBooleanSetting("SIMULATE_ECOCASH_SUCCESS")) {
|
||||
transaction.setStatus(Status.SUCCESS);
|
||||
transaction.setResponseCode("00");
|
||||
transaction.setTargetUrl("/home");
|
||||
return transaction;
|
||||
}
|
||||
|
||||
String token = restService.getMerchantToken();
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
@@ -114,8 +114,8 @@ public class RegistrationWorkflow extends WorkflowDefinition {
|
||||
try {
|
||||
String phone = payload.getPhone();
|
||||
Phonenumber.PhoneNumber zimProto = phoneUtil.parse(phone, "ZW");
|
||||
phone = String.valueOf(zimProto.getCountryCode()) + String.valueOf(zimProto.getNationalNumber());
|
||||
payload.setPhone(phone);
|
||||
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");
|
||||
|
||||
@@ -44,7 +44,10 @@ public class CleanupHandler implements HandlerInterface {
|
||||
if(provider == null){
|
||||
throw new ApiException("Provider not found for billClientId: " + transaction.getBillClientId());
|
||||
}
|
||||
String label = transaction.getType() + "_" + provider.getLabel();
|
||||
String label =
|
||||
transaction.getType() + "_" +
|
||||
provider.getLabel() + "_" +
|
||||
provider.getIntegrationProcessorLabel();
|
||||
|
||||
transaction.setConfirmationProcessorLabel(label);
|
||||
transaction.setProviderLabel(provider.getLabel());
|
||||
@@ -79,7 +82,7 @@ public class CleanupHandler implements HandlerInterface {
|
||||
// phone must begin with '+'
|
||||
Phonenumber.PhoneNumber zimPhoneNumber = phoneUtil.parse(debitPhoneNumber, "");
|
||||
String formattedPhoneNumber = phoneUtil.format(zimPhoneNumber, PhoneNumberUtil.PhoneNumberFormat.E164);
|
||||
transaction.setDebitPhone(formattedPhoneNumber);
|
||||
transaction.setDebitPhone(formattedPhoneNumber.replaceAll("[^0-9]", ""));
|
||||
} catch (NumberParseException e) {
|
||||
System.err.println("NumberParseException was thrown: " + e.toString());
|
||||
}
|
||||
@@ -89,7 +92,7 @@ public class CleanupHandler implements HandlerInterface {
|
||||
try {
|
||||
Phonenumber.PhoneNumber zimPhoneNumber = phoneUtil.parse(creditPhoneNumber, "ZW");
|
||||
String formattedPhoneNumber = phoneUtil.format(zimPhoneNumber, PhoneNumberUtil.PhoneNumberFormat.E164);
|
||||
transaction.setCreditPhone(formattedPhoneNumber);
|
||||
transaction.setCreditPhone(formattedPhoneNumber.replaceAll("[^0-9]", ""));
|
||||
} catch (NumberParseException e) {
|
||||
System.err.println("NumberParseException was thrown: " + e.toString());
|
||||
}
|
||||
|
||||
@@ -38,6 +38,11 @@ public class ConfirmHandler implements HandlerInterface {
|
||||
.build();
|
||||
transactionEventService.save(transactionEvent);
|
||||
|
||||
// String confirmationProcessorLabel =
|
||||
// transaction.getType() + "_" +
|
||||
// provider.getLabel() + "_" +
|
||||
// provider.getIntegrationProcessorLabel();
|
||||
|
||||
TransactionProcessorInterface transactionProcessorInterface =
|
||||
(TransactionProcessorInterface) paymentProcessorFactory.getPaymentProcessor(
|
||||
transaction.getConfirmationProcessorLabel());
|
||||
|
||||
Reference in New Issue
Block a user