completed zesa journey
This commit is contained in:
@@ -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<List<AdditionalData>> getAll() {
|
||||||
|
return ResponseEntity.ok(additionalDataService.getAllAdditionalData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/transaction/{transactionId}")
|
||||||
|
public ResponseEntity<AdditionalData> getByTransactionId(@PathVariable UUID transactionId) {
|
||||||
|
return additionalDataService.getAdditionalDataRepository().findByTransactionId(transactionId)
|
||||||
|
.map(ResponseEntity::ok)
|
||||||
|
.orElse(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ResponseEntity<AdditionalData> getById(@PathVariable UUID id) {
|
||||||
|
return additionalDataService.getAdditionalData(id)
|
||||||
|
.map(ResponseEntity::ok)
|
||||||
|
.orElse(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<AdditionalData> 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<AdditionalData> 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<Void> delete(@PathVariable UUID id) {
|
||||||
|
boolean deleted = additionalDataService.deleteAdditionalData(id);
|
||||||
|
if (!deleted) {
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
}
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@ import org.springframework.http.ResponseEntity;
|
|||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import zw.qantra.tm.domain.models.Category;
|
import zw.qantra.tm.domain.models.Category;
|
||||||
import zw.qantra.tm.domain.services.CategoryService;
|
import zw.qantra.tm.domain.services.CategoryService;
|
||||||
import zw.qantra.tm.rest.ApiResponse;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@@ -18,35 +17,35 @@ public class CategoryController {
|
|||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity getAllCategories() {
|
public ResponseEntity getAllCategories() {
|
||||||
return ApiResponse.ok(categoryService.getAllCategories());
|
return ResponseEntity.ok(categoryService.getAllCategories());
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
public ResponseEntity getCategory(@PathVariable UUID id) {
|
public ResponseEntity getCategory(@PathVariable UUID id) {
|
||||||
return categoryService.getCategory(id)
|
return categoryService.getCategory(id)
|
||||||
.map(ApiResponse::ok)
|
.map(ResponseEntity::ok)
|
||||||
.orElse(ApiResponse.notFound());
|
.orElse(ResponseEntity.notFound().build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
public ResponseEntity createCategory(@RequestBody Category category) {
|
public ResponseEntity createCategory(@RequestBody Category category) {
|
||||||
return ApiResponse.ok(categoryService.createCategory(category));
|
return ResponseEntity.ok(categoryService.createCategory(category));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/{id}")
|
@PutMapping("/{id}")
|
||||||
public ResponseEntity updateCategory(@PathVariable UUID id, @RequestBody Category category) {
|
public ResponseEntity updateCategory(@PathVariable UUID id, @RequestBody Category category) {
|
||||||
Category updatedCategory = categoryService.updateCategory(id, category);
|
Category updatedCategory = categoryService.updateCategory(id, category);
|
||||||
if (updatedCategory != null) {
|
if (updatedCategory != null) {
|
||||||
return ApiResponse.ok(updatedCategory);
|
return ResponseEntity.ok(updatedCategory);
|
||||||
}
|
}
|
||||||
return ApiResponse.notFound();
|
return ResponseEntity.notFound().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
public ResponseEntity deleteCategory(@PathVariable UUID id) {
|
public ResponseEntity deleteCategory(@PathVariable UUID id) {
|
||||||
if (categoryService.deleteCategory(id)) {
|
if (categoryService.deleteCategory(id)) {
|
||||||
return ApiResponse.ok("Category deleted successfully");
|
return ResponseEntity.ok("Category deleted successfully");
|
||||||
}
|
}
|
||||||
return ApiResponse.notFound();
|
return ResponseEntity.notFound().build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,10 +8,16 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
import zw.qantra.tm.domain.enums.ChargeConditionType;
|
import zw.qantra.tm.domain.enums.ChargeConditionType;
|
||||||
import zw.qantra.tm.domain.enums.ChargeLabelEnum;
|
import zw.qantra.tm.domain.enums.ChargeLabelEnum;
|
||||||
import zw.qantra.tm.domain.enums.CurrencyType;
|
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.Charge;
|
||||||
import zw.qantra.tm.domain.models.ChargeCondition;
|
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.ChargeConditionService;
|
||||||
import zw.qantra.tm.domain.services.ChargeService;
|
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.math.BigDecimal;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -24,9 +30,90 @@ import java.util.List;
|
|||||||
public class ConfigController {
|
public class ConfigController {
|
||||||
private final ChargeConditionService chargeConditionService;
|
private final ChargeConditionService chargeConditionService;
|
||||||
private final ChargeService chargeService;
|
private final ChargeService chargeService;
|
||||||
|
private final PaymentProcessorService paymentProcessorService;
|
||||||
@GetMapping("/charges")
|
private final ProviderService providerService;
|
||||||
|
private final CategoryService categoryService;
|
||||||
|
|
||||||
|
@GetMapping("/seed")
|
||||||
public ResponseEntity charges(){
|
public ResponseEntity charges(){
|
||||||
|
// clear existing categories before seeding
|
||||||
|
categoryService.getCategoryRepository().deleteAll();
|
||||||
|
// seed categories
|
||||||
|
List<Category> 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<Provider> 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<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
|
||||||
List<ChargeCondition> chargeConditions = Arrays.asList(
|
List<ChargeCondition> chargeConditions = Arrays.asList(
|
||||||
ChargeCondition.builder()
|
ChargeCondition.builder()
|
||||||
.name("Ecocash Gateway")
|
.name("Ecocash Gateway")
|
||||||
@@ -78,6 +165,6 @@ public class ConfigController {
|
|||||||
|
|
||||||
chargeService.getChargeRepository().saveAll(charges);
|
chargeService.getChargeRepository().saveAll(charges);
|
||||||
|
|
||||||
return ResponseEntity.ok("Charges configuration complete");
|
return ResponseEntity.ok("Project configuration seeding complete");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import org.springframework.http.ResponseEntity;
|
|||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import zw.qantra.tm.domain.models.Provider;
|
import zw.qantra.tm.domain.models.Provider;
|
||||||
import zw.qantra.tm.domain.services.ProviderService;
|
import zw.qantra.tm.domain.services.ProviderService;
|
||||||
import zw.qantra.tm.rest.ApiResponse;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@@ -18,40 +17,40 @@ public class ProviderController {
|
|||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity getProviders(@RequestParam(required = false) String category) {
|
public ResponseEntity getProviders(@RequestParam(required = false) String category) {
|
||||||
return ApiResponse.ok(providerService.getProviders(category));
|
return ResponseEntity.ok(providerService.getProviders(category));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}/products")
|
@GetMapping("/{id}/products")
|
||||||
public ResponseEntity getProducts(@PathVariable UUID id) {
|
public ResponseEntity getProducts(@PathVariable UUID id) {
|
||||||
return ApiResponse.ok(providerService.getProviderProducts(id));
|
return ResponseEntity.ok(providerService.getProviderProducts(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
public ResponseEntity getProvider(@PathVariable UUID id) {
|
public ResponseEntity getProvider(@PathVariable UUID id) {
|
||||||
return providerService.getProvider(id)
|
return providerService.getProvider(id)
|
||||||
.map(ApiResponse::ok)
|
.map(ResponseEntity::ok)
|
||||||
.orElse(ApiResponse.notFound());
|
.orElse(ResponseEntity.notFound().build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
public ResponseEntity createProvider(@RequestBody Provider provider) {
|
public ResponseEntity createProvider(@RequestBody Provider provider) {
|
||||||
return ApiResponse.ok(providerService.createProvider(provider));
|
return ResponseEntity.ok(providerService.createProvider(provider));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/{id}")
|
@PutMapping("/{id}")
|
||||||
public ResponseEntity updateProvider(@PathVariable UUID id, @RequestBody Provider provider) {
|
public ResponseEntity updateProvider(@PathVariable UUID id, @RequestBody Provider provider) {
|
||||||
Provider updatedProvider = providerService.updateProvider(id, provider);
|
Provider updatedProvider = providerService.updateProvider(id, provider);
|
||||||
if (updatedProvider != null) {
|
if (updatedProvider != null) {
|
||||||
return ApiResponse.ok(updatedProvider);
|
return ResponseEntity.ok(updatedProvider);
|
||||||
}
|
}
|
||||||
return ApiResponse.notFound();
|
return ResponseEntity.notFound().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
public ResponseEntity deleteProvider(@PathVariable UUID id) {
|
public ResponseEntity deleteProvider(@PathVariable UUID id) {
|
||||||
if (providerService.deleteProvider(id)) {
|
if (providerService.deleteProvider(id)) {
|
||||||
return ApiResponse.ok("Provider deleted successfully");
|
return ResponseEntity.ok("Provider deleted successfully");
|
||||||
}
|
}
|
||||||
return ApiResponse.notFound();
|
return ResponseEntity.notFound().build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import zw.qantra.tm.domain.services.TransactionService;
|
import zw.qantra.tm.domain.services.TransactionService;
|
||||||
import zw.qantra.tm.domain.models.Transaction;
|
import zw.qantra.tm.domain.models.Transaction;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
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 java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@@ -13,6 +16,7 @@ import java.util.UUID;
|
|||||||
@RequestMapping("/transaction")
|
@RequestMapping("/transaction")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class TransactonController {
|
public class TransactonController {
|
||||||
|
Logger logger = LoggerFactory.getLogger(TransactonController.class);
|
||||||
|
|
||||||
private final TransactionService transactionService;
|
private final TransactionService transactionService;
|
||||||
|
|
||||||
@@ -28,9 +32,17 @@ public class TransactonController {
|
|||||||
.orElse(ResponseEntity.notFound().build());
|
.orElse(ResponseEntity.notFound().build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/poll/{id}")
|
||||||
|
public ResponseEntity<Transaction> poll(@PathVariable UUID id) {
|
||||||
|
return ResponseEntity.ok(transactionService.poll(id));
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
public ResponseEntity<Transaction> create(@RequestBody Transaction transaction) {
|
public ResponseEntity<Transaction> 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}")
|
@PutMapping("/{id}")
|
||||||
|
|||||||
27
src/main/java/zw/qantra/tm/domain/dtos/SdkActionDto.java
Normal file
27
src/main/java/zw/qantra/tm/domain/dtos/SdkActionDto.java
Normal file
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
9
src/main/java/zw/qantra/tm/domain/enums/AuthType.java
Normal file
9
src/main/java/zw/qantra/tm/domain/enums/AuthType.java
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package zw.qantra.tm.domain.enums;
|
||||||
|
|
||||||
|
public enum AuthType {
|
||||||
|
WEB,
|
||||||
|
USSD,
|
||||||
|
WHATSAPP,
|
||||||
|
MOBILE,
|
||||||
|
REMOTE
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package zw.qantra.tm.domain.enums;
|
package zw.qantra.tm.domain.enums;
|
||||||
|
|
||||||
public enum RequestType {
|
public enum RequestType {
|
||||||
|
CONFIRM,
|
||||||
REQUEST,
|
REQUEST,
|
||||||
CONFIRM
|
INTEGRATION
|
||||||
}
|
}
|
||||||
|
|||||||
27
src/main/java/zw/qantra/tm/domain/models/AdditionalData.java
Normal file
27
src/main/java/zw/qantra/tm/domain/models/AdditionalData.java
Normal file
@@ -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;
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package zw.qantra.tm.domain.models;
|
package zw.qantra.tm.domain.models;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
|
|
||||||
@@ -14,5 +15,6 @@ public class PaymentProcessor extends BaseEntity {
|
|||||||
private String description;
|
private String description;
|
||||||
private String label;
|
private String label;
|
||||||
private String type;
|
private String type;
|
||||||
|
@Column(columnDefinition = "TEXT")
|
||||||
private String image;
|
private String image;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ package zw.qantra.tm.domain.models;
|
|||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
|
import zw.qantra.tm.domain.enums.AuthType;
|
||||||
import zw.qantra.tm.domain.enums.CurrencyType;
|
import zw.qantra.tm.domain.enums.CurrencyType;
|
||||||
import zw.qantra.tm.domain.enums.RequestType;
|
import zw.qantra.tm.domain.enums.RequestType;
|
||||||
import zw.qantra.tm.domain.enums.Status;
|
import zw.qantra.tm.domain.enums.Status;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Getter
|
@Getter
|
||||||
@@ -35,12 +35,14 @@ public class Transaction extends BaseEntity {
|
|||||||
private String channel;
|
private String channel;
|
||||||
private String debitPhone;
|
private String debitPhone;
|
||||||
private String debitAccount;
|
private String debitAccount;
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
private CurrencyType debitCurrency;
|
private CurrencyType debitCurrency;
|
||||||
private String debitName;
|
private String debitName;
|
||||||
private String debitCard;
|
private String debitCard;
|
||||||
private String debitRef;
|
private String debitRef;
|
||||||
private String creditPhone;
|
private String creditPhone;
|
||||||
private String creditAccount;
|
private String creditAccount;
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
private CurrencyType creditCurrency;
|
private CurrencyType creditCurrency;
|
||||||
private String creditName;
|
private String creditName;
|
||||||
private String creditCard;
|
private String creditCard;
|
||||||
@@ -50,12 +52,20 @@ public class Transaction extends BaseEntity {
|
|||||||
private String aggregatorId;
|
private String aggregatorId;
|
||||||
private String billName;
|
private String billName;
|
||||||
private String billProductName;
|
private String billProductName;
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
private RequestType type;
|
private RequestType type;
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
private AuthType authType;
|
||||||
|
|
||||||
|
@Column(columnDefinition = "TEXT")
|
||||||
private String errorMessage;
|
private String errorMessage;
|
||||||
private String responseCode;
|
private String responseCode;
|
||||||
private Status status;
|
private Status status;
|
||||||
|
@Column(columnDefinition = "TEXT")
|
||||||
|
private String targetUrl;
|
||||||
|
private String sdkActionId;
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
private List<Object> additionalData;
|
private Object additionalData;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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<AdditionalData, UUID> {
|
||||||
|
Optional<AdditionalData> findByTransactionId(UUID transactionId);
|
||||||
|
}
|
||||||
@@ -10,4 +10,5 @@ import java.util.UUID;
|
|||||||
@Repository
|
@Repository
|
||||||
public interface ProviderRepository extends JpaRepository<Provider, UUID> {
|
public interface ProviderRepository extends JpaRepository<Provider, UUID> {
|
||||||
List<Provider> findByCategory(String category);
|
List<Provider> findByCategory(String category);
|
||||||
|
Provider findByClientId(String clientId);
|
||||||
}
|
}
|
||||||
@@ -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<AdditionalData> getAllAdditionalData() {
|
||||||
|
return additionalDataRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<AdditionalData> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ package zw.qantra.tm.domain.services;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
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.beans.factory.annotation.Value;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
@@ -64,7 +66,7 @@ public class ProviderService {
|
|||||||
headers.setBearerAuth(token);
|
headers.setBearerAuth(token);
|
||||||
|
|
||||||
List<Provider> existingProviders;
|
List<Provider> existingProviders;
|
||||||
if (category != null) {
|
if (category != null && !category.isEmpty()) {
|
||||||
existingProviders = getProviderRepository().findByCategory(category);
|
existingProviders = getProviderRepository().findByCategory(category);
|
||||||
} else {
|
} else {
|
||||||
existingProviders = getProviderRepository().findAll();
|
existingProviders = getProviderRepository().findAll();
|
||||||
@@ -84,6 +86,24 @@ public class ProviderService {
|
|||||||
LinkedHashMap body = (LinkedHashMap) response.get("body");
|
LinkedHashMap body = (LinkedHashMap) response.get("body");
|
||||||
List<LinkedHashMap> content = (List<LinkedHashMap>) body.get("content");
|
List<LinkedHashMap> content = (List<LinkedHashMap>) 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;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,17 @@
|
|||||||
package zw.qantra.tm.domain.services;
|
package zw.qantra.tm.domain.services;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import zw.qantra.tm.domain.enums.RequestType;
|
||||||
import zw.qantra.tm.domain.enums.Status;
|
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.models.Transaction;
|
||||||
import zw.qantra.tm.domain.repositories.TransactionRepository;
|
import zw.qantra.tm.domain.repositories.TransactionRepository;
|
||||||
import zw.qantra.tm.domain.services.factories.PaymentProcessorFactory;
|
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.CalculateChargesHandler;
|
||||||
import zw.qantra.tm.domain.services.processors.handlers.LogicPipeline;
|
import zw.qantra.tm.domain.services.processors.handlers.LogicPipeline;
|
||||||
import zw.qantra.tm.domain.services.processors.handlers.ValidationHandler;
|
import zw.qantra.tm.domain.services.processors.handlers.ValidationHandler;
|
||||||
|
import zw.qantra.tm.exceptions.ApiException;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class TransactionService {
|
public class TransactionService {
|
||||||
|
Logger logger = LoggerFactory.getLogger(TransactionService.class);
|
||||||
|
|
||||||
|
@Getter
|
||||||
private final TransactionRepository transactionRepository;
|
private final TransactionRepository transactionRepository;
|
||||||
private final PaymentProcessorFactory paymentProcessorFactory;
|
private final PaymentProcessorFactory paymentProcessorFactory;
|
||||||
private final CalculateChargesHandler calculateChargesHandler;
|
private final CalculateChargesHandler calculateChargesHandler;
|
||||||
|
private final ProviderService providerService;
|
||||||
|
|
||||||
public TransactionRepository getTransactionRepository() {
|
public Transaction poll(UUID uuid){
|
||||||
return transactionRepository;
|
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) {
|
public Transaction execute(Transaction transaction) {
|
||||||
|
transaction.setReference(UUID.randomUUID().toString());
|
||||||
|
|
||||||
LogicPipeline<Transaction, Transaction> transactionLogic =
|
LogicPipeline<Transaction, Transaction> transactionLogic =
|
||||||
new LogicPipeline<>(calculateChargesHandler)
|
new LogicPipeline<>(calculateChargesHandler)
|
||||||
.addHandler(new ValidationHandler());
|
.addHandler(new ValidationHandler());
|
||||||
|
|
||||||
transaction = transactionLogic.execute(transaction);
|
transaction = transactionLogic.execute(transaction);
|
||||||
|
|
||||||
String label = transaction.getType() + "_" + transaction.getIntegrationProcessorLabel();
|
String label = getLabel(transaction);
|
||||||
|
logger.info("label: {}", label);
|
||||||
|
|
||||||
TransactionProcessorInterface transactionProcessorInterface =
|
TransactionProcessorInterface transactionProcessorInterface =
|
||||||
(TransactionProcessorInterface) paymentProcessorFactory.getPaymentProcessor(label);
|
(TransactionProcessorInterface) paymentProcessorFactory.getPaymentProcessor(label);
|
||||||
|
|
||||||
@@ -46,4 +82,27 @@ public class TransactionService {
|
|||||||
}
|
}
|
||||||
return transaction;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,4 +5,5 @@ import zw.qantra.tm.domain.models.Transaction;
|
|||||||
public interface TransactionProcessorInterface {
|
public interface TransactionProcessorInterface {
|
||||||
Object process(Transaction transaction) throws Exception;
|
Object process(Transaction transaction) throws Exception;
|
||||||
Object reverse(Transaction transaction);
|
Object reverse(Transaction transaction);
|
||||||
|
Object poll(Transaction transaction) throws Exception;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,45 @@
|
|||||||
package zw.qantra.tm.domain.services.processors.confirmations;
|
package zw.qantra.tm.domain.services.processors.confirmations;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.slf4j.Logger;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import zw.qantra.tm.domain.dtos.BillPaymentDto;
|
import zw.qantra.tm.domain.dtos.BillPaymentDto;
|
||||||
import zw.qantra.tm.domain.enums.RequestType;
|
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.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.domain.services.processors.TransactionProcessorInterface;
|
||||||
import zw.qantra.tm.rest.RestService;
|
import zw.qantra.tm.rest.RestService;
|
||||||
|
import zw.qantra.tm.utils.Utils;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.LinkedHashMap;
|
||||||
|
|
||||||
@Service("CONFIRM_ZESA")
|
@Service("CONFIRM_ZESA")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class ZesaConfirmationProcessor implements TransactionProcessorInterface {
|
public class ZesaConfirmationProcessor implements TransactionProcessorInterface {
|
||||||
|
Logger logger = LoggerFactory.getLogger(ZesaConfirmationProcessor.class);
|
||||||
|
|
||||||
private final RestService restService;
|
private final RestService restService;
|
||||||
|
private final TransactionService transactionService;
|
||||||
|
private final AdditionalDataService additionalDataService;
|
||||||
|
|
||||||
@Value("${sbz.aggregator.url}")
|
@Value("${sbz.aggregator.url}")
|
||||||
private String aggregatorUrl;
|
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
|
@Override
|
||||||
public Object process(Transaction transaction) throws Exception {
|
public Object process(Transaction transaction) throws Exception {
|
||||||
System.out.println("processing transaction");
|
logger.info("processing transaction");
|
||||||
String token = restService.getMerchantToken();
|
String token = restService.getMerchantToken();
|
||||||
|
|
||||||
BillPaymentDto request = BillPaymentDto.builder()
|
BillPaymentDto request = BillPaymentDto.builder()
|
||||||
@@ -30,25 +48,50 @@ public class ZesaConfirmationProcessor implements TransactionProcessorInterface
|
|||||||
.debitRef(transaction.getDebitRef())
|
.debitRef(transaction.getDebitRef())
|
||||||
.debitCurrency(transaction.getDebitCurrency())
|
.debitCurrency(transaction.getDebitCurrency())
|
||||||
.amount(transaction.getAmount())
|
.amount(transaction.getAmount())
|
||||||
.aggregatorId(transaction.getAggregatorId())
|
.aggregatorId(aggregatorClientId)
|
||||||
.debitPhone(transaction.getDebitPhone())
|
.debitPhone(aggregatorPhone) // qantra mobile number
|
||||||
.debitAccount(transaction.getDebitAccount())
|
.debitAccount(aggregatorAccount) // qantra corporate account
|
||||||
.creditAccount(transaction.getCreditAccount())
|
.creditAccount(transaction.getCreditAccount()) // customer's zesa meter number
|
||||||
.creditPhone(transaction.getCreditPhone())
|
.creditPhone(transaction.getCreditPhone()) // customer's contact phone number
|
||||||
.billName(transaction.getBillName())
|
.billName(transaction.getBillName())
|
||||||
.build();
|
.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
|
@Override
|
||||||
public Object reverse(Transaction transaction) {
|
public Object reverse(Transaction transaction) {
|
||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object poll(Transaction transaction) {
|
||||||
|
return transaction;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
package zw.qantra.tm.domain.services.processors.handlers;
|
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 org.springframework.stereotype.Service;
|
||||||
import zw.qantra.tm.domain.models.Charge;
|
import zw.qantra.tm.domain.models.Charge;
|
||||||
import zw.qantra.tm.domain.models.ChargeCondition;
|
import zw.qantra.tm.domain.models.ChargeCondition;
|
||||||
import zw.qantra.tm.domain.models.Transaction;
|
import zw.qantra.tm.domain.models.Transaction;
|
||||||
import zw.qantra.tm.domain.services.ChargeConditionService;
|
|
||||||
import zw.qantra.tm.domain.services.ChargeService;
|
import zw.qantra.tm.domain.services.ChargeService;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
@@ -15,15 +16,16 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Service
|
@Service
|
||||||
public class CalculateChargesHandler implements LogicHandler<Transaction, Transaction> {
|
public class CalculateChargesHandler implements LogicHandler<Transaction, Transaction> {
|
||||||
private ChargeConditionService chargeConditionService;
|
Logger logger = LoggerFactory.getLogger(CalculateChargesHandler.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
private ChargeService chargeService;
|
private ChargeService chargeService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Transaction process(Transaction transaction) {
|
public Transaction process(Transaction transaction) {
|
||||||
System.out.println("processing charges");
|
logger.info("processing charges");
|
||||||
transaction.setCharge(BigDecimal.ZERO);
|
transaction.setCharge(BigDecimal.ZERO);
|
||||||
transaction.setTax(BigDecimal.ZERO);
|
transaction.setTax(BigDecimal.ZERO);
|
||||||
transaction.setGatewayCharge(BigDecimal.ZERO);
|
transaction.setGatewayCharge(BigDecimal.ZERO);
|
||||||
@@ -122,6 +124,12 @@ public class CalculateChargesHandler implements LogicHandler<Transaction, Transa
|
|||||||
applyCharge(transaction, charge, chargeAmount);
|
applyCharge(transaction, charge, chargeAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// calculate total amount for ease of use later
|
||||||
|
transaction.setTotalAmount(
|
||||||
|
transaction.getCharge().add(
|
||||||
|
transaction.getTax().add(
|
||||||
|
transaction.getGatewayCharge())));
|
||||||
|
|
||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,4 +25,10 @@ public class ZesaIntegrationProcessor implements TransactionProcessorInterface {
|
|||||||
public Object reverse(Transaction transaction) {
|
public Object reverse(Transaction transaction) {
|
||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object poll(Transaction transaction) {
|
||||||
|
return transaction;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
package zw.qantra.tm.domain.services.processors.payments;
|
||||||
|
|
||||||
|
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.stereotype.Service;
|
||||||
|
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.services.TransactionService;
|
||||||
|
import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface;
|
||||||
|
import zw.qantra.tm.rest.RestService;
|
||||||
|
import zw.qantra.tm.utils.Utils;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
|
||||||
|
@Service("REQUEST_MPGS_WEB")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class MPGSWebPaymentProcessor implements TransactionProcessorInterface {
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(MPGSWebPaymentProcessor.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
|
||||||
|
public Object process(Transaction transaction) throws Exception {
|
||||||
|
logger.info("processing transaction");
|
||||||
|
String token = restService.getMerchantToken();
|
||||||
|
|
||||||
|
String concat = clientId+
|
||||||
|
encryptionKey+
|
||||||
|
transaction.getCreditPhone()+
|
||||||
|
transaction.getAmount();
|
||||||
|
|
||||||
|
String hex = getHash(concat);
|
||||||
|
|
||||||
|
SdkActionDto sdkActionDto = SdkActionDto.builder()
|
||||||
|
.clientId(clientId)
|
||||||
|
.clientSecret(clientSecret)
|
||||||
|
.phone(transaction.getCreditPhone())
|
||||||
|
.amount(transaction.getAmount().toString())
|
||||||
|
.currency(transaction.getDebitCurrency().toString())
|
||||||
|
.hash(hex)
|
||||||
|
.authType("WEB")
|
||||||
|
.billerReference(transaction.getReference())
|
||||||
|
.action("PAYMENT")
|
||||||
|
.sandbox(true)
|
||||||
|
.paymentProcessorLabel("MPGS")
|
||||||
|
.responseUrl("https://www.google.com")
|
||||||
|
.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);
|
||||||
|
logger.info("response: {}", Utils.toJson(response));
|
||||||
|
|
||||||
|
LinkedHashMap body = (LinkedHashMap) response.get("body");
|
||||||
|
transaction.setSdkActionId((String) body.get("uid"));
|
||||||
|
|
||||||
|
if(!body.get("status").equals("FAILED")){
|
||||||
|
transaction.setStatus(Status.PENDING);
|
||||||
|
transaction.setResponseCode("00");
|
||||||
|
transaction.setTargetUrl((String) body.get("targetUrl"));
|
||||||
|
}else{
|
||||||
|
transaction.setStatus(Status.FAILED);
|
||||||
|
transaction.setResponseCode("01");
|
||||||
|
transaction.setErrorMessage((String) body.get("message"));
|
||||||
|
}
|
||||||
|
} 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
|
||||||
|
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/mpgs/poll/" + transaction.getSdkActionId(),
|
||||||
|
headers, LinkedHashMap.class);
|
||||||
|
logger.info("response: {}", Utils.toJson(response));
|
||||||
|
|
||||||
|
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"));
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object reverse(Transaction 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +1,131 @@
|
|||||||
package zw.qantra.tm.domain.services.processors.payments;
|
package zw.qantra.tm.domain.services.processors.payments;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
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.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
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.processors.TransactionProcessorInterface;
|
import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface;
|
||||||
|
import zw.qantra.tm.domain.services.TransactionService;
|
||||||
import zw.qantra.tm.rest.RestService;
|
import zw.qantra.tm.rest.RestService;
|
||||||
|
import zw.qantra.tm.utils.Utils;
|
||||||
|
|
||||||
@Service("STEWARD_WEB")
|
@Service("REQUEST_STEWARD_WEB")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class StewardWebPaymentProcessor implements TransactionProcessorInterface {
|
public class StewardWebPaymentProcessor implements TransactionProcessorInterface {
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(StewardWebPaymentProcessor.class);
|
||||||
private final RestService restService;
|
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 {
|
||||||
return transaction;
|
logger.info("processing transaction");
|
||||||
|
String token = restService.getMerchantToken();
|
||||||
|
|
||||||
|
String concat = clientId+
|
||||||
|
encryptionKey+
|
||||||
|
transaction.getCreditPhone()+
|
||||||
|
transaction.getAmount();
|
||||||
|
|
||||||
|
String hex = getHash(concat);
|
||||||
|
|
||||||
|
SdkActionDto sdkActionDto = SdkActionDto.builder()
|
||||||
|
.clientId(clientId)
|
||||||
|
.clientSecret(clientSecret)
|
||||||
|
.phone(transaction.getCreditPhone())
|
||||||
|
.amount(transaction.getAmount().toString())
|
||||||
|
.currency(transaction.getDebitCurrency().toString())
|
||||||
|
.hash(hex)
|
||||||
|
.authType("WEB")
|
||||||
|
.billerReference(transaction.getReference())
|
||||||
|
.action("PAYMENT")
|
||||||
|
.sandbox(true)
|
||||||
|
.paymentProcessorLabel("STEWARD")
|
||||||
|
.responseUrl("https://www.google.com")
|
||||||
|
.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);
|
||||||
|
logger.info("response: {}", Utils.toJson(response));
|
||||||
|
|
||||||
|
LinkedHashMap body = (LinkedHashMap) response.get("body");
|
||||||
|
transaction.setSdkActionId((String) body.get("uid"));
|
||||||
|
|
||||||
|
if(!body.get("status").equals("FAILED")){
|
||||||
|
transaction.setStatus(Status.SUCCESS);
|
||||||
|
transaction.setResponseCode("00");
|
||||||
|
transaction.setTargetUrl((String) body.get("targetUrl"));
|
||||||
|
}else{
|
||||||
|
transaction.setStatus(Status.FAILED);
|
||||||
|
transaction.setResponseCode("01");
|
||||||
|
transaction.setErrorMessage((String) body.get("message"));
|
||||||
|
}
|
||||||
|
} 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
|
||||||
public Object reverse(Transaction transaction) {
|
public Object reverse(Transaction transaction) {
|
||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object poll(Transaction 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,10 +35,9 @@ public class RestTemplateInterceptor
|
|||||||
|
|
||||||
// we don't log file data
|
// we don't log file data
|
||||||
if(request.getHeaders().getContentLength() < 10000){
|
if(request.getHeaders().getContentLength() < 10000){
|
||||||
logger.info("Request: {}: {}\n{}\n{}",
|
logger.info("Request: {}: {}\n{}",
|
||||||
request.getMethod(),
|
request.getMethod(),
|
||||||
request.getURI(),
|
request.getURI(),
|
||||||
request.getHeaders().toSingleValueMap(),
|
|
||||||
gson.toJson(gson.fromJson(requestBody, HashMap.class)));
|
gson.toJson(gson.fromJson(requestBody, HashMap.class)));
|
||||||
}
|
}
|
||||||
ClientHttpResponse response = execution.execute(request, reqBody);
|
ClientHttpResponse response = execution.execute(request, reqBody);
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public class Utils {
|
|||||||
public static String getUid(){
|
public static String getUid(){
|
||||||
return UUID.randomUUID().toString();
|
return UUID.randomUUID().toString();
|
||||||
}
|
}
|
||||||
public static <T> T getJson(String json, Class<T> clazz){
|
public static <T> T fromJson(String json, Class<T> clazz){
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||||
mapper.setDateFormat(new StdDateFormat().withColonInTimeZone(true));
|
mapper.setDateFormat(new StdDateFormat().withColonInTimeZone(true));
|
||||||
@@ -56,7 +56,7 @@ public class Utils {
|
|||||||
return (T) obj;
|
return (T) obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String setJson(Object obj){
|
public static String toJson(Object obj){
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||||
mapper.setDateFormat(new StdDateFormat().withColonInTimeZone(true));
|
mapper.setDateFormat(new StdDateFormat().withColonInTimeZone(true));
|
||||||
|
|||||||
@@ -15,5 +15,10 @@ spring.datasource.username=postgres
|
|||||||
spring.datasource.password=password
|
spring.datasource.password=password
|
||||||
|
|
||||||
sbz.aggregator.url=http://192.168.16.29:24050/v1
|
sbz.aggregator.url=http://192.168.16.29:24050/v1
|
||||||
sbz.aggregator.client-id=small_bakery
|
sbz.aggregator.client-id=steward_pay
|
||||||
sbz.aggregator.client-secret=tmtCUeJA1dQezn405HSrYSdhV81XiBEi
|
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
|
||||||
Reference in New Issue
Block a user