initial integration done
This commit is contained in:
525
HotRecharge.postman_collection.json
Normal file
525
HotRecharge.postman_collection.json
Normal file
@@ -0,0 +1,525 @@
|
||||
{
|
||||
"info": {
|
||||
"_postman_id": "b240d6e9-e5d3-43a1-8d73-170edd02d714",
|
||||
"name": "HotRecharge",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
||||
"_exporter_id": "569823"
|
||||
},
|
||||
"item": [
|
||||
{
|
||||
"name": "Auth",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"// Parse the response JSON and extract the token",
|
||||
"const jsonData = pm.response.json();",
|
||||
"const token = jsonData.token;",
|
||||
"",
|
||||
"// Store the token in a collection variable",
|
||||
"pm.collectionVariables.set(\"token\", token);"
|
||||
],
|
||||
"type": "text/javascript",
|
||||
"packages": {},
|
||||
"requests": {}
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"AccessCode\": \"vusumuzi@qantra.co.zw\",\n \"Password\": \"Stikbanch@50\"\n}\n",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "https://ssl.hot.co.zw/api/v3/identity/login",
|
||||
"protocol": "https",
|
||||
"host": [
|
||||
"ssl",
|
||||
"hot",
|
||||
"co",
|
||||
"zw"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"v3",
|
||||
"identity",
|
||||
"login"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Get Balance Zesa",
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "bearer",
|
||||
"bearer": [
|
||||
{
|
||||
"key": "token",
|
||||
"value": "{{token}}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "https://ssl.hot.co.zw/api/v3/account/balance/4",
|
||||
"protocol": "https",
|
||||
"host": [
|
||||
"ssl",
|
||||
"hot",
|
||||
"co",
|
||||
"zw"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"v3",
|
||||
"account",
|
||||
"balance",
|
||||
"4"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Get Balance Airtime",
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "bearer",
|
||||
"bearer": [
|
||||
{
|
||||
"key": "token",
|
||||
"value": "{{token}}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "https://ssl.hot.co.zw/api/v3/account/balance/3",
|
||||
"protocol": "https",
|
||||
"host": [
|
||||
"ssl",
|
||||
"hot",
|
||||
"co",
|
||||
"zw"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"v3",
|
||||
"account",
|
||||
"balance",
|
||||
"3"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Get Products",
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "bearer",
|
||||
"bearer": [
|
||||
{
|
||||
"key": "token",
|
||||
"value": "{{token}}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "https://ssl.hot.co.zw/api/v3/products/",
|
||||
"protocol": "https",
|
||||
"host": [
|
||||
"ssl",
|
||||
"hot",
|
||||
"co",
|
||||
"zw"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"v3",
|
||||
"products",
|
||||
""
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Check Available Stock",
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "bearer",
|
||||
"bearer": [
|
||||
{
|
||||
"key": "token",
|
||||
"value": "{{token}}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "https://ssl.hot.co.zw/api/v3/query/stock/41",
|
||||
"protocol": "https",
|
||||
"host": [
|
||||
"ssl",
|
||||
"hot",
|
||||
"co",
|
||||
"zw"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"v3",
|
||||
"query",
|
||||
"stock",
|
||||
"41"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Check Customer",
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "bearer",
|
||||
"bearer": [
|
||||
{
|
||||
"key": "token",
|
||||
"value": "{{token}}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "https://ssl.hot.co.zw/api/v3/query/customer/41/07088597534",
|
||||
"protocol": "https",
|
||||
"host": [
|
||||
"ssl",
|
||||
"hot",
|
||||
"co",
|
||||
"zw"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"v3",
|
||||
"query",
|
||||
"customer",
|
||||
"41",
|
||||
"07088597534"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"name": "Check Customer",
|
||||
"originalRequest": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "https://ssl.hot.co.zw/api/v3/query/customer/41/07088597534",
|
||||
"protocol": "https",
|
||||
"host": [
|
||||
"ssl",
|
||||
"hot",
|
||||
"co",
|
||||
"zw"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"v3",
|
||||
"query",
|
||||
"customer",
|
||||
"41",
|
||||
"07088597534"
|
||||
]
|
||||
}
|
||||
},
|
||||
"status": "OK",
|
||||
"code": 200,
|
||||
"_postman_previewlanguage": null,
|
||||
"header": [
|
||||
{
|
||||
"key": ":status",
|
||||
"value": 200
|
||||
},
|
||||
{
|
||||
"key": "content-length",
|
||||
"value": "129"
|
||||
},
|
||||
{
|
||||
"key": "content-type",
|
||||
"value": "application/json; charset=utf-8"
|
||||
},
|
||||
{
|
||||
"key": "server",
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"key": "x-powered-by",
|
||||
"value": "ASP.NET"
|
||||
},
|
||||
{
|
||||
"key": "strict-transport-security",
|
||||
"value": "max-age=0; includeSubDomains"
|
||||
},
|
||||
{
|
||||
"key": "date",
|
||||
"value": "Sat, 02 May 2026 08:29:13 GMT"
|
||||
}
|
||||
],
|
||||
"cookie": [],
|
||||
"body": "{\n \"accountNumber\": \"07088597534\",\n \"details\": {\n \"AccountName\": \"CHIRINDO LETWINA\\n2144 MARLBOROUGH\",\n \"Status\": \"Active\",\n \"Currency\": \"ZWG\"\n }\n}"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Recharge Airtime",
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "bearer",
|
||||
"bearer": [
|
||||
{
|
||||
"key": "token",
|
||||
"value": "{{token}}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"AgentReference\": \"278917b4-4180-413d-9a31-59fcd4b9b654\",\n \"ProductId\": 101,\n \"Target\": \"0773591219\",\n \"Amount\": 0.1\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "https://ssl.hot.co.zw/api/v3/products/recharge",
|
||||
"protocol": "https",
|
||||
"host": [
|
||||
"ssl",
|
||||
"hot",
|
||||
"co",
|
||||
"zw"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"v3",
|
||||
"products",
|
||||
"recharge"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"name": "Recharge Airtime",
|
||||
"originalRequest": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"AgentReference\": \"278917b4-4180-413d-9a31-59fcd4b9b654\",\n \"ProductId\": 101,\n \"Target\": \"0773591219\",\n \"Amount\": 0.1\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "https://ssl.hot.co.zw/api/v3/products/recharge",
|
||||
"protocol": "https",
|
||||
"host": [
|
||||
"ssl",
|
||||
"hot",
|
||||
"co",
|
||||
"zw"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"v3",
|
||||
"products",
|
||||
"recharge"
|
||||
]
|
||||
}
|
||||
},
|
||||
"status": "OK",
|
||||
"code": 200,
|
||||
"_postman_previewlanguage": null,
|
||||
"header": [
|
||||
{
|
||||
"key": ":status",
|
||||
"value": 200
|
||||
},
|
||||
{
|
||||
"key": "content-length",
|
||||
"value": "218"
|
||||
},
|
||||
{
|
||||
"key": "content-type",
|
||||
"value": "application/json; charset=utf-8"
|
||||
},
|
||||
{
|
||||
"key": "server",
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"key": "x-powered-by",
|
||||
"value": "ASP.NET"
|
||||
},
|
||||
{
|
||||
"key": "strict-transport-security",
|
||||
"value": "max-age=0; includeSubDomains"
|
||||
},
|
||||
{
|
||||
"key": "date",
|
||||
"value": "Sat, 02 May 2026 08:37:11 GMT"
|
||||
}
|
||||
],
|
||||
"cookie": [],
|
||||
"body": "{\n \"successful\": true,\n \"rechargeId\": 190356544,\n \"amount\": 0.1,\n \"discount\": 5,\n \"balance\": {\n \"accountTypeId\": 3,\n \"name\": \"USD\",\n \"balance\": 49.715\n },\n \"message\": \"OK\",\n \"rechargeData\": {\n \"Network\": \"Econet\",\n \"Target\": \"0773591219\",\n \"Cost\": \"0.09500\"\n }\n}"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Recharge ZESA",
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "bearer",
|
||||
"bearer": [
|
||||
{
|
||||
"key": "token",
|
||||
"value": "{{token}}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"AgentReference\": \"{{$guid}}\",\n \"ProductId\": 41,\n \"Target\": \"07088597534\",\n \"Amount\": 5,\n \"RechargeOptions\": [\n {\n \"Name\": \"NotifyNumber\",\n \"ParameterType\": \"String\",\n \"Value\": \"0773591219\"\n }\n ]\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "https://ssl.hot.co.zw/api/v3/products/recharge",
|
||||
"protocol": "https",
|
||||
"host": [
|
||||
"ssl",
|
||||
"hot",
|
||||
"co",
|
||||
"zw"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"v3",
|
||||
"products",
|
||||
"recharge"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"name": "Recharge ZESA",
|
||||
"originalRequest": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"AgentReference\": \"{{$guid}}\",\n \"ProductId\": 41,\n \"Target\": \"07088597534\",\n \"Amount\": 5,\n \"RechargeOptions\": [\n {\n \"Name\": \"NotifyNumber\",\n \"ParameterType\": \"String\",\n \"Value\": \"0773591219\"\n }\n ]\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "https://ssl.hot.co.zw/api/v3/products/recharge",
|
||||
"protocol": "https",
|
||||
"host": [
|
||||
"ssl",
|
||||
"hot",
|
||||
"co",
|
||||
"zw"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"v3",
|
||||
"products",
|
||||
"recharge"
|
||||
]
|
||||
}
|
||||
},
|
||||
"status": "OK",
|
||||
"code": 200,
|
||||
"_postman_previewlanguage": null,
|
||||
"header": [
|
||||
{
|
||||
"key": ":status",
|
||||
"value": 200
|
||||
},
|
||||
{
|
||||
"key": "content-length",
|
||||
"value": "371"
|
||||
},
|
||||
{
|
||||
"key": "content-type",
|
||||
"value": "application/json; charset=utf-8"
|
||||
},
|
||||
{
|
||||
"key": "server",
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"key": "x-powered-by",
|
||||
"value": "ASP.NET"
|
||||
},
|
||||
{
|
||||
"key": "strict-transport-security",
|
||||
"value": "max-age=0; includeSubDomains"
|
||||
},
|
||||
{
|
||||
"key": "date",
|
||||
"value": "Sat, 02 May 2026 08:34:46 GMT"
|
||||
}
|
||||
],
|
||||
"cookie": [],
|
||||
"body": "{\n \"successful\": true,\n \"rechargeId\": 190356513,\n \"amount\": 5,\n \"discount\": 1.2,\n \"balance\": {\n \"accountTypeId\": 4,\n \"name\": \"Utility USD\",\n \"balance\": 45.06\n },\n \"message\": \"Transaction processed successfully\",\n \"rechargeData\": {\n \"Network\": \"ZESA\",\n \"Target\": \"07088597534\",\n \"Cost\": \"4.9400\",\n \"Token\": \"0099 4439 8936 4293 1759\",\n \"Units\": \"57.86\",\n \"NetAmount\": \"125.29\",\n \"TaxAmount\": \"0\",\n \"Levy\": \"7.52\",\n \"Arrears\": \"0\"\n }\n}"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"variable": [
|
||||
{
|
||||
"key": "token",
|
||||
"value": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package zw.qantra.tm.rest;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -8,7 +10,9 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.http.client.BufferingClientHttpRequestFactory;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@@ -17,7 +21,9 @@ import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import zw.qantra.tm.exceptions.RestTemplateResponseErrorHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -53,16 +59,15 @@ public class RestService {
|
||||
)
|
||||
);
|
||||
|
||||
this.restTemplate.setErrorHandler(new RestTemplateResponseErrorHandler());
|
||||
|
||||
// bootstrap rest template
|
||||
List<ClientHttpRequestInterceptor> interceptors
|
||||
= this.restTemplate.getInterceptors();
|
||||
if (CollectionUtils.isEmpty(interceptors)) {
|
||||
interceptors = new ArrayList<>();
|
||||
}
|
||||
interceptors.add(new RestTemplateInterceptor());
|
||||
interceptors.add(new LoggingInterceptor(this));
|
||||
this.restTemplate.setInterceptors(interceptors);
|
||||
|
||||
}
|
||||
|
||||
public String getUserToken(){
|
||||
@@ -230,4 +235,52 @@ public class RestService {
|
||||
public <T> ResponseEntity<?> exchangeGetNoAuth(String path, HttpEntity<?> httpEntity, Class<?> clazz, String pathVariable) {
|
||||
return this.restTemplate.exchange(path, HttpMethod.GET, httpEntity, clazz, pathVariable);
|
||||
}
|
||||
|
||||
private String prettyPrintJson(String json) {
|
||||
try {
|
||||
Object jsonObject = gson.fromJson(json, Object.class);
|
||||
return new GsonBuilder().setPrettyPrinting().create().toJson(jsonObject);
|
||||
} catch (JsonSyntaxException e) {
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
private static class LoggingInterceptor implements ClientHttpRequestInterceptor {
|
||||
Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);
|
||||
private final RestService restService;
|
||||
|
||||
public LoggingInterceptor(RestService restService) {
|
||||
this.restService = restService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
|
||||
ClientHttpRequestExecution execution) throws IOException {
|
||||
logRequest(request, body);
|
||||
ClientHttpResponse response = execution.execute(request, body);
|
||||
logResponse(response);
|
||||
return response;
|
||||
}
|
||||
|
||||
private void logRequest(HttpRequest request, byte[] body) {
|
||||
logger.info("=== REST REQUEST ===");
|
||||
logger.info("Method: {} | URI: {}", request.getMethod(), request.getURI());
|
||||
logger.info("Headers: {}", request.getHeaders());
|
||||
if (body.length > 0) {
|
||||
String bodyStr = new String(body, StandardCharsets.UTF_8);
|
||||
logger.info("Body: {}", restService.prettyPrintJson(bodyStr));
|
||||
}
|
||||
}
|
||||
|
||||
private void logResponse(ClientHttpResponse response) throws IOException {
|
||||
logger.info("=== REST RESPONSE ===");
|
||||
logger.info("Status: {}", response.getStatusCode());
|
||||
logger.info("Headers: {}", response.getHeaders());
|
||||
byte[] responseBody = response.getBody().readAllBytes();
|
||||
if (responseBody.length > 0) {
|
||||
String bodyStr = new String(responseBody, StandardCharsets.UTF_8);
|
||||
logger.info("Body: {}", restService.prettyPrintJson(bodyStr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,3 +77,12 @@ management.tracing.enabled=true
|
||||
management.tracing.sampling.probability=1.0
|
||||
management.otlp.tracing.endpoint=http://62.171.179.108:10428/insert/opentelemetry/v1/traces
|
||||
management.otlp.logging.endpoint=http://62.171.179.108:9428/insert/opentelemetry/v1/logs
|
||||
|
||||
hot-recharge.url=https://ssl.hot.co.zw/api/v3
|
||||
hot-recharge.username=vusumuzi@qantra.co.zw
|
||||
hot-recharge.password=Stikbanch@50
|
||||
hot.api.url=https://ssl.hot.co.zw/api/v3
|
||||
hot.auth.url=https://ssl.hot.co.zw/api/v3/identity/login
|
||||
hot.access.code=vusumuzi@qantra.co.zw
|
||||
hot.password=Stikbanch@50
|
||||
hot.token.expiry.minutes=30
|
||||
Reference in New Issue
Block a user