improving ecocash payment handling
This commit is contained in:
1520
logs/application.2025-07-13.0.log
Normal file
1520
logs/application.2025-07-13.0.log
Normal file
File diff suppressed because it is too large
Load Diff
6876
logs/application.log
Normal file
6876
logs/application.log
Normal file
File diff suppressed because it is too large
Load Diff
@@ -38,7 +38,34 @@ public class ConfigController {
|
|||||||
private final IntegrationProcessorService integrationProcessorService;
|
private final IntegrationProcessorService integrationProcessorService;
|
||||||
|
|
||||||
@GetMapping("/seed")
|
@GetMapping("/seed")
|
||||||
public ResponseEntity charges(){
|
public ResponseEntity seedConfig(){
|
||||||
|
|
||||||
|
// clear existing payment processors before seeding
|
||||||
|
paymentProcessorService.getPaymentProcessorRepository().deleteAll();
|
||||||
|
paymentProcessorService.getPaymentProcessorRepository().saveAll(Arrays.asList(
|
||||||
|
PaymentProcessor.builder()
|
||||||
|
.name("Ecocash")
|
||||||
|
.label("ECOCASH")
|
||||||
|
.type("GATEWAY")
|
||||||
|
.image("ecocash.png")
|
||||||
|
.accountFieldName("phoneNumber")
|
||||||
|
.accountFieldLabel("EcoCash Phone Number")
|
||||||
|
.description("Pay using your mobile wallet")
|
||||||
|
.authType("REMOTE")
|
||||||
|
.build(),
|
||||||
|
PaymentProcessor.builder()
|
||||||
|
.name("Visa/MasterCard")
|
||||||
|
.label("MPGS")
|
||||||
|
.type("GATEWAY")
|
||||||
|
.image("visa-mastercard.png")
|
||||||
|
.accountFieldName("cardNumber")
|
||||||
|
.accountFieldLabel("Card Number")
|
||||||
|
.description("Pay using your international card")
|
||||||
|
.authType("WEB")
|
||||||
|
.build()
|
||||||
|
));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
integrationProcessorService.getIntegrationProcessorRepository().deleteAll();
|
integrationProcessorService.getIntegrationProcessorRepository().deleteAll();
|
||||||
integrationProcessorService.getIntegrationProcessorRepository().saveAll(Arrays.asList(
|
integrationProcessorService.getIntegrationProcessorRepository().saveAll(Arrays.asList(
|
||||||
@@ -79,6 +106,7 @@ public class ConfigController {
|
|||||||
.image("econet.png")
|
.image("econet.png")
|
||||||
.externalId("78f1f497-c9eb-401c-b22c-884756e68e40")
|
.externalId("78f1f497-c9eb-401c-b22c-884756e68e40")
|
||||||
.integrationProcessorLabel("STEWARD")
|
.integrationProcessorLabel("STEWARD")
|
||||||
|
.accountFieldName("Phone Number")
|
||||||
.build(),
|
.build(),
|
||||||
Provider.builder()
|
Provider.builder()
|
||||||
.description("NetOne Airtime")
|
.description("NetOne Airtime")
|
||||||
@@ -88,6 +116,7 @@ public class ConfigController {
|
|||||||
.image("netone.png")
|
.image("netone.png")
|
||||||
.externalId("95d485a7-39c9-4559-8a27-6521969abe8f")
|
.externalId("95d485a7-39c9-4559-8a27-6521969abe8f")
|
||||||
.integrationProcessorLabel("STEWARD")
|
.integrationProcessorLabel("STEWARD")
|
||||||
|
.accountFieldName("Phone Number")
|
||||||
.build(),
|
.build(),
|
||||||
Provider.builder()
|
Provider.builder()
|
||||||
.description("Prepaid Zesa")
|
.description("Prepaid Zesa")
|
||||||
@@ -97,6 +126,7 @@ public class ConfigController {
|
|||||||
.image("zesa.png")
|
.image("zesa.png")
|
||||||
.externalId("0f67f7cc-b62b-4fb5-915f-6740b2afdba6")
|
.externalId("0f67f7cc-b62b-4fb5-915f-6740b2afdba6")
|
||||||
.integrationProcessorLabel("STEWARD")
|
.integrationProcessorLabel("STEWARD")
|
||||||
|
.accountFieldName("Meter Number")
|
||||||
.build(),
|
.build(),
|
||||||
Provider.builder()
|
Provider.builder()
|
||||||
.description("Econet Broadband")
|
.description("Econet Broadband")
|
||||||
@@ -106,30 +136,11 @@ public class ConfigController {
|
|||||||
.image("econet.png")
|
.image("econet.png")
|
||||||
.externalId("287863e2-a791-4106-b629-79dadc9a6779")
|
.externalId("287863e2-a791-4106-b629-79dadc9a6779")
|
||||||
.integrationProcessorLabel("STEWARD")
|
.integrationProcessorLabel("STEWARD")
|
||||||
|
.accountFieldName("Phone Number")
|
||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
providerService.getProviderRepository().saveAll(providers);
|
providerService.getProviderRepository().saveAll(providers);
|
||||||
|
|
||||||
// Clear existing payment processors before seeding
|
|
||||||
paymentProcessorService.getPaymentProcessorRepository().deleteAll();
|
|
||||||
|
|
||||||
// seed payment processors
|
|
||||||
List<PaymentProcessor> paymentProcessors = Arrays.asList(
|
|
||||||
PaymentProcessor.builder()
|
|
||||||
.name("Ecocash")
|
|
||||||
.label("ECOCASH")
|
|
||||||
.type("GATEWAY")
|
|
||||||
.image("https://www.ecocash.co.zw/images/logo.png")
|
|
||||||
.build(),
|
|
||||||
PaymentProcessor.builder()
|
|
||||||
.name("Mastercard")
|
|
||||||
.label("MPGS")
|
|
||||||
.type("GATEWAY")
|
|
||||||
.image("https://www.mastercard.com/content/dam/public/mastercardcom/global/en/icons/mc-symbol.svg")
|
|
||||||
.build()
|
|
||||||
);
|
|
||||||
paymentProcessorService.getPaymentProcessorRepository().saveAll(paymentProcessors);
|
|
||||||
|
|
||||||
// Clear existing charges and charge conditions before seeding
|
// Clear existing charges and charge conditions before seeding
|
||||||
List<ChargeCondition> chargeConditions = Arrays.asList(
|
List<ChargeCondition> chargeConditions = Arrays.asList(
|
||||||
ChargeCondition.builder()
|
ChargeCondition.builder()
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package zw.qantra.tm.domain.controllers;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import zw.qantra.tm.domain.models.PaymentProcessor;
|
||||||
|
import zw.qantra.tm.domain.services.PaymentProcessorService;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/payment-processors")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PaymentProcessorController {
|
||||||
|
|
||||||
|
private final PaymentProcessorService paymentProcessorService;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ResponseEntity<List<PaymentProcessor>> getAllPaymentProcessors() {
|
||||||
|
return ResponseEntity.ok(paymentProcessorService.getPaymentProcessorRepository().findAll());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ResponseEntity<PaymentProcessor> getPaymentProcessorById(@PathVariable UUID id) {
|
||||||
|
Optional<PaymentProcessor> paymentProcessor = paymentProcessorService.getPaymentProcessorRepository().findById(id);
|
||||||
|
return paymentProcessor.map(ResponseEntity::ok)
|
||||||
|
.orElse(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/type/{type}")
|
||||||
|
public ResponseEntity<List<PaymentProcessor>> getPaymentProcessorsByType(@PathVariable String type) {
|
||||||
|
// Note: This would require adding a findByType method to the repository
|
||||||
|
// For now, filtering in memory
|
||||||
|
List<PaymentProcessor> allProcessors = paymentProcessorService.getPaymentProcessorRepository().findAll();
|
||||||
|
List<PaymentProcessor> filteredProcessors = allProcessors.stream()
|
||||||
|
.filter(processor -> type.equals(processor.getType()))
|
||||||
|
.toList();
|
||||||
|
return ResponseEntity.ok(filteredProcessors);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/label/{label}")
|
||||||
|
public ResponseEntity<PaymentProcessor> getPaymentProcessorByLabel(@PathVariable String label) {
|
||||||
|
// Note: This would require adding a findByLabel method to the repository
|
||||||
|
// For now, filtering in memory
|
||||||
|
Optional<PaymentProcessor> paymentProcessor = paymentProcessorService.getPaymentProcessorRepository().findAll().stream()
|
||||||
|
.filter(processor -> label.equals(processor.getLabel()))
|
||||||
|
.findFirst();
|
||||||
|
return paymentProcessor.map(ResponseEntity::ok)
|
||||||
|
.orElse(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<PaymentProcessor> createPaymentProcessor(@RequestBody PaymentProcessor paymentProcessor) {
|
||||||
|
PaymentProcessor createdProcessor = paymentProcessorService.getPaymentProcessorRepository().save(paymentProcessor);
|
||||||
|
return ResponseEntity.ok(createdProcessor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ResponseEntity<PaymentProcessor> updatePaymentProcessor(@PathVariable UUID id, @RequestBody PaymentProcessor paymentProcessor) {
|
||||||
|
if (!paymentProcessorService.getPaymentProcessorRepository().existsById(id)) {
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
}
|
||||||
|
paymentProcessor.setId(id);
|
||||||
|
PaymentProcessor updatedProcessor = paymentProcessorService.getPaymentProcessorRepository().save(paymentProcessor);
|
||||||
|
return ResponseEntity.ok(updatedProcessor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ResponseEntity<Void> deletePaymentProcessor(@PathVariable UUID id) {
|
||||||
|
if (!paymentProcessorService.getPaymentProcessorRepository().existsById(id)) {
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
}
|
||||||
|
paymentProcessorService.getPaymentProcessorRepository().deleteById(id);
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import zw.qantra.tm.utils.Utils;
|
import zw.qantra.tm.utils.LogUtils;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -71,9 +71,9 @@ public class TransactonController {
|
|||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
public ResponseEntity<Transaction> create(@RequestBody Transaction transaction) {
|
public ResponseEntity<Transaction> create(@RequestBody Transaction transaction) {
|
||||||
logger.info("incoming transaction: {}", Utils.toJson(transaction));
|
LogUtils.logPrettyJson(logger, "incoming transaction", transaction);
|
||||||
Transaction createdTransaction = transactionService.execute(transaction);
|
Transaction createdTransaction = transactionService.execute(transaction);
|
||||||
logger.info("outgoing transaction: {}", Utils.toJson(createdTransaction));
|
LogUtils.logPrettyJson(logger, "outgoing transaction", createdTransaction);
|
||||||
return ResponseEntity.ok(createdTransaction);
|
return ResponseEntity.ok(createdTransaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,4 +17,7 @@ public class PaymentProcessor extends BaseEntity {
|
|||||||
private String type;
|
private String type;
|
||||||
@Column(columnDefinition = "TEXT")
|
@Column(columnDefinition = "TEXT")
|
||||||
private String image;
|
private String image;
|
||||||
|
private String accountFieldName;
|
||||||
|
private String accountFieldLabel;
|
||||||
|
private String authType;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,9 +170,14 @@ public class TransactionService {
|
|||||||
public Transaction findById(UUID id) {
|
public Transaction findById(UUID id) {
|
||||||
Transaction transaction = transactionRepository.findById(id).orElseThrow(() -> new ApiException("Transaction not found for ID: " + id));
|
Transaction transaction = transactionRepository.findById(id).orElseThrow(() -> new ApiException("Transaction not found for ID: " + id));
|
||||||
|
|
||||||
AdditionalData additionalData = additionalDataService.getAdditionalDataRepository()
|
try {
|
||||||
.findByTransactionId(id).orElseThrow(() -> new ApiException("Additional data not found for transaction ID: " + id));
|
AdditionalData additionalData = additionalDataService.getAdditionalDataRepository()
|
||||||
transaction.setAdditionalData(Utils.fromJson(additionalData.getJsonString(), List.class));
|
.findByTransactionId(id).orElseThrow(() -> new ApiException("Additional data not found for transaction ID: " + id));
|
||||||
|
transaction.setAdditionalData(Utils.fromJson(additionalData.getJsonString(), List.class));
|
||||||
|
} catch (ApiException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,8 +60,6 @@ public class EconetConfirmationProcessor implements TransactionProcessorInterfac
|
|||||||
|
|
||||||
LinkedHashMap response = restService.postWithToken(token, url, request, LinkedHashMap.class);
|
LinkedHashMap response = restService.postWithToken(token, url, request, LinkedHashMap.class);
|
||||||
|
|
||||||
logger.info("response: {}", response.toString());
|
|
||||||
|
|
||||||
LinkedHashMap body = (LinkedHashMap) response.get("body");
|
LinkedHashMap body = (LinkedHashMap) response.get("body");
|
||||||
|
|
||||||
if (body.get("status").equals("SUCCESS")) {
|
if (body.get("status").equals("SUCCESS")) {
|
||||||
@@ -78,7 +76,6 @@ public class EconetConfirmationProcessor implements TransactionProcessorInterfac
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
transaction.setStatus(Status.FAILED);
|
transaction.setStatus(Status.FAILED);
|
||||||
transaction.setErrorMessage((String) body.get("message"));
|
|
||||||
transaction.setResponseCode((String) body.get("responseCode"));
|
transaction.setResponseCode((String) body.get("responseCode"));
|
||||||
transaction.setErrorMessage((String) body.get("errorMessage"));
|
transaction.setErrorMessage((String) body.get("errorMessage"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,8 +60,6 @@ public class ZesaConfirmationProcessor implements TransactionProcessorInterface
|
|||||||
|
|
||||||
LinkedHashMap response = restService.postWithToken(token, url, request, LinkedHashMap.class);
|
LinkedHashMap response = restService.postWithToken(token, url, request, LinkedHashMap.class);
|
||||||
|
|
||||||
logger.info("response: {}", response.toString());
|
|
||||||
|
|
||||||
LinkedHashMap body = (LinkedHashMap) response.get("body");
|
LinkedHashMap body = (LinkedHashMap) response.get("body");
|
||||||
|
|
||||||
if (body.get("status").equals("SUCCESS")) {
|
if (body.get("status").equals("SUCCESS")) {
|
||||||
@@ -78,7 +76,6 @@ public class ZesaConfirmationProcessor implements TransactionProcessorInterface
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
transaction.setStatus(Status.FAILED);
|
transaction.setStatus(Status.FAILED);
|
||||||
transaction.setErrorMessage((String) body.get("message"));
|
|
||||||
transaction.setResponseCode((String) body.get("responseCode"));
|
transaction.setResponseCode((String) body.get("responseCode"));
|
||||||
transaction.setErrorMessage((String) body.get("errorMessage"));
|
transaction.setErrorMessage((String) body.get("errorMessage"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ public class StewardIntegrationProcessor implements TransactionProcessorInterfac
|
|||||||
if (body.get("status").equals("SUCCESS")) {
|
if (body.get("status").equals("SUCCESS")) {
|
||||||
transaction.setStatus(Status.SUCCESS);
|
transaction.setStatus(Status.SUCCESS);
|
||||||
transaction.setResponseCode((String) body.get("responseCode"));
|
transaction.setResponseCode((String) body.get("responseCode"));
|
||||||
|
transaction.setCreditRef((String) body.get("creditReference"));
|
||||||
transaction.setAdditionalData(body.get("additionalData"));
|
transaction.setAdditionalData(body.get("additionalData"));
|
||||||
transactionService.save(transaction);
|
transactionService.save(transaction);
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,103 @@
|
|||||||
package zw.qantra.tm.domain.services.processors.payments;
|
package zw.qantra.tm.domain.services.processors.payments;
|
||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
import java.math.BigDecimal;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import zw.qantra.tm.domain.dtos.SdkActionDto;
|
||||||
|
import zw.qantra.tm.domain.enums.Status;
|
||||||
import zw.qantra.tm.domain.models.Transaction;
|
import zw.qantra.tm.domain.models.Transaction;
|
||||||
|
import zw.qantra.tm.domain.services.TransactionService;
|
||||||
import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface;
|
import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface;
|
||||||
|
import zw.qantra.tm.rest.RestService;
|
||||||
|
|
||||||
@Service("REQUEST_ECOCASH_REMOTE")
|
@Service("REQUEST_ECOCASH_REMOTE")
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class EcocashRemotePaymentProcessor implements TransactionProcessorInterface {
|
public class EcocashRemotePaymentProcessor implements TransactionProcessorInterface {
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(EcocashRemotePaymentProcessor.class);
|
||||||
|
private final RestService restService;
|
||||||
|
private final TransactionService transactionService;
|
||||||
|
|
||||||
|
@Value("${sbz.aggregator.encryption-key}")
|
||||||
|
private String encryptionKey;
|
||||||
|
@Value("${sbz.aggregator.client-id}")
|
||||||
|
private String clientId;
|
||||||
|
@Value("${sbz.aggregator.client-secret}")
|
||||||
|
private String clientSecret;
|
||||||
|
@Value("${steward.payment.processor.url}")
|
||||||
|
private String url;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object process(Transaction transaction) throws Exception {
|
public Object process(Transaction transaction) throws Exception {
|
||||||
|
logger.info("processing transaction");
|
||||||
|
String token = restService.getMerchantToken();
|
||||||
|
|
||||||
return transaction;
|
// send total less gateway charges
|
||||||
|
BigDecimal totalAmount = transaction.getAmount().add(transaction.getTax()).add(transaction.getCharge());
|
||||||
|
|
||||||
|
String concat = clientId+
|
||||||
|
encryptionKey+
|
||||||
|
transaction.getCreditPhone()+
|
||||||
|
totalAmount;
|
||||||
|
|
||||||
|
String hex = getHash(concat);
|
||||||
|
|
||||||
|
SdkActionDto sdkActionDto = SdkActionDto.builder()
|
||||||
|
.clientId(clientId)
|
||||||
|
.clientSecret(clientSecret)
|
||||||
|
.phone(transaction.getCreditPhone())
|
||||||
|
.amount(totalAmount.toString())
|
||||||
|
.currency(transaction.getDebitCurrency().toString())
|
||||||
|
.hash(hex)
|
||||||
|
.authType("REMOTE")
|
||||||
|
.billerReference(transaction.getReference())
|
||||||
|
.action("PAYMENT")
|
||||||
|
.sandbox(true)
|
||||||
|
.paymentProcessorLabel("ECOCASH")
|
||||||
|
.responseUrl("https://peakapi.stewardpay.co.zw")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.set("Authorization", "Bearer " + token);
|
||||||
|
headers.set("Content-Type", "application/json");
|
||||||
|
|
||||||
|
try {
|
||||||
|
LinkedHashMap response = restService.postAs(url + "/merchant/sdk/action",
|
||||||
|
sdkActionDto, headers, LinkedHashMap.class);
|
||||||
|
|
||||||
|
LinkedHashMap body = (LinkedHashMap) response.get("body");
|
||||||
|
transaction.setSdkActionId((String) body.get("uid"));
|
||||||
|
|
||||||
|
if(body.get("status").equals("SUCCESS")){
|
||||||
|
transaction.setStatus(Status.SUCCESS);
|
||||||
|
transaction.setResponseCode("00");
|
||||||
|
|
||||||
|
LinkedHashMap transactionData = (LinkedHashMap) body.get("transaction");
|
||||||
|
transaction.setDebitRef((String) transactionData.get("debitReference"));
|
||||||
|
transactionService.save(transaction);
|
||||||
|
}else{
|
||||||
|
transaction.setStatus(Status.FAILED);
|
||||||
|
transaction.setResponseCode("01");
|
||||||
|
LinkedHashMap transactionData = (LinkedHashMap) body.get("transaction");
|
||||||
|
transaction.setErrorMessage((String) transactionData.get("errorMessage"));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Network error during payment processing: {}", e.getMessage(), e);
|
||||||
|
transaction.setStatus(Status.FAILED);
|
||||||
|
transaction.setResponseCode("99");
|
||||||
|
transaction.setErrorMessage("Network error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactionService.save(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -20,6 +107,64 @@ public class EcocashRemotePaymentProcessor implements TransactionProcessorInterf
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object poll(Transaction transaction) throws Exception {
|
public Object poll(Transaction transaction) throws Exception {
|
||||||
|
logger.info("polling transaction");
|
||||||
|
String token = restService.getMerchantToken();
|
||||||
|
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.set("Authorization", "Bearer " + token);
|
||||||
|
headers.set("Content-Type", "application/json");
|
||||||
|
|
||||||
|
try {
|
||||||
|
LinkedHashMap response = restService.getAs(url + "/merchant/sdk/reference/" + transaction.getReference(),
|
||||||
|
headers, LinkedHashMap.class);
|
||||||
|
|
||||||
|
LinkedHashMap body = (LinkedHashMap) response.get("body");
|
||||||
|
|
||||||
|
// only update tran status if polling returns a success
|
||||||
|
if(body.get("status").equals("SUCCESS")){
|
||||||
|
transaction.setStatus(Status.SUCCESS);
|
||||||
|
transaction.setResponseCode("00");
|
||||||
|
transaction.setTargetUrl((String) body.get("targetUrl"));
|
||||||
|
|
||||||
|
LinkedHashMap transactionData = (LinkedHashMap) body.get("transaction");
|
||||||
|
transaction.setDebitRef((String) transactionData.get("debitReference"));
|
||||||
|
|
||||||
|
transactionService.save(transaction);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Network error during payment processing: {}", e.getMessage(), e);
|
||||||
|
transaction.setStatus(Status.FAILED);
|
||||||
|
transaction.setResponseCode("99");
|
||||||
|
transaction.setErrorMessage("Network error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getHash(String originalString){
|
||||||
|
String hex = "";
|
||||||
|
MessageDigest digest = null;
|
||||||
|
try {
|
||||||
|
digest = MessageDigest.getInstance("SHA-256");
|
||||||
|
byte[] encodedhash = digest.digest(
|
||||||
|
originalString.getBytes(StandardCharsets.UTF_8));
|
||||||
|
hex = bytesToHex(encodedhash);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String bytesToHex(byte[] hash) {
|
||||||
|
StringBuilder hexString = new StringBuilder(2 * hash.length);
|
||||||
|
for (int i = 0; i < hash.length; i++) {
|
||||||
|
String hex = Integer.toHexString(0xff & hash[i]);
|
||||||
|
if(hex.length() == 1) {
|
||||||
|
hexString.append('0');
|
||||||
|
}
|
||||||
|
hexString.append(hex);
|
||||||
|
}
|
||||||
|
return hexString.toString();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ public class MPGSWebPaymentProcessor implements TransactionProcessorInterface {
|
|||||||
LinkedHashMap body = (LinkedHashMap) response.get("body");
|
LinkedHashMap body = (LinkedHashMap) response.get("body");
|
||||||
transaction.setSdkActionId((String) body.get("uid"));
|
transaction.setSdkActionId((String) body.get("uid"));
|
||||||
|
|
||||||
|
// if status is not failed, set the transaction to pending and send customer to the target url
|
||||||
if(!body.get("status").equals("FAILED")){
|
if(!body.get("status").equals("FAILED")){
|
||||||
transaction.setStatus(Status.PENDING);
|
transaction.setStatus(Status.PENDING);
|
||||||
transaction.setResponseCode("00");
|
transaction.setResponseCode("00");
|
||||||
@@ -86,7 +87,8 @@ public class MPGSWebPaymentProcessor implements TransactionProcessorInterface {
|
|||||||
}else{
|
}else{
|
||||||
transaction.setStatus(Status.FAILED);
|
transaction.setStatus(Status.FAILED);
|
||||||
transaction.setResponseCode("01");
|
transaction.setResponseCode("01");
|
||||||
transaction.setErrorMessage((String) body.get("message"));
|
LinkedHashMap transactionData = (LinkedHashMap) body.get("transaction");
|
||||||
|
transaction.setErrorMessage((String) transactionData.get("errorMessage"));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Network error during payment processing: {}", e.getMessage(), e);
|
logger.error("Network error during payment processing: {}", e.getMessage(), e);
|
||||||
@@ -119,6 +121,10 @@ public class MPGSWebPaymentProcessor implements TransactionProcessorInterface {
|
|||||||
transaction.setStatus(Status.SUCCESS);
|
transaction.setStatus(Status.SUCCESS);
|
||||||
transaction.setResponseCode("00");
|
transaction.setResponseCode("00");
|
||||||
transaction.setTargetUrl((String) body.get("targetUrl"));
|
transaction.setTargetUrl((String) body.get("targetUrl"));
|
||||||
|
|
||||||
|
LinkedHashMap transactionData = (LinkedHashMap) body.get("transaction");
|
||||||
|
transaction.setDebitRef((String) transactionData.get("debitReference"));
|
||||||
|
|
||||||
transactionService.save(transaction);
|
transactionService.save(transaction);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
80
src/main/java/zw/qantra/tm/utils/LogUtils.java
Normal file
80
src/main/java/zw/qantra/tm/utils/LogUtils.java
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package zw.qantra.tm.utils;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.util.StdDateFormat;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for pretty printing JSON in logs
|
||||||
|
*/
|
||||||
|
public class LogUtils {
|
||||||
|
|
||||||
|
private static final ObjectMapper prettyMapper = new ObjectMapper()
|
||||||
|
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
|
||||||
|
.setDateFormat(new StdDateFormat().withColonInTimeZone(true))
|
||||||
|
.registerModule(new JavaTimeModule())
|
||||||
|
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
||||||
|
.enable(SerializationFeature.INDENT_OUTPUT);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log an object as pretty JSON
|
||||||
|
* @param logger The SLF4J logger instance
|
||||||
|
* @param level The log level (info, debug, etc.)
|
||||||
|
* @param message The log message
|
||||||
|
* @param object The object to pretty print
|
||||||
|
*/
|
||||||
|
public static void logPrettyJson(Logger logger, String level, String message, Object object) {
|
||||||
|
try {
|
||||||
|
String prettyJson = prettyMapper.writeValueAsString(object);
|
||||||
|
switch (level.toLowerCase()) {
|
||||||
|
case "debug":
|
||||||
|
logger.debug("{}:\n{}", message, prettyJson);
|
||||||
|
break;
|
||||||
|
case "info":
|
||||||
|
logger.info("{}:\n{}", message, prettyJson);
|
||||||
|
break;
|
||||||
|
case "warn":
|
||||||
|
logger.warn("{}:\n{}", message, prettyJson);
|
||||||
|
break;
|
||||||
|
case "error":
|
||||||
|
logger.error("{}:\n{}", message, prettyJson);
|
||||||
|
break;
|
||||||
|
case "trace":
|
||||||
|
logger.trace("{}:\n{}", message, prettyJson);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger.info("{}:\n{}", message, prettyJson);
|
||||||
|
}
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
logger.error("Failed to pretty print JSON: {}", e.getMessage());
|
||||||
|
logger.info("{}: {}", message, object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log an object as pretty JSON at INFO level
|
||||||
|
* @param logger The SLF4J logger instance
|
||||||
|
* @param message The log message
|
||||||
|
* @param object The object to pretty print
|
||||||
|
*/
|
||||||
|
public static void logPrettyJson(Logger logger, String message, Object object) {
|
||||||
|
logPrettyJson(logger, "info", message, object);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get pretty JSON string from object
|
||||||
|
* @param object The object to convert
|
||||||
|
* @return Pretty formatted JSON string
|
||||||
|
*/
|
||||||
|
public static String toPrettyJson(Object object) {
|
||||||
|
try {
|
||||||
|
return prettyMapper.writeValueAsString(object);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
return object.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/main/resources/logback-spring.xml
Normal file
45
src/main/resources/logback-spring.xml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration>
|
||||||
|
<!-- Console appender with pattern that handles multiline JSON -->
|
||||||
|
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
|
||||||
|
<layout class="ch.qos.logback.classic.PatternLayout">
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
|
</layout>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- File appender for persistent logging -->
|
||||||
|
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>logs/application.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>logs/application.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
|
||||||
|
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||||
|
<maxFileSize>10MB</maxFileSize>
|
||||||
|
</timeBasedFileNamingAndTriggeringPolicy>
|
||||||
|
<maxHistory>30</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
|
||||||
|
<layout class="ch.qos.logback.classic.PatternLayout">
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
|
</layout>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- Root logger configuration -->
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="CONSOLE" />
|
||||||
|
<appender-ref ref="FILE" />
|
||||||
|
</root>
|
||||||
|
|
||||||
|
<!-- Specific logger for your application -->
|
||||||
|
<logger name="zw.qantra.tm" level="INFO" additivity="false">
|
||||||
|
<appender-ref ref="CONSOLE" />
|
||||||
|
<appender-ref ref="FILE" />
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<!-- Reduce noise from Spring and other frameworks -->
|
||||||
|
<logger name="org.springframework" level="WARN" />
|
||||||
|
<logger name="org.hibernate" level="WARN" />
|
||||||
|
<logger name="org.apache" level="WARN" />
|
||||||
|
</configuration>
|
||||||
Reference in New Issue
Block a user