bug fixes
This commit is contained in:
@@ -23,6 +23,7 @@ import java.util.UUID;
|
|||||||
public class Transaction extends BaseEntity {
|
public class Transaction extends BaseEntity {
|
||||||
private String userId;
|
private String userId;
|
||||||
private String trace;
|
private String trace;
|
||||||
|
private String region;
|
||||||
private BigDecimal amount;
|
private BigDecimal amount;
|
||||||
private BigDecimal charge;
|
private BigDecimal charge;
|
||||||
private BigDecimal gatewayCharge;
|
private BigDecimal gatewayCharge;
|
||||||
|
|||||||
@@ -15,4 +15,5 @@ public interface ChargeRepository extends JpaRepository<Charge, UUID> {
|
|||||||
List<Charge> findAllByCurrency(CurrencyType currency);
|
List<Charge> findAllByCurrency(CurrencyType currency);
|
||||||
boolean existsByChargeLabelAndCurrency(ChargeLabelEnum chargeLabel, CurrencyType currency);
|
boolean existsByChargeLabelAndCurrency(ChargeLabelEnum chargeLabel, CurrencyType currency);
|
||||||
boolean existsByChargeLabelAndCurrencyAndPercentageRate(ChargeLabelEnum chargeLabel, CurrencyType currency, BigDecimal percentageRate);
|
boolean existsByChargeLabelAndCurrencyAndPercentageRate(ChargeLabelEnum chargeLabel, CurrencyType currency, BigDecimal percentageRate);
|
||||||
|
boolean existsByChargeLabelAndCurrencyAndDescription(ChargeLabelEnum chargeLabel, CurrencyType currency, String description);
|
||||||
}
|
}
|
||||||
@@ -34,52 +34,6 @@ public class HotRechargeTokenService {
|
|||||||
private long cacheExpiryTime;
|
private long cacheExpiryTime;
|
||||||
|
|
||||||
public synchronized String getToken() {
|
public synchronized String getToken() {
|
||||||
if (isCacheValid()) {
|
|
||||||
logger.info("Cache is valid, attempting token refresh");
|
|
||||||
return refreshCachedToken();
|
|
||||||
}
|
|
||||||
return fetchNewToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isCacheValid() {
|
|
||||||
return cachedToken != null && cachedRefreshToken != null && System.currentTimeMillis() < cacheExpiryTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized String refreshCachedToken() {
|
|
||||||
if (!isCacheValid()) {
|
|
||||||
return fetchNewToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
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 {
|
try {
|
||||||
logger.info("Fetching new HotRecharge token via login");
|
logger.info("Fetching new HotRecharge token via login");
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
@@ -99,9 +53,33 @@ public class HotRechargeTokenService {
|
|||||||
logger.info("New HotRecharge token cached, cache expires in {} minutes", cacheDurationMinutes);
|
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;
|
logger.info("Trying to refresh token");
|
||||||
cachedRefreshToken = null;
|
return refreshCachedToken();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized String refreshCachedToken() {
|
||||||
|
try {
|
||||||
|
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);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,10 +155,10 @@ public class VelocityPaymentService {
|
|||||||
.paymentProcessorLabel(paymentProcessorLabel)
|
.paymentProcessorLabel(paymentProcessorLabel)
|
||||||
.debitCurrency(transaction.getDebitCurrency().name())
|
.debitCurrency(transaction.getDebitCurrency().name())
|
||||||
.debitRef(transaction.getErpSalesRef())
|
.debitRef(transaction.getErpSalesRef())
|
||||||
.creditPhone(velocityApiProperties.getCreditPhone())
|
.creditPhone("")
|
||||||
.creditRegion("ZW")
|
.creditRegion("ZW")
|
||||||
.creditAccount(velocityApiProperties.getCreditAccount())
|
.creditAccount("")
|
||||||
.authType(velocityApiProperties.getDefaultAuthType())
|
.authType(paymentProcessorLabel.equals("VMC") ? "WEB" : "REMOTE")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
if ("MPGS".equalsIgnoreCase(transaction.getPaymentProcessorLabel())) {
|
if ("MPGS".equalsIgnoreCase(transaction.getPaymentProcessorLabel())) {
|
||||||
|
|||||||
@@ -59,9 +59,15 @@ public class NetoneConfirmationHotProcessor implements TransactionProcessorInter
|
|||||||
put("value", transaction.getBillName());
|
put("value", transaction.getBillName());
|
||||||
}});
|
}});
|
||||||
additionalDataList.add(new HashMap() {{
|
additionalDataList.add(new HashMap() {{
|
||||||
put("name", "Destination Phone Number");
|
put("name", "From");
|
||||||
|
put("value", transaction.getDebitPhone());
|
||||||
|
}});
|
||||||
|
|
||||||
|
additionalDataList.add(new HashMap() {{
|
||||||
|
put("name", "To");
|
||||||
put("value", transaction.getCreditAccount());
|
put("value", transaction.getCreditAccount());
|
||||||
}});
|
}});
|
||||||
|
|
||||||
if(transaction.getBillProductName() != null && !transaction.getBillProductName().isEmpty()) {
|
if(transaction.getBillProductName() != null && !transaction.getBillProductName().isEmpty()) {
|
||||||
additionalDataList.add(new HashMap() {{
|
additionalDataList.add(new HashMap() {{
|
||||||
put("name", "Product");
|
put("name", "Product");
|
||||||
|
|||||||
@@ -74,14 +74,12 @@ public class CleanupHandler implements HandlerInterface {
|
|||||||
String debitPhoneNumber = transaction.getDebitPhone();
|
String debitPhoneNumber = transaction.getDebitPhone();
|
||||||
String creditPhoneNumber = transaction.getCreditPhone();
|
String creditPhoneNumber = transaction.getCreditPhone();
|
||||||
|
|
||||||
PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
|
|
||||||
|
|
||||||
if(debitPhoneNumber != null && !debitPhoneNumber.isEmpty()){
|
if(debitPhoneNumber != null && !debitPhoneNumber.isEmpty()){
|
||||||
transaction.setDebitPhone(Utils.formatPhoneNumber(debitPhoneNumber, ""));
|
transaction.setDebitPhone(Utils.formatPhoneNumber(debitPhoneNumber, transaction.getRegion()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(creditPhoneNumber != null && !creditPhoneNumber.isEmpty()){
|
if(creditPhoneNumber != null && !creditPhoneNumber.isEmpty()){
|
||||||
transaction.setDebitPhone(Utils.formatPhoneNumber(creditPhoneNumber, "ZW"));
|
transaction.setCreditAccount(Utils.formatPhoneNumber(creditPhoneNumber, "ZW"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return transaction;
|
return transaction;
|
||||||
|
|||||||
@@ -39,34 +39,34 @@ public class ChargeSeeder implements Seeder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ecocash Gateway Fee (1%)
|
// Ecocash Gateway Fee (1%)
|
||||||
if (chargeRepository.existsByChargeLabelAndCurrencyAndPercentageRate(
|
if (chargeRepository.existsByChargeLabelAndCurrencyAndDescription(
|
||||||
ChargeLabelEnum.GATEWAY_FEE, CurrencyType.USD, new BigDecimal("1"))) {
|
ChargeLabelEnum.GATEWAY_FEE, CurrencyType.USD, "Ecocash Gateway fee")) {
|
||||||
log.info("Charge 'Gateway fee (1%)' already exists, skipping.");
|
log.info("Charge 'Ecocash Gateway fee (1%)' already exists, skipping.");
|
||||||
} else {
|
} else {
|
||||||
chargeRepository.save(Charge.builder()
|
chargeRepository.save(Charge.builder()
|
||||||
.description("Gateway fee")
|
.description("Ecocash Gateway fee")
|
||||||
.chargeLabel(ChargeLabelEnum.GATEWAY_FEE)
|
.chargeLabel(ChargeLabelEnum.GATEWAY_FEE)
|
||||||
.currency(CurrencyType.USD)
|
.currency(CurrencyType.USD)
|
||||||
.percentageRate(new BigDecimal("1"))
|
.percentageRate(new BigDecimal("1"))
|
||||||
.includes(new HashSet<>(Arrays.asList(ecocashCondition)))
|
.includes(new HashSet<>(Arrays.asList(ecocashCondition)))
|
||||||
.build());
|
.build());
|
||||||
log.info("Charge 'Gateway fee (1%)' seeded.");
|
log.info("Charge 'Ecocash Gateway fee (1%)' seeded.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// MPGS Gateway Fee (2%, min 0.55)
|
// MPGS Gateway Fee (2%, min 0.55)
|
||||||
if (chargeRepository.existsByChargeLabelAndCurrencyAndPercentageRate(
|
if (chargeRepository.existsByChargeLabelAndCurrencyAndDescription(
|
||||||
ChargeLabelEnum.GATEWAY_FEE, CurrencyType.USD, new BigDecimal("2"))) {
|
ChargeLabelEnum.GATEWAY_FEE, CurrencyType.USD, "MPGS Gateway fee")) {
|
||||||
log.info("Charge 'Gateway fee (2%)' already exists, skipping.");
|
log.info("Charge 'MPGS Gateway fee (1% min 0.50)' already exists, skipping.");
|
||||||
} else {
|
} else {
|
||||||
chargeRepository.save(Charge.builder()
|
chargeRepository.save(Charge.builder()
|
||||||
.description("Gateway fee")
|
.description("MPGS Gateway fee")
|
||||||
.chargeLabel(ChargeLabelEnum.GATEWAY_FEE)
|
.chargeLabel(ChargeLabelEnum.GATEWAY_FEE)
|
||||||
.currency(CurrencyType.USD)
|
.currency(CurrencyType.USD)
|
||||||
.percentageRate(new BigDecimal("1"))
|
.percentageRate(new BigDecimal("1"))
|
||||||
.min(new BigDecimal("0.20"))
|
.min(new BigDecimal("0.50"))
|
||||||
.includes(new HashSet<>(Arrays.asList(mpgsCondition)))
|
.includes(new HashSet<>(Arrays.asList(mpgsCondition)))
|
||||||
.build());
|
.build());
|
||||||
log.info("Charge 'Gateway fee (2%)' seeded.");
|
log.info("Charge 'MPGS Gateway fee (1% min 0.50)' seeded.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,5 +68,5 @@ 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
|
||||||
velocity.api.cancel-url=https://pay.velocityafrica/poll
|
velocity.api.cancel-url=https://pay.velocityafrica.net/poll
|
||||||
velocity.api.success-url=https://pay.velocityafrica/poll
|
velocity.api.success-url=https://pay.velocityafrica.net/poll
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
|
|
||||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:pro="http://www.liquibase.org/xml/ns/pro" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/pro http://www.liquibase.org/xml/ns/pro/liquibase-pro-latest.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
|
|
||||||
<changeSet author="khoza (generated)" id="1780061546055-17">
|
|
||||||
<addColumn tableName="transaction">
|
|
||||||
<column name="order_id" type="varchar(255)"/>
|
|
||||||
</addColumn>
|
|
||||||
</changeSet>
|
|
||||||
<changeSet author="khoza (generated)" id="1780061546055-18">
|
|
||||||
<addColumn tableName="transaction">
|
|
||||||
<column name="order_trace" type="varchar(255)"/>
|
|
||||||
</addColumn>
|
|
||||||
</changeSet>
|
|
||||||
<changeSet author="khoza (generated)" id="1780061546055-19">
|
|
||||||
<addColumn tableName="transaction">
|
|
||||||
<column name="order_transaction_trace" type="varchar(255)"/>
|
|
||||||
</addColumn>
|
|
||||||
</changeSet>
|
|
||||||
<changeSet author="khoza (generated)" id="1780061546055-20">
|
|
||||||
<dropColumn columnName="version" tableName="transaction"/>
|
|
||||||
</changeSet>
|
|
||||||
<changeSet author="khoza (generated)" id="1780061546055-1">
|
|
||||||
<addDefaultValue columnDataType="boolean" columnName="deleted" defaultValueBoolean="false" tableName="additional_data"/>
|
|
||||||
</changeSet>
|
|
||||||
<changeSet author="khoza (generated)" id="1780061546055-2">
|
|
||||||
<addDefaultValue columnDataType="boolean" columnName="deleted" defaultValueBoolean="false" tableName="category"/>
|
|
||||||
</changeSet>
|
|
||||||
<changeSet author="khoza (generated)" id="1780061546055-3">
|
|
||||||
<addDefaultValue columnDataType="boolean" columnName="deleted" defaultValueBoolean="false" tableName="charge"/>
|
|
||||||
</changeSet>
|
|
||||||
<changeSet author="khoza (generated)" id="1780061546055-4">
|
|
||||||
<addDefaultValue columnDataType="boolean" columnName="deleted" defaultValueBoolean="false" tableName="charge_condition"/>
|
|
||||||
</changeSet>
|
|
||||||
<changeSet author="khoza (generated)" id="1780061546055-5">
|
|
||||||
<addDefaultValue columnDataType="boolean" columnName="deleted" defaultValueBoolean="false" tableName="currency"/>
|
|
||||||
</changeSet>
|
|
||||||
<changeSet author="khoza (generated)" id="1780061546055-6">
|
|
||||||
<addDefaultValue columnDataType="boolean" columnName="deleted" defaultValueBoolean="false" tableName="integration_processor"/>
|
|
||||||
</changeSet>
|
|
||||||
<changeSet author="khoza (generated)" id="1780061546055-7">
|
|
||||||
<addDefaultValue columnDataType="boolean" columnName="deleted" defaultValueBoolean="false" tableName="otp"/>
|
|
||||||
</changeSet>
|
|
||||||
<changeSet author="khoza (generated)" id="1780061546055-8">
|
|
||||||
<addDefaultValue columnDataType="boolean" columnName="deleted" defaultValueBoolean="false" tableName="payment_processor"/>
|
|
||||||
</changeSet>
|
|
||||||
<changeSet author="khoza (generated)" id="1780061546055-9">
|
|
||||||
<addDefaultValue columnDataType="boolean" columnName="deleted" defaultValueBoolean="false" tableName="provider"/>
|
|
||||||
</changeSet>
|
|
||||||
<changeSet author="khoza (generated)" id="1780061546055-10">
|
|
||||||
<addDefaultValue columnDataType="boolean" columnName="deleted" defaultValueBoolean="false" tableName="recipient"/>
|
|
||||||
</changeSet>
|
|
||||||
<changeSet author="khoza (generated)" id="1780061546055-11">
|
|
||||||
<addDefaultValue columnDataType="boolean" columnName="deleted" defaultValueBoolean="false" tableName="setting"/>
|
|
||||||
</changeSet>
|
|
||||||
<changeSet author="khoza (generated)" id="1780061546055-12">
|
|
||||||
<addDefaultValue columnDataType="boolean" columnName="deleted" defaultValueBoolean="false" tableName="transaction"/>
|
|
||||||
</changeSet>
|
|
||||||
<changeSet author="khoza (generated)" id="1780061546055-13">
|
|
||||||
<addDefaultValue columnDataType="boolean" columnName="deleted" defaultValueBoolean="false" tableName="transaction_event"/>
|
|
||||||
</changeSet>
|
|
||||||
<changeSet author="khoza (generated)" id="1780061546055-14">
|
|
||||||
<addDefaultValue columnDataType="boolean" columnName="deleted" defaultValueBoolean="false" tableName="users"/>
|
|
||||||
</changeSet>
|
|
||||||
<changeSet author="khoza (generated)" id="1780061546055-15">
|
|
||||||
<dropUniqueConstraint constraintName="UC_USERSUSERNAME_COL" tableName="users"/>
|
|
||||||
</changeSet>
|
|
||||||
<changeSet author="khoza (generated)" id="1780061546055-16">
|
|
||||||
<addUniqueConstraint columnNames="username" constraintName="UC_USERSUSERNAME_COL" tableName="users"/>
|
|
||||||
</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>
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
changeLogFile=src/main/resources/liquibase-diff-changeLog.xml
|
changeLogFile=src/main/resources/liquibase-diff-changeLog.xml
|
||||||
url=jdbc:postgresql://localhost:5432/qpay?serverTimezone=Africa/Harare&useLegacyDatetimeCode=false
|
url=jdbc:postgresql://localhost:5432/qpay?serverTimezone=Africa/Harare&useLegacyDatetimeCode=false
|
||||||
username=postgres
|
username=postgres
|
||||||
password=zdDZMzq6F4B4L1IUl
|
password=example
|
||||||
|
|
||||||
driver=org.postgresql.Driver
|
driver=org.postgresql.Driver
|
||||||
referenceUrl=hibernate:spring:zw.qantra.tm.domain.models?dialect=org.hibernate.dialect.PostgreSQLDialect&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy&hibernate.physical_naming_strategy=org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy
|
referenceUrl=hibernate:spring:zw.qantra.tm.domain.models?dialect=org.hibernate.dialect.PostgreSQLDialect&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy&hibernate.physical_naming_strategy=org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy
|
||||||
|
|||||||
Reference in New Issue
Block a user