completed initial charge logic

This commit is contained in:
2025-06-18 09:28:01 +02:00
parent b214d42d2f
commit 9ae78635c7
34 changed files with 614 additions and 102 deletions

View File

@@ -2,6 +2,9 @@ package zw.qantra.tm;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import com.google.gson.Gson;
@SpringBootApplication
public class TmApplication {
@@ -10,4 +13,8 @@ public class TmApplication {
SpringApplication.run(TmApplication.class, args);
}
@Bean
public Gson gson() {
return new Gson();
}
}

View File

@@ -0,0 +1,83 @@
package zw.qantra.tm.domain.controllers;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
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.Charge;
import zw.qantra.tm.domain.models.ChargeCondition;
import zw.qantra.tm.domain.services.ChargeConditionService;
import zw.qantra.tm.domain.services.ChargeService;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
@RestController
@RequestMapping("/configs")
@RequiredArgsConstructor
public class ConfigController {
private final ChargeConditionService chargeConditionService;
private final ChargeService chargeService;
@GetMapping("/charges")
public ResponseEntity charges(){
List<ChargeCondition> chargeConditions = Arrays.asList(
ChargeCondition.builder()
.name("Ecocash Gateway")
.type(ChargeConditionType.GATEWAY)
.codes("ECOCASH")
.build(),
ChargeCondition.builder()
.name("Steward Gateway")
.type(ChargeConditionType.GATEWAY)
.codes("STEWARD")
.build(),
ChargeCondition.builder()
.name("Mastercard Gateway")
.type(ChargeConditionType.GATEWAY)
.codes("MPGS")
.build()
);
chargeService.getChargeRepository().deleteAll();
chargeConditionService.getChargeConditionRepository().deleteAll();
chargeConditionService.getChargeConditionRepository().saveAll(chargeConditions);
List<Charge> charges = Arrays.asList(
Charge.builder()
.description("Gateway fee")
.chargeLabel(ChargeLabelEnum.GATEWAY_FEE)
.currency(CurrencyType.USD)
.percentageRate(new BigDecimal("1"))
.includes(new HashSet<>() {{
add(chargeConditions.get(0));
add(chargeConditions.get(1));
add(chargeConditions.get(2));
}})
.build(),
Charge.builder()
.description("Tax")
.chargeLabel(ChargeLabelEnum.TAX)
.currency(CurrencyType.USD)
.percentageRate(new BigDecimal("2"))
.max(new BigDecimal("10150"))
.minimumAmount(new BigDecimal("5"))
.maximumAmount(new BigDecimal("500000"))
.includes(new HashSet<>() {{
add(chargeConditions.get(0));
add(chargeConditions.get(1));
add(chargeConditions.get(2));
}})
.build()
);
chargeService.getChargeRepository().saveAll(charges);
return ResponseEntity.ok("Charges configuration complete");
}
}

View File

@@ -0,0 +1,57 @@
package zw.qantra.tm.domain.controllers;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import zw.qantra.tm.domain.models.Provider;
import zw.qantra.tm.domain.services.ProviderService;
import zw.qantra.tm.rest.ApiResponse;
import java.util.UUID;
@RestController
@RequestMapping("/providers")
@RequiredArgsConstructor
public class ProviderController {
private final ProviderService providerService;
@GetMapping
public ResponseEntity getProviders() {
return ApiResponse.ok(providerService.getProviders());
}
@GetMapping("/{id}/products")
public ResponseEntity getProducts(@PathVariable UUID id) {
return ApiResponse.ok(providerService.getProviderProducts(id));
}
@GetMapping("/{id}")
public ResponseEntity getProvider(@PathVariable UUID id) {
return providerService.getProvider(id)
.map(ApiResponse::ok)
.orElse(ApiResponse.notFound());
}
@PostMapping
public ResponseEntity createProvider(@RequestBody Provider provider) {
return ApiResponse.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 ApiResponse.notFound();
}
@DeleteMapping("/{id}")
public ResponseEntity deleteProvider(@PathVariable UUID id) {
if (providerService.deleteProvider(id)) {
return ApiResponse.ok("Provider deleted successfully");
}
return ApiResponse.notFound();
}
}

