diff --git a/src/main/java/zw/qantra/tm/domain/controllers/AdditionalDataController.java b/src/main/java/zw/qantra/tm/domain/controllers/AdditionalDataController.java new file mode 100644 index 0000000..08d6fdd --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/controllers/AdditionalDataController.java @@ -0,0 +1,66 @@ +package zw.qantra.tm.domain.controllers; + +import lombok.RequiredArgsConstructor; +import zw.qantra.tm.domain.services.AdditionalDataService; +import zw.qantra.tm.domain.models.AdditionalData; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import java.util.List; +import java.util.UUID; + +@RestController +@RequestMapping("/additional-data") +@RequiredArgsConstructor +public class AdditionalDataController { + Logger logger = LoggerFactory.getLogger(AdditionalDataController.class); + + private final AdditionalDataService additionalDataService; + + @GetMapping + public ResponseEntity> getAll() { + return ResponseEntity.ok(additionalDataService.getAllAdditionalData()); + } + + @GetMapping("/transaction/{transactionId}") + public ResponseEntity getByTransactionId(@PathVariable UUID transactionId) { + return additionalDataService.getAdditionalDataRepository().findByTransactionId(transactionId) + .map(ResponseEntity::ok) + .orElse(ResponseEntity.notFound().build()); + } + + @GetMapping("/{id}") + public ResponseEntity getById(@PathVariable UUID id) { + return additionalDataService.getAdditionalData(id) + .map(ResponseEntity::ok) + .orElse(ResponseEntity.notFound().build()); + } + + @PostMapping + public ResponseEntity create(@RequestBody AdditionalData additionalData) { + logger.info("incoming additional data: {}", additionalData); + AdditionalData createdAdditionalData = additionalDataService.createAdditionalData(additionalData); + logger.info("outgoing additional data: {}", createdAdditionalData); + return ResponseEntity.ok(createdAdditionalData); + } + + @PutMapping("/{id}") + public ResponseEntity update(@PathVariable UUID id, @RequestBody AdditionalData additionalData) { + AdditionalData updatedAdditionalData = additionalDataService.updateAdditionalData(id, additionalData); + if (updatedAdditionalData == null) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok(updatedAdditionalData); + } + + @DeleteMapping("/{id}") + public ResponseEntity delete(@PathVariable UUID id) { + boolean deleted = additionalDataService.deleteAdditionalData(id); + if (!deleted) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.noContent().build(); + } +} \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/controllers/CategoryController.java b/src/main/java/zw/qantra/tm/domain/controllers/CategoryController.java index 44fa0a6..aa8cf57 100644 --- a/src/main/java/zw/qantra/tm/domain/controllers/CategoryController.java +++ b/src/main/java/zw/qantra/tm/domain/controllers/CategoryController.java @@ -5,7 +5,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import zw.qantra.tm.domain.models.Category; import zw.qantra.tm.domain.services.CategoryService; -import zw.qantra.tm.rest.ApiResponse; import java.util.UUID; @@ -18,35 +17,35 @@ public class CategoryController { @GetMapping public ResponseEntity getAllCategories() { - return ApiResponse.ok(categoryService.getAllCategories()); + return ResponseEntity.ok(categoryService.getAllCategories()); } @GetMapping("/{id}") public ResponseEntity getCategory(@PathVariable UUID id) { return categoryService.getCategory(id) - .map(ApiResponse::ok) - .orElse(ApiResponse.notFound()); + .map(ResponseEntity::ok) + .orElse(ResponseEntity.notFound().build()); } @PostMapping public ResponseEntity createCategory(@RequestBody Category category) { - return ApiResponse.ok(categoryService.createCategory(category)); + return ResponseEntity.ok(categoryService.createCategory(category)); } @PutMapping("/{id}") public ResponseEntity updateCategory(@PathVariable UUID id, @RequestBody Category category) { Category updatedCategory = categoryService.updateCategory(id, category); if (updatedCategory != null) { - return ApiResponse.ok(updatedCategory); + return ResponseEntity.ok(updatedCategory); } - return ApiResponse.notFound(); + return ResponseEntity.notFound().build(); } @DeleteMapping("/{id}") public ResponseEntity deleteCategory(@PathVariable UUID id) { if (categoryService.deleteCategory(id)) { - return ApiResponse.ok("Category deleted successfully"); + return ResponseEntity.ok("Category deleted successfully"); } - return ApiResponse.notFound(); + return ResponseEntity.notFound().build(); } } \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/controllers/ConfigController.java b/src/main/java/zw/qantra/tm/domain/controllers/ConfigController.java index 03a386f..84cb852 100644 --- a/src/main/java/zw/qantra/tm/domain/controllers/ConfigController.java +++ b/src/main/java/zw/qantra/tm/domain/controllers/ConfigController.java @@ -8,10 +8,16 @@ import org.springframework.web.bind.annotation.RestController; import zw.qantra.tm.domain.enums.ChargeConditionType; import zw.qantra.tm.domain.enums.ChargeLabelEnum; import zw.qantra.tm.domain.enums.CurrencyType; +import zw.qantra.tm.domain.models.Category; import zw.qantra.tm.domain.models.Charge; import zw.qantra.tm.domain.models.ChargeCondition; +import zw.qantra.tm.domain.models.PaymentProcessor; +import zw.qantra.tm.domain.models.Provider; import zw.qantra.tm.domain.services.ChargeConditionService; import zw.qantra.tm.domain.services.ChargeService; +import zw.qantra.tm.domain.services.CategoryService; +import zw.qantra.tm.domain.services.PaymentProcessorService; +import zw.qantra.tm.domain.services.ProviderService; import java.math.BigDecimal; import java.util.Arrays; @@ -24,9 +30,90 @@ import java.util.List; public class ConfigController { private final ChargeConditionService chargeConditionService; private final ChargeService chargeService; - - @GetMapping("/charges") + private final PaymentProcessorService paymentProcessorService; + private final ProviderService providerService; + private final CategoryService categoryService; + + @GetMapping("/seed") public ResponseEntity charges(){ + // clear existing categories before seeding + categoryService.getCategoryRepository().deleteAll(); + // seed categories + List categories = Arrays.asList( + Category.builder() + .name("Airtime") + .description("Airtime for local mobile networks") + .label("AIRTIME") + .build(), + Category.builder() + .name("Utilities") + .description("Council bills & rates") + .label("UTILITIES") + .build() + ); + categoryService.getCategoryRepository().saveAll(categories); + + // clear existing providers before seeding + providerService.getProviderRepository().deleteAll(); + + // seed providers + List providers = Arrays.asList( + Provider.builder() + .description("Econet Airtime") + .clientId("econet_airtime") + .label("ECONET") + .category("AIRTIME") + .image("econet.png") + .externalId("78f1f497-c9eb-401c-b22c-884756e68e40") + .build(), + Provider.builder() + .description("NetOne Airtime") + .clientId("netone_usd_airtime") + .label("NETONE") + .category("AIRTIME") + .image("netone.png") + .externalId("95d485a7-39c9-4559-8a27-6521969abe8f") + .build(), + Provider.builder() + .description("Prepaid Zesa") + .clientId("powertel_zesa") + .label("ZESA") + .category("UTILITIES") + .image("zesa.png") + .externalId("0f67f7cc-b62b-4fb5-915f-6740b2afdba6") + .build(), + Provider.builder() + .description("Econet Broadband") + .clientId("econet_broadband_usd") + .label("ECONET_BROADBAND") + .category("AIRTIME") + .image("econet.png") + .externalId("287863e2-a791-4106-b629-79dadc9a6779") + .build() + ); + providerService.getProviderRepository().saveAll(providers); + + // Clear existing payment processors before seeding + paymentProcessorService.getPaymentProcessorRepository().deleteAll(); + + // seed payment processors + List 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 List chargeConditions = Arrays.asList( ChargeCondition.builder() .name("Ecocash Gateway") @@ -78,6 +165,6 @@ public class ConfigController { chargeService.getChargeRepository().saveAll(charges); - return ResponseEntity.ok("Charges configuration complete"); + return ResponseEntity.ok("Project configuration seeding complete"); } } diff --git a/src/main/java/zw/qantra/tm/domain/controllers/ProviderController.java b/src/main/java/zw/qantra/tm/domain/controllers/ProviderController.java index 7e9c0b4..3e857ba 100644 --- a/src/main/java/zw/qantra/tm/domain/controllers/ProviderController.java +++ b/src/main/java/zw/qantra/tm/domain/controllers/ProviderController.java @@ -5,7 +5,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import zw.qantra.tm.domain.models.Provider; import zw.qantra.tm.domain.services.ProviderService; -import zw.qantra.tm.rest.ApiResponse; import java.util.UUID; @@ -18,40 +17,40 @@ public class ProviderController { @GetMapping public ResponseEntity getProviders(@RequestParam(required = false) String category) { - return ApiResponse.ok(providerService.getProviders(category)); + return ResponseEntity.ok(providerService.getProviders(category)); } @GetMapping("/{id}/products") public ResponseEntity getProducts(@PathVariable UUID id) { - return ApiResponse.ok(providerService.getProviderProducts(id)); + return ResponseEntity.ok(providerService.getProviderProducts(id)); } @GetMapping("/{id}") public ResponseEntity getProvider(@PathVariable UUID id) { return providerService.getProvider(id) - .map(ApiResponse::ok) - .orElse(ApiResponse.notFound()); + .map(ResponseEntity::ok) + .orElse(ResponseEntity.notFound().build()); } @PostMapping public ResponseEntity createProvider(@RequestBody Provider provider) { - return ApiResponse.ok(providerService.createProvider(provider)); + return ResponseEntity.ok(providerService.createProvider(provider)); } @PutMapping("/{id}") public ResponseEntity updateProvider(@PathVariable UUID id, @RequestBody Provider provider) { Provider updatedProvider = providerService.updateProvider(id, provider); if (updatedProvider != null) { - return ApiResponse.ok(updatedProvider); + return ResponseEntity.ok(updatedProvider); } - return ApiResponse.notFound(); + return ResponseEntity.notFound().build(); } @DeleteMapping("/{id}") public ResponseEntity deleteProvider(@PathVariable UUID id) { if (providerService.deleteProvider(id)) { - return ApiResponse.ok("Provider deleted successfully"); + return ResponseEntity.ok("Provider deleted successfully"); } - return ApiResponse.notFound(); + return ResponseEntity.notFound().build(); } } diff --git a/src/main/java/zw/qantra/tm/domain/controllers/TransactonController.java b/src/main/java/zw/qantra/tm/domain/controllers/TransactonController.java index 162c508..e120136 100644 --- a/src/main/java/zw/qantra/tm/domain/controllers/TransactonController.java +++ b/src/main/java/zw/qantra/tm/domain/controllers/TransactonController.java @@ -4,8 +4,11 @@ import lombok.RequiredArgsConstructor; import zw.qantra.tm.domain.services.TransactionService; import zw.qantra.tm.domain.models.Transaction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; + import java.util.List; import java.util.UUID; @@ -13,6 +16,7 @@ import java.util.UUID; @RequestMapping("/transaction") @RequiredArgsConstructor public class TransactonController { + Logger logger = LoggerFactory.getLogger(TransactonController.class); private final TransactionService transactionService; @@ -28,9 +32,17 @@ public class TransactonController { .orElse(ResponseEntity.notFound().build()); } + @GetMapping("/poll/{id}") + public ResponseEntity poll(@PathVariable UUID id) { + return ResponseEntity.ok(transactionService.poll(id)); + } + @PostMapping public ResponseEntity create(@RequestBody Transaction transaction) { - return ResponseEntity.ok(transactionService.execute(transaction)); + logger.info("incoming transaction: {}", transaction); + Transaction createdTransaction = transactionService.execute(transaction); + logger.info("outgoing transaction: {}", createdTransaction); + return ResponseEntity.ok(createdTransaction); } @PutMapping("/{id}") diff --git a/src/main/java/zw/qantra/tm/domain/dtos/SdkActionDto.java b/src/main/java/zw/qantra/tm/domain/dtos/SdkActionDto.java new file mode 100644 index 0000000..e986cf5 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/dtos/SdkActionDto.java @@ -0,0 +1,27 @@ +package zw.qantra.tm.domain.dtos; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SdkActionDto { + + private String clientId; + private String clientSecret; + private String phone; + private String amount; + private String currency; + private String hash; + private String authType; + private String billerReference; + private String action; + private Boolean sandbox; + private String paymentProcessorLabel; + private String responseUrl; + +} diff --git a/src/main/java/zw/qantra/tm/domain/enums/AuthType.java b/src/main/java/zw/qantra/tm/domain/enums/AuthType.java new file mode 100644 index 0000000..52d9027 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/enums/AuthType.java @@ -0,0 +1,9 @@ +package zw.qantra.tm.domain.enums; + +public enum AuthType { + WEB, + USSD, + WHATSAPP, + MOBILE, + REMOTE +} \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/enums/RequestType.java b/src/main/java/zw/qantra/tm/domain/enums/RequestType.java index b4386e0..5338475 100644 --- a/src/main/java/zw/qantra/tm/domain/enums/RequestType.java +++ b/src/main/java/zw/qantra/tm/domain/enums/RequestType.java @@ -1,6 +1,7 @@ package zw.qantra.tm.domain.enums; public enum RequestType { + CONFIRM, REQUEST, - CONFIRM + INTEGRATION } diff --git a/src/main/java/zw/qantra/tm/domain/models/AdditionalData.java b/src/main/java/zw/qantra/tm/domain/models/AdditionalData.java new file mode 100644 index 0000000..848da4c --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/models/AdditionalData.java @@ -0,0 +1,27 @@ +package zw.qantra.tm.domain.models; + + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@Entity +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class AdditionalData extends BaseEntity { + @OneToOne + @JoinColumn(name = "transaction_id") + private Transaction transaction; + + @Column(columnDefinition = "TEXT") + private String jsonString; +} diff --git a/src/main/java/zw/qantra/tm/domain/models/PaymentProcessor.java b/src/main/java/zw/qantra/tm/domain/models/PaymentProcessor.java index b30e326..a151d9b 100644 --- a/src/main/java/zw/qantra/tm/domain/models/PaymentProcessor.java +++ b/src/main/java/zw/qantra/tm/domain/models/PaymentProcessor.java @@ -1,5 +1,6 @@ package zw.qantra.tm.domain.models; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import lombok.*; @@ -14,5 +15,6 @@ public class PaymentProcessor extends BaseEntity { private String description; private String label; private String type; + @Column(columnDefinition = "TEXT") private String image; } diff --git a/src/main/java/zw/qantra/tm/domain/models/Transaction.java b/src/main/java/zw/qantra/tm/domain/models/Transaction.java index a6228de..0833e4e 100644 --- a/src/main/java/zw/qantra/tm/domain/models/Transaction.java +++ b/src/main/java/zw/qantra/tm/domain/models/Transaction.java @@ -3,12 +3,12 @@ package zw.qantra.tm.domain.models; import com.fasterxml.jackson.annotation.JsonInclude; import jakarta.persistence.*; import lombok.*; +import zw.qantra.tm.domain.enums.AuthType; import zw.qantra.tm.domain.enums.CurrencyType; import zw.qantra.tm.domain.enums.RequestType; import zw.qantra.tm.domain.enums.Status; import java.math.BigDecimal; -import java.util.List; @Entity @Getter @@ -35,12 +35,14 @@ public class Transaction extends BaseEntity { private String channel; private String debitPhone; private String debitAccount; + @Enumerated(EnumType.STRING) private CurrencyType debitCurrency; private String debitName; private String debitCard; private String debitRef; private String creditPhone; private String creditAccount; + @Enumerated(EnumType.STRING) private CurrencyType creditCurrency; private String creditName; private String creditCard; @@ -50,12 +52,20 @@ public class Transaction extends BaseEntity { private String aggregatorId; private String billName; private String billProductName; + @Enumerated(EnumType.STRING) private RequestType type; - + @Enumerated(EnumType.STRING) + private AuthType authType; + + @Column(columnDefinition = "TEXT") private String errorMessage; private String responseCode; private Status status; + @Column(columnDefinition = "TEXT") + private String targetUrl; + private String sdkActionId; + @Transient - private List additionalData; + private Object additionalData; } \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/repositories/AdditionalDataRepository.java b/src/main/java/zw/qantra/tm/domain/repositories/AdditionalDataRepository.java new file mode 100644 index 0000000..2bb3df5 --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/repositories/AdditionalDataRepository.java @@ -0,0 +1,13 @@ +package zw.qantra.tm.domain.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import zw.qantra.tm.domain.models.AdditionalData; + +import java.util.Optional; +import java.util.UUID; + +@Repository +public interface AdditionalDataRepository extends JpaRepository { + Optional findByTransactionId(UUID transactionId); +} \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/repositories/ProviderRepository.java b/src/main/java/zw/qantra/tm/domain/repositories/ProviderRepository.java index a014887..352aad2 100644 --- a/src/main/java/zw/qantra/tm/domain/repositories/ProviderRepository.java +++ b/src/main/java/zw/qantra/tm/domain/repositories/ProviderRepository.java @@ -10,4 +10,5 @@ import java.util.UUID; @Repository public interface ProviderRepository extends JpaRepository { List findByCategory(String category); + Provider findByClientId(String clientId); } \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/services/AdditionalDataService.java b/src/main/java/zw/qantra/tm/domain/services/AdditionalDataService.java new file mode 100644 index 0000000..4a6569f --- /dev/null +++ b/src/main/java/zw/qantra/tm/domain/services/AdditionalDataService.java @@ -0,0 +1,46 @@ +package zw.qantra.tm.domain.services; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import zw.qantra.tm.domain.models.AdditionalData; +import zw.qantra.tm.domain.repositories.AdditionalDataRepository; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Getter +@Service +@RequiredArgsConstructor +public class AdditionalDataService { + private final AdditionalDataRepository additionalDataRepository; + + public List getAllAdditionalData() { + return additionalDataRepository.findAll(); + } + + public Optional getAdditionalData(UUID id) { + return additionalDataRepository.findById(id); + } + + public AdditionalData createAdditionalData(AdditionalData additionalData) { + return additionalDataRepository.save(additionalData); + } + + public AdditionalData updateAdditionalData(UUID id, AdditionalData additionalData) { + if (additionalDataRepository.existsById(id)) { + additionalData.setId(id); + return additionalDataRepository.save(additionalData); + } + return null; + } + + public boolean deleteAdditionalData(UUID id) { + if (additionalDataRepository.existsById(id)) { + additionalDataRepository.deleteById(id); + return true; + } + return false; + } +} \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/services/ProviderService.java b/src/main/java/zw/qantra/tm/domain/services/ProviderService.java index 6936227..c419bff 100644 --- a/src/main/java/zw/qantra/tm/domain/services/ProviderService.java +++ b/src/main/java/zw/qantra/tm/domain/services/ProviderService.java @@ -3,6 +3,8 @@ package zw.qantra.tm.domain.services; import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.WordUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -64,7 +66,7 @@ public class ProviderService { headers.setBearerAuth(token); List existingProviders; - if (category != null) { + if (category != null && !category.isEmpty()) { existingProviders = getProviderRepository().findByCategory(category); } else { existingProviders = getProviderRepository().findAll(); @@ -84,6 +86,24 @@ public class ProviderService { LinkedHashMap body = (LinkedHashMap) response.get("body"); List content = (List) body.get("content"); + for (LinkedHashMap provider : content) { + String clientId = (String) provider.get("clientId"); + if (existingProviderIds.contains(clientId)) { + // Update existing provider + Provider existingProvider = existingProviders.stream() + .filter(p -> p.getClientId().equals(clientId)) + .findFirst() + .orElse(null); + if (existingProvider != null) { + provider.put("name", WordUtils.capitalizeFully((String)provider.get("name"))); + provider.put("description", existingProvider.getDescription()); + provider.put("image", existingProvider.getImage()); + provider.put("label", existingProvider.getLabel()); + provider.put("category", existingProvider.getCategory()); + } + } + } + return content; } diff --git a/src/main/java/zw/qantra/tm/domain/services/TransactionService.java b/src/main/java/zw/qantra/tm/domain/services/TransactionService.java index c116808..0108630 100644 --- a/src/main/java/zw/qantra/tm/domain/services/TransactionService.java +++ b/src/main/java/zw/qantra/tm/domain/services/TransactionService.java @@ -1,8 +1,17 @@ package zw.qantra.tm.domain.services; +import lombok.Getter; import lombok.RequiredArgsConstructor; + +import java.util.UUID; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; 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.Provider; import zw.qantra.tm.domain.models.Transaction; import zw.qantra.tm.domain.repositories.TransactionRepository; import zw.qantra.tm.domain.services.factories.PaymentProcessorFactory; @@ -10,27 +19,54 @@ import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface; import zw.qantra.tm.domain.services.processors.handlers.CalculateChargesHandler; import zw.qantra.tm.domain.services.processors.handlers.LogicPipeline; import zw.qantra.tm.domain.services.processors.handlers.ValidationHandler; +import zw.qantra.tm.exceptions.ApiException; @Service @RequiredArgsConstructor public class TransactionService { + Logger logger = LoggerFactory.getLogger(TransactionService.class); + + @Getter private final TransactionRepository transactionRepository; private final PaymentProcessorFactory paymentProcessorFactory; private final CalculateChargesHandler calculateChargesHandler; + private final ProviderService providerService; - public TransactionRepository getTransactionRepository() { - return transactionRepository; + public Transaction poll(UUID uuid){ + if(!transactionRepository.existsById(uuid)){ + throw new ApiException("Transaction not found for ID: " + uuid); + } + + Transaction transaction = transactionRepository.findById(uuid).get(); + + // no persistence happens during polling unless the transaction status changes + String label = getLabel(transaction); + logger.info("label: {}", label); + + TransactionProcessorInterface transactionProcessorInterface = + (TransactionProcessorInterface) paymentProcessorFactory.getPaymentProcessor(label); + try { + transactionProcessorInterface.poll(transaction); + } catch (Exception e) { + e.printStackTrace(); + transaction.setStatus(Status.FAILED); + transaction.setResponseCode("01"); + transaction.setErrorMessage(e.getMessage()); + } + return transaction; } public Transaction execute(Transaction transaction) { + transaction.setReference(UUID.randomUUID().toString()); LogicPipeline transactionLogic = new LogicPipeline<>(calculateChargesHandler) .addHandler(new ValidationHandler()); - transaction = transactionLogic.execute(transaction); - String label = transaction.getType() + "_" + transaction.getIntegrationProcessorLabel(); + String label = getLabel(transaction); + logger.info("label: {}", label); + TransactionProcessorInterface transactionProcessorInterface = (TransactionProcessorInterface) paymentProcessorFactory.getPaymentProcessor(label); @@ -46,4 +82,27 @@ public class TransactionService { } return transaction; } + + private String getLabel(Transaction transaction){ + String label = ""; + if(transaction.getType() == RequestType.CONFIRM){ + // get processor label from billClientId + Provider provider = providerService.getProviderRepository() + .findByClientId(transaction.getBillClientId()); + if(provider == null){ + throw new ApiException("Provider not found for billClientId: " + transaction.getBillClientId()); + } + label = transaction.getType() + "_" + + provider.getLabel(); + }else{ + label = transaction.getType() + "_" + + transaction.getPaymentProcessorLabel() + "_" + + transaction.getAuthType(); + } + return label; + } + + public Transaction save(Transaction transaction){ + return transactionRepository.save(transaction); + } } \ No newline at end of file diff --git a/src/main/java/zw/qantra/tm/domain/services/processors/TransactionProcessorInterface.java b/src/main/java/zw/qantra/tm/domain/services/processors/TransactionProcessorInterface.java index c7f8596..7d9cd8f 100644 --- a/src/main/java/zw/qantra/tm/domain/services/processors/TransactionProcessorInterface.java +++ b/src/main/java/zw/qantra/tm/domain/services/processors/TransactionProcessorInterface.java @@ -5,4 +5,5 @@ import zw.qantra.tm.domain.models.Transaction; public interface TransactionProcessorInterface { Object process(Transaction transaction) throws Exception; Object reverse(Transaction transaction); + Object poll(Transaction transaction) throws Exception; } diff --git a/src/main/java/zw/qantra/tm/domain/services/processors/confirmations/ZesaConfirmationProcessor.java b/src/main/java/zw/qantra/tm/domain/services/processors/confirmations/ZesaConfirmationProcessor.java index f55f31d..331f6a9 100644 --- a/src/main/java/zw/qantra/tm/domain/services/processors/confirmations/ZesaConfirmationProcessor.java +++ b/src/main/java/zw/qantra/tm/domain/services/processors/confirmations/ZesaConfirmationProcessor.java @@ -1,27 +1,45 @@ package zw.qantra.tm.domain.services.processors.confirmations; import lombok.RequiredArgsConstructor; + +import org.slf4j.LoggerFactory; +import org.slf4j.Logger; 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.Map; +import java.util.LinkedHashMap; @Service("CONFIRM_ZESA") @RequiredArgsConstructor public class ZesaConfirmationProcessor implements TransactionProcessorInterface { + Logger logger = LoggerFactory.getLogger(ZesaConfirmationProcessor.class); + private final RestService restService; + private final TransactionService transactionService; + private final AdditionalDataService additionalDataService; @Value("${sbz.aggregator.url}") private String aggregatorUrl; + @Value("${sbz.aggregator.client-id}") + private String aggregatorClientId; + @Value("${sbz.aggregator.phone}") + private String aggregatorPhone; + @Value("${sbz.aggregator.account}") + private String aggregatorAccount; @Override public Object process(Transaction transaction) throws Exception { - System.out.println("processing transaction"); + logger.info("processing transaction"); String token = restService.getMerchantToken(); BillPaymentDto request = BillPaymentDto.builder() @@ -30,25 +48,50 @@ public class ZesaConfirmationProcessor implements TransactionProcessorInterface .debitRef(transaction.getDebitRef()) .debitCurrency(transaction.getDebitCurrency()) .amount(transaction.getAmount()) - .aggregatorId(transaction.getAggregatorId()) - .debitPhone(transaction.getDebitPhone()) - .debitAccount(transaction.getDebitAccount()) - .creditAccount(transaction.getCreditAccount()) - .creditPhone(transaction.getCreditPhone()) + .aggregatorId(aggregatorClientId) + .debitPhone(aggregatorPhone) // qantra mobile number + .debitAccount(aggregatorAccount) // qantra corporate account + .creditAccount(transaction.getCreditAccount()) // customer's zesa meter number + .creditPhone(transaction.getCreditPhone()) // customer's contact phone number .billName(transaction.getBillName()) .build(); - String url = aggregatorUrl + "/v1/merchant/aggregation/transaction"; + String url = aggregatorUrl + "/merchant/aggregation/transaction"; - Map response = restService.postWithToken(token, url, request, Map.class); + LinkedHashMap response = restService.postWithToken(token, url, request, LinkedHashMap.class); - System.out.println(response); + logger.info("response: {}", response.toString()); - return transaction; + LinkedHashMap body = (LinkedHashMap) response.get("body"); + + if (body.get("status").equals("SUCCESS")) { + transaction.setStatus(Status.SUCCESS); + transaction.setResponseCode((String) body.get("responseCode")); + transaction.setAdditionalData(body.get("additionalData")); + transactionService.save(transaction); + + AdditionalData additionalData = AdditionalData.builder() + .transaction(transaction) + .jsonString(Utils.toJson(body.get("additionalData"))) + .build(); + additionalDataService.getAdditionalDataRepository().save(additionalData); + + } else { + transaction.setStatus(Status.FAILED); + transaction.setErrorMessage((String) body.get("message")); + transaction.setResponseCode((String) body.get("responseCode")); + } + + return transactionService.save(transaction); } @Override public Object reverse(Transaction transaction) { return transaction; } + + @Override + public Object poll(Transaction transaction) { + return transaction; + } } diff --git a/src/main/java/zw/qantra/tm/domain/services/processors/handlers/CalculateChargesHandler.java b/src/main/java/zw/qantra/tm/domain/services/processors/handlers/CalculateChargesHandler.java index 420056d..ac9bb4a 100644 --- a/src/main/java/zw/qantra/tm/domain/services/processors/handlers/CalculateChargesHandler.java +++ b/src/main/java/zw/qantra/tm/domain/services/processors/handlers/CalculateChargesHandler.java @@ -1,11 +1,12 @@ package zw.qantra.tm.domain.services.processors.handlers; -import lombok.AllArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import zw.qantra.tm.domain.models.Charge; import zw.qantra.tm.domain.models.ChargeCondition; import zw.qantra.tm.domain.models.Transaction; -import zw.qantra.tm.domain.services.ChargeConditionService; import zw.qantra.tm.domain.services.ChargeService; import java.math.BigDecimal; @@ -15,15 +16,16 @@ import java.util.Arrays; import java.util.List; -@AllArgsConstructor @Service public class CalculateChargesHandler implements LogicHandler { - private ChargeConditionService chargeConditionService; + Logger logger = LoggerFactory.getLogger(CalculateChargesHandler.class); + + @Autowired private ChargeService chargeService; @Override public Transaction process(Transaction transaction) { - System.out.println("processing charges"); + logger.info("processing charges"); transaction.setCharge(BigDecimal.ZERO); transaction.setTax(BigDecimal.ZERO); transaction.setGatewayCharge(BigDecimal.ZERO); @@ -122,6 +124,12 @@ public class CalculateChargesHandler implements LogicHandler T getJson(String json, Class clazz){ + public static T fromJson(String json, Class clazz){ ObjectMapper mapper = new ObjectMapper(); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); mapper.setDateFormat(new StdDateFormat().withColonInTimeZone(true)); @@ -56,7 +56,7 @@ public class Utils { return (T) obj; } - public static String setJson(Object obj){ + public static String toJson(Object obj){ ObjectMapper mapper = new ObjectMapper(); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); mapper.setDateFormat(new StdDateFormat().withColonInTimeZone(true)); diff --git a/src/main/resources/application-lab.properties b/src/main/resources/application-lab.properties index 29aa4f1..c8e0ca6 100644 --- a/src/main/resources/application-lab.properties +++ b/src/main/resources/application-lab.properties @@ -15,5 +15,10 @@ spring.datasource.username=postgres spring.datasource.password=password sbz.aggregator.url=http://192.168.16.29:24050/v1 -sbz.aggregator.client-id=small_bakery -sbz.aggregator.client-secret=tmtCUeJA1dQezn405HSrYSdhV81XiBEi \ No newline at end of file +sbz.aggregator.client-id=steward_pay +sbz.aggregator.client-secret=XjkbBAJeJCnAplYDTbp5xOjeyySVnYGp +sbz.aggregator.phone=263773591219 +sbz.aggregator.account=1046737845 +sbz.aggregator.encryption-key=735470ce-8efb-4c0f-ba3f-7298f805ab33 + +steward.payment.processor.url=http://192.168.16.29:24050/v1 \ No newline at end of file