customer creation now pointing to velocity
This commit is contained in:
@@ -8,10 +8,12 @@ import org.springframework.context.annotation.Bean;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
|
||||
@SpringBootApplication
|
||||
@Import(RestConfiguration.class)
|
||||
@EnableCaching
|
||||
@EnableAsync
|
||||
public class TmApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
@@ -15,7 +15,7 @@ public class CacheConfig {
|
||||
|
||||
@Bean
|
||||
public CacheManager cacheManager() {
|
||||
CaffeineCacheManager cacheManager = new CaffeineCacheManager("providerProducts");
|
||||
CaffeineCacheManager cacheManager = new CaffeineCacheManager("providerProducts", "hotrechargeBalance");
|
||||
cacheManager.setCaffeine(Caffeine.newBuilder()
|
||||
.expireAfterWrite(24, TimeUnit.HOURS)
|
||||
.maximumSize(10000));
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package zw.qantra.tm.configs;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "velocity.api")
|
||||
public class VelocityApiProperties {
|
||||
private String baseUrl;
|
||||
private String apiKey;
|
||||
private String cancelUrl;
|
||||
private String successUrl;
|
||||
private String creditPhone;
|
||||
private String creditAccount;
|
||||
private String defaultAuthType;
|
||||
private long pollIntervalMs;
|
||||
}
|
||||
@@ -1,20 +1,12 @@
|
||||
package zw.qantra.tm.domain.controllers;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.CacheManager;
|
||||
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.Category;
|
||||
import zw.qantra.tm.domain.models.Charge;
|
||||
import zw.qantra.tm.domain.models.ChargeCondition;
|
||||
import zw.qantra.tm.domain.models.IntegrationProcessor;
|
||||
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.IntegrationProcessorService;
|
||||
@@ -22,11 +14,7 @@ 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;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/configs")
|
||||
@RequiredArgsConstructor
|
||||
@@ -45,333 +33,4 @@ public class ConfigController {
|
||||
return ResponseEntity.ok("Cache cleared");
|
||||
}
|
||||
|
||||
@GetMapping("/seed/live")
|
||||
public ResponseEntity seedLiveConfig(){
|
||||
|
||||
// clear existing payment processors before seeding
|
||||
paymentProcessorService.getPaymentProcessorRepository().deleteAll();
|
||||
paymentProcessorService.getPaymentProcessorRepository().saveAll(Arrays.asList(
|
||||
PaymentProcessor.builder()
|
||||
.name("Ecocash")
|
||||
.label("ECOCASH")
|
||||
.type("GATEWAY")
|
||||
.image("ecocash.png")
|
||||
.accountFieldName("phoneNumber")
|
||||
.accountFieldLabel("EcoCash Phone Number")
|
||||
.description("Pay using your mobile wallet")
|
||||
.authType("REMOTE")
|
||||
.build(),
|
||||
PaymentProcessor.builder()
|
||||
.name("Visa/MasterCard")
|
||||
.label("MPGS")
|
||||
.type("GATEWAY")
|
||||
.image("visa-mastercard.png")
|
||||
.accountFieldName("cardNumber")
|
||||
.accountFieldLabel("Card Number")
|
||||
.description("Pay using your international card")
|
||||
.authType("WEB")
|
||||
.build()
|
||||
));
|
||||
|
||||
|
||||
|
||||
integrationProcessorService.getIntegrationProcessorRepository().deleteAll();
|
||||
integrationProcessorService.getIntegrationProcessorRepository().saveAll(Arrays.asList(
|
||||
IntegrationProcessor.builder()
|
||||
.name("STEWARD")
|
||||
.label("STEWARD")
|
||||
.description("Steward Integration Processor")
|
||||
.build()
|
||||
));
|
||||
|
||||
// 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")
|
||||
.integrationProcessorLabel("STEWARD")
|
||||
.accountFieldName("Phone Number")
|
||||
.percentageCommission(6)
|
||||
.build(),
|
||||
Provider.builder()
|
||||
.description("NetOne Airtime")
|
||||
.clientId("netone_usd_airtime")
|
||||
.label("NETONE")
|
||||
.category("AIRTIME")
|
||||
.image("netone.png")
|
||||
.externalId("95d485a7-39c9-4559-8a27-6521969abe8f")
|
||||
.integrationProcessorLabel("STEWARD")
|
||||
.accountFieldName("Phone Number")
|
||||
.percentageCommission(5.5)
|
||||
.build(),
|
||||
Provider.builder()
|
||||
.description("Prepaid Zesa")
|
||||
.clientId("zesa_prepaid_usd")
|
||||
.label("ZESA")
|
||||
.category("UTILITIES")
|
||||
.image("zesa.png")
|
||||
.externalId("0f67f7cc-b62b-4fb5-915f-6740b2afdba6")
|
||||
.integrationProcessorLabel("STEWARD")
|
||||
.accountFieldName("Meter Number")
|
||||
.percentageCommission(1.5)
|
||||
.build(),
|
||||
Provider.builder()
|
||||
.description("Econet Broadband")
|
||||
.clientId("econet_broadband_usd")
|
||||
.label("ECONET_BROADBAND")
|
||||
.category("AIRTIME")
|
||||
.image("econet.png")
|
||||
.externalId("287863e2-a791-4106-b629-79dadc9a6779")
|
||||
.integrationProcessorLabel("STEWARD")
|
||||
.accountFieldName("Phone Number")
|
||||
.percentageCommission(6)
|
||||
.build()
|
||||
);
|
||||
providerService.getProviderRepository().saveAll(providers);
|
||||
|
||||
// Clear existing charges and charge conditions before seeding
|
||||
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));
|
||||
}})
|
||||
.build(),
|
||||
Charge.builder()
|
||||
.description("Gateway fee")
|
||||
.chargeLabel(ChargeLabelEnum.GATEWAY_FEE)
|
||||
.currency(CurrencyType.USD)
|
||||
.percentageRate(new BigDecimal("2"))
|
||||
.min(new BigDecimal("0.55"))
|
||||
.includes(new HashSet<>() {{
|
||||
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("Project configuration seeding complete");
|
||||
}
|
||||
|
||||
@GetMapping("/seed")
|
||||
public ResponseEntity seedConfig(){
|
||||
|
||||
// clear existing payment processors before seeding
|
||||
paymentProcessorService.getPaymentProcessorRepository().deleteAll();
|
||||
paymentProcessorService.getPaymentProcessorRepository().saveAll(Arrays.asList(
|
||||
PaymentProcessor.builder()
|
||||
.name("Ecocash")
|
||||
.label("ECOCASH")
|
||||
.type("GATEWAY")
|
||||
.image("ecocash.png")
|
||||
.accountFieldName("phoneNumber")
|
||||
.accountFieldLabel("EcoCash Phone Number")
|
||||
.description("Pay using your mobile wallet")
|
||||
.authType("REMOTE")
|
||||
.build(),
|
||||
PaymentProcessor.builder()
|
||||
.name("Visa/MasterCard")
|
||||
.label("MPGS")
|
||||
.type("GATEWAY")
|
||||
.image("visa-mastercard.png")
|
||||
.accountFieldName("cardNumber")
|
||||
.accountFieldLabel("Card Number")
|
||||
.description("Pay using your international card")
|
||||
.authType("WEB")
|
||||
.build()
|
||||
));
|
||||
|
||||
|
||||
|
||||
integrationProcessorService.getIntegrationProcessorRepository().deleteAll();
|
||||
integrationProcessorService.getIntegrationProcessorRepository().saveAll(Arrays.asList(
|
||||
IntegrationProcessor.builder()
|
||||
.name("STEWARD")
|
||||
.label("STEWARD")
|
||||
.description("Steward Integration Processor")
|
||||
.build()
|
||||
));
|
||||
|
||||
// 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")
|
||||
.integrationProcessorLabel("STEWARD")
|
||||
.accountFieldName("Phone Number")
|
||||
.build(),
|
||||
Provider.builder()
|
||||
.description("NetOne Airtime")
|
||||
.clientId("netone_usd_airtime")
|
||||
.label("NETONE")
|
||||
.category("AIRTIME")
|
||||
.image("netone.png")
|
||||
.externalId("95d485a7-39c9-4559-8a27-6521969abe8f")
|
||||
.integrationProcessorLabel("STEWARD")
|
||||
.accountFieldName("Phone Number")
|
||||
.build(),
|
||||
Provider.builder()
|
||||
.description("Prepaid Zesa")
|
||||
.clientId("powertel_zesa")
|
||||
.label("ZESA")
|
||||
.category("UTILITIES")
|
||||
.image("zesa.png")
|
||||
.externalId("0f67f7cc-b62b-4fb5-915f-6740b2afdba6")
|
||||
.integrationProcessorLabel("STEWARD")
|
||||
.accountFieldName("Meter Number")
|
||||
.build(),
|
||||
Provider.builder()
|
||||
.description("Econet Broadband")
|
||||
.clientId("econet_broadband_usd")
|
||||
.label("ECONET_BROADBAND")
|
||||
.category("AIRTIME")
|
||||
.image("econet.png")
|
||||
.externalId("287863e2-a791-4106-b629-79dadc9a6779")
|
||||
.integrationProcessorLabel("STEWARD")
|
||||
.accountFieldName("Phone Number")
|
||||
.build()
|
||||
);
|
||||
providerService.getProviderRepository().saveAll(providers);
|
||||
|
||||
// Clear existing charges and charge conditions before seeding
|
||||
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("Project configuration seeding complete");
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package zw.qantra.tm.domain.controllers;
|
||||
|
||||
import io.nflow.engine.internal.dao.WorkflowInstanceDao;
|
||||
import io.nflow.engine.service.WorkflowInstanceService;
|
||||
import io.nflow.engine.workflow.instance.QueryWorkflowInstances;
|
||||
import io.nflow.engine.workflow.instance.WorkflowInstance;
|
||||
@@ -15,8 +14,8 @@ import org.springframework.web.bind.annotation.*;
|
||||
import zw.qantra.tm.domain.dtos.*;
|
||||
import zw.qantra.tm.domain.services.OtpService;
|
||||
import zw.qantra.tm.domain.services.UserService;
|
||||
import zw.qantra.tm.domain.workflows.RegistrationWorkflow;
|
||||
import zw.qantra.tm.domain.workflows.WorkflowUtils;
|
||||
import zw.qantra.tm.domain.services.processors.workflows.RegistrationWorkflow;
|
||||
import zw.qantra.tm.domain.services.processors.workflows.WorkflowUtils;
|
||||
import zw.qantra.tm.utils.Utils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package zw.qantra.tm.domain.controllers;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.i18n.phonenumbers.NumberParseException;
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||
import com.google.i18n.phonenumbers.Phonenumber;
|
||||
@@ -28,8 +27,8 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import zw.qantra.tm.domain.workflows.TransactionWorkflow;
|
||||
import zw.qantra.tm.domain.workflows.WorkflowUtils;
|
||||
import zw.qantra.tm.domain.services.processors.workflows.TransactionWorkflow;
|
||||
import zw.qantra.tm.domain.services.processors.workflows.WorkflowUtils;
|
||||
import zw.qantra.tm.utils.LogUtils;
|
||||
import zw.qantra.tm.utils.Utils;
|
||||
|
||||
@@ -189,16 +188,10 @@ public class TransactonController {
|
||||
throws ExecutionException, InterruptedException, NumberParseException {
|
||||
LogUtils.logPrettyJson(logger, "incoming transaction", transaction);
|
||||
|
||||
// phone must begin with '+'
|
||||
// need to format the debit phone number since it's the business key
|
||||
PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
|
||||
Phonenumber.PhoneNumber number = phoneUtil.parse(transaction.getDebitPhone(), "");
|
||||
String formattedPhoneNumber = phoneUtil.format(number, PhoneNumberUtil.PhoneNumberFormat.E164);
|
||||
|
||||
var instance = workflowInstanceFactory.newWorkflowInstanceBuilder()
|
||||
.setType(TransactionWorkflow.TYPE)
|
||||
.setExternalId(transaction.getUserId() + "-" + new Instant().getMillis())
|
||||
.setBusinessKey(formattedPhoneNumber)
|
||||
.setBusinessKey(transaction.getUserId())
|
||||
.putStateVariable("confirmation", Utils.toJson(transaction))
|
||||
.setNextActivation(DateTime.now())
|
||||
.build();
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package zw.qantra.tm.domain.dtos.erp;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.*;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
public class CustomerDto {
|
||||
@JsonProperty("doctype")
|
||||
private String doctype;
|
||||
|
||||
@JsonProperty("customer_name")
|
||||
private String customerName;
|
||||
|
||||
@JsonProperty("customer_group")
|
||||
private String customerGroup;
|
||||
|
||||
@JsonProperty("territory")
|
||||
private String territory;
|
||||
|
||||
@JsonProperty("customer_type")
|
||||
private String customerType;
|
||||
|
||||
@JsonProperty("language")
|
||||
private String language;
|
||||
|
||||
@JsonProperty("mobile_no")
|
||||
private String mobileNo;
|
||||
|
||||
@JsonProperty("email_id")
|
||||
private String emailId;
|
||||
|
||||
@JsonProperty("docstatus")
|
||||
private Integer docstatus;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package zw.qantra.tm.domain.dtos.erp;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class JournalEntryAccountDto {
|
||||
private String doctype;
|
||||
private String account;
|
||||
@JsonProperty("debit_in_account_currency")
|
||||
private BigDecimal debitInAccountCurrency;
|
||||
@JsonProperty("credit_in_account_currency")
|
||||
private BigDecimal creditInAccountCurrency;
|
||||
@JsonProperty("cost_center")
|
||||
private String costCenter;
|
||||
@JsonProperty("party_type")
|
||||
private String partyType;
|
||||
private String party;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package zw.qantra.tm.domain.dtos.erp;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class JournalEntryDto {
|
||||
private String doctype;
|
||||
private String company;
|
||||
@JsonProperty("posting_date")
|
||||
private String postingDate;
|
||||
private int docstatus;
|
||||
private List<JournalEntryAccountDto> accounts;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package zw.qantra.tm.domain.dtos.erp;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PaymentEntryDto {
|
||||
private String doctype;
|
||||
private String company;
|
||||
@JsonProperty("payment_type")
|
||||
private String paymentType;
|
||||
@JsonProperty("party_type")
|
||||
private String partyType;
|
||||
private String party;
|
||||
@JsonProperty("paid_from")
|
||||
private String paidFrom;
|
||||
@JsonProperty("paid_to")
|
||||
private String paidTo;
|
||||
@JsonProperty("paid_amount")
|
||||
private BigDecimal paidAmount;
|
||||
@JsonProperty("received_amount")
|
||||
private BigDecimal receivedAmount;
|
||||
@JsonProperty("posting_date")
|
||||
private String postingDate;
|
||||
@JsonProperty("reference_no")
|
||||
private String referenceNo;
|
||||
@JsonProperty("reference_date")
|
||||
private String referenceDate;
|
||||
private List<PaymentReferenceDto> references;
|
||||
private int docstatus;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package zw.qantra.tm.domain.dtos.erp;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PaymentReferenceDto {
|
||||
@JsonProperty("reference_doctype")
|
||||
private String referenceDoctype;
|
||||
@JsonProperty("reference_name")
|
||||
private String referenceName;
|
||||
@JsonProperty("allocated_amount")
|
||||
private BigDecimal allocatedAmount;
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package zw.qantra.tm.domain.dtos.erp;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PurchaseInvoiceDto {
|
||||
|
||||
private String doctype;
|
||||
private String supplier;
|
||||
@JsonProperty("posting_date")
|
||||
private String postingDate;
|
||||
@JsonProperty("due_date")
|
||||
private String dueDate;
|
||||
private String company;
|
||||
@JsonProperty("is_return")
|
||||
private int isReturn;
|
||||
private String currency;
|
||||
@JsonProperty("conversion_rate")
|
||||
private int conversionRate;
|
||||
@JsonProperty("net_total")
|
||||
private BigDecimal netTotal;
|
||||
@JsonProperty("grand_total")
|
||||
private BigDecimal grandTotal;
|
||||
@JsonProperty("is_paid")
|
||||
private int isPaid;
|
||||
private int docstatus;
|
||||
private List<PurchaseInvoiceItemDto> items;
|
||||
private List<PurchaseTaxDto> taxes;
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package zw.qantra.tm.domain.dtos.erp;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PurchaseInvoiceItemDto {
|
||||
|
||||
private String doctype;
|
||||
@JsonProperty("item_code")
|
||||
private String itemCode;
|
||||
@JsonProperty("item_name")
|
||||
private String itemName;
|
||||
private BigDecimal qty;
|
||||
private int rate;
|
||||
private BigDecimal amount;
|
||||
private String uom;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package zw.qantra.tm.domain.dtos.erp;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PurchaseTaxDto {
|
||||
|
||||
private String doctype;
|
||||
@JsonProperty("charge_type")
|
||||
private String chargeType;
|
||||
@JsonProperty("account_head")
|
||||
private String accountHead;
|
||||
private BigDecimal rate;
|
||||
@JsonProperty("cost_center")
|
||||
private String costCenter;
|
||||
@JsonProperty("tax_amount")
|
||||
private BigDecimal taxAmount;
|
||||
private BigDecimal total;
|
||||
private String description;
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package zw.qantra.tm.domain.dtos.erp;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SalesInvoiceDto {
|
||||
private String doctype;
|
||||
private String customer;
|
||||
@JsonProperty("posting_date")
|
||||
private String postingDate;
|
||||
@JsonProperty("due_date")
|
||||
private String dueDate;
|
||||
private String company;
|
||||
private int docstatus;
|
||||
private List<SalesInvoiceItemDto> items;
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package zw.qantra.tm.domain.dtos.erp;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SalesInvoiceItemDto {
|
||||
@JsonProperty("item_code")
|
||||
private String itemCode;
|
||||
private int qty;
|
||||
private BigDecimal rate;
|
||||
private BigDecimal amount;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package zw.qantra.tm.domain.dtos.erp;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class VelocityCustomerDto {
|
||||
private String name;
|
||||
private String phone;
|
||||
private String email;
|
||||
private String billingCurrencyString;
|
||||
private String companyIdString;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package zw.qantra.tm.domain.dtos.erp;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class VelocityCustomerResponseDto {
|
||||
private String id;
|
||||
private String createdAt;
|
||||
private String name;
|
||||
private String phone;
|
||||
private String email;
|
||||
private String status;
|
||||
private String billingCurrencyString;
|
||||
private String companyIdString;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package zw.qantra.tm.domain.dtos.erp;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class VelocitySalesOrderDto {
|
||||
private String currencyCodeString;
|
||||
private String orderDate;
|
||||
private String dueDate;
|
||||
private String notes;
|
||||
private Boolean authorized;
|
||||
private List<ItemDto> items;
|
||||
private List<ChargeDto> charges;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class ItemDto {
|
||||
private String name;
|
||||
private String itemCode;
|
||||
private BigDecimal qty;
|
||||
private BigDecimal unitPrice;
|
||||
private BigDecimal amount;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class ChargeDto {
|
||||
private BigDecimal amount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package zw.qantra.tm.domain.dtos.erp;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class VelocitySalesOrderResponseDto {
|
||||
private String state;
|
||||
private String status;
|
||||
private BodyDto body;
|
||||
private String message;
|
||||
private String workflowId;
|
||||
private String externalId;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public static class BodyDto {
|
||||
private String id;
|
||||
private String createdAt;
|
||||
private Long orderDate;
|
||||
private Long dueDate;
|
||||
private BigDecimal paidAmount;
|
||||
private BigDecimal changeAmount;
|
||||
private BigDecimal outstandingAmount;
|
||||
private String trace;
|
||||
private Boolean authorized;
|
||||
private String name;
|
||||
private BigDecimal grandTotal;
|
||||
private BigDecimal taxAmount;
|
||||
private BigDecimal netTotal;
|
||||
private String notes;
|
||||
private BigDecimal totalQty;
|
||||
private String status;
|
||||
private String createdBy;
|
||||
private String companyIdString;
|
||||
private String customerIdString;
|
||||
private CurrencyDto currency;
|
||||
private String currencyCodeString;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public static class CurrencyDto {
|
||||
private String id;
|
||||
private String createdAt;
|
||||
private String name;
|
||||
private String symbol;
|
||||
private String code;
|
||||
private BigDecimal rate;
|
||||
private Boolean defaultCurrency;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package zw.qantra.tm.domain.dtos.erp;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class VelocityTransactionDto {
|
||||
private BigDecimal amount;
|
||||
private String paymentProcessorLabel;
|
||||
private String debitPhone;
|
||||
private String debitRegion;
|
||||
private String debitCurrency;
|
||||
private String debitRef;
|
||||
private String creditPhone;
|
||||
private String creditRegion;
|
||||
private String creditAccount;
|
||||
private String type;
|
||||
private String authType;
|
||||
private String salesOrderId;
|
||||
private String cancelUrl;
|
||||
private String successUrl;
|
||||
}
|
||||
@@ -42,7 +42,7 @@ public class Charge extends BaseEntity {
|
||||
private TargetEvent targetEvent; // include / exclude
|
||||
|
||||
@JsonIgnore
|
||||
@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
|
||||
@ManyToMany(fetch = FetchType.EAGER)
|
||||
@JoinTable(
|
||||
name = "charge_include_products",
|
||||
joinColumns = @JoinColumn(name = "charge_id"),
|
||||
@@ -51,7 +51,7 @@ public class Charge extends BaseEntity {
|
||||
private Set<ChargeCondition> includes = new HashSet<>();
|
||||
|
||||
@JsonIgnore
|
||||
@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
|
||||
@ManyToMany(fetch = FetchType.EAGER)
|
||||
@JoinTable(
|
||||
name = "charge_exclude_products",
|
||||
joinColumns = @JoinColumn(name = "charge_id"),
|
||||
|
||||
@@ -77,6 +77,11 @@ public class Transaction extends BaseEntity {
|
||||
@Enumerated(EnumType.STRING)
|
||||
private Status pollingStatus;
|
||||
|
||||
|
||||
private String orderId;
|
||||
private String orderTrace;
|
||||
private String orderTransactionTrace;
|
||||
|
||||
private String erpSalesRef;
|
||||
private String erpJournalRef;
|
||||
private String erpSalesPaymentRef;
|
||||
|
||||
@@ -8,4 +8,5 @@ import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface CategoryRepository extends JpaRepository<Category, UUID> {
|
||||
boolean existsByName(String name);
|
||||
}
|
||||
@@ -4,8 +4,11 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import zw.qantra.tm.domain.models.ChargeCondition;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface ChargeConditionRepository extends JpaRepository<ChargeCondition, UUID> {
|
||||
boolean existsByCodes(String codes);
|
||||
Optional<ChargeCondition> findByCodes(String codes);
|
||||
}
|
||||
@@ -2,13 +2,17 @@ package zw.qantra.tm.domain.repositories;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import zw.qantra.tm.domain.enums.ChargeLabelEnum;
|
||||
import zw.qantra.tm.domain.enums.CurrencyType;
|
||||
import zw.qantra.tm.domain.models.Charge;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface ChargeRepository extends JpaRepository<Charge, UUID> {
|
||||
List<Charge> findAllByCurrency(CurrencyType currency);
|
||||
boolean existsByChargeLabelAndCurrency(ChargeLabelEnum chargeLabel, CurrencyType currency);
|
||||
boolean existsByChargeLabelAndCurrencyAndPercentageRate(ChargeLabelEnum chargeLabel, CurrencyType currency, BigDecimal percentageRate);
|
||||
}
|
||||
@@ -8,4 +8,5 @@ import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface IntegrationProcessorRepository extends JpaRepository<IntegrationProcessor, UUID> {
|
||||
boolean existsByName(String name);
|
||||
}
|
||||
@@ -8,4 +8,5 @@ import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface PaymentProcessorRepository extends JpaRepository<PaymentProcessor, UUID> {
|
||||
boolean existsByLabel(String label);
|
||||
}
|
||||
@@ -11,4 +11,5 @@ import java.util.UUID;
|
||||
public interface ProviderRepository extends JpaRepository<Provider, UUID> {
|
||||
List<Provider> findByCategory(String category);
|
||||
Provider findByClientId(String clientId);
|
||||
boolean existsByClientId(String clientId);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package zw.qantra.tm.domain.services;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@@ -10,6 +11,7 @@ public class EmailService {
|
||||
@Autowired
|
||||
private JavaMailSender emailSender;
|
||||
|
||||
@Async
|
||||
public void sendSimpleMessage(
|
||||
String to, String subject, String text) {
|
||||
SimpleMailMessage message = new SimpleMailMessage();
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package zw.qantra.tm.domain.services;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.stereotype.Service;
|
||||
import zw.qantra.tm.rest.RestService;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.LinkedList;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class HotRechargeBalanceService {
|
||||
|
||||
Logger logger = LoggerFactory.getLogger(HotRechargeBalanceService.class);
|
||||
|
||||
private final RestService restService;
|
||||
|
||||
@Value("${hot.api.url:https://ssl.hot.co.zw/api/v3}")
|
||||
private String hotApiUrl;
|
||||
|
||||
@Cacheable(value = "hotrechargeBalance", key = "#token + '_' + #amount.toString()")
|
||||
public boolean checkBalance(String token, BigDecimal amount) {
|
||||
try {
|
||||
String balanceUrl = hotApiUrl + "/account/balance/4";
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setBearerAuth(token);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
LinkedList<LinkedHashMap<String, Object>> responseEntity = restService.getAs(balanceUrl, headers, LinkedList.class);
|
||||
LinkedHashMap<String, Object> response = responseEntity.get(0);
|
||||
logger.info("Balance response: {}", response);
|
||||
|
||||
if (response != null && response.containsKey("balance")) {
|
||||
BigDecimal balance = new BigDecimal(response.get("balance").toString());
|
||||
logger.info("Current balance: {}", balance);
|
||||
return balance.compareTo(amount) > 0;
|
||||
}
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to check balance: {}", e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@CacheEvict(value = "hotrechargeBalance", allEntries = true)
|
||||
public void evictCache() {
|
||||
logger.info("Evicting HotRecharge balance cache after successful transaction");
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ public class HotRechargeTokenService {
|
||||
private String hotAccessCode;
|
||||
@Value("${hot.password}")
|
||||
private String hotPassword;
|
||||
@Value("${hot.token.expiry.minutes:30}")
|
||||
@Value("${hot.token.expiry.minutes:25}")
|
||||
private int tokenExpiryMinutes;
|
||||
|
||||
private String cachedToken;
|
||||
|
||||
@@ -26,4 +26,17 @@ public class SettingService {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String getStringSetting(String key) {
|
||||
try {
|
||||
Setting setting = settingRepository.findBySettingName(key);
|
||||
if (setting != null) {
|
||||
return setting.getSettingValue();
|
||||
}
|
||||
return "";
|
||||
} catch (Exception exception) {
|
||||
exception.printStackTrace();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
package zw.qantra.tm.domain.services;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import zw.qantra.tm.domain.dtos.RegisterRequest;
|
||||
import zw.qantra.tm.domain.dtos.erp.*;
|
||||
import zw.qantra.tm.domain.enums.CurrencyType;
|
||||
import zw.qantra.tm.domain.enums.Status;
|
||||
import zw.qantra.tm.domain.models.Transaction;
|
||||
import zw.qantra.tm.domain.repositories.TransactionRepository;
|
||||
import zw.qantra.tm.configs.VelocityApiProperties;
|
||||
import zw.qantra.tm.exceptions.AlreadyExistsException;
|
||||
import zw.qantra.tm.rest.RestService;
|
||||
import zw.qantra.tm.utils.Utils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class VelocityPaymentService {
|
||||
private final TransactionRepository transactionRepository;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final RestService restService;
|
||||
private final VelocityApiProperties velocityApiProperties;
|
||||
private final SettingService settingService;
|
||||
|
||||
|
||||
private static final String VELOCITY_API_CREATE_ORDER_PATH = "/sales-orders";
|
||||
private static final String VELOCITY_API_INITIATE_PAYMENT_PATH = "/transactions";
|
||||
private static final String VELOCITY_API_POLL_PAYMENT_PATH = "/transactions/poll/{traceId}";
|
||||
private static final String VELOCITY_API_UPDATE_WORKFLOW_PATH = "/sales-orders/update-workflow/{traceId}";
|
||||
private static final String VELOCITY_API_CUSTOMERS = "/customers";
|
||||
|
||||
|
||||
public VelocityCustomerResponseDto createCustomer(RegisterRequest registerRequest) {
|
||||
VelocityCustomerDto payload = VelocityCustomerDto.builder()
|
||||
.name(registerRequest.getFirstName() + " " + registerRequest.getLastName())
|
||||
.email(registerRequest.getEmail())
|
||||
.phone(registerRequest.getPhone())
|
||||
.billingCurrencyString(CurrencyType.USD.name())
|
||||
.companyIdString(settingService.getStringSetting("VELOCITY_COMPANY_ID"))
|
||||
.build();
|
||||
|
||||
try {
|
||||
LinkedHashMap responseMap = makeRequest(HttpMethod.POST, VELOCITY_API_CUSTOMERS, payload);
|
||||
String status = (String) responseMap.get("status");
|
||||
String id = (String) responseMap.get("id");
|
||||
log.info("Customer created with status: {}, id: {}", status, id);
|
||||
|
||||
if("PENDING".equalsIgnoreCase(status) && id != null) {
|
||||
LinkedHashMap authResponse = makeRequest(HttpMethod.PUT,
|
||||
VELOCITY_API_CUSTOMERS + "/authorize/" + id, null);
|
||||
String workflowStatus = (String) authResponse.get("status");
|
||||
|
||||
if("finished".equalsIgnoreCase(workflowStatus)) {
|
||||
return Utils.deepCopy(
|
||||
authResponse.get("body"), VelocityCustomerResponseDto.class);
|
||||
}
|
||||
}else {
|
||||
List<String> errors = (List<String>) responseMap.get("errors");
|
||||
if(errors != null && !errors.isEmpty()){
|
||||
String errorMessage = errors.get(0);
|
||||
|
||||
if(errorMessage.contains("already exists")) {
|
||||
log.warn("Customer already exists in Velocity: {}", errorMessage);
|
||||
throw new AlreadyExistsException(errorMessage); // or handle as needed
|
||||
}
|
||||
log.error("Error creating customer: {}", errorMessage);
|
||||
throw new IllegalStateException("Error creating customer: " + errorMessage);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
log.error("Error creating customer: {}", e.getMessage(), e);
|
||||
throw new IllegalStateException("Error creating customer: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Transaction processPayment(Transaction transaction) {
|
||||
Transaction orderTransaction = createSalesOrder(transaction);
|
||||
|
||||
return initiatePayment(orderTransaction);
|
||||
}
|
||||
|
||||
public Transaction createSalesOrder(Transaction transaction) {
|
||||
BigDecimal amount = transaction.getTotalAmount();
|
||||
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
VelocitySalesOrderDto payload = VelocitySalesOrderDto.builder()
|
||||
.currencyCodeString(transaction.getDebitCurrency().name())
|
||||
.orderDate(LocalDate.now().format(fmt))
|
||||
.dueDate(LocalDate.now().plusDays(7).format(fmt))
|
||||
.notes(transaction.getReference())
|
||||
.authorized(Boolean.TRUE)
|
||||
.items(Collections.singletonList(VelocitySalesOrderDto.ItemDto.builder()
|
||||
.name(transaction.getBillName())
|
||||
.itemCode("VLT-" + transaction.getProviderLabel())
|
||||
.qty(BigDecimal.ONE)
|
||||
.unitPrice(amount)
|
||||
.amount(amount)
|
||||
.build()))
|
||||
.charges(Collections.emptyList())
|
||||
.build();
|
||||
|
||||
try {
|
||||
LinkedHashMap responseMap = makeRequest(HttpMethod.POST, VELOCITY_API_CREATE_ORDER_PATH, payload);
|
||||
VelocitySalesOrderResponseDto response = Utils.deepCopy(
|
||||
responseMap, VelocitySalesOrderResponseDto.class);
|
||||
|
||||
String workflowStatus = response.getStatus();
|
||||
VelocitySalesOrderResponseDto.BodyDto body = response.getBody();
|
||||
String orderStatus = body.getStatus();
|
||||
String velocityOrderId = body.getId();
|
||||
String velocityOrderTrace = body.getTrace();
|
||||
String velocityOrderName = body.getName();
|
||||
if ("manual".equalsIgnoreCase(workflowStatus) && "UNPAID".equalsIgnoreCase(orderStatus)) {
|
||||
transaction.setOrderId(velocityOrderId);
|
||||
transaction.setOrderTrace(velocityOrderTrace);
|
||||
transaction.setErpSalesRef(velocityOrderName);
|
||||
return transaction;
|
||||
}
|
||||
|
||||
log.error("Unexpected sales order response from Velocity: {}", response);
|
||||
throw new IllegalStateException("Unexpected sales order response from Velocity");
|
||||
} catch (Exception e) {
|
||||
log.error("Error creating sales order: {}", e.getMessage(), e);
|
||||
throw new IllegalStateException("Error creating sales order: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Transaction initiatePayment(Transaction transaction) {
|
||||
String paymentProcessorLabel = Objects.equals(transaction.getPaymentProcessorLabel(), "MPGS")
|
||||
? "VMC" : transaction.getPaymentProcessorLabel();
|
||||
VelocityTransactionDto payload = VelocityTransactionDto.builder()
|
||||
.salesOrderId(transaction.getOrderId())
|
||||
.amount(transaction.getTotalAmount())
|
||||
.debitPhone(transaction.getDebitPhone())
|
||||
.debitRegion("ZW")
|
||||
.type("REQUEST")
|
||||
.paymentProcessorLabel(paymentProcessorLabel)
|
||||
.debitCurrency(transaction.getDebitCurrency().name())
|
||||
.debitRef(transaction.getErpSalesRef())
|
||||
.creditPhone(velocityApiProperties.getCreditPhone())
|
||||
.creditRegion("ZW")
|
||||
.creditAccount(velocityApiProperties.getCreditAccount())
|
||||
.authType(velocityApiProperties.getDefaultAuthType())
|
||||
.build();
|
||||
|
||||
if ("MPGS".equalsIgnoreCase(transaction.getPaymentProcessorLabel())) {
|
||||
payload.setCancelUrl(velocityApiProperties.getCancelUrl() + "/" + transaction.getOrderId());
|
||||
payload.setSuccessUrl(velocityApiProperties.getSuccessUrl() + "/" + transaction.getOrderId());
|
||||
}
|
||||
|
||||
try {
|
||||
LinkedHashMap response = makeRequest(
|
||||
HttpMethod.POST, VELOCITY_API_INITIATE_PAYMENT_PATH, payload);
|
||||
LinkedHashMap body = (LinkedHashMap) response.get("body");
|
||||
String transactionStatus = (String) body.get("status");
|
||||
String tranTrace = (String) body.get("trace");
|
||||
String transactionName = (String) body.get("name");
|
||||
String redirectUrl = body.containsKey("redirectUrl") ? (String) body.get("redirectUrl") : null;
|
||||
|
||||
if ("SUCCESS".equalsIgnoreCase(transactionStatus)) {
|
||||
transaction.setErpSalesPaymentRef(transactionName);
|
||||
transaction.setOrderTransactionTrace(tranTrace);
|
||||
transaction.setTargetUrl(redirectUrl);
|
||||
return transaction;
|
||||
}
|
||||
return null;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error initiating payment: {}", e.getMessage(), e);
|
||||
throw new IllegalStateException("Error initiating payment: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public Transaction pollPaymentStatus(Transaction transaction) {
|
||||
LinkedHashMap response = makeRequest(
|
||||
HttpMethod.PUT,
|
||||
VELOCITY_API_POLL_PAYMENT_PATH.replace("{traceId}", transaction.getOrderTransactionTrace()),
|
||||
null
|
||||
);
|
||||
|
||||
LinkedHashMap body = (LinkedHashMap) response.get("body");
|
||||
transaction.setPaymentStatus(Status.valueOf((String) body.get("paymentStatus")));
|
||||
transaction.setPollingStatus(Status.valueOf((String) body.get("pollStatus")));
|
||||
transaction.setDebitRef((String) body.get("debitRef"));
|
||||
transaction.setPaymentProcessorRef((String) body.get("paymentProcessorRef"));
|
||||
|
||||
return transaction;
|
||||
}
|
||||
|
||||
@Async
|
||||
public void updateSalesOrderWorkflow(Transaction transaction) {
|
||||
LinkedHashMap response = makeRequest(
|
||||
HttpMethod.PUT,
|
||||
VELOCITY_API_UPDATE_WORKFLOW_PATH.replace("{traceId}", transaction.getOrderTrace()),
|
||||
null
|
||||
);
|
||||
|
||||
LinkedHashMap body = (LinkedHashMap) response.get("body");
|
||||
LinkedHashMap salesOrderNode = (LinkedHashMap) body.get("salesOrder");
|
||||
String status = (String) salesOrderNode.get("status");
|
||||
|
||||
if (!"PAID".equals(status)) {
|
||||
log.info("Sales order status is not PAID. Status: {}", status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private LinkedHashMap makeRequest(HttpMethod method, String path, Object payload) {
|
||||
long startNs = System.nanoTime();
|
||||
ResponseEntity<LinkedHashMap> response;
|
||||
try {
|
||||
response = restService.exchange(
|
||||
buildUrl(path),
|
||||
method,
|
||||
new HttpEntity<>(payload, headers()),
|
||||
LinkedHashMap.class
|
||||
);
|
||||
} catch (Exception ex) {
|
||||
log.error("Velocity API request failed. method={}, path={}", method, path, ex);
|
||||
throw new IllegalStateException("Velocity API request failed: " + ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
LinkedHashMap body = response.getBody();
|
||||
if (!response.getStatusCode().is2xxSuccessful()) {
|
||||
throw new IllegalStateException("Velocity API request failed with status: " + response.getStatusCode());
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
private JsonNode resolveBody(JsonNode response) {
|
||||
JsonNode body = response.path("body");
|
||||
return body.isMissingNode() || body.isNull() ? response : body;
|
||||
}
|
||||
|
||||
private String normalize(String value) {
|
||||
return value == null ? null : value.toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private Double numberValue(JsonNode node, String key, Double fallback) {
|
||||
JsonNode value = node.get(key);
|
||||
if (value == null || value.isNull()) {
|
||||
return fallback;
|
||||
}
|
||||
return value.asDouble();
|
||||
}
|
||||
|
||||
private HttpHeaders headers() {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
if (velocityApiProperties.getApiKey() != null && !velocityApiProperties.getApiKey().isBlank()) {
|
||||
headers.set("X-API-Key", velocityApiProperties.getApiKey());
|
||||
}
|
||||
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
|
||||
return headers;
|
||||
}
|
||||
|
||||
private String buildUrl(String path) {
|
||||
return UriComponentsBuilder.fromUriString(velocityApiProperties.getBaseUrl())
|
||||
.path(path)
|
||||
.build()
|
||||
.toUriString();
|
||||
}
|
||||
|
||||
private String text(JsonNode node, String... keys) {
|
||||
for (String key : keys) {
|
||||
JsonNode value = node.get(key);
|
||||
if (value != null && !value.isNull()) {
|
||||
return value.asText();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isTerminal(String status) {
|
||||
if (status == null) {
|
||||
return false;
|
||||
}
|
||||
String normalized = status.toUpperCase();
|
||||
return normalized.equals("SUCCESS")
|
||||
|| normalized.equals("PAID")
|
||||
|| normalized.equals("FAILED")
|
||||
|| normalized.equals("DECLINED")
|
||||
|| normalized.equals("CANCELLED");
|
||||
}
|
||||
|
||||
private void sleep() {
|
||||
try {
|
||||
Thread.sleep(Math.max(200L, velocityApiProperties.getPollIntervalMs()));
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IllegalStateException("Interrupted while polling Velocity payment status", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package zw.qantra.tm.domain.services.processors.confirmations.hotrecharge;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import zw.qantra.tm.domain.services.AdditionalDataService;
|
||||
import zw.qantra.tm.domain.services.HotRechargeBalanceService;
|
||||
import zw.qantra.tm.domain.services.HotRechargeTokenService;
|
||||
import zw.qantra.tm.rest.RestService;
|
||||
|
||||
@@ -11,7 +12,8 @@ public class EconetBundlesConfirmationHotProcessor extends NetoneConfirmationHot
|
||||
public EconetBundlesConfirmationHotProcessor(
|
||||
AdditionalDataService additionalDataService,
|
||||
HotRechargeTokenService hotRechargeTokenService,
|
||||
HotRechargeBalanceService hotRechargeBalanceService,
|
||||
RestService restService) {
|
||||
super(additionalDataService, hotRechargeTokenService, restService);
|
||||
super(additionalDataService, hotRechargeTokenService, hotRechargeBalanceService, restService);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package zw.qantra.tm.domain.services.processors.confirmations.hotrecharge;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import zw.qantra.tm.domain.services.AdditionalDataService;
|
||||
import zw.qantra.tm.domain.services.HotRechargeBalanceService;
|
||||
import zw.qantra.tm.domain.services.HotRechargeTokenService;
|
||||
import zw.qantra.tm.rest.RestService;
|
||||
|
||||
@@ -11,7 +12,8 @@ public class EconetConfirmationHotProcessor extends NetoneConfirmationHotProcess
|
||||
public EconetConfirmationHotProcessor(
|
||||
AdditionalDataService additionalDataService,
|
||||
HotRechargeTokenService hotRechargeTokenService,
|
||||
HotRechargeBalanceService hotRechargeBalanceService,
|
||||
RestService restService) {
|
||||
super(additionalDataService, hotRechargeTokenService, restService);
|
||||
super(additionalDataService, hotRechargeTokenService, hotRechargeBalanceService, restService);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ 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.HotRechargeBalanceService;
|
||||
import zw.qantra.tm.domain.services.HotRechargeTokenService;
|
||||
import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface;
|
||||
import zw.qantra.tm.rest.RestService;
|
||||
@@ -23,9 +24,10 @@ import java.util.*;
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class NetoneConfirmationHotProcessor implements TransactionProcessorInterface {
|
||||
private final AdditionalDataService additionalDataService;
|
||||
private final HotRechargeTokenService hotRechargeTokenService;
|
||||
private final RestService restService;
|
||||
protected final AdditionalDataService additionalDataService;
|
||||
protected final HotRechargeTokenService hotRechargeTokenService;
|
||||
protected final HotRechargeBalanceService hotRechargeBalanceService;
|
||||
protected final RestService restService;
|
||||
|
||||
@Value("${hot.api.url:https://ssl.hot.co.zw/api/v3}")
|
||||
private String hotApiUrl;
|
||||
@@ -35,7 +37,7 @@ public class NetoneConfirmationHotProcessor implements TransactionProcessorInter
|
||||
public Object process(Transaction transaction) throws Exception {
|
||||
|
||||
String token = hotRechargeTokenService.getToken();
|
||||
if (!checkBalance(token, transaction.getAmount())) {
|
||||
if (!hotRechargeBalanceService.checkBalance(token, transaction.getAmount())) {
|
||||
transaction.setStatus(Status.FAILED);
|
||||
transaction.setResponseCode("92");
|
||||
transaction.setErrorMessage("There's a technical issue on our end. Please try again later.");
|
||||
@@ -82,26 +84,4 @@ public class NetoneConfirmationHotProcessor implements TransactionProcessorInter
|
||||
return transaction;
|
||||
}
|
||||
|
||||
private boolean checkBalance(String token, BigDecimal amount) {
|
||||
try {
|
||||
String balanceUrl = hotApiUrl + "/account/balance/4";
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setBearerAuth(token);
|
||||
|
||||
LinkedList responseEntity = restService.getAs(balanceUrl, headers, LinkedList.class);
|
||||
LinkedHashMap response = (LinkedHashMap) responseEntity.get(0);
|
||||
log.info("Response: {}", response);
|
||||
|
||||
if (response != null && response.containsKey("balance")) {
|
||||
Object balance = response.get("balance");
|
||||
log.info("Current balance: {}", balance);
|
||||
return (new BigDecimal(balance.toString())).compareTo(amount) > 0;
|
||||
}
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to check balance: {}", e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import zw.qantra.tm.domain.models.AdditionalData;
|
||||
import zw.qantra.tm.domain.models.Provider;
|
||||
import zw.qantra.tm.domain.models.Transaction;
|
||||
import zw.qantra.tm.domain.services.AdditionalDataService;
|
||||
import zw.qantra.tm.domain.services.HotRechargeBalanceService;
|
||||
import zw.qantra.tm.domain.services.HotRechargeTokenService;
|
||||
import zw.qantra.tm.domain.services.ProviderService;
|
||||
import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface;
|
||||
@@ -33,6 +34,7 @@ public class ZesaConfirmationHotProcessor implements TransactionProcessorInterfa
|
||||
private final AdditionalDataService additionalDataService;
|
||||
private final ProviderService providerService;
|
||||
private final HotRechargeTokenService hotRechargeTokenService;
|
||||
private final HotRechargeBalanceService hotRechargeBalanceService;
|
||||
|
||||
@Value("${hot.api.url:https://ssl.hot.co.zw/api/v3}")
|
||||
private String hotApiUrl;
|
||||
@@ -50,7 +52,7 @@ public class ZesaConfirmationHotProcessor implements TransactionProcessorInterfa
|
||||
return transaction;
|
||||
}
|
||||
|
||||
if (!checkBalance(token, transaction.getAmount())) {
|
||||
if (!hotRechargeBalanceService.checkBalance(token, transaction.getAmount())) {
|
||||
transaction.setStatus(Status.FAILED);
|
||||
transaction.setResponseCode("92");
|
||||
transaction.setErrorMessage("There's a technical issue on our end. Please try again later.");
|
||||
@@ -103,28 +105,6 @@ public class ZesaConfirmationHotProcessor implements TransactionProcessorInterfa
|
||||
return additionalDataList;
|
||||
}
|
||||
|
||||
private boolean checkBalance(String token, BigDecimal amount) {
|
||||
try {
|
||||
String balanceUrl = hotApiUrl + "/account/balance/4";
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setBearerAuth(token);
|
||||
|
||||
LinkedList responseEntity = restService.getAs(balanceUrl, headers, LinkedList.class);
|
||||
LinkedHashMap response = (LinkedHashMap) responseEntity.get(0);
|
||||
logger.info("Response: " + response);
|
||||
|
||||
if (response != null && response.containsKey("balance")) {
|
||||
Object balance = response.get("balance");
|
||||
logger.info("Current balance: " + balance);
|
||||
return (new BigDecimal(balance.toString())).compareTo(amount) > 0;
|
||||
}
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to check balance: " + e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> checkCustomer(String token, Transaction transaction) {
|
||||
try {
|
||||
Provider provider = providerService.getProviderRepository()
|
||||
|
||||
@@ -31,6 +31,7 @@ public class HotRechargeIntegrationProcessor implements TransactionProcessorInte
|
||||
private final SettingService settingService;
|
||||
private final HotRechargeTokenService hotRechargeTokenService;
|
||||
private final ProviderService providerService;
|
||||
private final HotRechargeBalanceService hotRechargeBalanceService;
|
||||
|
||||
@Value("${hot.api.url:https://ssl.hot.co.zw/api/v3}")
|
||||
private String hotApiUrl;
|
||||
@@ -53,7 +54,7 @@ public class HotRechargeIntegrationProcessor implements TransactionProcessorInte
|
||||
if(settingService.getBooleanSetting("SIMULATE_HOT_SUCCESS")){
|
||||
transaction.setStatus(Status.SUCCESS);
|
||||
transaction.setResponseCode("responseCode");
|
||||
transaction.setCreditRef("creditRef");
|
||||
transaction.setCreditRef("Simulated Integration Success");
|
||||
transaction.setErrorMessage(null);
|
||||
return transaction;
|
||||
}
|
||||
@@ -69,7 +70,7 @@ public class HotRechargeIntegrationProcessor implements TransactionProcessorInte
|
||||
return transaction;
|
||||
}
|
||||
|
||||
if (!checkBalance(token, transaction.getAmount())) {
|
||||
if (!hotRechargeBalanceService.checkBalance(token, transaction.getAmount())) {
|
||||
transaction.setStatus(Status.FAILED);
|
||||
transaction.setResponseCode("92");
|
||||
transaction.setErrorMessage("There's a technical issue on our end. Please try again later.");
|
||||
@@ -86,6 +87,7 @@ public class HotRechargeIntegrationProcessor implements TransactionProcessorInte
|
||||
|
||||
Boolean successful = (Boolean) rechargeResponse.get("successful");
|
||||
if (successful != null && successful) {
|
||||
hotRechargeBalanceService.evictCache();
|
||||
transaction.setStatus(Status.SUCCESS);
|
||||
transaction.setResponseCode("00");
|
||||
transaction.setCreditRef(rechargeResponse.get("rechargeId").toString());
|
||||
@@ -123,29 +125,6 @@ public class HotRechargeIntegrationProcessor implements TransactionProcessorInte
|
||||
|
||||
}
|
||||
|
||||
private boolean checkBalance(String token, BigDecimal amount) {
|
||||
try {
|
||||
String balanceUrl = hotApiUrl + "/account/balance/4";
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setBearerAuth(token);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
LinkedList<LinkedHashMap<String, Object>> responseEntity = restService.getAs(balanceUrl, headers, LinkedList.class);
|
||||
LinkedHashMap<String, Object> response = responseEntity.get(0);
|
||||
logger.info("Balance response: {}", response);
|
||||
|
||||
if (response != null && response.containsKey("balance")) {
|
||||
BigDecimal balance = new BigDecimal(response.get("balance").toString());
|
||||
logger.info("Current balance: {}", balance);
|
||||
return balance.compareTo(amount) > 0;
|
||||
}
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to check balance: {}", e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private LinkedHashMap<String, Object> performRecharge(String token, Transaction transaction) {
|
||||
try {
|
||||
String rechargeUrl = hotApiUrl + "/products/recharge";
|
||||
@@ -159,7 +138,12 @@ public class HotRechargeIntegrationProcessor implements TransactionProcessorInte
|
||||
request.put("Target", transaction.getCreditAccount());
|
||||
request.put("Amount", transaction.getAmount());
|
||||
|
||||
// do this for zesa
|
||||
// for zesa skip this
|
||||
if(!providerService.getProviderId(transaction.getBillClientId()).equals("41")) {
|
||||
request.put("CustomerSMS", "%COMPANYNAME% topped up your account with $%AMOUNT%.");
|
||||
}
|
||||
|
||||
// do this for zesa only
|
||||
if(providerService.getProviderId(transaction.getBillClientId()).equals("41")) {
|
||||
List<Map<String, String>> rechargeOptions = new ArrayList<>();
|
||||
if (transaction.getDebitPhone() != null) {
|
||||
|
||||
@@ -57,6 +57,14 @@ public class StewardIntegrationProcessor implements TransactionProcessorInterfac
|
||||
return transaction;
|
||||
}
|
||||
|
||||
if(settingService.getBooleanSetting("SIMULATE_STEWARD_SUCCESS")){
|
||||
transaction.setStatus(Status.SUCCESS);
|
||||
transaction.setResponseCode("responseCode");
|
||||
transaction.setCreditRef("Simulated Integration Success");
|
||||
transaction.setErrorMessage(null);
|
||||
return transaction;
|
||||
}
|
||||
|
||||
logger.info("processing integration");
|
||||
String token = restService.getMerchantToken();
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import zw.qantra.tm.domain.enums.Status;
|
||||
import zw.qantra.tm.domain.models.Transaction;
|
||||
import zw.qantra.tm.domain.services.SettingService;
|
||||
import zw.qantra.tm.domain.services.TransactionService;
|
||||
import zw.qantra.tm.domain.services.VelocityPaymentService;
|
||||
import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface;
|
||||
import zw.qantra.tm.rest.RestService;
|
||||
|
||||
@@ -29,6 +30,7 @@ public class EcocashRemotePaymentProcessor implements TransactionProcessorInterf
|
||||
private final RestService restService;
|
||||
private final TransactionService transactionService;
|
||||
private final SettingService settingService;
|
||||
private final VelocityPaymentService velocityPaymentService;
|
||||
|
||||
@Value("${sbz.aggregator.encryption-key}")
|
||||
private String encryptionKey;
|
||||
@@ -51,78 +53,10 @@ public class EcocashRemotePaymentProcessor implements TransactionProcessorInterf
|
||||
return transaction;
|
||||
}
|
||||
|
||||
String token = restService.getMerchantToken();
|
||||
|
||||
// send total less gateway charges
|
||||
BigDecimal totalAmount = transaction.getAmount()
|
||||
.add(transaction.getTax())
|
||||
.add(transaction.getCharge())
|
||||
.add(transaction.getGatewayCharge());
|
||||
|
||||
String concat = clientId+
|
||||
encryptionKey+
|
||||
transaction.getDebitPhone()+
|
||||
totalAmount;
|
||||
|
||||
String hex = getHash(concat);
|
||||
|
||||
SdkActionDto sdkActionDto = SdkActionDto.builder()
|
||||
.clientId(clientId)
|
||||
.clientSecret(clientSecret)
|
||||
.phone(transaction.getDebitPhone())
|
||||
.amount(totalAmount.toString())
|
||||
.currency(transaction.getDebitCurrency().toString())
|
||||
.hash(hex)
|
||||
.authType("REMOTE")
|
||||
.billerReference(transaction.getReference())
|
||||
.action("PAYMENT")
|
||||
.sandbox(true)
|
||||
.paymentProcessorLabel("ECOCASH")
|
||||
.responseUrl("https://peakapi.qantra.co.zw")
|
||||
.build();
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("Authorization", "Bearer " + token);
|
||||
headers.set("Content-Type", "application/json");
|
||||
|
||||
try {
|
||||
LinkedHashMap response = restService.postAs(url + "/merchant/sdk/action",
|
||||
sdkActionDto, headers, LinkedHashMap.class);
|
||||
|
||||
LinkedHashMap body = (LinkedHashMap) response.get("body");
|
||||
transaction.setSdkActionId((String) body.get("uid"));
|
||||
|
||||
if(body.get("status").equals("SUCCESS")){
|
||||
transaction.setStatus(Status.SUCCESS);
|
||||
transaction.setResponseCode("00");
|
||||
|
||||
LinkedHashMap transactionData = (LinkedHashMap) body.get("transaction");
|
||||
transaction.setPaymentProcessorRef((String) transactionData.get("debitReference"));
|
||||
|
||||
// getting omni channel rrn from additional data
|
||||
java.util.List<LinkedHashMap> additionalData =
|
||||
(java.util.List<LinkedHashMap>) transactionData.get("additionalData");
|
||||
if (additionalData != null) {
|
||||
for (LinkedHashMap dataItem : additionalData) {
|
||||
if ("Transaction Reference".equals(dataItem.get("name"))) {
|
||||
transaction.setDebitRef((String) dataItem.get("value"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transactionService.save(transaction);
|
||||
}else{
|
||||
transaction.setStatus(Status.FAILED);
|
||||
transaction.setResponseCode("01");
|
||||
String errorMessage = "Problem processing payment. Please try again.";
|
||||
if(body.get("transaction") != null) {
|
||||
LinkedHashMap transactionData = (LinkedHashMap) body.get("transaction");
|
||||
errorMessage = (String) transactionData.get("errorMessage");
|
||||
}
|
||||
transaction.setErrorMessage(errorMessage);
|
||||
}
|
||||
transaction = velocityPaymentService.processPayment(transaction);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
logger.error("Network error during payment processing: {}", e.getMessage(), e);
|
||||
transaction.setStatus(Status.FAILED);
|
||||
transaction.setResponseCode("99");
|
||||
@@ -149,42 +83,15 @@ public class EcocashRemotePaymentProcessor implements TransactionProcessorInterf
|
||||
return transaction;
|
||||
}
|
||||
|
||||
String token = restService.getMerchantToken();
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("Authorization", "Bearer " + token);
|
||||
headers.set("Content-Type", "application/json");
|
||||
|
||||
try {
|
||||
LinkedHashMap response = restService.getAs(url + "/merchant/sdk/reference/" + transaction.getReference(),
|
||||
headers, LinkedHashMap.class);
|
||||
|
||||
LinkedHashMap body = (LinkedHashMap) response.get("body");
|
||||
|
||||
// only update tran status if polling returns a success
|
||||
if(body.get("status").equals("SUCCESS")){
|
||||
transaction = velocityPaymentService.pollPaymentStatus(transaction);
|
||||
transaction.setStatus(Status.SUCCESS);
|
||||
transaction.setResponseCode("00");
|
||||
transaction.setTargetUrl((String) body.get("targetUrl"));
|
||||
|
||||
LinkedHashMap transactionData = (LinkedHashMap) body.get("transaction");
|
||||
transaction.setPaymentProcessorRef((String) transactionData.get("debitReference"));
|
||||
|
||||
// getting omni channel rrn from additional data
|
||||
java.util.List<LinkedHashMap> additionalData =
|
||||
(java.util.List<LinkedHashMap>) transactionData.get("additionalData");
|
||||
if (additionalData != null) {
|
||||
for (LinkedHashMap dataItem : additionalData) {
|
||||
if ("Transaction Reference".equals(dataItem.get("name"))) {
|
||||
transaction.setDebitRef((String) dataItem.get("value"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transactionService.save(transaction);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
logger.error("Network error during payment processing: {}", e.getMessage(), e);
|
||||
transaction.setStatus(Status.FAILED);
|
||||
transaction.setResponseCode("99");
|
||||
|
||||
@@ -12,6 +12,7 @@ import zw.qantra.tm.domain.enums.Status;
|
||||
import zw.qantra.tm.domain.models.Transaction;
|
||||
import zw.qantra.tm.domain.services.SettingService;
|
||||
import zw.qantra.tm.domain.services.TransactionService;
|
||||
import zw.qantra.tm.domain.services.VelocityPaymentService;
|
||||
import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface;
|
||||
import zw.qantra.tm.rest.RestService;
|
||||
import zw.qantra.tm.utils.Utils;
|
||||
@@ -33,6 +34,7 @@ public class MPGSWebPaymentProcessor implements TransactionProcessorInterface {
|
||||
private final RestService restService;
|
||||
private final TransactionService transactionService;
|
||||
private final SettingService settingService;
|
||||
private final VelocityPaymentService velocityPaymentService;
|
||||
|
||||
@Value("${sbz.aggregator.encryption-key}")
|
||||
private String encryptionKey;
|
||||
@@ -48,70 +50,25 @@ public class MPGSWebPaymentProcessor implements TransactionProcessorInterface {
|
||||
@Override
|
||||
public Object process(Transaction transaction) throws Exception {
|
||||
logger.info("processing transaction");
|
||||
String token = restService.getMerchantToken();
|
||||
|
||||
// send total less gateway charges
|
||||
BigDecimal totalAmount = transaction.getAmount()
|
||||
.add(transaction.getTax())
|
||||
.add(transaction.getCharge())
|
||||
.add(transaction.getGatewayCharge());
|
||||
|
||||
String concat = clientId+
|
||||
encryptionKey+
|
||||
transaction.getCreditPhone()+
|
||||
totalAmount;
|
||||
|
||||
String hex = getHash(concat);
|
||||
|
||||
SdkActionDto sdkActionDto = SdkActionDto.builder()
|
||||
.clientId(clientId)
|
||||
.clientSecret(clientSecret)
|
||||
.channel("api")
|
||||
.phone(transaction.getCreditPhone())
|
||||
.amount(totalAmount.toString())
|
||||
.currency(transaction.getDebitCurrency().toString())
|
||||
.hash(hex)
|
||||
.authType("WEB")
|
||||
.billerReference(transaction.getReference())
|
||||
.action("PAYMENT")
|
||||
.sandbox(true)
|
||||
.paymentProcessorLabel("MPGS")
|
||||
.responseUrl("https://stewardbank.co.zw")
|
||||
.returnUrl(returnUrl + "/" + transaction.getReference())
|
||||
.build();
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("Authorization", "Bearer " + token);
|
||||
headers.set("Content-Type", "application/json");
|
||||
if(settingService.getBooleanSetting("SIMULATE_MPGS_SUCCESS")) {
|
||||
transaction.setStatus(Status.SUCCESS);
|
||||
transaction.setResponseCode("00");
|
||||
transaction.setPaymentProcessorRef(RandomStringUtils.randomNumeric(10));
|
||||
transaction.setDebitRef(RandomStringUtils.randomNumeric(10));
|
||||
return transaction;
|
||||
}
|
||||
|
||||
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 status is not failed, set the transaction to pending and send customer to the target url
|
||||
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");
|
||||
LinkedHashMap transactionData = (LinkedHashMap) body.get("transaction");
|
||||
if (transactionData != null) {
|
||||
transaction.setErrorMessage((String) transactionData.get("errorMessage"));
|
||||
} else {
|
||||
transaction.setErrorMessage(body.get("errorMessage").toString());
|
||||
}
|
||||
}
|
||||
// velocity expects VMC
|
||||
transaction = velocityPaymentService.processPayment(transaction);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
logger.error("Network error during payment processing: {}", e.getMessage(), e);
|
||||
transaction.setStatus(Status.FAILED);
|
||||
transaction.setResponseCode("99");
|
||||
transaction.setErrorMessage("Network error: " + e.getMessage());
|
||||
transaction.setSystemErrorMessage("Network error: " + e.getMessage());
|
||||
transaction.setErrorMessage("Payment failed. Please try again");
|
||||
}
|
||||
|
||||
return transactionService.save(transaction);
|
||||
@@ -120,56 +77,29 @@ public class MPGSWebPaymentProcessor implements TransactionProcessorInterface {
|
||||
@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");
|
||||
|
||||
// todo: REMOVE THIS
|
||||
if(settingService.getBooleanSetting("SIMULATE_MPGS_SUCCESS")){
|
||||
body.put("status", "SUCCESS");
|
||||
LinkedHashMap hashMap = new LinkedHashMap();
|
||||
hashMap.put("creditReference", RandomStringUtils.randomNumeric(10));
|
||||
body.put("transaction", hashMap);
|
||||
}
|
||||
|
||||
// only update tran status if polling returns a success
|
||||
if(!body.get("status").equals("SUCCESS")){
|
||||
logger.error(body.get("errorMessage").toString());
|
||||
transaction.setStatus(Status.FAILED);
|
||||
transaction.setResponseCode(body.get("responseCode").toString());
|
||||
transaction.setErrorMessage(body.get("errorMessage").toString());
|
||||
transactionService.save(transaction);
|
||||
if(settingService.getBooleanSetting("SIMULATE_MPGS_SUCCESS")) {
|
||||
transaction.setStatus(Status.SUCCESS);
|
||||
transaction.setResponseCode("00");
|
||||
transaction.setTargetUrl("/home");
|
||||
return transaction;
|
||||
}
|
||||
|
||||
transaction.setStatus(Status.SUCCESS);
|
||||
transaction.setPaymentStatus(Status.SUCCESS);
|
||||
transaction.setResponseCode("00");
|
||||
transaction.setTargetUrl((String) body.get("targetUrl"));
|
||||
|
||||
try {
|
||||
LinkedHashMap transactionData = (LinkedHashMap) body.get("transaction");
|
||||
transaction.setDebitRef((String) transactionData.get("debitReference"));
|
||||
} catch (Exception e) {
|
||||
logger.info("Notification not yet posted at the bank");
|
||||
}
|
||||
transaction = velocityPaymentService.pollPaymentStatus(transaction);
|
||||
|
||||
transaction.setStatus(Status.SUCCESS);
|
||||
transaction.setResponseCode("00");
|
||||
|
||||
transactionService.save(transaction);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
logger.error("Network error during payment processing: {}", e.getMessage(), e);
|
||||
transaction.setStatus(Status.FAILED);
|
||||
transaction.setResponseCode("99");
|
||||
transaction.setErrorMessage("Network error: " + e.getMessage());
|
||||
transaction.setSystemErrorMessage("Network error: " + e.getMessage());
|
||||
transaction.setErrorMessage("Payment failed. Please try again");
|
||||
}
|
||||
|
||||
return transaction;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package zw.qantra.tm.domain.workflows;
|
||||
package zw.qantra.tm.domain.services.processors.workflows;
|
||||
|
||||
import io.nflow.engine.service.WorkflowInstanceService;
|
||||
import io.nflow.engine.workflow.curated.CronWorkflow;
|
||||
import io.nflow.engine.workflow.curated.State;
|
||||
import io.nflow.engine.workflow.definition.*;
|
||||
import io.nflow.engine.workflow.instance.WorkflowInstance;
|
||||
import io.nflow.engine.workflow.instance.WorkflowInstanceAction;
|
||||
@@ -18,7 +17,6 @@ import zw.qantra.tm.domain.services.TransactionService;
|
||||
import zw.qantra.tm.utils.LogUtils;
|
||||
import zw.qantra.tm.utils.Utils;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
@@ -1,4 +1,4 @@
|
||||
package zw.qantra.tm.domain.workflows;
|
||||
package zw.qantra.tm.domain.services.processors.workflows;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.nflow.engine.workflow.curated.State;
|
||||
@@ -11,8 +11,8 @@ import org.joda.time.DateTime;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import zw.qantra.tm.domain.models.Transaction;
|
||||
import zw.qantra.tm.domain.workflows.handlers.HandlerInterface;
|
||||
import zw.qantra.tm.domain.workflows.handlers.WorkflowHandlerFactory;
|
||||
import zw.qantra.tm.domain.services.processors.workflows.handlers.HandlerInterface;
|
||||
import zw.qantra.tm.domain.services.processors.workflows.handlers.WorkflowHandlerFactory;
|
||||
import zw.qantra.tm.utils.Utils;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -1,4 +1,4 @@
|
||||
package zw.qantra.tm.domain.workflows;
|
||||
package zw.qantra.tm.domain.services.processors.workflows;
|
||||
|
||||
import com.google.i18n.phonenumbers.NumberParseException;
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||
@@ -15,7 +15,7 @@ import io.nflow.engine.workflow.definition.NextAction;
|
||||
import io.nflow.engine.workflow.definition.StateExecution;
|
||||
import io.nflow.engine.workflow.definition.WorkflowDefinition;
|
||||
import zw.qantra.tm.domain.dtos.*;
|
||||
import zw.qantra.tm.domain.dtos.erp.CustomerDto;
|
||||
import zw.qantra.tm.domain.dtos.erp.VelocityCustomerResponseDto;
|
||||
import zw.qantra.tm.domain.models.Otp;
|
||||
import zw.qantra.tm.domain.models.User;
|
||||
import zw.qantra.tm.domain.services.EmailService;
|
||||
@@ -25,6 +25,8 @@ import io.nflow.engine.workflow.curated.State;
|
||||
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.Validator;
|
||||
import zw.qantra.tm.domain.services.VelocityPaymentService;
|
||||
import zw.qantra.tm.exceptions.AlreadyExistsException;
|
||||
import zw.qantra.tm.rest.RestService;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
@@ -46,21 +48,8 @@ public class RegistrationWorkflow extends WorkflowDefinition {
|
||||
private RestService restService;
|
||||
@Autowired
|
||||
private EmailService emailService;
|
||||
|
||||
@Value("${infobip.url}")
|
||||
private String infobipUrl;
|
||||
@Value("${infobip.apikey}")
|
||||
private String infobipApiKey;
|
||||
@Value("${erpnext.url}")
|
||||
private String erpnextUrl;
|
||||
@Value("${erpnext.api.key}")
|
||||
private String erpnextApiKey;
|
||||
@Value("${erpnext.api.secret}")
|
||||
private String erpnextApiSecret;
|
||||
@Value("${erpnext.username}")
|
||||
private String erpnextUsername;
|
||||
@Value("${erpnext.password}")
|
||||
private String erpnextPassword;
|
||||
@Autowired
|
||||
private VelocityPaymentService velocityPaymentService;
|
||||
|
||||
public static final String TYPE = "registrationWorkflow";
|
||||
|
||||
@@ -190,75 +179,17 @@ public class RegistrationWorkflow extends WorkflowDefinition {
|
||||
public NextAction updateErp(StateExecution execution) {
|
||||
RegisterRequest registerRequest = execution.getVariable("registerRequest", RegisterRequest.class);
|
||||
|
||||
// login to erp
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("Content-Type", "application/json");
|
||||
headers.add("Accept", "application/json");
|
||||
|
||||
Map<String, String> loginRequest = Map.of(
|
||||
"usr", erpnextUsername,
|
||||
"pwd", erpnextPassword);
|
||||
|
||||
String sid = "";
|
||||
|
||||
try {
|
||||
ResponseEntity response = restService.postAsEntity(
|
||||
erpnextUrl + "/method/login", loginRequest, headers, LinkedHashMap.class);
|
||||
|
||||
sid = response.getHeaders().getFirst("Set-Cookie");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return NextAction.retryAfter(DateTime.now().plusSeconds(30), "Failed to login to ERP: " + e.getMessage());
|
||||
}
|
||||
|
||||
// create customer in erp
|
||||
String basicAuth = "Basic " + Base64.getEncoder().encodeToString(
|
||||
(erpnextApiKey + ":" + erpnextApiSecret).getBytes(StandardCharsets.UTF_8));
|
||||
headers.add("Authorization", basicAuth);
|
||||
headers.add("Cookie", sid);
|
||||
|
||||
// check if customer exists first
|
||||
String customerName = registerRequest.getFirstName() + " " + registerRequest.getLastName();
|
||||
try {
|
||||
LinkedHashMap checkResponse = restService.getAs(erpnextUrl + "/v2/document/Customer" +
|
||||
"?fields=[\"name\"]&filters=[[\"name\", \"=\", " +
|
||||
"\"" + customerName + "\"]]", headers, LinkedHashMap.class);
|
||||
List<LinkedHashMap> dataList = (List) checkResponse.get("data");
|
||||
|
||||
if(!dataList.isEmpty()){
|
||||
User user = userService.fetchUserByUsername(registerRequest.getUsername());
|
||||
user.setErpCustomerRef(customerName);
|
||||
userService.getUserRepository().save(user);
|
||||
|
||||
return NextAction.stopInState(DONE, "Customer already exists in ERP. Ignoring update: " +
|
||||
registerRequest.getUsername());
|
||||
}
|
||||
|
||||
// if not proceed
|
||||
CustomerDto customerDto = CustomerDto.builder()
|
||||
.doctype("Customer")
|
||||
.customerName(customerName)
|
||||
.customerGroup("Commercial")
|
||||
.territory("All Territories")
|
||||
.customerType("Individual")
|
||||
.language("en")
|
||||
.mobileNo(registerRequest.getPhone())
|
||||
.emailId(registerRequest.getEmail())
|
||||
.docstatus(1)
|
||||
.build();
|
||||
|
||||
LinkedHashMap response = restService.postAs(
|
||||
erpnextUrl + "/v2/document/Customer", customerDto, headers, LinkedHashMap.class);
|
||||
|
||||
LinkedHashMap data = (LinkedHashMap) response.get("data");
|
||||
String customerId = (String) data.get("name");
|
||||
VelocityCustomerResponseDto customer = velocityPaymentService.createCustomer(registerRequest);
|
||||
|
||||
User user = userService.fetchUserByUsername(registerRequest.getUsername());
|
||||
user.setErpCustomerRef(customerId);
|
||||
user.setErpCustomerRef(customer.getId());
|
||||
userService.getUserRepository().save(user);
|
||||
} catch (AlreadyExistsException e){
|
||||
log.error("Customer already exists in ERP: {}", e.getMessage(), e);
|
||||
return NextAction.stopInState(DONE, "ERP updated successfully for: " + registerRequest.getUsername());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
|
||||
return NextAction.retryAfter(DateTime.now().plusMinutes(5), "Failed to update ERP: " + e.getMessage());
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package zw.qantra.tm.domain.workflows;
|
||||
package zw.qantra.tm.domain.services.processors.workflows;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.nflow.engine.workflow.curated.State;
|
||||
@@ -12,8 +12,8 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import zw.qantra.tm.domain.models.Transaction;
|
||||
import zw.qantra.tm.domain.services.TransactionService;
|
||||
import zw.qantra.tm.domain.workflows.handlers.HandlerInterface;
|
||||
import zw.qantra.tm.domain.workflows.handlers.WorkflowHandlerFactory;
|
||||
import zw.qantra.tm.domain.services.processors.workflows.handlers.HandlerInterface;
|
||||
import zw.qantra.tm.domain.services.processors.workflows.handlers.WorkflowHandlerFactory;
|
||||
import zw.qantra.tm.utils.Utils;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -1,16 +1,14 @@
|
||||
package zw.qantra.tm.domain.workflows;
|
||||
package zw.qantra.tm.domain.services.processors.workflows;
|
||||
|
||||
import io.nflow.engine.service.WorkflowInstanceService;
|
||||
import io.nflow.engine.workflow.instance.WorkflowInstanceFactory;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Instant;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.stereotype.Component;
|
||||
import zw.qantra.tm.utils.Utils;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@@ -1,25 +1,17 @@
|
||||
package zw.qantra.tm.domain.workflows;
|
||||
package zw.qantra.tm.domain.services.processors.workflows;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.nflow.engine.service.WorkflowInstanceService;
|
||||
import io.nflow.engine.workflow.curated.State;
|
||||
import io.nflow.engine.workflow.definition.NextAction;
|
||||
import io.nflow.engine.workflow.definition.StateExecution;
|
||||
import io.nflow.engine.workflow.definition.WorkflowDefinition;
|
||||
import io.nflow.engine.workflow.definition.WorkflowStateType;
|
||||
import io.nflow.engine.workflow.instance.WorkflowInstanceFactory;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Instant;
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
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.workflows.handlers.HandlerInterface;
|
||||
import zw.qantra.tm.domain.workflows.handlers.WorkflowHandlerFactory;
|
||||
import zw.qantra.tm.exceptions.ApiException;
|
||||
import zw.qantra.tm.domain.services.VelocityPaymentService;
|
||||
import zw.qantra.tm.domain.services.processors.workflows.handlers.*;
|
||||
import zw.qantra.tm.utils.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -32,13 +24,23 @@ public class TransactionWorkflow extends WorkflowDefinition {
|
||||
@Autowired
|
||||
private WorkflowHandlerFactory workflowHandlerFactory;
|
||||
@Autowired
|
||||
private TransactionService transactionService;
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
@Autowired
|
||||
private WorkflowInstanceService workflowInstanceService;
|
||||
private VelocityPaymentService velocityPaymentService;
|
||||
@Autowired
|
||||
private WorkflowInstanceFactory workflowInstanceFactory;
|
||||
private CalculateChargesHandler calculateChargesHandler;
|
||||
@Autowired
|
||||
private CleanupHandler cleanupHandler;
|
||||
@Autowired
|
||||
private RecipientsUpdateHandler recipientsUpdateHandler;
|
||||
@Autowired
|
||||
private GatewayPaymentHandler gatewayPaymentHandler;
|
||||
@Autowired
|
||||
private ConfirmHandler confirmHandler;
|
||||
@Autowired
|
||||
private PollStatusHandler pollStatusHandler;
|
||||
@Autowired
|
||||
private IntegrationHandler integrationHandler;
|
||||
|
||||
public static final String TYPE = "transactionWorkflow";
|
||||
|
||||
@@ -79,9 +81,7 @@ public class TransactionWorkflow extends WorkflowDefinition {
|
||||
byte[] json = objectMapper.writeValueAsBytes(map);
|
||||
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
||||
|
||||
HandlerInterface handlerInterface =
|
||||
(HandlerInterface) workflowHandlerFactory.getWorkflowHandler("calculateCharges");
|
||||
transaction = (Transaction) handlerInterface.process(transaction);
|
||||
transaction = calculateChargesHandler.process(transaction);
|
||||
|
||||
execution.setVariable("body", Utils.toJson(transaction));
|
||||
|
||||
@@ -98,9 +98,7 @@ public class TransactionWorkflow extends WorkflowDefinition {
|
||||
byte[] json = objectMapper.writeValueAsBytes(map);
|
||||
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
||||
|
||||
HandlerInterface handlerInterface =
|
||||
(HandlerInterface) workflowHandlerFactory.getWorkflowHandler("cleanup");
|
||||
transaction = (Transaction) handlerInterface.process(transaction);
|
||||
transaction = (Transaction) cleanupHandler.process(transaction);
|
||||
|
||||
execution.setVariable("body", Utils.toJson(transaction));
|
||||
|
||||
@@ -118,9 +116,7 @@ public class TransactionWorkflow extends WorkflowDefinition {
|
||||
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
||||
|
||||
transaction.setWorkflowId(execution.getWorkflowInstanceId());
|
||||
HandlerInterface handlerInterface =
|
||||
(HandlerInterface) workflowHandlerFactory.getWorkflowHandler("confirm");
|
||||
transaction = (Transaction) handlerInterface.process(transaction);
|
||||
transaction = (Transaction) confirmHandler.process(transaction);
|
||||
execution.setVariable("body", Utils.toJson(transaction));
|
||||
return NextAction.moveToState(RECIPIENTS_UPDATE, "Recipients updated successfully");
|
||||
} catch (Exception e) {
|
||||
@@ -134,9 +130,7 @@ public class TransactionWorkflow extends WorkflowDefinition {
|
||||
byte[] json = objectMapper.writeValueAsBytes(map);
|
||||
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
||||
|
||||
HandlerInterface handlerInterface =
|
||||
(HandlerInterface) workflowHandlerFactory.getWorkflowHandler("recipientsUpdate");
|
||||
handlerInterface.process(transaction);
|
||||
recipientsUpdateHandler.process(transaction);
|
||||
|
||||
return NextAction.moveToState(CONFIRM_SUCCESS, "Transaction confirmed successfully");
|
||||
}
|
||||
@@ -152,9 +146,7 @@ public class TransactionWorkflow extends WorkflowDefinition {
|
||||
byte[] json = objectMapper.writeValueAsBytes(map);
|
||||
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
||||
|
||||
HandlerInterface handlerInterface =
|
||||
(HandlerInterface) workflowHandlerFactory.getWorkflowHandler("gatewayPayment");
|
||||
transaction = (Transaction) handlerInterface.process(transaction);
|
||||
transaction = (Transaction) gatewayPaymentHandler.process(transaction);
|
||||
|
||||
execution.setVariable("body", Utils.toJson(transaction));
|
||||
return NextAction.moveToState(GATEWAY_PAYMENT_SUCCESS, "Gateway payment initiated");
|
||||
@@ -165,25 +157,7 @@ public class TransactionWorkflow extends WorkflowDefinition {
|
||||
}
|
||||
|
||||
public void gatewayPaymentSuccess(StateExecution execution) {
|
||||
try {
|
||||
LinkedHashMap map = execution.getVariable("body", LinkedHashMap.class);
|
||||
byte[] json = objectMapper.writeValueAsBytes(map);
|
||||
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
||||
|
||||
var instance = workflowInstanceFactory.newWorkflowInstanceBuilder()
|
||||
.setType(SalesInvoiceWorkflow.TYPE)
|
||||
.setExternalId(transaction.getUserId() + "-" + new Instant().getMillis())
|
||||
.setBusinessKey(execution.getBusinessKey())
|
||||
.putStateVariable("body", Utils.toJson(transaction))
|
||||
.setNextActivation(DateTime.now())
|
||||
.build();
|
||||
|
||||
long workflowId = workflowInstanceService.insertWorkflowInstance(instance);
|
||||
|
||||
log.info("Gateway payment initiation complete");
|
||||
}catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public NextAction pollStatus(StateExecution execution) throws IOException {
|
||||
@@ -193,9 +167,7 @@ public class TransactionWorkflow extends WorkflowDefinition {
|
||||
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
||||
|
||||
try {
|
||||
HandlerInterface handlerInterface =
|
||||
(HandlerInterface) workflowHandlerFactory.getWorkflowHandler("pollStatus");
|
||||
transaction = (Transaction) handlerInterface.process(transaction);
|
||||
transaction = (Transaction) pollStatusHandler.process(transaction);
|
||||
|
||||
execution.setVariable("body", Utils.toJson(transaction));
|
||||
return NextAction.moveToState(POLL_STATUS_SUCCESS, "Polling complete");
|
||||
@@ -216,9 +188,7 @@ public class TransactionWorkflow extends WorkflowDefinition {
|
||||
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
||||
|
||||
try {
|
||||
HandlerInterface handlerInterface =
|
||||
(HandlerInterface) workflowHandlerFactory.getWorkflowHandler("integration");
|
||||
transaction = (Transaction) handlerInterface.process(transaction);
|
||||
transaction = (Transaction) integrationHandler.process(transaction);
|
||||
|
||||
execution.setVariable("body", Utils.toJson(transaction));
|
||||
return NextAction.moveToState(INTEGRATION_STATUS_SUCCESS, "Integration request successful");
|
||||
@@ -235,15 +205,7 @@ public class TransactionWorkflow extends WorkflowDefinition {
|
||||
byte[] json = objectMapper.writeValueAsBytes(map);
|
||||
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
||||
|
||||
var instance = workflowInstanceFactory.newWorkflowInstanceBuilder()
|
||||
.setType(PurchaseInvoiceWorkflow.TYPE)
|
||||
.setExternalId(transaction.getUserId() + "-" + new Instant().getMillis())
|
||||
.setBusinessKey(execution.getBusinessKey())
|
||||
.putStateVariable("body", Utils.toJson(transaction))
|
||||
.setNextActivation(DateTime.now())
|
||||
.build();
|
||||
|
||||
long workflowId = workflowInstanceService.insertWorkflowInstance(instance);
|
||||
velocityPaymentService.updateSalesOrderWorkflow(transaction);
|
||||
|
||||
log.info("Transaction workflow complete");
|
||||
}catch (Exception e) {
|
||||
@@ -1,4 +1,4 @@
|
||||
package zw.qantra.tm.domain.workflows;
|
||||
package zw.qantra.tm.domain.services.processors.workflows;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
@@ -6,8 +6,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import io.nflow.engine.model.ModelObject;
|
||||
import io.nflow.engine.service.WorkflowInstanceInclude.*;
|
||||
import io.nflow.engine.workflow.instance.WorkflowInstanceAction;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package zw.qantra.tm.domain.workflows.handlers;
|
||||
package zw.qantra.tm.domain.services.processors.workflows.handlers;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.slf4j.Logger;
|
||||
@@ -8,7 +8,6 @@ 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.ChargeService;
|
||||
import zw.qantra.tm.domain.services.processors.handlers.LogicHandler;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
@@ -1,10 +1,9 @@
|
||||
package zw.qantra.tm.domain.workflows.handlers;
|
||||
package zw.qantra.tm.domain.services.processors.workflows.handlers;
|
||||
|
||||
import com.google.i18n.phonenumbers.NumberParseException;
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||
import com.google.i18n.phonenumbers.Phonenumber;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.aspectj.apache.bcel.generic.RET;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -1,10 +1,8 @@
|
||||
package zw.qantra.tm.domain.workflows.handlers;
|
||||
package zw.qantra.tm.domain.services.processors.workflows.handlers;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import zw.qantra.tm.domain.enums.RequestType;
|
||||
import zw.qantra.tm.domain.enums.Status;
|
||||
import zw.qantra.tm.domain.models.AdditionalData;
|
||||
import zw.qantra.tm.domain.models.Transaction;
|
||||
import zw.qantra.tm.domain.models.TransactionEvent;
|
||||
import zw.qantra.tm.domain.services.AdditionalDataService;
|
||||
@@ -12,13 +10,8 @@ import zw.qantra.tm.domain.services.TransactionEventService;
|
||||
import zw.qantra.tm.domain.services.TransactionService;
|
||||
import zw.qantra.tm.domain.services.factories.PaymentProcessorFactory;
|
||||
import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface;
|
||||
import zw.qantra.tm.domain.services.processors.handlers.*;
|
||||
import zw.qantra.tm.utils.Utils;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service("confirm")
|
||||
@RequiredArgsConstructor
|
||||
public class ConfirmHandler implements HandlerInterface {
|
||||
@@ -43,11 +36,11 @@ public class ConfirmHandler implements HandlerInterface {
|
||||
// provider.getLabel() + "_" +
|
||||
// provider.getIntegrationProcessorLabel();
|
||||
|
||||
try {
|
||||
TransactionProcessorInterface transactionProcessorInterface =
|
||||
(TransactionProcessorInterface) paymentProcessorFactory.getPaymentProcessor(
|
||||
transaction.getConfirmationProcessorLabel());
|
||||
|
||||
try {
|
||||
// MAIN TRANSACTION LOGIC!
|
||||
transaction = (Transaction) transactionProcessorInterface.process(transaction);
|
||||
transaction.setConfirmationStatus(transaction.getStatus());
|
||||
@@ -1,4 +1,4 @@
|
||||
package zw.qantra.tm.domain.workflows.handlers;
|
||||
package zw.qantra.tm.domain.services.processors.workflows.handlers;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.slf4j.Logger;
|
||||
@@ -14,7 +14,7 @@ import zw.qantra.tm.exceptions.ApiException;
|
||||
|
||||
@Service("gatewayPayment")
|
||||
@RequiredArgsConstructor
|
||||
public class PaymentHandler implements HandlerInterface {
|
||||
public class GatewayPaymentHandler implements HandlerInterface {
|
||||
Logger logger = LoggerFactory.getLogger(TransactionService.class);
|
||||
|
||||
private final PaymentProcessorFactory paymentProcessorFactory;
|
||||
@@ -26,6 +26,10 @@ public class PaymentHandler implements HandlerInterface {
|
||||
@Override
|
||||
public Object process(Transaction incomingTransaction) throws Exception {
|
||||
Transaction transaction = incomingTransaction;
|
||||
|
||||
if(incomingTransaction == null) {
|
||||
throw new ApiException("Transaction not found");
|
||||
}
|
||||
if(incomingTransaction.getId() != null){
|
||||
transaction = transactionService.getTransactionRepository().findById(incomingTransaction.getId()).orElseThrow(() ->
|
||||
new ApiException("Transaction not found for ID: " + incomingTransaction.getId()));
|
||||
@@ -56,6 +60,7 @@ public class PaymentHandler implements HandlerInterface {
|
||||
// MAIN TRANSACTION LOGIC!
|
||||
transactionProcessorInterface.process(transaction);
|
||||
transaction.setPaymentStatus(transaction.getStatus());
|
||||
transaction.setPollingStatus(Status.PENDING);
|
||||
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
@@ -1,4 +1,4 @@
|
||||
package zw.qantra.tm.domain.workflows.handlers;
|
||||
package zw.qantra.tm.domain.services.processors.workflows.handlers;
|
||||
|
||||
import zw.qantra.tm.domain.models.Transaction;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package zw.qantra.tm.domain.workflows.handlers;
|
||||
package zw.qantra.tm.domain.services.processors.workflows.handlers;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.slf4j.Logger;
|
||||
@@ -1,4 +1,4 @@
|
||||
package zw.qantra.tm.domain.workflows.handlers;
|
||||
package zw.qantra.tm.domain.services.processors.workflows.handlers;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.slf4j.Logger;
|
||||
@@ -1,4 +1,4 @@
|
||||
package zw.qantra.tm.domain.workflows.handlers;
|
||||
package zw.qantra.tm.domain.services.processors.workflows.handlers;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -6,7 +6,6 @@ import zw.qantra.tm.domain.enums.Status;
|
||||
import zw.qantra.tm.domain.models.Recipient;
|
||||
import zw.qantra.tm.domain.models.Transaction;
|
||||
import zw.qantra.tm.domain.services.RecipientService;
|
||||
import zw.qantra.tm.domain.services.processors.handlers.LogicHandler;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package zw.qantra.tm.domain.workflows.handlers;
|
||||
package zw.qantra.tm.domain.services.processors.workflows.handlers;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.slf4j.Logger;
|
||||
@@ -1,154 +0,0 @@
|
||||
package zw.qantra.tm.domain.workflows.handlers.erp;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import zw.qantra.tm.domain.dtos.erp.JournalEntryAccountDto;
|
||||
import zw.qantra.tm.domain.dtos.erp.JournalEntryDto;
|
||||
import zw.qantra.tm.domain.enums.RequestType;
|
||||
import zw.qantra.tm.domain.enums.Status;
|
||||
import zw.qantra.tm.domain.models.Transaction;
|
||||
import zw.qantra.tm.domain.models.TransactionEvent;
|
||||
import zw.qantra.tm.domain.models.User;
|
||||
import zw.qantra.tm.domain.repositories.TransactionRepository;
|
||||
import zw.qantra.tm.domain.services.TransactionEventService;
|
||||
import zw.qantra.tm.domain.services.UserService;
|
||||
import zw.qantra.tm.domain.services.processors.handlers.LogicHandler;
|
||||
import zw.qantra.tm.domain.workflows.handlers.HandlerInterface;
|
||||
import zw.qantra.tm.exceptions.ApiException;
|
||||
import zw.qantra.tm.rest.RestService;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
@Service("journalEntry")
|
||||
@RequiredArgsConstructor
|
||||
public class JournalEntryHandler implements HandlerInterface {
|
||||
Logger logger = LoggerFactory.getLogger(JournalEntryHandler.class);
|
||||
|
||||
private final RestService restService;
|
||||
private final UserService userService;
|
||||
private final TransactionEventService transactionEventService;
|
||||
private final TransactionRepository transactionRepository;
|
||||
|
||||
@Value("${erpnext.url}")
|
||||
private String erpnextUrl;
|
||||
@Value("${erpnext.api.key}")
|
||||
private String erpnextApiKey;
|
||||
@Value("${erpnext.api.secret}")
|
||||
private String erpnextApiSecret;
|
||||
@Value("${erpnext.username}")
|
||||
private String erpnextUsername;
|
||||
@Value("${erpnext.password}")
|
||||
private String erpnextPassword;
|
||||
@Value("${erpnext.company}")
|
||||
private String erpnextCompany;
|
||||
@Value("${erpnext.accounts.gatewayexpense}")
|
||||
private String gatewayExpenseAccount;
|
||||
@Value("${erpnext.accounts.accountsreceivable}")
|
||||
private String accountsReceivableAccount;
|
||||
@Value("${erpnext.accounts.cash}")
|
||||
private String cashAccount;
|
||||
@Value("${erpnext.cost.center}")
|
||||
private String costCenter;
|
||||
|
||||
@Override
|
||||
public Transaction process(Transaction transaction) {
|
||||
logger.info("Processing journal entry for transaction: {}", transaction.getReference());
|
||||
|
||||
if(transaction.getErpJournalRef() != null)
|
||||
return transaction;
|
||||
|
||||
TransactionEvent transactionEvent = TransactionEvent.builder()
|
||||
.transactionId(transaction.getId().toString())
|
||||
.event(RequestType.JOURNAL_ENTRY.name())
|
||||
.status(Status.PROCESSING)
|
||||
.build();
|
||||
transactionEventService.save(transactionEvent);
|
||||
|
||||
if(transaction.getStatus() == Status.SUCCESS){
|
||||
try {
|
||||
logger.info("Creating journal entry in ERPNext for successful transaction");
|
||||
|
||||
// login to erp
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("Content-Type", "application/json");
|
||||
headers.add("Accept", "application/json");
|
||||
|
||||
Map<String, String> loginRequest = Map.of(
|
||||
"usr", erpnextUsername,
|
||||
"pwd", erpnextPassword);
|
||||
|
||||
String sid = "";
|
||||
|
||||
try {
|
||||
ResponseEntity response = restService.postAsEntity(
|
||||
erpnextUrl + "/method/login", loginRequest, headers, LinkedHashMap.class);
|
||||
|
||||
sid = response.getHeaders().getFirst("Set-Cookie");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new ApiException("Failed to login to ERPNext");
|
||||
}
|
||||
|
||||
// create customer in erp
|
||||
String basicAuth = "Basic " + Base64.getEncoder().encodeToString(
|
||||
(erpnextApiKey + ":" + erpnextApiSecret).getBytes(StandardCharsets.UTF_8));
|
||||
headers.add("Authorization", basicAuth);
|
||||
headers.add("Cookie", sid);
|
||||
|
||||
JournalEntryAccountDto expenseEntry = JournalEntryAccountDto.builder()
|
||||
.doctype("Journal Entry Account")
|
||||
.account(gatewayExpenseAccount)
|
||||
.debitInAccountCurrency(transaction.getGatewayCharge())
|
||||
.costCenter(costCenter)
|
||||
.build();
|
||||
JournalEntryAccountDto cashAccEntry = JournalEntryAccountDto.builder()
|
||||
.doctype("Journal Entry Account")
|
||||
.account(cashAccount)
|
||||
.creditInAccountCurrency(transaction.getGatewayCharge())
|
||||
.build();
|
||||
|
||||
JournalEntryDto journalEntryDto = JournalEntryDto.builder()
|
||||
.doctype("Journal Entry")
|
||||
.company(erpnextCompany)
|
||||
.postingDate(transaction.getCreatedAt().toLocalDate().toString())
|
||||
.docstatus(1)
|
||||
.accounts(Arrays.asList(cashAccEntry, expenseEntry))
|
||||
.build();
|
||||
|
||||
try {
|
||||
LinkedHashMap response = restService.postAs(
|
||||
erpnextUrl + "/v2/document/Journal Entry", journalEntryDto, headers, LinkedHashMap.class);
|
||||
|
||||
LinkedHashMap data = (LinkedHashMap) response.get("data");
|
||||
String transactionId = (String) data.get("name");
|
||||
|
||||
transaction.setErpJournalRef(transactionId);
|
||||
transactionEvent.setStatus(Status.SUCCESS);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new ApiException("Failed to create journal entry in ERPNext");
|
||||
}
|
||||
|
||||
logger.info("journal entry created successfully in ERPNext");
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to create journal entry in ERPNext: {}", e.getMessage(), e);
|
||||
// Don't fail the transaction, just log the error
|
||||
// The ERP invoice creation is a side effect, not critical to the payment flow
|
||||
transactionEvent.setStatus(Status.FAILED);
|
||||
transactionEvent.setMessage(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
logger.info("Transaction status is not SUCCESS ({}), skipping journal entry creation", transaction.getStatus());
|
||||
}
|
||||
|
||||
transactionEventService.save(transactionEvent);
|
||||
|
||||
return transactionRepository.save(transaction);
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
package zw.qantra.tm.domain.workflows.handlers.erp;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import zw.qantra.tm.domain.dtos.erp.PaymentEntryDto;
|
||||
import zw.qantra.tm.domain.dtos.erp.PaymentReferenceDto;
|
||||
import zw.qantra.tm.domain.enums.RequestType;
|
||||
import zw.qantra.tm.domain.enums.Status;
|
||||
import zw.qantra.tm.domain.models.Transaction;
|
||||
import zw.qantra.tm.domain.models.TransactionEvent;
|
||||
import zw.qantra.tm.domain.models.User;
|
||||
import zw.qantra.tm.domain.repositories.TransactionRepository;
|
||||
import zw.qantra.tm.domain.services.TransactionEventService;
|
||||
import zw.qantra.tm.domain.services.UserService;
|
||||
import zw.qantra.tm.domain.services.processors.handlers.LogicHandler;
|
||||
import zw.qantra.tm.domain.workflows.handlers.HandlerInterface;
|
||||
import zw.qantra.tm.exceptions.ApiException;
|
||||
import zw.qantra.tm.rest.RestService;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
@Service("salesPayment")
|
||||
@RequiredArgsConstructor
|
||||
public class PaymentEntryHandler implements HandlerInterface {
|
||||
Logger logger = LoggerFactory.getLogger(PaymentEntryHandler.class);
|
||||
|
||||
private final RestService restService;
|
||||
private final UserService userService;
|
||||
private final TransactionEventService transactionEventService;
|
||||
private final TransactionRepository transactionRepository;
|
||||
|
||||
@Value("${erpnext.url}")
|
||||
private String erpnextUrl;
|
||||
@Value("${erpnext.api.key}")
|
||||
private String erpnextApiKey;
|
||||
@Value("${erpnext.api.secret}")
|
||||
private String erpnextApiSecret;
|
||||
@Value("${erpnext.username}")
|
||||
private String erpnextUsername;
|
||||
@Value("${erpnext.password}")
|
||||
private String erpnextPassword;
|
||||
@Value("${erpnext.company}")
|
||||
private String erpnextCompany;
|
||||
@Value("${erpnext.accounts.accountsreceivable}")
|
||||
private String accountsReceivableAccount;
|
||||
@Value("${erpnext.accounts.cash}")
|
||||
private String cashAccount;
|
||||
|
||||
@Override
|
||||
public Transaction process(Transaction transaction) {
|
||||
logger.info("Processing payment entry for transaction: {}", transaction.getReference());
|
||||
|
||||
if(transaction.getErpSalesPaymentRef() != null)
|
||||
return transaction;
|
||||
|
||||
TransactionEvent transactionEvent = TransactionEvent.builder()
|
||||
.transactionId(transaction.getId().toString())
|
||||
.event(RequestType.SALES_PAYMENT.name())
|
||||
.status(Status.PROCESSING)
|
||||
.build();
|
||||
transactionEventService.save(transactionEvent);
|
||||
|
||||
if(transaction.getPaymentStatus() == Status.SUCCESS){
|
||||
try {
|
||||
logger.info("Creating payment entry in ERPNext for successful transaction");
|
||||
|
||||
// login to erp
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("Content-Type", "application/json");
|
||||
headers.add("Accept", "application/json");
|
||||
|
||||
Map<String, String> loginRequest = Map.of(
|
||||
"usr", erpnextUsername,
|
||||
"pwd", erpnextPassword);
|
||||
|
||||
String sid = "";
|
||||
|
||||
try {
|
||||
ResponseEntity response = restService.postAsEntity(
|
||||
erpnextUrl + "/method/login", loginRequest, headers, LinkedHashMap.class);
|
||||
|
||||
sid = response.getHeaders().getFirst("Set-Cookie");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new ApiException("Failed to login to ERPNext");
|
||||
}
|
||||
|
||||
// create customer in erp
|
||||
String basicAuth = "Basic " + Base64.getEncoder().encodeToString(
|
||||
(erpnextApiKey + ":" + erpnextApiSecret).getBytes(StandardCharsets.UTF_8));
|
||||
headers.add("Authorization", basicAuth);
|
||||
headers.add("Cookie", sid);
|
||||
|
||||
String erpCustomerRef = "";
|
||||
User user = userService.getUserRepository().findById(UUID.fromString(transaction.getUserId())).orElse(null);
|
||||
erpCustomerRef = (user == null) ? "Anonymous" : user.getErpCustomerRef();
|
||||
|
||||
BigDecimal amount = transaction.getAmount().add(transaction.getGatewayCharge());
|
||||
|
||||
PaymentReferenceDto paymentReferenceDto = PaymentReferenceDto.builder()
|
||||
.referenceDoctype("Sales Invoice")
|
||||
.referenceName(transaction.getErpSalesRef())
|
||||
.allocatedAmount(amount)
|
||||
.build();
|
||||
|
||||
PaymentEntryDto paymentEntryDto = PaymentEntryDto.builder()
|
||||
.doctype("Payment Entry")
|
||||
.company(erpnextCompany)
|
||||
.docstatus(1)
|
||||
.paymentType("Receive")
|
||||
.party(erpCustomerRef)
|
||||
.partyType("Customer")
|
||||
.paidFrom(accountsReceivableAccount)
|
||||
.paidTo(cashAccount)
|
||||
.paidAmount(amount)
|
||||
.receivedAmount(amount)
|
||||
.referenceNo(transaction.getId().toString())
|
||||
.referenceDate(transaction.getCreatedAt().toLocalDate().toString())
|
||||
.postingDate(transaction.getCreatedAt().toLocalDate().toString())
|
||||
.references(Arrays.asList(paymentReferenceDto))
|
||||
.build();
|
||||
|
||||
try {
|
||||
LinkedHashMap response = restService.postAs(
|
||||
erpnextUrl + "/v2/document/Payment Entry", paymentEntryDto, headers, LinkedHashMap.class);
|
||||
|
||||
LinkedHashMap data = (LinkedHashMap) response.get("data");
|
||||
String transactionId = (String) data.get("name");
|
||||
|
||||
transaction.setErpSalesPaymentRef(transactionId);
|
||||
transactionEvent.setStatus(Status.SUCCESS);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new ApiException("Failed to create payment entry in ERPNext");
|
||||
}
|
||||
|
||||
logger.info("payment entry created successfully in ERPNext");
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to create payment entry in ERPNext: {}", e.getMessage(), e);
|
||||
// Don't fail the transaction, just log the error
|
||||
// The ERP invoice creation is a side effect, not critical to the payment flow
|
||||
transactionEvent.setStatus(Status.FAILED);
|
||||
transactionEvent.setMessage(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
logger.info("Transaction status is not SUCCESS ({}), skipping payment entry creation", transaction.getStatus());
|
||||
}
|
||||
|
||||
transactionEventService.save(transactionEvent);
|
||||
|
||||
return transactionRepository.save(transaction);
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
package zw.qantra.tm.domain.workflows.handlers.erp;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import zw.qantra.tm.domain.dtos.erp.PurchaseInvoiceDto;
|
||||
import zw.qantra.tm.domain.dtos.erp.PurchaseInvoiceItemDto;
|
||||
import zw.qantra.tm.domain.dtos.erp.PurchaseTaxDto;
|
||||
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.models.TransactionEvent;
|
||||
import zw.qantra.tm.domain.models.User;
|
||||
import zw.qantra.tm.domain.repositories.TransactionRepository;
|
||||
import zw.qantra.tm.domain.services.ProviderService;
|
||||
import zw.qantra.tm.domain.services.TransactionEventService;
|
||||
import zw.qantra.tm.domain.services.UserService;
|
||||
import zw.qantra.tm.domain.services.processors.handlers.LogicHandler;
|
||||
import zw.qantra.tm.domain.workflows.handlers.HandlerInterface;
|
||||
import zw.qantra.tm.exceptions.ApiException;
|
||||
import zw.qantra.tm.rest.RestService;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
import static java.time.OffsetDateTime.now;
|
||||
|
||||
@Service("purchaseInvoice")
|
||||
@RequiredArgsConstructor
|
||||
public class PurchaseInvoiceHandler implements HandlerInterface {
|
||||
Logger logger = LoggerFactory.getLogger(PurchaseInvoiceHandler.class);
|
||||
|
||||
private final RestService restService;
|
||||
private final UserService userService;
|
||||
private final TransactionEventService transactionEventService;
|
||||
private final TransactionRepository transactionRepository;
|
||||
private final ProviderService providerService;
|
||||
|
||||
@Value("${erpnext.url}")
|
||||
private String erpnextUrl;
|
||||
@Value("${erpnext.api.key}")
|
||||
private String erpnextApiKey;
|
||||
@Value("${erpnext.api.secret}")
|
||||
private String erpnextApiSecret;
|
||||
@Value("${erpnext.username}")
|
||||
private String erpnextUsername;
|
||||
@Value("${erpnext.password}")
|
||||
private String erpnextPassword;
|
||||
@Value("${erpnext.company}")
|
||||
private String erpnextCompany;
|
||||
@Value("${erpnext.cost.center}")
|
||||
private String erpnextCostCenter;
|
||||
@Value("${erpnext.accounts.commission}")
|
||||
private String erpnextCommissionAccount;
|
||||
|
||||
@Override
|
||||
public Transaction process(Transaction transaction) {
|
||||
logger.info("Processing purchase invoice for transaction: {}", transaction.getReference());
|
||||
|
||||
if(transaction.getErpPurchaseRef() != null)
|
||||
return transaction;
|
||||
|
||||
TransactionEvent transactionEvent = TransactionEvent.builder()
|
||||
.transactionId(transaction.getId().toString())
|
||||
.event(RequestType.PURCHASE_INVOICE.name())
|
||||
.status(Status.PROCESSING)
|
||||
.build();
|
||||
transactionEventService.save(transactionEvent);
|
||||
|
||||
if(transaction.getErpPurchaseRef() != null)
|
||||
return transaction;
|
||||
|
||||
if(transaction.getStatus() == Status.SUCCESS){
|
||||
try {
|
||||
logger.info("Creating purchase invoice in ERPNext for successful transaction");
|
||||
|
||||
// login to erp
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("Content-Type", "application/json");
|
||||
headers.add("Accept", "application/json");
|
||||
|
||||
Map<String, String> loginRequest = Map.of(
|
||||
"usr", erpnextUsername,
|
||||
"pwd", erpnextPassword);
|
||||
|
||||
String sid = "";
|
||||
|
||||
try {
|
||||
ResponseEntity response = restService.postAsEntity(
|
||||
erpnextUrl + "/method/login", loginRequest, headers, LinkedHashMap.class);
|
||||
|
||||
sid = response.getHeaders().getFirst("Set-Cookie");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new ApiException("Failed to login to ERPNext");
|
||||
}
|
||||
|
||||
// create customer in erp
|
||||
String basicAuth = "Basic " + Base64.getEncoder().encodeToString(
|
||||
(erpnextApiKey + ":" + erpnextApiSecret).getBytes(StandardCharsets.UTF_8));
|
||||
headers.add("Authorization", basicAuth);
|
||||
headers.add("Cookie", sid);
|
||||
|
||||
PurchaseInvoiceItemDto purchaseInvoiceItemDto = PurchaseInvoiceItemDto.builder()
|
||||
.doctype("Purchase Invoice Item")
|
||||
.uom("Nos")
|
||||
.itemCode(transaction.getProviderLabel())
|
||||
.itemName(transaction.getProviderLabel())
|
||||
.qty(transaction.getAmount())
|
||||
.rate(1)
|
||||
.amount(transaction.getAmount())
|
||||
.build();
|
||||
|
||||
Provider provider = providerService.getProviderRepository()
|
||||
.findByClientId(transaction.getBillClientId());
|
||||
BigDecimal commissionPercentage = BigDecimal.valueOf(provider.getPercentageCommission())
|
||||
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_EVEN);
|
||||
BigDecimal commission = transaction.getAmount().multiply(commissionPercentage)
|
||||
.setScale(2, RoundingMode.HALF_EVEN);
|
||||
BigDecimal amount = transaction.getAmount().subtract(commission);
|
||||
|
||||
PurchaseTaxDto purchaseTaxDto = PurchaseTaxDto.builder()
|
||||
.doctype("Purchase Taxes and Charges")
|
||||
.chargeType("On Net Total")
|
||||
.accountHead(erpnextCommissionAccount)
|
||||
.rate(BigDecimal.valueOf(provider.getPercentageCommission()).negate())
|
||||
.costCenter(erpnextCostCenter)
|
||||
.taxAmount(commission.negate())
|
||||
.total(amount)
|
||||
.description("Commission")
|
||||
.build();
|
||||
|
||||
PurchaseInvoiceDto purchaseInvoiceDto = PurchaseInvoiceDto.builder()
|
||||
.doctype("Purchase Invoice")
|
||||
.supplier("Steward Bank")
|
||||
.company(erpnextCompany)
|
||||
.postingDate(now().toLocalDate().toString())
|
||||
.dueDate(now().toLocalDate().plusDays(1).toString())
|
||||
.docstatus(1)
|
||||
.isReturn(0)
|
||||
.isPaid(0)
|
||||
.currency(transaction.getDebitCurrency().toString())
|
||||
.conversionRate(1)
|
||||
.netTotal(amount)
|
||||
.grandTotal(amount)
|
||||
.items(Collections.singletonList(purchaseInvoiceItemDto))
|
||||
.taxes(Collections.singletonList(purchaseTaxDto))
|
||||
.build();
|
||||
|
||||
try {
|
||||
LinkedHashMap response = restService.postAs(
|
||||
erpnextUrl + "/v2/document/Purchase Invoice", purchaseInvoiceDto, headers, LinkedHashMap.class);
|
||||
|
||||
LinkedHashMap data = (LinkedHashMap) response.get("data");
|
||||
String transactionId = (String) data.get("name");
|
||||
|
||||
transaction.setErpPurchaseRef(transactionId);
|
||||
transactionEvent.setStatus(Status.SUCCESS);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new ApiException("Failed to create purchase invoice in ERPNext");
|
||||
}
|
||||
|
||||
logger.info("purchase invoice created successfully in ERPNext");
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to create purchase invoice in ERPNext: {}", e.getMessage(), e);
|
||||
// Don't fail the transaction, just log the error
|
||||
// The ERP invoice creation is a side effect, not critical to the payment flow
|
||||
transactionEvent.setStatus(Status.FAILED);
|
||||
transactionEvent.setMessage(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
logger.info("Transaction status is not SUCCESS ({}), skipping purchase invoice creation", transaction.getStatus());
|
||||
}
|
||||
|
||||
transactionEventService.save(transactionEvent);
|
||||
|
||||
return transactionRepository.save(transaction);
|
||||
}
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
package zw.qantra.tm.domain.workflows.handlers.erp;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import zw.qantra.tm.domain.dtos.erp.PaymentEntryDto;
|
||||
import zw.qantra.tm.domain.dtos.erp.PaymentReferenceDto;
|
||||
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.models.TransactionEvent;
|
||||
import zw.qantra.tm.domain.models.User;
|
||||
import zw.qantra.tm.domain.repositories.TransactionRepository;
|
||||
import zw.qantra.tm.domain.services.ProviderService;
|
||||
import zw.qantra.tm.domain.services.TransactionEventService;
|
||||
import zw.qantra.tm.domain.services.UserService;
|
||||
import zw.qantra.tm.domain.services.processors.handlers.LogicHandler;
|
||||
import zw.qantra.tm.domain.workflows.handlers.HandlerInterface;
|
||||
import zw.qantra.tm.exceptions.ApiException;
|
||||
import zw.qantra.tm.rest.RestService;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
@Service("purchaseInvoicePayment")
|
||||
@RequiredArgsConstructor
|
||||
public class PurchasePaymentEntryHandler implements HandlerInterface {
|
||||
Logger logger = LoggerFactory.getLogger(PurchasePaymentEntryHandler.class);
|
||||
|
||||
private final RestService restService;
|
||||
private final UserService userService;
|
||||
private final TransactionEventService transactionEventService;
|
||||
private final TransactionRepository transactionRepository;
|
||||
private final ProviderService providerService;
|
||||
|
||||
@Value("${erpnext.url}")
|
||||
private String erpnextUrl;
|
||||
@Value("${erpnext.api.key}")
|
||||
private String erpnextApiKey;
|
||||
@Value("${erpnext.api.secret}")
|
||||
private String erpnextApiSecret;
|
||||
@Value("${erpnext.username}")
|
||||
private String erpnextUsername;
|
||||
@Value("${erpnext.password}")
|
||||
private String erpnextPassword;
|
||||
@Value("${erpnext.company}")
|
||||
private String erpnextCompany;
|
||||
@Value("${erpnext.accounts.accountsreceivable}")
|
||||
private String accountsReceivableAccount;
|
||||
@Value("${erpnext.accounts.cash}")
|
||||
private String cashAccount;
|
||||
@Value("${erpnext.accounts.payable}")
|
||||
private String payableAccount;
|
||||
|
||||
@Override
|
||||
public Transaction process(Transaction transaction) {
|
||||
logger.info("Processing purchase payment entry for transaction: {}", transaction.getReference());
|
||||
|
||||
if(transaction.getErpPurchasePaymentRef() != null)
|
||||
return transaction;
|
||||
|
||||
TransactionEvent transactionEvent = TransactionEvent.builder()
|
||||
.transactionId(transaction.getId().toString())
|
||||
.event(RequestType.PURCHASE_PAYMENT.name())
|
||||
.status(Status.PROCESSING)
|
||||
.build();
|
||||
transactionEventService.save(transactionEvent);
|
||||
|
||||
if(transaction.getErpPurchasePaymentRef() != null)
|
||||
return transaction;
|
||||
|
||||
if(transaction.getStatus() == Status.SUCCESS){
|
||||
try {
|
||||
logger.info("Creating purchase payment entry in ERPNext for successful transaction");
|
||||
|
||||
// login to erp
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("Content-Type", "application/json");
|
||||
headers.add("Accept", "application/json");
|
||||
|
||||
Map<String, String> loginRequest = Map.of(
|
||||
"usr", erpnextUsername,
|
||||
"pwd", erpnextPassword);
|
||||
|
||||
String sid = "";
|
||||
|
||||
try {
|
||||
ResponseEntity response = restService.postAsEntity(
|
||||
erpnextUrl + "/method/login", loginRequest, headers, LinkedHashMap.class);
|
||||
|
||||
sid = response.getHeaders().getFirst("Set-Cookie");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new ApiException("Failed to login to ERPNext");
|
||||
}
|
||||
|
||||
// create customer in erp
|
||||
String basicAuth = "Basic " + Base64.getEncoder().encodeToString(
|
||||
(erpnextApiKey + ":" + erpnextApiSecret).getBytes(StandardCharsets.UTF_8));
|
||||
headers.add("Authorization", basicAuth);
|
||||
headers.add("Cookie", sid);
|
||||
|
||||
Provider provider = providerService.getProviderRepository()
|
||||
.findByClientId(transaction.getBillClientId());
|
||||
BigDecimal commissionPercentage = BigDecimal.valueOf(provider.getPercentageCommission())
|
||||
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_EVEN);
|
||||
BigDecimal commission = transaction.getAmount().multiply(commissionPercentage)
|
||||
.setScale(2, RoundingMode.HALF_EVEN);
|
||||
BigDecimal amount = transaction.getAmount().subtract(commission);
|
||||
|
||||
PaymentReferenceDto paymentReferenceDto = PaymentReferenceDto.builder()
|
||||
.referenceDoctype("Purchase Invoice")
|
||||
.referenceName(transaction.getErpPurchaseRef())
|
||||
.allocatedAmount(amount)
|
||||
.build();
|
||||
|
||||
String refNo = transaction.getCreditRef().isBlank() ? transaction.getId().toString() : transaction.getCreditRef();
|
||||
|
||||
PaymentEntryDto paymentEntryDto = PaymentEntryDto.builder()
|
||||
.doctype("Payment Entry")
|
||||
.company(erpnextCompany)
|
||||
.docstatus(1)
|
||||
.paymentType("Pay")
|
||||
.party("Steward Bank")
|
||||
.partyType("Supplier")
|
||||
.paidFrom(cashAccount)
|
||||
.paidTo(payableAccount)
|
||||
.paidAmount(amount)
|
||||
.receivedAmount(amount)
|
||||
.referenceNo(refNo)
|
||||
.referenceDate(transaction.getCreatedAt().toLocalDate().toString())
|
||||
.postingDate(transaction.getCreatedAt().toLocalDate().toString())
|
||||
.references(Arrays.asList(paymentReferenceDto))
|
||||
.build();
|
||||
|
||||
try {
|
||||
LinkedHashMap response = restService.postAs(
|
||||
erpnextUrl + "/v2/document/Payment Entry", paymentEntryDto, headers, LinkedHashMap.class);
|
||||
|
||||
LinkedHashMap data = (LinkedHashMap) response.get("data");
|
||||
String transactionId = (String) data.get("name");
|
||||
|
||||
transaction.setErpPurchasePaymentRef(transactionId);
|
||||
transactionEvent.setStatus(Status.SUCCESS);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new ApiException("Failed to create purchase payment entry in ERPNext");
|
||||
}
|
||||
|
||||
logger.info("purchase payment entry created successfully in ERPNext");
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to create purchase payment entry in ERPNext: {}", e.getMessage(), e);
|
||||
// Don't fail the transaction, just log the error
|
||||
// The ERP invoice creation is a side effect, not critical to the payment flow
|
||||
transactionEvent.setStatus(Status.FAILED);
|
||||
transactionEvent.setMessage(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
logger.info("Transaction status is not SUCCESS ({}), skipping purchase payment entry creation", transaction.getStatus());
|
||||
}
|
||||
|
||||
transactionEventService.save(transactionEvent);
|
||||
|
||||
return transactionRepository.save(transaction);
|
||||
}
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
package zw.qantra.tm.domain.workflows.handlers.erp;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.joda.time.LocalDateTime;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import zw.qantra.tm.domain.dtos.erp.SalesInvoiceDto;
|
||||
import zw.qantra.tm.domain.dtos.erp.SalesInvoiceItemDto;
|
||||
import zw.qantra.tm.domain.enums.RequestType;
|
||||
import zw.qantra.tm.domain.enums.Status;
|
||||
import zw.qantra.tm.domain.models.Transaction;
|
||||
import zw.qantra.tm.domain.models.TransactionEvent;
|
||||
import zw.qantra.tm.domain.models.User;
|
||||
import zw.qantra.tm.domain.repositories.TransactionRepository;
|
||||
import zw.qantra.tm.domain.services.TransactionEventService;
|
||||
import zw.qantra.tm.domain.services.TransactionService;
|
||||
import zw.qantra.tm.domain.services.UserService;
|
||||
import zw.qantra.tm.domain.services.processors.handlers.LogicHandler;
|
||||
import zw.qantra.tm.domain.workflows.handlers.HandlerInterface;
|
||||
import zw.qantra.tm.exceptions.ApiException;
|
||||
import zw.qantra.tm.rest.RestService;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
@Service("salesInvoice")
|
||||
@RequiredArgsConstructor
|
||||
public class SalesInvoiceHandler implements HandlerInterface {
|
||||
Logger logger = LoggerFactory.getLogger(SalesInvoiceHandler.class);
|
||||
|
||||
private final RestService restService;
|
||||
private final UserService userService;
|
||||
private final TransactionEventService transactionEventService;
|
||||
private final TransactionService transactionService;
|
||||
|
||||
@Value("${erpnext.url}")
|
||||
private String erpnextUrl;
|
||||
@Value("${erpnext.api.key}")
|
||||
private String erpnextApiKey;
|
||||
@Value("${erpnext.api.secret}")
|
||||
private String erpnextApiSecret;
|
||||
@Value("${erpnext.username}")
|
||||
private String erpnextUsername;
|
||||
@Value("${erpnext.password}")
|
||||
private String erpnextPassword;
|
||||
@Value("${erpnext.company}")
|
||||
private String erpnextCompany;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Transaction process(Transaction transaction) {
|
||||
logger.info("Processing sales invoice for transaction: {}", transaction.getReference());
|
||||
|
||||
if(transaction.getErpSalesRef() != null)
|
||||
return transaction;
|
||||
|
||||
TransactionEvent transactionEvent = TransactionEvent.builder()
|
||||
.transactionId(transaction.getId().toString())
|
||||
.event(RequestType.SALES_INVOICE.name())
|
||||
.status(Status.PROCESSING)
|
||||
.build();
|
||||
transactionEventService.save(transactionEvent);
|
||||
|
||||
try {
|
||||
logger.info("Creating sales invoice in ERPNext for successful transaction");
|
||||
|
||||
// login to erp
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("Content-Type", "application/json");
|
||||
headers.add("Accept", "application/json");
|
||||
|
||||
Map<String, String> loginRequest = Map.of(
|
||||
"usr", erpnextUsername,
|
||||
"pwd", erpnextPassword);
|
||||
|
||||
String sid = "";
|
||||
|
||||
try {
|
||||
ResponseEntity response = restService.postAsEntity(
|
||||
erpnextUrl + "/method/login", loginRequest, headers, LinkedHashMap.class);
|
||||
|
||||
sid = response.getHeaders().getFirst("Set-Cookie");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new ApiException("Failed to login to ERPNext");
|
||||
}
|
||||
|
||||
// create customer in erp
|
||||
String basicAuth = "Basic " + Base64.getEncoder().encodeToString(
|
||||
(erpnextApiKey + ":" + erpnextApiSecret).getBytes(StandardCharsets.UTF_8));
|
||||
headers.add("Authorization", basicAuth);
|
||||
headers.add("Cookie", sid);
|
||||
|
||||
String erpCustomerRef = "";
|
||||
User user = userService.getUserRepository().findById(UUID.fromString(transaction.getUserId())).orElse(null);
|
||||
erpCustomerRef = (user == null) ? "Anonymous" : user.getErpCustomerRef();
|
||||
|
||||
SalesInvoiceDto salesInvoiceDto = SalesInvoiceDto.builder()
|
||||
.doctype("Sales Invoice")
|
||||
.customer(erpCustomerRef)
|
||||
.postingDate(new LocalDateTime().toLocalDate().toString())
|
||||
.dueDate(new LocalDateTime().plusDays(1).toLocalDate().toString())
|
||||
.company(erpnextCompany)
|
||||
.docstatus(1)
|
||||
.items(Arrays.asList(SalesInvoiceItemDto.builder()
|
||||
.itemCode(transaction.getProviderLabel())
|
||||
.qty(1)
|
||||
.rate(transaction.getAmount().add(transaction.getGatewayCharge()))
|
||||
.amount(transaction.getAmount().add(transaction.getGatewayCharge()))
|
||||
.build()))
|
||||
.build();
|
||||
|
||||
try {
|
||||
LinkedHashMap response = restService.postAs(
|
||||
erpnextUrl + "/v2/document/Sales Invoice", salesInvoiceDto, headers, LinkedHashMap.class);
|
||||
|
||||
LinkedHashMap data = (LinkedHashMap) response.get("data");
|
||||
String transactionId = (String) data.get("name");
|
||||
|
||||
transaction.setErpSalesRef(transactionId);
|
||||
transactionEvent.setStatus(Status.SUCCESS);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new ApiException("Failed to create sales invoice in ERPNext");
|
||||
}
|
||||
|
||||
logger.info("Sales invoice created successfully in ERPNext");
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to create sales invoice in ERPNext: {}", e.getMessage(), e);
|
||||
// Don't fail the transaction, just log the error
|
||||
// The ERP invoice creation is a side effect, not critical to the payment flow
|
||||
transactionEvent.setStatus(Status.FAILED);
|
||||
transactionEvent.setMessage(e.getMessage());
|
||||
}
|
||||
|
||||
transactionEventService.save(transactionEvent);
|
||||
|
||||
return transactionService.save(transaction);
|
||||
}
|
||||
}
|
||||
15
src/main/java/zw/qantra/tm/exceptions/AlreadyExistsException.java
Executable file
15
src/main/java/zw/qantra/tm/exceptions/AlreadyExistsException.java
Executable file
@@ -0,0 +1,15 @@
|
||||
package zw.qantra.tm.exceptions;
|
||||
|
||||
public class AlreadyExistsException extends RuntimeException {
|
||||
|
||||
private String message;
|
||||
|
||||
public AlreadyExistsException(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
@@ -232,8 +232,8 @@ public class RestService {
|
||||
return this.restTemplate.getForObject(path, clazz);
|
||||
}
|
||||
|
||||
public <T> ResponseEntity<?> exchangeGetNoAuth(String path, HttpEntity<?> httpEntity, Class<?> clazz, String pathVariable) {
|
||||
return this.restTemplate.exchange(path, HttpMethod.GET, httpEntity, clazz, pathVariable);
|
||||
public ResponseEntity exchange(String path, HttpMethod method, HttpEntity<Object> objectHttpEntity, Class jsonNodeClass) {
|
||||
return this.restTemplate.exchange(path, method, objectHttpEntity, jsonNodeClass);
|
||||
}
|
||||
|
||||
private String prettyPrintJson(String json) {
|
||||
|
||||
48
src/main/java/zw/qantra/tm/seeders/CategorySeeder.java
Normal file
48
src/main/java/zw/qantra/tm/seeders/CategorySeeder.java
Normal file
@@ -0,0 +1,48 @@
|
||||
package zw.qantra.tm.seeders;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import zw.qantra.tm.domain.models.Category;
|
||||
import zw.qantra.tm.domain.repositories.CategoryRepository;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Seeds Category entities.
|
||||
* Priority: 3rd - Must run after IntegrationProcessorSeeder and before ProviderSeeder.
|
||||
* Only seeds entities that don't already exist (checked by name).
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class CategorySeeder implements Seeder {
|
||||
|
||||
private final CategoryRepository categoryRepository;
|
||||
|
||||
@Override
|
||||
public void seed() {
|
||||
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()
|
||||
);
|
||||
|
||||
for (Category category : categories) {
|
||||
if (categoryRepository.existsByName(category.getName())) {
|
||||
log.info("Category '{}' already exists, skipping.", category.getName());
|
||||
} else {
|
||||
categoryRepository.save(category);
|
||||
log.info("Category '{}' seeded.", category.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package zw.qantra.tm.seeders;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import zw.qantra.tm.domain.enums.ChargeConditionType;
|
||||
import zw.qantra.tm.domain.models.ChargeCondition;
|
||||
import zw.qantra.tm.domain.repositories.ChargeConditionRepository;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Seeds ChargeCondition entities.
|
||||
* Priority: 5th - Must run after ProviderSeeder and before ChargeSeeder.
|
||||
* Only seeds entities that don't already exist (checked by codes).
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ChargeConditionSeeder implements Seeder {
|
||||
|
||||
private final ChargeConditionRepository chargeConditionRepository;
|
||||
|
||||
@Override
|
||||
public void seed() {
|
||||
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()
|
||||
);
|
||||
|
||||
for (ChargeCondition condition : chargeConditions) {
|
||||
if (chargeConditionRepository.existsByCodes(condition.getCodes())) {
|
||||
log.info("ChargeCondition '{}' already exists, skipping.", condition.getCodes());
|
||||
} else {
|
||||
chargeConditionRepository.save(condition);
|
||||
log.info("ChargeCondition '{}' seeded.", condition.getCodes());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
72
src/main/java/zw/qantra/tm/seeders/ChargeSeeder.java
Normal file
72
src/main/java/zw/qantra/tm/seeders/ChargeSeeder.java
Normal file
@@ -0,0 +1,72 @@
|
||||
package zw.qantra.tm.seeders;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
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.repositories.ChargeConditionRepository;
|
||||
import zw.qantra.tm.domain.repositories.ChargeRepository;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* Seeds Charge entities.
|
||||
* Priority: 6th - Must run last, after ChargeConditionSeeder.
|
||||
* Only seeds entities that don't already exist (checked by chargeLabel, currency, and percentageRate).
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ChargeSeeder implements Seeder {
|
||||
|
||||
private final ChargeRepository chargeRepository;
|
||||
private final ChargeConditionRepository chargeConditionRepository;
|
||||
|
||||
@Override
|
||||
public void seed() {
|
||||
// Look up persisted ChargeConditions by their codes
|
||||
ChargeCondition ecocashCondition = chargeConditionRepository.findByCodes("ECOCASH").orElse(null);
|
||||
ChargeCondition mpgsCondition = chargeConditionRepository.findByCodes("MPGS").orElse(null);
|
||||
|
||||
if (ecocashCondition == null || mpgsCondition == null) {
|
||||
log.warn("Required ChargeConditions not found. Skipping Charge seeding.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Ecocash Gateway Fee (1%)
|
||||
if (chargeRepository.existsByChargeLabelAndCurrencyAndPercentageRate(
|
||||
ChargeLabelEnum.GATEWAY_FEE, CurrencyType.USD, new BigDecimal("1"))) {
|
||||
log.info("Charge 'Gateway fee (1%)' already exists, skipping.");
|
||||
} else {
|
||||
chargeRepository.save(Charge.builder()
|
||||
.description("Gateway fee")
|
||||
.chargeLabel(ChargeLabelEnum.GATEWAY_FEE)
|
||||
.currency(CurrencyType.USD)
|
||||
.percentageRate(new BigDecimal("1"))
|
||||
.includes(new HashSet<>(Arrays.asList(ecocashCondition)))
|
||||
.build());
|
||||
log.info("Charge 'Gateway fee (1%)' seeded.");
|
||||
}
|
||||
|
||||
// MPGS Gateway Fee (2%, min 0.55)
|
||||
if (chargeRepository.existsByChargeLabelAndCurrencyAndPercentageRate(
|
||||
ChargeLabelEnum.GATEWAY_FEE, CurrencyType.USD, new BigDecimal("2"))) {
|
||||
log.info("Charge 'Gateway fee (2%)' already exists, skipping.");
|
||||
} else {
|
||||
chargeRepository.save(Charge.builder()
|
||||
.description("Gateway fee")
|
||||
.chargeLabel(ChargeLabelEnum.GATEWAY_FEE)
|
||||
.currency(CurrencyType.USD)
|
||||
.percentageRate(new BigDecimal("1"))
|
||||
.min(new BigDecimal("0.20"))
|
||||
.includes(new HashSet<>(Arrays.asList(mpgsCondition)))
|
||||
.build());
|
||||
log.info("Charge 'Gateway fee (2%)' seeded.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package zw.qantra.tm.seeders;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import zw.qantra.tm.domain.models.IntegrationProcessor;
|
||||
import zw.qantra.tm.domain.repositories.IntegrationProcessorRepository;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Seeds IntegrationProcessor entities.
|
||||
* Priority: 2nd - Must run after PaymentProcessorSeeder.
|
||||
* Only seeds entities that don't already exist (checked by name).
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class IntegrationProcessorSeeder implements Seeder {
|
||||
|
||||
private final IntegrationProcessorRepository integrationProcessorRepository;
|
||||
|
||||
@Override
|
||||
public void seed() {
|
||||
List<IntegrationProcessor> processors = Arrays.asList(
|
||||
IntegrationProcessor.builder()
|
||||
.name("HOT")
|
||||
.label("HOT")
|
||||
.description("Hot Recharge Integration Processor")
|
||||
.build(),
|
||||
IntegrationProcessor.builder()
|
||||
.name("STEWARD")
|
||||
.label("STEWARD")
|
||||
.description("Steward Integration Processor")
|
||||
.build()
|
||||
);
|
||||
|
||||
for (IntegrationProcessor processor : processors) {
|
||||
if (integrationProcessorRepository.existsByName(processor.getName())) {
|
||||
log.info("IntegrationProcessor '{}' already exists, skipping.", processor.getName());
|
||||
} else {
|
||||
integrationProcessorRepository.save(processor);
|
||||
log.info("IntegrationProcessor '{}' seeded.", processor.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
65
src/main/java/zw/qantra/tm/seeders/LiveConfigSeeder.java
Normal file
65
src/main/java/zw/qantra/tm/seeders/LiveConfigSeeder.java
Normal file
@@ -0,0 +1,65 @@
|
||||
package zw.qantra.tm.seeders;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Orchestrates all seeders in the correct priority order matching
|
||||
* ConfigController.seedLiveConfig() execution sequence:
|
||||
* <p>
|
||||
* 1. PaymentProcessorSeeder
|
||||
* 2. IntegrationProcessorSeeder
|
||||
* 3. CategorySeeder
|
||||
* 4. ProviderSeeder
|
||||
* 5. ChargeConditionSeeder
|
||||
* 6. ChargeSeeder
|
||||
* 7. SettingSeeder
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
public class LiveConfigSeeder {
|
||||
|
||||
private final PaymentProcessorSeeder paymentProcessorSeeder;
|
||||
private final IntegrationProcessorSeeder integrationProcessorSeeder;
|
||||
private final CategorySeeder categorySeeder;
|
||||
private final ProviderSeeder providerSeeder;
|
||||
private final ChargeConditionSeeder chargeConditionSeeder;
|
||||
private final ChargeSeeder chargeSeeder;
|
||||
private final SettingSeeder settingSeeder;
|
||||
|
||||
/**
|
||||
* Runs all seeders in the correct priority order.
|
||||
* This method is idempotent and can be called multiple times.
|
||||
*/
|
||||
@PostConstruct
|
||||
public void seedAll() {
|
||||
log.info("Starting live configuration seeding...");
|
||||
|
||||
log.info("Seeding Payment Processors...");
|
||||
paymentProcessorSeeder.seed();
|
||||
|
||||
log.info("Seeding Integration Processors...");
|
||||
integrationProcessorSeeder.seed();
|
||||
|
||||
log.info("Seeding Categories...");
|
||||
categorySeeder.seed();
|
||||
|
||||
log.info("Seeding Providers...");
|
||||
providerSeeder.seed();
|
||||
|
||||
log.info("Seeding Charge Conditions...");
|
||||
chargeConditionSeeder.seed();
|
||||
|
||||
log.info("Seeding Charges...");
|
||||
chargeSeeder.seed();
|
||||
|
||||
log.info("Seeding Settings...");
|
||||
settingSeeder.seed();
|
||||
|
||||
log.info("Live configuration seeding complete.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package zw.qantra.tm.seeders;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import zw.qantra.tm.domain.models.PaymentProcessor;
|
||||
import zw.qantra.tm.domain.repositories.PaymentProcessorRepository;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Seeds PaymentProcessor entities.
|
||||
* Priority: 1st - Must run before any other seeder.
|
||||
* Only seeds entities that don't already exist (checked by label).
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class PaymentProcessorSeeder implements Seeder {
|
||||
|
||||
private final PaymentProcessorRepository paymentProcessorRepository;
|
||||
|
||||
@Override
|
||||
public void seed() {
|
||||
List<PaymentProcessor> processors = Arrays.asList(
|
||||
PaymentProcessor.builder()
|
||||
.name("Ecocash")
|
||||
.label("ECOCASH")
|
||||
.type("GATEWAY")
|
||||
.image("ecocash.png")
|
||||
.accountFieldName("phoneNumber")
|
||||
.accountFieldLabel("EcoCash Phone Number")
|
||||
.description("Pay using your mobile wallet")
|
||||
.authType("REMOTE")
|
||||
.build(),
|
||||
PaymentProcessor.builder()
|
||||
.name("Visa/MasterCard")
|
||||
.label("MPGS")
|
||||
.type("GATEWAY")
|
||||
.image("visa-mastercard.png")
|
||||
.accountFieldName("cardNumber")
|
||||
.accountFieldLabel("Card Number")
|
||||
.description("Pay using your international card")
|
||||
.authType("WEB")
|
||||
.build()
|
||||
);
|
||||
|
||||
for (PaymentProcessor processor : processors) {
|
||||
if (paymentProcessorRepository.existsByLabel(processor.getLabel())) {
|
||||
log.info("PaymentProcessor '{}' already exists, skipping.", processor.getLabel());
|
||||
} else {
|
||||
paymentProcessorRepository.save(processor);
|
||||
log.info("PaymentProcessor '{}' seeded.", processor.getLabel());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
103
src/main/java/zw/qantra/tm/seeders/ProviderSeeder.java
Normal file
103
src/main/java/zw/qantra/tm/seeders/ProviderSeeder.java
Normal file
@@ -0,0 +1,103 @@
|
||||
package zw.qantra.tm.seeders;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import zw.qantra.tm.domain.models.Provider;
|
||||
import zw.qantra.tm.domain.repositories.ProviderRepository;
|
||||
import zw.qantra.tm.utils.Utils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Seeds Provider entities.
|
||||
* Priority: 4th - Must run after CategorySeeder (providers reference categories).
|
||||
* Only seeds entities that don't already exist (checked by clientId).
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ProviderSeeder implements Seeder {
|
||||
|
||||
private final ProviderRepository providerRepository;
|
||||
|
||||
@Override
|
||||
public void seed() {
|
||||
List<Provider> providers = Arrays.asList(
|
||||
Provider.builder()
|
||||
.name("Econet Airtime")
|
||||
.description("Econet Airtime")
|
||||
.clientId("econet_airtime")
|
||||
.label("ECONET")
|
||||
.category("AIRTIME")
|
||||
.image("econet.png")
|
||||
.externalId("78f1f497-c9eb-401c-b22c-884756e68e40")
|
||||
.integrationProcessorLabel("HOT")
|
||||
.accountFieldName("Phone Number")
|
||||
.percentageCommission(5)
|
||||
.requiresAccount(true)
|
||||
.requiresAmount(true)
|
||||
.requiresPhone(true)
|
||||
.uid(Utils.getUid())
|
||||
.build(),
|
||||
Provider.builder()
|
||||
.name("NetOne Airtime")
|
||||
.description("NetOne Airtime")
|
||||
.clientId("netone_usd_airtime")
|
||||
.label("NETONE")
|
||||
.category("AIRTIME")
|
||||
.image("netone.png")
|
||||
.externalId("95d485a7-39c9-4559-8a27-6521969abe8f")
|
||||
.integrationProcessorLabel("HOT")
|
||||
.accountFieldName("Phone Number")
|
||||
.percentageCommission(5)
|
||||
.requiresAccount(true)
|
||||
.requiresAmount(true)
|
||||
.requiresPhone(true)
|
||||
.uid(Utils.getUid())
|
||||
.build(),
|
||||
Provider.builder()
|
||||
.name("Zesa Prepaid")
|
||||
.description("Prepaid Zesa")
|
||||
.clientId("zesa_prepaid_usd")
|
||||
.label("ZESA")
|
||||
.category("UTILITIES")
|
||||
.image("zesa.png")
|
||||
.externalId("0f67f7cc-b62b-4fb5-915f-6740b2afdba6")
|
||||
.integrationProcessorLabel("HOT")
|
||||
.accountFieldName("Meter Number")
|
||||
.percentageCommission(1.5)
|
||||
.requiresAccount(true)
|
||||
.requiresAmount(true)
|
||||
.requiresPhone(true)
|
||||
.uid(Utils.getUid())
|
||||
.build(),
|
||||
Provider.builder()
|
||||
.name("Econet Bundles")
|
||||
.description("Econet Bundles")
|
||||
.clientId("econet_bundles_usd")
|
||||
.label("ECONET_BUNDLES")
|
||||
.category("AIRTIME")
|
||||
.image("econet.png")
|
||||
.externalId("287863e2-a791-4106-b629-79dadc9a6779")
|
||||
.integrationProcessorLabel("HOT")
|
||||
.accountFieldName("Phone Number")
|
||||
.percentageCommission(5)
|
||||
.requiresAccount(true)
|
||||
.requiresAmount(false)
|
||||
.requiresPhone(true)
|
||||
.uid(Utils.getUid())
|
||||
.build()
|
||||
);
|
||||
|
||||
for (Provider provider : providers) {
|
||||
if (providerRepository.existsByClientId(provider.getClientId())) {
|
||||
log.info("Provider '{}' already exists, skipping.", provider.getClientId());
|
||||
} else {
|
||||
providerRepository.save(provider);
|
||||
log.info("Provider '{}' seeded.", provider.getClientId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/main/java/zw/qantra/tm/seeders/Seeder.java
Normal file
10
src/main/java/zw/qantra/tm/seeders/Seeder.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package zw.qantra.tm.seeders;
|
||||
|
||||
/**
|
||||
* Base interface for all seeders.
|
||||
* Each seeder encapsulates the seeding logic for a specific entity
|
||||
* following the order defined in the original seedLiveConfig method.
|
||||
*/
|
||||
public interface Seeder {
|
||||
void seed();
|
||||
}
|
||||
61
src/main/java/zw/qantra/tm/seeders/SettingSeeder.java
Normal file
61
src/main/java/zw/qantra/tm/seeders/SettingSeeder.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package zw.qantra.tm.seeders;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.stereotype.Component;
|
||||
import zw.qantra.tm.domain.models.Setting;
|
||||
import zw.qantra.tm.domain.repositories.SettingRepository;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Seeds Setting entities for simulation flags.
|
||||
* Uses findBySettingName to check for existing records before seeding.
|
||||
* When the "lab" Spring profile is active, simulation flags default to "true".
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class SettingSeeder implements Seeder {
|
||||
|
||||
private final SettingRepository settingRepository;
|
||||
private final Environment environment;
|
||||
|
||||
@Override
|
||||
public void seed() {
|
||||
boolean isLab = environment.matchesProfiles("lab");
|
||||
String defaultValue = isLab ? "true" : "false";
|
||||
|
||||
log.info("Lab profile active: {}. Setting simulation defaults to '{}'.", isLab, defaultValue);
|
||||
|
||||
List<Setting> settings = Arrays.asList(
|
||||
Setting.builder()
|
||||
.settingName("SIMULATE_MPGS_SUCCESS")
|
||||
.settingValue(defaultValue)
|
||||
.build(),
|
||||
Setting.builder()
|
||||
.settingName("SIMULATE_ECOCASH_SUCCESS")
|
||||
.settingValue(defaultValue)
|
||||
.build(),
|
||||
Setting.builder()
|
||||
.settingName("SIMULATE_HOT_SUCCESS")
|
||||
.settingValue(defaultValue)
|
||||
.build(),
|
||||
Setting.builder()
|
||||
.settingName("VELOCITY_COMPANY_ID")
|
||||
.settingValue("d5d381af-f632-464b-a97b-048a8b17f99d")
|
||||
.build()
|
||||
);
|
||||
|
||||
for (Setting setting : settings) {
|
||||
if (settingRepository.findBySettingName(setting.getSettingName()) != null) {
|
||||
log.info("Setting '{}' already exists, skipping.", setting.getSettingName());
|
||||
} else {
|
||||
settingRepository.save(setting);
|
||||
log.info("Setting '{}' seeded with value '{}'.", setting.getSettingName(), setting.getSettingValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package zw.qantra.tm.utils;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.util.StdDateFormat;
|
||||
@@ -47,6 +48,7 @@ public class Utils {
|
||||
public static <T> T fromJson(String json, Class<T> clazz){
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
mapper.setDateFormat(new StdDateFormat().withColonInTimeZone(true));
|
||||
mapper.registerModule(new JavaTimeModule());
|
||||
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
|
||||
@@ -10,9 +10,9 @@ server.servlet.context-path=/api
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
spring.datasource.driver-class-name=org.postgresql.Driver
|
||||
|
||||
spring.datasource.url=jdbc:postgresql://173.212.247.232:5532/qpay?useLegacyDatetimeCode=false
|
||||
spring.datasource.url=jdbc:postgresql://localhost:5432/qpay?useLegacyDatetimeCode=false
|
||||
spring.datasource.username=postgres
|
||||
spring.datasource.password=zdDZMzq6F4B4L1IUl
|
||||
spring.datasource.password=example
|
||||
|
||||
|
||||
# sbz.merchant.url=http://192.168.16.29:24050/v1
|
||||
@@ -28,29 +28,19 @@ sbz.aggregator.encryption-key=735470ce-8efb-4c0f-ba3f-7298f805ab33
|
||||
|
||||
steward.payment.processor.url=${sbz.merchant.url}
|
||||
|
||||
# ERPNext Configuration
|
||||
erpnext.url=http://173.212.247.232:8085/api
|
||||
erpnext.api.key=1102c6da0312f75
|
||||
erpnext.api.secret=e3d95692bcf16c7
|
||||
erpnext.username=Administrator
|
||||
erpnext.password=admin
|
||||
erpnext.company=Qantra
|
||||
erpnext.accounts.cash=Bank Account - Q
|
||||
erpnext.accounts.gatewayexpense=Gateway Expenses - Q
|
||||
erpnext.accounts.accountsreceivable=Debtors - Q
|
||||
erpnext.accounts.commission=Discounts Received - Q
|
||||
erpnext.accounts.payable=Creditors - Q
|
||||
erpnext.income.account=Sales Income - Q
|
||||
erpnext.cost.center=Main - Q
|
||||
erpnext.tax.account=VAT - Q
|
||||
erpnext.tax.rate=15.0
|
||||
|
||||
logging.level.root=WARN
|
||||
spring.datasource.nflow.url=jdbc:postgresql://173.212.247.232:5532/nflow
|
||||
spring.datasource.nflow.url=jdbc:postgresql://localhost:5432/qpay-nflow
|
||||
spring.datasource.nflow.username=postgres
|
||||
spring.datasource.nflow.password=zdDZMzq6F4B4L1IUl
|
||||
spring.datasource.nflow.password=example
|
||||
|
||||
powertel.clientid=powertel_zesa
|
||||
|
||||
ecocash.apikey=OUzGezwTgktLg_9v0I48I6WUM4LVLGFl
|
||||
ecocash.url=https://developers.ecocash.co.zw/api/ecocash_pay/api/v2/payment/instant/c2b/sandbox
|
||||
|
||||
velocity.api.base-url=https://api.velocityafrica.net
|
||||
velocity.api.api-key=z5WdSlItIhYL6nd7rxzf-jFdKN3jJyL1LubhDFBvXmc
|
||||
#velocity.api.base-url=http://localhost:9005
|
||||
#velocity.api.api-key=gH13nirZdUQdqC9impgzTzqEqv-jdbLOTx_HD5fQr_A
|
||||
velocity.api.cancel-url=http://localhost:9005/poll
|
||||
velocity.api.success-url=http://localhost:9005/poll
|
||||
42
src/main/resources/application-staging.properties
Normal file
42
src/main/resources/application-staging.properties
Normal file
@@ -0,0 +1,42 @@
|
||||
spring.application.name=tm
|
||||
debug=false
|
||||
|
||||
logging.level.org.springframework.boot.autoconfigure=INFO
|
||||
|
||||
server.port=6950
|
||||
|
||||
server.servlet.context-path=/api
|
||||
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
spring.datasource.driver-class-name=org.postgresql.Driver
|
||||
|
||||
spring.datasource.url=jdbc:postgresql://173.212.247.232:5532/qpay?useLegacyDatetimeCode=false
|
||||
spring.datasource.username=postgres
|
||||
spring.datasource.password=zdDZMzq6F4B4L1IUl
|
||||
|
||||
|
||||
# sbz.merchant.url=http://192.168.16.29:24050/v1
|
||||
# sbz.merchant.url=http://localhost:24000/v1
|
||||
sbz.merchant.url=https://api.stewardpay.co.zw/lab/v2
|
||||
sbz.aggregator.url=${sbz.merchant.url}
|
||||
sbz.aggregator.phone=263773591219
|
||||
sbz.aggregator.account=1046737845
|
||||
|
||||
sbz.aggregator.client-id=steward_pay
|
||||
sbz.aggregator.client-secret=XjkbBAJeJCnAplYDTbp5xOjeyySVnYGp
|
||||
sbz.aggregator.encryption-key=735470ce-8efb-4c0f-ba3f-7298f805ab33
|
||||
|
||||
steward.payment.processor.url=${sbz.merchant.url}
|
||||
|
||||
logging.level.root=WARN
|
||||
spring.datasource.nflow.url=jdbc:postgresql://173.212.247.232:5532/qpay-nflow
|
||||
spring.datasource.nflow.username=postgres
|
||||
spring.datasource.nflow.password=zdDZMzq6F4B4L1IUl
|
||||
|
||||
powertel.clientid=powertel_zesa
|
||||
|
||||
ecocash.apikey=OUzGezwTgktLg_9v0I48I6WUM4LVLGFl
|
||||
ecocash.url=https://developers.ecocash.co.zw/api/ecocash_pay/api/v2/payment/instant/c2b/sandbox
|
||||
|
||||
velocity.api.cancel-url=http://localhost:9005/poll
|
||||
velocity.api.success-url=http://localhost:9005/poll
|
||||
@@ -30,23 +30,6 @@ steward.payment.processor.url=${sbz.merchant.url}
|
||||
|
||||
mpgs.return-url=https://vdt.co.zw/#/return
|
||||
|
||||
# ERPNext Configuration
|
||||
erpnext.url=http://62.171.179.108:8080/api
|
||||
erpnext.api.key=dff94c65b52ecbd
|
||||
erpnext.api.secret=49218fe805c9fb4
|
||||
erpnext.username=Administrator
|
||||
erpnext.password=admin
|
||||
erpnext.company=QantraTech
|
||||
erpnext.accounts.cash=Steward Bank Account - Q
|
||||
erpnext.accounts.gatewayexpense=Gateway Expenses - Q
|
||||
erpnext.accounts.accountsreceivable=Debtors - Q
|
||||
erpnext.accounts.commission=Discounts Received - Q
|
||||
erpnext.accounts.payable=Creditors - Q
|
||||
erpnext.income.account=Sales Income - Q
|
||||
erpnext.cost.center=Main - Q
|
||||
erpnext.tax.account=VAT - Q
|
||||
erpnext.tax.rate=15.0
|
||||
|
||||
# JWT Configuration
|
||||
jwt.secret=your-super-secret-jwt-key-here-make-it-very-long-and-secure-in-production
|
||||
jwt.expiration=86400000
|
||||
@@ -57,10 +40,6 @@ spring.datasource.nflow.username=postgres
|
||||
spring.datasource.nflow.password=zdDZMzq6F4B4L1IUla
|
||||
spring.main.allow-bean-definition-overriding=true
|
||||
|
||||
# Email Configuration
|
||||
infobip.url=https://z3m696.api.infobip.com/sms/2/text/advanced
|
||||
infobip.apikey=0ede918b7c1110465a0518021521638c-ebc4fdb7-547d-413b-bbca-781949ac6597
|
||||
|
||||
spring.mail.host=mail.zimscape.com
|
||||
spring.mail.port=587
|
||||
spring.mail.username=notifications@qantra.co.zw
|
||||
@@ -85,4 +64,9 @@ hot.api.url=https://ssl.hot.co.zw/api/v3
|
||||
hot.auth.url=https://ssl.hot.co.zw/api/v3/identity/login
|
||||
hot.access.code=vusumuzi@qantra.co.zw
|
||||
hot.password=Stikbanch@50
|
||||
hot.token.expiry.minutes=30
|
||||
hot.token.expiry.minutes=25
|
||||
|
||||
velocity.api.base-url=https://api.velocityafrica.net
|
||||
velocity.api.api-key=z5WdSlItIhYL6nd7rxzf-jFdKN3jJyL1LubhDFBvXmc
|
||||
velocity.api.cancel-url=https://pay.velocityafrica/poll
|
||||
velocity.api.success-url=https://pay.velocityafrica/poll
|
||||
@@ -1,5 +1,5 @@
|
||||
changeLogFile=src/main/resources/liquibase-diff-changeLog.xml
|
||||
url=jdbc:postgresql://173.212.247.232:5532/qpay?serverTimezone=Africa/Harare&useLegacyDatetimeCode=false
|
||||
url=jdbc:postgresql://localhost:5432/qpay?serverTimezone=Africa/Harare&useLegacyDatetimeCode=false
|
||||
username=postgres
|
||||
password=zdDZMzq6F4B4L1IUl
|
||||
|
||||
|
||||
Reference in New Issue
Block a user