View File

@@ -1,18 +1,53 @@
package zw.qantra.tm.domain.controllers;
import lombok.RequiredArgsConstructor;
import zw.qantra.tm.domain.services.TransactionService;
import zw.qantra.tm.domain.models.Transaction;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.UUID;
@RestController
@RequestMapping("/transaction")
@RequiredArgsConstructor
public class TransactonController {
@PostMapping()
public ResponseEntity post(@RequestBody Object transactionRequest) {
return ResponseEntity.ok("Transaction endpoint is working");
private final TransactionService transactionService;
@GetMapping
public ResponseEntity<List<Transaction>> getAll() {
return ResponseEntity.ok(transactionService.getTransactionRepository().findAll());
}
@GetMapping("/{id}")
public ResponseEntity<Transaction> getById(@PathVariable UUID id) {
return transactionService.getTransactionRepository().findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<Transaction> create(@RequestBody Transaction transaction) {
return ResponseEntity.ok(transactionService.execute(transaction));
}
@PutMapping("/{id}")
public ResponseEntity<Transaction> update(@PathVariable UUID id, @RequestBody Transaction transaction) {
if (!transactionService.getTransactionRepository().existsById(id)) {
return ResponseEntity.notFound().build();
}
transaction.setId(id);
return ResponseEntity.ok(transactionService.execute(transaction));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable UUID id) {
if (!transactionService.getTransactionRepository().existsById(id)) {
return ResponseEntity.notFound().build();
}
transactionService.getTransactionRepository().deleteById(id);
return ResponseEntity.noContent().build();
}
}

View File

@@ -0,0 +1,22 @@
package zw.qantra.tm.domain.dtos;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.util.UUID;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SBZProductDto {
private String name;
private String description;
private String displayName;
private BigDecimal defaultAmount;
private boolean requiresAmount;
private UUID uid;
}

View File

@@ -0,0 +1,28 @@
package zw.qantra.tm.domain.models;
import jakarta.persistence.Column;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.MappedSuperclass;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
import java.util.UUID;
@Getter
@Setter
@MappedSuperclass
public class BaseEntity {
@Id
@GeneratedValue(generator = "UUID")
@GenericGenerator(
name = "UUID",
strategy = "org.hibernate.id.UUIDGenerator"
)
@JdbcTypeCode(SqlTypes.UUID)
@Column(name = "id", updatable = false, nullable = false)
private UUID id;
}

View File

@@ -10,7 +10,6 @@ import zw.qantra.tm.domain.enums.TargetEvent;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
@Entity
@Getter
@@ -18,11 +17,7 @@ import java.util.UUID;
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Charge {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
public class Charge extends BaseEntity {
private String description;
// % rate to charge

View File

@@ -5,7 +5,6 @@ import lombok.*;
import zw.qantra.tm.domain.enums.ChargeConditionType;
import java.util.List;
import java.util.UUID;
@Entity
@Getter
@@ -13,15 +12,11 @@ import java.util.UUID;
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ChargeCondition {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
public class ChargeCondition extends BaseEntity {
private String name;
@Enumerated(EnumType.STRING)
private ChargeConditionType type;
@Column(columnDefinition = "longtext")
@Column(columnDefinition = "TEXT")
private String codes;
@ManyToMany(mappedBy = "includes")

View File

@@ -1,11 +1,6 @@
package zw.qantra.tm.domain.models;
import java.util.UUID;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.*;
@Entity
@@ -14,11 +9,7 @@ import lombok.*;
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Currency {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
public class Currency extends BaseEntity {
private String isoCode;
private String name;
private String displayName;

View File

@@ -1,11 +1,6 @@
package zw.qantra.tm.domain.models;
import java.util.UUID;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.*;
@Entity
@@ -14,12 +9,8 @@ import lombok.*;
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class IntegrationProcessor {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
public class IntegrationProcessor extends BaseEntity {
private String name;
private String description;
private String type;
private String label;
}

View File

@@ -3,7 +3,7 @@ package zw.qantra.tm.domain.models;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Column;
import lombok.*;
import java.util.UUID;
@@ -14,9 +14,9 @@ import java.util.UUID;
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PaymentProcessor {
@Id
public class PaymentProcessor extends BaseEntity {
@GeneratedValue(strategy = GenerationType.UUID)
@Column(columnDefinition = "VARCHAR(36)")
private UUID id;
private String name;

View File

@@ -1,12 +1,8 @@
package zw.qantra.tm.domain.models;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.*;
import java.util.UUID;
@Entity
@Getter
@@ -14,12 +10,9 @@ import java.util.UUID;
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Provider {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
public class Provider extends BaseEntity {
private String clientId;
private String name;
private String label;
private String description;
private String externalId;
}

View File

@@ -1,11 +1,6 @@
package zw.qantra.tm.domain.models;
import java.util.UUID;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
@@ -18,11 +13,7 @@ import lombok.Setter;
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Setting {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
public class Setting extends BaseEntity {
private String name;
private String value;
}

View File

@@ -9,7 +9,6 @@ import zw.qantra.tm.domain.enums.Status;
import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
@Entity
@Getter
@@ -18,11 +17,7 @@ import java.util.UUID;
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Transaction {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
public class Transaction extends BaseEntity {
private String trace;
private BigDecimal amount;
private BigDecimal charge;
@@ -34,6 +29,7 @@ public class Transaction {
private String productUid;
private String paymentProcessorLabel;
private String integrationProcessorLabel;
private String providerLabel;
private String rrn;
private String channelName;
private String channel;

View File

@@ -0,0 +1,11 @@
package zw.qantra.tm.domain.repositories;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import zw.qantra.tm.domain.models.ChargeCondition;
import java.util.UUID;
@Repository
public interface ChargeConditionRepository extends JpaRepository<ChargeCondition, UUID> {
}

View File

@@ -0,0 +1,14 @@
package zw.qantra.tm.domain.repositories;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import zw.qantra.tm.domain.enums.CurrencyType;
import zw.qantra.tm.domain.models.Charge;
import java.util.List;
import java.util.UUID;
@Repository
public interface ChargeRepository extends JpaRepository<Charge, UUID> {
List<Charge> findAllByCurrency(CurrencyType currency);
}

View File

@@ -0,0 +1,20 @@
package zw.qantra.tm.domain.services;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import zw.qantra.tm.domain.models.ChargeCondition;
import zw.qantra.tm.domain.repositories.ChargeConditionRepository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@Service
@RequiredArgsConstructor
public class ChargeConditionService {
private final ChargeConditionRepository chargeConditionRepository;
public ChargeConditionRepository getChargeConditionRepository() {
return chargeConditionRepository;
}
}

View File

@@ -0,0 +1,20 @@
package zw.qantra.tm.domain.services;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import zw.qantra.tm.domain.models.Charge;
import zw.qantra.tm.domain.repositories.ChargeRepository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@Service
@RequiredArgsConstructor
public class ChargeService {
private final ChargeRepository chargeRepository;
public ChargeRepository getChargeRepository() {
return chargeRepository;
}
}

View File

@@ -8,4 +8,8 @@ import zw.qantra.tm.domain.repositories.CurrencyRepository;
@RequiredArgsConstructor
public class CurrencyService {
private final CurrencyRepository currencyRepository;
public CurrencyRepository getCurrencyRepository() {
return currencyRepository;
}
}

View File

@@ -8,4 +8,8 @@ import zw.qantra.tm.domain.repositories.IntegrationProcessorRepository;
@RequiredArgsConstructor
public class IntegrationProcessorService {
private final IntegrationProcessorRepository integrationProcessorRepository;
public IntegrationProcessorRepository getIntegrationProcessorRepository() {
return integrationProcessorRepository;
}
}

View File

@@ -8,4 +8,8 @@ import zw.qantra.tm.domain.repositories.PaymentProcessorRepository;
@RequiredArgsConstructor
public class PaymentProcessorService {
private final PaymentProcessorRepository paymentProcessorRepository;
public PaymentProcessorRepository getPaymentProcessorRepository() {
return paymentProcessorRepository;
}
}

View File

@@ -1,11 +1,87 @@
package zw.qantra.tm.domain.services;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.ObjectMapper;
import zw.qantra.tm.domain.dtos.SBZProductDto;
import zw.qantra.tm.domain.models.Provider;
import zw.qantra.tm.domain.repositories.ChargeRepository;
import zw.qantra.tm.domain.repositories.ProviderRepository;
import zw.qantra.tm.rest.RestService;
import java.util.*;
@Service
@RequiredArgsConstructor
public class ProviderService {
private final ProviderRepository providerRepository;
private final RestService restService;
@Value("${sbz.aggregator.url}")
private String aggregatorUrl;
public ProviderRepository getProviderRepository() {
return providerRepository;
}
public List<SBZProductDto> getProviderProducts(UUID providerId) {
String token = restService.getMerchantToken();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.setBearerAuth(token);
LinkedHashMap response = this.restService.getAs(
aggregatorUrl + "/merchant/aggregation/products?size=100&clientUid=" + providerId.toString(),
headers,
LinkedHashMap.class
);
LinkedHashMap body = (LinkedHashMap) response.get("body");
List<LinkedHashMap> content = (List<LinkedHashMap>) body.get("content");
List<SBZProductDto> products = new ArrayList<>();
for (LinkedHashMap product : content) {
ObjectMapper mapper = new ObjectMapper();
SBZProductDto productDto = mapper.convertValue(product, SBZProductDto.class);
products.add(productDto);
}
return products;
}
public List<Provider> getProviders() {
return providerRepository.findAll();
}
public Optional<Provider> getProvider(UUID id) {
return providerRepository.findById(id);
}
public Provider createProvider(Provider provider) {
return providerRepository.save(provider);
}
public Provider updateProvider(UUID id, Provider provider) {
if (providerRepository.existsById(id)) {
provider.setId(id);
return providerRepository.save(provider);
}
return null;
}
public boolean deleteProvider(UUID id) {
if (providerRepository.existsById(id)) {
providerRepository.deleteById(id);
return true;
}
return false;
}
}

View File

@@ -8,4 +8,8 @@ import zw.qantra.tm.domain.repositories.SettingRepository;
@RequiredArgsConstructor
public class SettingService {
private final SettingRepository settingRepository;
public SettingRepository getSettingRepository() {
return settingRepository;
}
}

View File

@@ -3,7 +3,6 @@ package zw.qantra.tm.domain.services;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import zw.qantra.tm.domain.enums.Status;
import zw.qantra.tm.domain.models.PaymentProcessor;
import zw.qantra.tm.domain.models.Transaction;
import zw.qantra.tm.domain.repositories.TransactionRepository;
import zw.qantra.tm.domain.services.factories.PaymentProcessorFactory;
@@ -17,11 +16,18 @@ import zw.qantra.tm.domain.services.processors.handlers.ValidationHandler;
public class TransactionService {
private final TransactionRepository transactionRepository;
private final PaymentProcessorFactory paymentProcessorFactory;
private final CalculateChargesHandler calculateChargesHandler;
public TransactionRepository getTransactionRepository() {
return transactionRepository;
}
public Transaction execute(Transaction transaction) {
LogicPipeline<Transaction, Transaction> transactionLogic =
new LogicPipeline<>(new CalculateChargesHandler())
new LogicPipeline<>(calculateChargesHandler)
.addHandler(new ValidationHandler());
transaction = transactionLogic.execute(transaction);
String label = transaction.getType() + "_" + transaction.getIntegrationProcessorLabel();

View File

@@ -18,8 +18,10 @@ public class ZesaConfirmationProcessor implements TransactionProcessorInterface
@Override
public Object process(Transaction transaction) throws Exception {
System.out.println("processing transaction");
throw new Exception("Challenge encountered, please contact support.");
// return transaction;
return transaction;
}
@Override

View File

@@ -1,11 +1,135 @@
package zw.qantra.tm.domain.services.processors.handlers;
import lombok.AllArgsConstructor;
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;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@AllArgsConstructor
@Service
public class CalculateChargesHandler implements LogicHandler<Transaction, Transaction> {
private ChargeConditionService chargeConditionService;
private ChargeService chargeService;
@Override
public Transaction process(Transaction transaction) {
System.out.println("processing charges");
transaction.setCharge(BigDecimal.ZERO);
transaction.setTax(BigDecimal.ZERO);
transaction.setGatewayCharge(BigDecimal.ZERO);
BigDecimal amountToCharge = transaction.getAmount();
BigDecimal chargeAmount = BigDecimal.ZERO;
List<Charge> chargeList = chargeService.getChargeRepository().findAllByCurrency(transaction.getDebitCurrency());
List<Charge> appropriateCharges = new ArrayList<>();
for (Charge charge : chargeList) {
boolean isPresent = false;
for (ChargeCondition chargeCondition: charge.getIncludes()){
List<String> conditionTypes = Arrays.asList(chargeCondition.getCodes().split(","));
switch (chargeCondition.getType()){
case GATEWAY:
isPresent = conditionTypes.contains(transaction.getPaymentProcessorLabel());
break;
case PROVIDER:
isPresent = conditionTypes.contains(transaction.getIntegrationProcessorLabel());
break;
}
if(isPresent) break; // we've found a matching condition, no need to check rest of conditions
}
if(!isPresent) continue; // if we did not find a match, means this charge does not apply to this transaction
boolean isExempt = false;
for (ChargeCondition chargeCondition: charge.getExcludes()){
List<String> conditionTypes = Arrays.asList(chargeCondition.getCodes().split(","));
switch (chargeCondition.getType()){
case GATEWAY:
isExempt = conditionTypes.contains(transaction.getPaymentProcessorLabel());
break;
case PROVIDER:
isExempt = conditionTypes.contains(transaction.getIntegrationProcessorLabel());
break;
}
if(isExempt) break; // we've found a matching condition, no need to check rest of conditions
}
if(isExempt) continue; // if we found a match, means this transaction is exempt from this charge
appropriateCharges.add(charge);
}
for (Charge charge : appropriateCharges) {
// if minimum amount chargeable is lower than the amount to charge, apply the min charge
if(charge.getMinimumAmount() != null) {
if(charge.getMinimumAmount().compareTo(amountToCharge) > 0) {
applyCharge(transaction, charge, charge.getMin());
continue;
}
}
// if maximum amount chargeable is higher than the amount to charge, apply the max charge
if(charge.getMaximumAmount() != null) {
if(charge.getMaximumAmount().compareTo(amountToCharge) < 0) {
applyCharge(transaction, charge, charge.getMax());
continue;
}
}
// calculate unrestricted charge amount
// if percentage rate is not present then it's a flat charge, else it's a percentage
if(charge.getPercentageRate() == null) {
if(charge.getFlat() == null)
continue;
chargeAmount = charge.getFlat();
}else {
BigDecimal fraction = charge.getPercentageRate().divide(
new BigDecimal(100), 4, RoundingMode.CEILING);
chargeAmount = amountToCharge.multiply(fraction)
.setScale(2, RoundingMode.CEILING);
// if it's a composite charge then add the flat component as well
if(charge.isComposite()) {
chargeAmount = chargeAmount.add(charge.getFlat())
.setScale(2, RoundingMode.CEILING);
}
}
// if minimum charge is set, check if the charge amount is less than the minimum
if(charge.getMin() != null) {
if(charge.getMin().compareTo(chargeAmount) > 0){
chargeAmount = charge.getMin();
}
}
// if maximum charge is set, check if the charge amount is more than the maximum
if(charge.getMax() != null) {
if(charge.getMax().compareTo(chargeAmount) < 0) {
chargeAmount = charge.getMax();
}
}
applyCharge(transaction, charge, chargeAmount);
}
return transaction;
}
void applyCharge(Transaction transaction, Charge charge, BigDecimal chargeAmount) {
switch (charge.getChargeLabel()) {
case FEE -> transaction.setCharge(chargeAmount);
case TAX -> transaction.setTax(chargeAmount);
case GATEWAY_FEE -> transaction.setGatewayCharge(chargeAmount);
}
}
}

View File

@@ -40,7 +40,7 @@ public class RestTemplateResponseErrorHandler
BufferedReader reader = new BufferedReader(new InputStreamReader(httpResponse.getBody()));
String httpBodyResponse = reader.lines().collect(Collectors.joining(""));
throw new RestException(httpBodyResponse, "Client Error");
throw new RestException(httpBodyResponse, "Client Error: " + httpResponse.getStatusCode());
}
}
}

View File

@@ -39,6 +39,13 @@ public class RestService {
public final RestTemplate restTemplate;
@Value("${sbz.aggregator.client-id}")
private String clientId;
@Value("${sbz.aggregator.client-secret}")
private String clientSecret;
@Value("${sbz.aggregator.url}")
private String aggregatorUrl;
public RestService() {
this.restTemplate = new RestTemplate(
new BufferingClientHttpRequestFactory(
@@ -54,7 +61,7 @@ public class RestService {
if (CollectionUtils.isEmpty(interceptors)) {
interceptors = new ArrayList<>();
}
interceptors.add(new RestTemplateInterceptor(gson));
interceptors.add(new RestTemplateInterceptor());
this.restTemplate.setInterceptors(interceptors);
}
@@ -81,6 +88,24 @@ public class RestService {
return clientToken;
}
public String getMerchantToken() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
Map<String, String> payload = Map.of(
"clientId", clientId,
"clientSecret", clientSecret
);
HttpEntity<Map<String, String>> entity = new HttpEntity<>(payload, headers);
Map response = this.restTemplate.postForObject(aggregatorUrl + "/merchant/client/token",entity, Map.class);
Map<String, String> body = (Map<String, String>) response.get("body");
return (String) body.get("access_token");
}
public Map getToken(MultiValueMap payload){
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
@@ -120,6 +145,12 @@ public class RestService {
return this.restTemplate.postForObject(path, entity, clazz);
}
public <T> T getAs(String path, HttpHeaders headers, Class<T> clazz){
// build the request
HttpEntity entity = new HttpEntity<>(headers);
return this.restTemplate.exchange(path, HttpMethod.GET, entity, clazz).getBody();
}
public <T> T post(String path, Object payload, Class<T> clazz){
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);

View File

@@ -1,6 +1,7 @@
package zw.qantra.tm.rest;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import lombok.RequiredArgsConstructor;
import org.json.JSONArray;
import org.json.JSONException;
@@ -19,11 +20,10 @@ import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.stream.Collectors;
@RequiredArgsConstructor
public class RestTemplateInterceptor
implements ClientHttpRequestInterceptor {
private final Logger logger = LoggerFactory.getLogger(getClass().getName());
private final Gson gson;
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
@Override
public ClientHttpResponse intercept(
@@ -35,18 +35,20 @@ public class RestTemplateInterceptor
// we don't log file data
if(request.getHeaders().getContentLength() < 10000){
logger.info("Request: "
+ request.getMethod() + ": "
+ request.getURI(), "http", gson.fromJson(requestBody, HashMap.class));
logger.info("Request: {}: {}\n{}\n{}",
request.getMethod(),
request.getURI(),
request.getHeaders().toSingleValueMap(),
gson.toJson(gson.fromJson(requestBody, HashMap.class)));
}
ClientHttpResponse response = execution.execute(request, reqBody);
InputStreamReader isr = new InputStreamReader(response.getBody());
String body = new BufferedReader(isr).lines()
.collect(Collectors.joining("\n"));
try{
logger.info("Response body: {}", "http", gson.fromJson(body, HashMap.class));
logger.info("Response body:\n{}", gson.toJson(gson.fromJson(body, HashMap.class)));
}catch (Exception exception){
logger.info("Problem serializing resp: {}", "http", exception.getMessage());
logger.info("Problem serializing resp: {}", exception.getMessage());
}
return response;
}