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 com.google.gson.Gson;
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@Import(RestConfiguration.class)
|
@Import(RestConfiguration.class)
|
||||||
@EnableCaching
|
@EnableCaching
|
||||||
|
@EnableAsync
|
||||||
public class TmApplication {
|
public class TmApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public class CacheConfig {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public CacheManager cacheManager() {
|
public CacheManager cacheManager() {
|
||||||
CaffeineCacheManager cacheManager = new CaffeineCacheManager("providerProducts");
|
CaffeineCacheManager cacheManager = new CaffeineCacheManager("providerProducts", "hotrechargeBalance");
|
||||||
cacheManager.setCaffeine(Caffeine.newBuilder()
|
cacheManager.setCaffeine(Caffeine.newBuilder()
|
||||||
.expireAfterWrite(24, TimeUnit.HOURS)
|
.expireAfterWrite(24, TimeUnit.HOURS)
|
||||||
.maximumSize(10000));
|
.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;
|
package zw.qantra.tm.domain.controllers;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.cache.CacheManager;
|
import org.springframework.cache.CacheManager;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
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.ChargeConditionService;
|
||||||
import zw.qantra.tm.domain.services.ChargeService;
|
import zw.qantra.tm.domain.services.ChargeService;
|
||||||
import zw.qantra.tm.domain.services.IntegrationProcessorService;
|
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.PaymentProcessorService;
|
||||||
import zw.qantra.tm.domain.services.ProviderService;
|
import zw.qantra.tm.domain.services.ProviderService;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
@Slf4j
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/configs")
|
@RequestMapping("/configs")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -45,333 +33,4 @@ public class ConfigController {
|
|||||||
return ResponseEntity.ok("Cache cleared");
|
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;
|
package zw.qantra.tm.domain.controllers;
|
||||||
|
|
||||||
import io.nflow.engine.internal.dao.WorkflowInstanceDao;
|
|
||||||
import io.nflow.engine.service.WorkflowInstanceService;
|
import io.nflow.engine.service.WorkflowInstanceService;
|
||||||
import io.nflow.engine.workflow.instance.QueryWorkflowInstances;
|
import io.nflow.engine.workflow.instance.QueryWorkflowInstances;
|
||||||
import io.nflow.engine.workflow.instance.WorkflowInstance;
|
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.dtos.*;
|
||||||
import zw.qantra.tm.domain.services.OtpService;
|
import zw.qantra.tm.domain.services.OtpService;
|
||||||
import zw.qantra.tm.domain.services.UserService;
|
import zw.qantra.tm.domain.services.UserService;
|
||||||
import zw.qantra.tm.domain.workflows.RegistrationWorkflow;
|
import zw.qantra.tm.domain.services.processors.workflows.RegistrationWorkflow;
|
||||||
import zw.qantra.tm.domain.workflows.WorkflowUtils;
|
import zw.qantra.tm.domain.services.processors.workflows.WorkflowUtils;
|
||||||
import zw.qantra.tm.utils.Utils;
|
import zw.qantra.tm.utils.Utils;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package zw.qantra.tm.domain.controllers;
|
package zw.qantra.tm.domain.controllers;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.google.i18n.phonenumbers.NumberParseException;
|
import com.google.i18n.phonenumbers.NumberParseException;
|
||||||
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||||
import com.google.i18n.phonenumbers.Phonenumber;
|
import com.google.i18n.phonenumbers.Phonenumber;
|
||||||
@@ -28,8 +27,8 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import zw.qantra.tm.domain.workflows.TransactionWorkflow;
|
import zw.qantra.tm.domain.services.processors.workflows.TransactionWorkflow;
|
||||||
import zw.qantra.tm.domain.workflows.WorkflowUtils;
|
import zw.qantra.tm.domain.services.processors.workflows.WorkflowUtils;
|
||||||
import zw.qantra.tm.utils.LogUtils;
|
import zw.qantra.tm.utils.LogUtils;
|
||||||
import zw.qantra.tm.utils.Utils;
|
import zw.qantra.tm.utils.Utils;
|
||||||
|
|
||||||
@@ -189,16 +188,10 @@ public class TransactonController {
|
|||||||
throws ExecutionException, InterruptedException, NumberParseException {
|
throws ExecutionException, InterruptedException, NumberParseException {
|
||||||
LogUtils.logPrettyJson(logger, "incoming transaction", transaction);
|
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()
|
var instance = workflowInstanceFactory.newWorkflowInstanceBuilder()
|
||||||
.setType(TransactionWorkflow.TYPE)
|
.setType(TransactionWorkflow.TYPE)
|
||||||
.setExternalId(transaction.getUserId() + "-" + new Instant().getMillis())
|
.setExternalId(transaction.getUserId() + "-" + new Instant().getMillis())
|
||||||
.setBusinessKey(formattedPhoneNumber)
|
.setBusinessKey(transaction.getUserId())
|
||||||
.putStateVariable("confirmation", Utils.toJson(transaction))
|
.putStateVariable("confirmation", Utils.toJson(transaction))
|
||||||
.setNextActivation(DateTime.now())
|
.setNextActivation(DateTime.now())
|
||||||
.build();
|
.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
|
private TargetEvent targetEvent; // include / exclude
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
|
@ManyToMany(fetch = FetchType.EAGER)
|
||||||
@JoinTable(
|
@JoinTable(
|
||||||
name = "charge_include_products",
|
name = "charge_include_products",
|
||||||
joinColumns = @JoinColumn(name = "charge_id"),
|
joinColumns = @JoinColumn(name = "charge_id"),
|
||||||
@@ -51,7 +51,7 @@ public class Charge extends BaseEntity {
|
|||||||
private Set<ChargeCondition> includes = new HashSet<>();
|
private Set<ChargeCondition> includes = new HashSet<>();
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
|
@ManyToMany(fetch = FetchType.EAGER)
|
||||||
@JoinTable(
|
@JoinTable(
|
||||||
name = "charge_exclude_products",
|
name = "charge_exclude_products",
|
||||||
joinColumns = @JoinColumn(name = "charge_id"),
|
joinColumns = @JoinColumn(name = "charge_id"),
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import java.util.List;
|
|||||||
@Builder
|
@Builder
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class ChargeCondition extends BaseEntity {
|
public class ChargeCondition extends BaseEntity {
|
||||||
private String name;
|
private String name;
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
private ChargeConditionType type;
|
private ChargeConditionType type;
|
||||||
|
|||||||
@@ -77,6 +77,11 @@ public class Transaction extends BaseEntity {
|
|||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
private Status pollingStatus;
|
private Status pollingStatus;
|
||||||
|
|
||||||
|
|
||||||
|
private String orderId;
|
||||||
|
private String orderTrace;
|
||||||
|
private String orderTransactionTrace;
|
||||||
|
|
||||||
private String erpSalesRef;
|
private String erpSalesRef;
|
||||||
private String erpJournalRef;
|
private String erpJournalRef;
|
||||||
private String erpSalesPaymentRef;
|
private String erpSalesPaymentRef;
|
||||||
|
|||||||
@@ -8,4 +8,5 @@ import java.util.UUID;
|
|||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface CategoryRepository extends JpaRepository<Category, UUID> {
|
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 org.springframework.stereotype.Repository;
|
||||||
import zw.qantra.tm.domain.models.ChargeCondition;
|
import zw.qantra.tm.domain.models.ChargeCondition;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface ChargeConditionRepository extends JpaRepository<ChargeCondition, UUID> {
|
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.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
import zw.qantra.tm.domain.enums.ChargeLabelEnum;
|
||||||
import zw.qantra.tm.domain.enums.CurrencyType;
|
import zw.qantra.tm.domain.enums.CurrencyType;
|
||||||
import zw.qantra.tm.domain.models.Charge;
|
import zw.qantra.tm.domain.models.Charge;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface ChargeRepository extends JpaRepository<Charge, UUID> {
|
public interface ChargeRepository extends JpaRepository<Charge, UUID> {
|
||||||
List<Charge> findAllByCurrency(CurrencyType currency);
|
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
|
@Repository
|
||||||
public interface IntegrationProcessorRepository extends JpaRepository<IntegrationProcessor, UUID> {
|
public interface IntegrationProcessorRepository extends JpaRepository<IntegrationProcessor, UUID> {
|
||||||
}
|
boolean existsByName(String name);
|
||||||
|
}
|
||||||
@@ -8,4 +8,5 @@ import java.util.UUID;
|
|||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface PaymentProcessorRepository extends JpaRepository<PaymentProcessor, UUID> {
|
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> {
|
public interface ProviderRepository extends JpaRepository<Provider, UUID> {
|
||||||
List<Provider> findByCategory(String category);
|
List<Provider> findByCategory(String category);
|
||||||
Provider findByClientId(String clientId);
|
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.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.mail.SimpleMailMessage;
|
import org.springframework.mail.SimpleMailMessage;
|
||||||
import org.springframework.mail.javamail.JavaMailSender;
|
import org.springframework.mail.javamail.JavaMailSender;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@@ -10,6 +11,7 @@ public class EmailService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private JavaMailSender emailSender;
|
private JavaMailSender emailSender;
|
||||||
|
|
||||||
|
@Async
|
||||||
public void sendSimpleMessage(
|
public void sendSimpleMessage(
|
||||||
String to, String subject, String text) {
|
String to, String subject, String text) {
|
||||||
SimpleMailMessage message = new SimpleMailMessage();
|
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;
|
private String hotAccessCode;
|
||||||
@Value("${hot.password}")
|
@Value("${hot.password}")
|
||||||
private String hotPassword;
|
private String hotPassword;
|
||||||
@Value("${hot.token.expiry.minutes:30}")
|
@Value("${hot.token.expiry.minutes:25}")
|
||||||
private int tokenExpiryMinutes;
|
private int tokenExpiryMinutes;
|
||||||
|
|
||||||
private String cachedToken;
|
private String cachedToken;
|
||||||
|
|||||||
@@ -26,4 +26,17 @@ public class SettingService {
|
|||||||
return false;
|
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 org.springframework.stereotype.Service;
|
||||||
import zw.qantra.tm.domain.services.AdditionalDataService;
|
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.HotRechargeTokenService;
|
||||||
import zw.qantra.tm.rest.RestService;
|
import zw.qantra.tm.rest.RestService;
|
||||||
|
|
||||||
@@ -11,7 +12,8 @@ public class EconetBundlesConfirmationHotProcessor extends NetoneConfirmationHot
|
|||||||
public EconetBundlesConfirmationHotProcessor(
|
public EconetBundlesConfirmationHotProcessor(
|
||||||
AdditionalDataService additionalDataService,
|
AdditionalDataService additionalDataService,
|
||||||
HotRechargeTokenService hotRechargeTokenService,
|
HotRechargeTokenService hotRechargeTokenService,
|
||||||
|
HotRechargeBalanceService hotRechargeBalanceService,
|
||||||
RestService restService) {
|
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 org.springframework.stereotype.Service;
|
||||||
import zw.qantra.tm.domain.services.AdditionalDataService;
|
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.HotRechargeTokenService;
|
||||||
import zw.qantra.tm.rest.RestService;
|
import zw.qantra.tm.rest.RestService;
|
||||||
|
|
||||||
@@ -11,7 +12,8 @@ public class EconetConfirmationHotProcessor extends NetoneConfirmationHotProcess
|
|||||||
public EconetConfirmationHotProcessor(
|
public EconetConfirmationHotProcessor(
|
||||||
AdditionalDataService additionalDataService,
|
AdditionalDataService additionalDataService,
|
||||||
HotRechargeTokenService hotRechargeTokenService,
|
HotRechargeTokenService hotRechargeTokenService,
|
||||||
|
HotRechargeBalanceService hotRechargeBalanceService,
|
||||||
RestService restService) {
|
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.AdditionalData;
|
||||||
import zw.qantra.tm.domain.models.Transaction;
|
import zw.qantra.tm.domain.models.Transaction;
|
||||||
import zw.qantra.tm.domain.services.AdditionalDataService;
|
import zw.qantra.tm.domain.services.AdditionalDataService;
|
||||||
|
import zw.qantra.tm.domain.services.HotRechargeBalanceService;
|
||||||
import zw.qantra.tm.domain.services.HotRechargeTokenService;
|
import zw.qantra.tm.domain.services.HotRechargeTokenService;
|
||||||
import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface;
|
import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface;
|
||||||
import zw.qantra.tm.rest.RestService;
|
import zw.qantra.tm.rest.RestService;
|
||||||
@@ -23,9 +24,10 @@ import java.util.*;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class NetoneConfirmationHotProcessor implements TransactionProcessorInterface {
|
public class NetoneConfirmationHotProcessor implements TransactionProcessorInterface {
|
||||||
private final AdditionalDataService additionalDataService;
|
protected final AdditionalDataService additionalDataService;
|
||||||
private final HotRechargeTokenService hotRechargeTokenService;
|
protected final HotRechargeTokenService hotRechargeTokenService;
|
||||||
private final RestService restService;
|
protected final HotRechargeBalanceService hotRechargeBalanceService;
|
||||||
|
protected final RestService restService;
|
||||||
|
|
||||||
@Value("${hot.api.url:https://ssl.hot.co.zw/api/v3}")
|
@Value("${hot.api.url:https://ssl.hot.co.zw/api/v3}")
|
||||||
private String hotApiUrl;
|
private String hotApiUrl;
|
||||||
@@ -35,7 +37,7 @@ public class NetoneConfirmationHotProcessor implements TransactionProcessorInter
|
|||||||
public Object process(Transaction transaction) throws Exception {
|
public Object process(Transaction transaction) throws Exception {
|
||||||
|
|
||||||
String token = hotRechargeTokenService.getToken();
|
String token = hotRechargeTokenService.getToken();
|
||||||
if (!checkBalance(token, transaction.getAmount())) {
|
if (!hotRechargeBalanceService.checkBalance(token, transaction.getAmount())) {
|
||||||
transaction.setStatus(Status.FAILED);
|
transaction.setStatus(Status.FAILED);
|
||||||
transaction.setResponseCode("92");
|
transaction.setResponseCode("92");
|
||||||
transaction.setErrorMessage("There's a technical issue on our end. Please try again later.");
|
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;
|
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.Provider;
|
||||||
import zw.qantra.tm.domain.models.Transaction;
|
import zw.qantra.tm.domain.models.Transaction;
|
||||||
import zw.qantra.tm.domain.services.AdditionalDataService;
|
import zw.qantra.tm.domain.services.AdditionalDataService;
|
||||||
|
import zw.qantra.tm.domain.services.HotRechargeBalanceService;
|
||||||
import zw.qantra.tm.domain.services.HotRechargeTokenService;
|
import zw.qantra.tm.domain.services.HotRechargeTokenService;
|
||||||
import zw.qantra.tm.domain.services.ProviderService;
|
import zw.qantra.tm.domain.services.ProviderService;
|
||||||
import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface;
|
import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface;
|
||||||
@@ -33,6 +34,7 @@ public class ZesaConfirmationHotProcessor implements TransactionProcessorInterfa
|
|||||||
private final AdditionalDataService additionalDataService;
|
private final AdditionalDataService additionalDataService;
|
||||||
private final ProviderService providerService;
|
private final ProviderService providerService;
|
||||||
private final HotRechargeTokenService hotRechargeTokenService;
|
private final HotRechargeTokenService hotRechargeTokenService;
|
||||||
|
private final HotRechargeBalanceService hotRechargeBalanceService;
|
||||||
|
|
||||||
@Value("${hot.api.url:https://ssl.hot.co.zw/api/v3}")
|
@Value("${hot.api.url:https://ssl.hot.co.zw/api/v3}")
|
||||||
private String hotApiUrl;
|
private String hotApiUrl;
|
||||||
@@ -50,7 +52,7 @@ public class ZesaConfirmationHotProcessor implements TransactionProcessorInterfa
|
|||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!checkBalance(token, transaction.getAmount())) {
|
if (!hotRechargeBalanceService.checkBalance(token, transaction.getAmount())) {
|
||||||
transaction.setStatus(Status.FAILED);
|
transaction.setStatus(Status.FAILED);
|
||||||
transaction.setResponseCode("92");
|
transaction.setResponseCode("92");
|
||||||
transaction.setErrorMessage("There's a technical issue on our end. Please try again later.");
|
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;
|
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) {
|
private Map<String, Object> checkCustomer(String token, Transaction transaction) {
|
||||||
try {
|
try {
|
||||||
Provider provider = providerService.getProviderRepository()
|
Provider provider = providerService.getProviderRepository()
|
||||||
@@ -160,4 +140,4 @@ public class ZesaConfirmationHotProcessor implements TransactionProcessorInterfa
|
|||||||
public Object poll(Transaction transaction) throws Exception {
|
public Object poll(Transaction transaction) throws Exception {
|
||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -31,6 +31,7 @@ public class HotRechargeIntegrationProcessor implements TransactionProcessorInte
|
|||||||
private final SettingService settingService;
|
private final SettingService settingService;
|
||||||
private final HotRechargeTokenService hotRechargeTokenService;
|
private final HotRechargeTokenService hotRechargeTokenService;
|
||||||
private final ProviderService providerService;
|
private final ProviderService providerService;
|
||||||
|
private final HotRechargeBalanceService hotRechargeBalanceService;
|
||||||
|
|
||||||
@Value("${hot.api.url:https://ssl.hot.co.zw/api/v3}")
|
@Value("${hot.api.url:https://ssl.hot.co.zw/api/v3}")
|
||||||
private String hotApiUrl;
|
private String hotApiUrl;
|
||||||
@@ -53,7 +54,7 @@ public class HotRechargeIntegrationProcessor implements TransactionProcessorInte
|
|||||||
if(settingService.getBooleanSetting("SIMULATE_HOT_SUCCESS")){
|
if(settingService.getBooleanSetting("SIMULATE_HOT_SUCCESS")){
|
||||||
transaction.setStatus(Status.SUCCESS);
|
transaction.setStatus(Status.SUCCESS);
|
||||||
transaction.setResponseCode("responseCode");
|
transaction.setResponseCode("responseCode");
|
||||||
transaction.setCreditRef("creditRef");
|
transaction.setCreditRef("Simulated Integration Success");
|
||||||
transaction.setErrorMessage(null);
|
transaction.setErrorMessage(null);
|
||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
@@ -69,7 +70,7 @@ public class HotRechargeIntegrationProcessor implements TransactionProcessorInte
|
|||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!checkBalance(token, transaction.getAmount())) {
|
if (!hotRechargeBalanceService.checkBalance(token, transaction.getAmount())) {
|
||||||
transaction.setStatus(Status.FAILED);
|
transaction.setStatus(Status.FAILED);
|
||||||
transaction.setResponseCode("92");
|
transaction.setResponseCode("92");
|
||||||
transaction.setErrorMessage("There's a technical issue on our end. Please try again later.");
|
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");
|
Boolean successful = (Boolean) rechargeResponse.get("successful");
|
||||||
if (successful != null && successful) {
|
if (successful != null && successful) {
|
||||||
|
hotRechargeBalanceService.evictCache();
|
||||||
transaction.setStatus(Status.SUCCESS);
|
transaction.setStatus(Status.SUCCESS);
|
||||||
transaction.setResponseCode("00");
|
transaction.setResponseCode("00");
|
||||||
transaction.setCreditRef(rechargeResponse.get("rechargeId").toString());
|
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) {
|
private LinkedHashMap<String, Object> performRecharge(String token, Transaction transaction) {
|
||||||
try {
|
try {
|
||||||
String rechargeUrl = hotApiUrl + "/products/recharge";
|
String rechargeUrl = hotApiUrl + "/products/recharge";
|
||||||
@@ -159,7 +138,12 @@ public class HotRechargeIntegrationProcessor implements TransactionProcessorInte
|
|||||||
request.put("Target", transaction.getCreditAccount());
|
request.put("Target", transaction.getCreditAccount());
|
||||||
request.put("Amount", transaction.getAmount());
|
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")) {
|
if(providerService.getProviderId(transaction.getBillClientId()).equals("41")) {
|
||||||
List<Map<String, String>> rechargeOptions = new ArrayList<>();
|
List<Map<String, String>> rechargeOptions = new ArrayList<>();
|
||||||
if (transaction.getDebitPhone() != null) {
|
if (transaction.getDebitPhone() != null) {
|
||||||
@@ -246,4 +230,4 @@ public class HotRechargeIntegrationProcessor implements TransactionProcessorInte
|
|||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -57,6 +57,14 @@ public class StewardIntegrationProcessor implements TransactionProcessorInterfac
|
|||||||
return transaction;
|
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");
|
logger.info("processing integration");
|
||||||
String token = restService.getMerchantToken();
|
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.models.Transaction;
|
||||||
import zw.qantra.tm.domain.services.SettingService;
|
import zw.qantra.tm.domain.services.SettingService;
|
||||||
import zw.qantra.tm.domain.services.TransactionService;
|
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.domain.services.processors.TransactionProcessorInterface;
|
||||||
import zw.qantra.tm.rest.RestService;
|
import zw.qantra.tm.rest.RestService;
|
||||||
|
|
||||||
@@ -29,6 +30,7 @@ public class EcocashRemotePaymentProcessor implements TransactionProcessorInterf
|
|||||||
private final RestService restService;
|
private final RestService restService;
|
||||||
private final TransactionService transactionService;
|
private final TransactionService transactionService;
|
||||||
private final SettingService settingService;
|
private final SettingService settingService;
|
||||||
|
private final VelocityPaymentService velocityPaymentService;
|
||||||
|
|
||||||
@Value("${sbz.aggregator.encryption-key}")
|
@Value("${sbz.aggregator.encryption-key}")
|
||||||
private String encryptionKey;
|
private String encryptionKey;
|
||||||
@@ -51,78 +53,10 @@ public class EcocashRemotePaymentProcessor implements TransactionProcessorInterf
|
|||||||
return transaction;
|
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 {
|
try {
|
||||||
LinkedHashMap response = restService.postAs(url + "/merchant/sdk/action",
|
transaction = velocityPaymentService.processPayment(transaction);
|
||||||
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);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
logger.error("Network error during payment processing: {}", e.getMessage(), e);
|
logger.error("Network error during payment processing: {}", e.getMessage(), e);
|
||||||
transaction.setStatus(Status.FAILED);
|
transaction.setStatus(Status.FAILED);
|
||||||
transaction.setResponseCode("99");
|
transaction.setResponseCode("99");
|
||||||
@@ -149,42 +83,15 @@ public class EcocashRemotePaymentProcessor implements TransactionProcessorInterf
|
|||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
String token = restService.getMerchantToken();
|
|
||||||
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.set("Authorization", "Bearer " + token);
|
|
||||||
headers.set("Content-Type", "application/json");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
LinkedHashMap response = restService.getAs(url + "/merchant/sdk/reference/" + transaction.getReference(),
|
transaction = velocityPaymentService.pollPaymentStatus(transaction);
|
||||||
headers, LinkedHashMap.class);
|
transaction.setStatus(Status.SUCCESS);
|
||||||
|
transaction.setResponseCode("00");
|
||||||
|
|
||||||
LinkedHashMap body = (LinkedHashMap) response.get("body");
|
transactionService.save(transaction);
|
||||||
|
|
||||||
// only update tran status if polling returns a success
|
|
||||||
if(body.get("status").equals("SUCCESS")){
|
|
||||||
transaction.setStatus(Status.SUCCESS);
|
|
||||||
transaction.setResponseCode("00");
|
|
||||||
transaction.setTargetUrl((String) body.get("targetUrl"));
|
|
||||||
|
|
||||||
LinkedHashMap transactionData = (LinkedHashMap) body.get("transaction");
|
|
||||||
transaction.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) {
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
logger.error("Network error during payment processing: {}", e.getMessage(), e);
|
logger.error("Network error during payment processing: {}", e.getMessage(), e);
|
||||||
transaction.setStatus(Status.FAILED);
|
transaction.setStatus(Status.FAILED);
|
||||||
transaction.setResponseCode("99");
|
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.models.Transaction;
|
||||||
import zw.qantra.tm.domain.services.SettingService;
|
import zw.qantra.tm.domain.services.SettingService;
|
||||||
import zw.qantra.tm.domain.services.TransactionService;
|
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.domain.services.processors.TransactionProcessorInterface;
|
||||||
import zw.qantra.tm.rest.RestService;
|
import zw.qantra.tm.rest.RestService;
|
||||||
import zw.qantra.tm.utils.Utils;
|
import zw.qantra.tm.utils.Utils;
|
||||||
@@ -33,6 +34,7 @@ public class MPGSWebPaymentProcessor implements TransactionProcessorInterface {
|
|||||||
private final RestService restService;
|
private final RestService restService;
|
||||||
private final TransactionService transactionService;
|
private final TransactionService transactionService;
|
||||||
private final SettingService settingService;
|
private final SettingService settingService;
|
||||||
|
private final VelocityPaymentService velocityPaymentService;
|
||||||
|
|
||||||
@Value("${sbz.aggregator.encryption-key}")
|
@Value("${sbz.aggregator.encryption-key}")
|
||||||
private String encryptionKey;
|
private String encryptionKey;
|
||||||
@@ -48,70 +50,25 @@ public class MPGSWebPaymentProcessor implements TransactionProcessorInterface {
|
|||||||
@Override
|
@Override
|
||||||
public Object process(Transaction transaction) throws Exception {
|
public Object process(Transaction transaction) throws Exception {
|
||||||
logger.info("processing transaction");
|
logger.info("processing transaction");
|
||||||
String token = restService.getMerchantToken();
|
|
||||||
|
|
||||||
// send total less gateway charges
|
if(settingService.getBooleanSetting("SIMULATE_MPGS_SUCCESS")) {
|
||||||
BigDecimal totalAmount = transaction.getAmount()
|
transaction.setStatus(Status.SUCCESS);
|
||||||
.add(transaction.getTax())
|
transaction.setResponseCode("00");
|
||||||
.add(transaction.getCharge())
|
transaction.setPaymentProcessorRef(RandomStringUtils.randomNumeric(10));
|
||||||
.add(transaction.getGatewayCharge());
|
transaction.setDebitRef(RandomStringUtils.randomNumeric(10));
|
||||||
|
return transaction;
|
||||||
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");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
LinkedHashMap response = restService.postAs(url + "/merchant/sdk/action",
|
// velocity expects VMC
|
||||||
sdkActionDto, headers, LinkedHashMap.class);
|
transaction = velocityPaymentService.processPayment(transaction);
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
logger.error("Network error during payment processing: {}", e.getMessage(), e);
|
logger.error("Network error during payment processing: {}", e.getMessage(), e);
|
||||||
transaction.setStatus(Status.FAILED);
|
transaction.setStatus(Status.FAILED);
|
||||||
transaction.setResponseCode("99");
|
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);
|
return transactionService.save(transaction);
|
||||||
@@ -120,56 +77,29 @@ public class MPGSWebPaymentProcessor implements TransactionProcessorInterface {
|
|||||||
@Override
|
@Override
|
||||||
public Object poll(Transaction transaction) throws Exception {
|
public Object poll(Transaction transaction) throws Exception {
|
||||||
logger.info("polling transaction");
|
logger.info("polling transaction");
|
||||||
String token = restService.getMerchantToken();
|
|
||||||
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
if(settingService.getBooleanSetting("SIMULATE_MPGS_SUCCESS")) {
|
||||||
headers.set("Authorization", "Bearer " + token);
|
transaction.setStatus(Status.SUCCESS);
|
||||||
headers.set("Content-Type", "application/json");
|
transaction.setResponseCode("00");
|
||||||
|
transaction.setTargetUrl("/home");
|
||||||
|
return transaction;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
LinkedHashMap response = restService.getAs(url + "/merchant/sdk/mpgs/poll/" + transaction.getSdkActionId(),
|
transaction = velocityPaymentService.pollPaymentStatus(transaction);
|
||||||
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);
|
|
||||||
return transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
transaction.setStatus(Status.SUCCESS);
|
transaction.setStatus(Status.SUCCESS);
|
||||||
transaction.setPaymentStatus(Status.SUCCESS);
|
|
||||||
transaction.setResponseCode("00");
|
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
transactionService.save(transaction);
|
transactionService.save(transaction);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
logger.error("Network error during payment processing: {}", e.getMessage(), e);
|
logger.error("Network error during payment processing: {}", e.getMessage(), e);
|
||||||
transaction.setStatus(Status.FAILED);
|
transaction.setStatus(Status.FAILED);
|
||||||
transaction.setResponseCode("99");
|
transaction.setResponseCode("99");
|
||||||
transaction.setErrorMessage("Network error: " + e.getMessage());
|
transaction.setSystemErrorMessage("Network error: " + e.getMessage());
|
||||||
|
transaction.setErrorMessage("Payment failed. Please try again");
|
||||||
}
|
}
|
||||||
|
|
||||||
return transaction;
|
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.service.WorkflowInstanceService;
|
||||||
import io.nflow.engine.workflow.curated.CronWorkflow;
|
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.definition.*;
|
||||||
import io.nflow.engine.workflow.instance.WorkflowInstance;
|
import io.nflow.engine.workflow.instance.WorkflowInstance;
|
||||||
import io.nflow.engine.workflow.instance.WorkflowInstanceAction;
|
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.LogUtils;
|
||||||
import zw.qantra.tm.utils.Utils;
|
import zw.qantra.tm.utils.Utils;
|
||||||
|
|
||||||
import java.sql.Timestamp;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
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 com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import io.nflow.engine.workflow.curated.State;
|
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.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import zw.qantra.tm.domain.models.Transaction;
|
import zw.qantra.tm.domain.models.Transaction;
|
||||||
import zw.qantra.tm.domain.workflows.handlers.HandlerInterface;
|
import zw.qantra.tm.domain.services.processors.workflows.handlers.HandlerInterface;
|
||||||
import zw.qantra.tm.domain.workflows.handlers.WorkflowHandlerFactory;
|
import zw.qantra.tm.domain.services.processors.workflows.handlers.WorkflowHandlerFactory;
|
||||||
import zw.qantra.tm.utils.Utils;
|
import zw.qantra.tm.utils.Utils;
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
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.NumberParseException;
|
||||||
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
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.StateExecution;
|
||||||
import io.nflow.engine.workflow.definition.WorkflowDefinition;
|
import io.nflow.engine.workflow.definition.WorkflowDefinition;
|
||||||
import zw.qantra.tm.domain.dtos.*;
|
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.Otp;
|
||||||
import zw.qantra.tm.domain.models.User;
|
import zw.qantra.tm.domain.models.User;
|
||||||
import zw.qantra.tm.domain.services.EmailService;
|
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.ConstraintViolation;
|
||||||
import jakarta.validation.Validator;
|
import jakarta.validation.Validator;
|
||||||
|
import zw.qantra.tm.domain.services.VelocityPaymentService;
|
||||||
|
import zw.qantra.tm.exceptions.AlreadyExistsException;
|
||||||
import zw.qantra.tm.rest.RestService;
|
import zw.qantra.tm.rest.RestService;
|
||||||
|
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
@@ -46,21 +48,8 @@ public class RegistrationWorkflow extends WorkflowDefinition {
|
|||||||
private RestService restService;
|
private RestService restService;
|
||||||
@Autowired
|
@Autowired
|
||||||
private EmailService emailService;
|
private EmailService emailService;
|
||||||
|
@Autowired
|
||||||
@Value("${infobip.url}")
|
private VelocityPaymentService velocityPaymentService;
|
||||||
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;
|
|
||||||
|
|
||||||
public static final String TYPE = "registrationWorkflow";
|
public static final String TYPE = "registrationWorkflow";
|
||||||
|
|
||||||
@@ -190,75 +179,17 @@ public class RegistrationWorkflow extends WorkflowDefinition {
|
|||||||
public NextAction updateErp(StateExecution execution) {
|
public NextAction updateErp(StateExecution execution) {
|
||||||
RegisterRequest registerRequest = execution.getVariable("registerRequest", RegisterRequest.class);
|
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 {
|
try {
|
||||||
ResponseEntity response = restService.postAsEntity(
|
VelocityCustomerResponseDto customer = velocityPaymentService.createCustomer(registerRequest);
|
||||||
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");
|
|
||||||
|
|
||||||
User user = userService.fetchUserByUsername(registerRequest.getUsername());
|
User user = userService.fetchUserByUsername(registerRequest.getUsername());
|
||||||
user.setErpCustomerRef(customerId);
|
user.setErpCustomerRef(customer.getId());
|
||||||
userService.getUserRepository().save(user);
|
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) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|
||||||
return NextAction.retryAfter(DateTime.now().plusMinutes(5), "Failed to update ERP: " + e.getMessage());
|
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 com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import io.nflow.engine.workflow.curated.State;
|
import io.nflow.engine.workflow.curated.State;
|
||||||
@@ -12,8 +12,8 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import zw.qantra.tm.domain.models.Transaction;
|
import zw.qantra.tm.domain.models.Transaction;
|
||||||
import zw.qantra.tm.domain.services.TransactionService;
|
import zw.qantra.tm.domain.services.TransactionService;
|
||||||
import zw.qantra.tm.domain.workflows.handlers.HandlerInterface;
|
import zw.qantra.tm.domain.services.processors.workflows.handlers.HandlerInterface;
|
||||||
import zw.qantra.tm.domain.workflows.handlers.WorkflowHandlerFactory;
|
import zw.qantra.tm.domain.services.processors.workflows.handlers.WorkflowHandlerFactory;
|
||||||
import zw.qantra.tm.utils.Utils;
|
import zw.qantra.tm.utils.Utils;
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
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.service.WorkflowInstanceService;
|
||||||
import io.nflow.engine.workflow.instance.WorkflowInstanceFactory;
|
import io.nflow.engine.workflow.instance.WorkflowInstanceFactory;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.joda.time.Instant;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.context.ApplicationListener;
|
import org.springframework.context.ApplicationListener;
|
||||||
import org.springframework.context.event.ContextRefreshedEvent;
|
import org.springframework.context.event.ContextRefreshedEvent;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import zw.qantra.tm.utils.Utils;
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@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 com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import io.nflow.engine.service.WorkflowInstanceService;
|
|
||||||
import io.nflow.engine.workflow.curated.State;
|
import io.nflow.engine.workflow.curated.State;
|
||||||
import io.nflow.engine.workflow.definition.NextAction;
|
import io.nflow.engine.workflow.definition.NextAction;
|
||||||
import io.nflow.engine.workflow.definition.StateExecution;
|
import io.nflow.engine.workflow.definition.StateExecution;
|
||||||
import io.nflow.engine.workflow.definition.WorkflowDefinition;
|
import io.nflow.engine.workflow.definition.WorkflowDefinition;
|
||||||
import io.nflow.engine.workflow.definition.WorkflowStateType;
|
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.slf4j.Logger;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import zw.qantra.tm.domain.enums.Status;
|
|
||||||
import zw.qantra.tm.domain.models.Transaction;
|
import zw.qantra.tm.domain.models.Transaction;
|
||||||
import zw.qantra.tm.domain.services.TransactionService;
|
import zw.qantra.tm.domain.services.VelocityPaymentService;
|
||||||
import zw.qantra.tm.domain.workflows.handlers.HandlerInterface;
|
import zw.qantra.tm.domain.services.processors.workflows.handlers.*;
|
||||||
import zw.qantra.tm.domain.workflows.handlers.WorkflowHandlerFactory;
|
|
||||||
import zw.qantra.tm.exceptions.ApiException;
|
|
||||||
import zw.qantra.tm.utils.Utils;
|
import zw.qantra.tm.utils.Utils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -32,13 +24,23 @@ public class TransactionWorkflow extends WorkflowDefinition {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private WorkflowHandlerFactory workflowHandlerFactory;
|
private WorkflowHandlerFactory workflowHandlerFactory;
|
||||||
@Autowired
|
@Autowired
|
||||||
private TransactionService transactionService;
|
|
||||||
@Autowired
|
|
||||||
private ObjectMapper objectMapper;
|
private ObjectMapper objectMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
private WorkflowInstanceService workflowInstanceService;
|
private VelocityPaymentService velocityPaymentService;
|
||||||
@Autowired
|
@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";
|
public static final String TYPE = "transactionWorkflow";
|
||||||
|
|
||||||
@@ -79,9 +81,7 @@ public class TransactionWorkflow extends WorkflowDefinition {
|
|||||||
byte[] json = objectMapper.writeValueAsBytes(map);
|
byte[] json = objectMapper.writeValueAsBytes(map);
|
||||||
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
||||||
|
|
||||||
HandlerInterface handlerInterface =
|
transaction = calculateChargesHandler.process(transaction);
|
||||||
(HandlerInterface) workflowHandlerFactory.getWorkflowHandler("calculateCharges");
|
|
||||||
transaction = (Transaction) handlerInterface.process(transaction);
|
|
||||||
|
|
||||||
execution.setVariable("body", Utils.toJson(transaction));
|
execution.setVariable("body", Utils.toJson(transaction));
|
||||||
|
|
||||||
@@ -98,9 +98,7 @@ public class TransactionWorkflow extends WorkflowDefinition {
|
|||||||
byte[] json = objectMapper.writeValueAsBytes(map);
|
byte[] json = objectMapper.writeValueAsBytes(map);
|
||||||
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
||||||
|
|
||||||
HandlerInterface handlerInterface =
|
transaction = (Transaction) cleanupHandler.process(transaction);
|
||||||
(HandlerInterface) workflowHandlerFactory.getWorkflowHandler("cleanup");
|
|
||||||
transaction = (Transaction) handlerInterface.process(transaction);
|
|
||||||
|
|
||||||
execution.setVariable("body", Utils.toJson(transaction));
|
execution.setVariable("body", Utils.toJson(transaction));
|
||||||
|
|
||||||
@@ -118,9 +116,7 @@ public class TransactionWorkflow extends WorkflowDefinition {
|
|||||||
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
||||||
|
|
||||||
transaction.setWorkflowId(execution.getWorkflowInstanceId());
|
transaction.setWorkflowId(execution.getWorkflowInstanceId());
|
||||||
HandlerInterface handlerInterface =
|
transaction = (Transaction) confirmHandler.process(transaction);
|
||||||
(HandlerInterface) workflowHandlerFactory.getWorkflowHandler("confirm");
|
|
||||||
transaction = (Transaction) handlerInterface.process(transaction);
|
|
||||||
execution.setVariable("body", Utils.toJson(transaction));
|
execution.setVariable("body", Utils.toJson(transaction));
|
||||||
return NextAction.moveToState(RECIPIENTS_UPDATE, "Recipients updated successfully");
|
return NextAction.moveToState(RECIPIENTS_UPDATE, "Recipients updated successfully");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -134,9 +130,7 @@ public class TransactionWorkflow extends WorkflowDefinition {
|
|||||||
byte[] json = objectMapper.writeValueAsBytes(map);
|
byte[] json = objectMapper.writeValueAsBytes(map);
|
||||||
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
||||||
|
|
||||||
HandlerInterface handlerInterface =
|
recipientsUpdateHandler.process(transaction);
|
||||||
(HandlerInterface) workflowHandlerFactory.getWorkflowHandler("recipientsUpdate");
|
|
||||||
handlerInterface.process(transaction);
|
|
||||||
|
|
||||||
return NextAction.moveToState(CONFIRM_SUCCESS, "Transaction confirmed successfully");
|
return NextAction.moveToState(CONFIRM_SUCCESS, "Transaction confirmed successfully");
|
||||||
}
|
}
|
||||||
@@ -152,9 +146,7 @@ public class TransactionWorkflow extends WorkflowDefinition {
|
|||||||
byte[] json = objectMapper.writeValueAsBytes(map);
|
byte[] json = objectMapper.writeValueAsBytes(map);
|
||||||
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
||||||
|
|
||||||
HandlerInterface handlerInterface =
|
transaction = (Transaction) gatewayPaymentHandler.process(transaction);
|
||||||
(HandlerInterface) workflowHandlerFactory.getWorkflowHandler("gatewayPayment");
|
|
||||||
transaction = (Transaction) handlerInterface.process(transaction);
|
|
||||||
|
|
||||||
execution.setVariable("body", Utils.toJson(transaction));
|
execution.setVariable("body", Utils.toJson(transaction));
|
||||||
return NextAction.moveToState(GATEWAY_PAYMENT_SUCCESS, "Gateway payment initiated");
|
return NextAction.moveToState(GATEWAY_PAYMENT_SUCCESS, "Gateway payment initiated");
|
||||||
@@ -165,25 +157,7 @@ public class TransactionWorkflow extends WorkflowDefinition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void gatewayPaymentSuccess(StateExecution execution) {
|
public void gatewayPaymentSuccess(StateExecution execution) {
|
||||||
try {
|
log.info("Gateway payment initiation complete");
|
||||||
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 {
|
public NextAction pollStatus(StateExecution execution) throws IOException {
|
||||||
@@ -193,9 +167,7 @@ public class TransactionWorkflow extends WorkflowDefinition {
|
|||||||
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
HandlerInterface handlerInterface =
|
transaction = (Transaction) pollStatusHandler.process(transaction);
|
||||||
(HandlerInterface) workflowHandlerFactory.getWorkflowHandler("pollStatus");
|
|
||||||
transaction = (Transaction) handlerInterface.process(transaction);
|
|
||||||
|
|
||||||
execution.setVariable("body", Utils.toJson(transaction));
|
execution.setVariable("body", Utils.toJson(transaction));
|
||||||
return NextAction.moveToState(POLL_STATUS_SUCCESS, "Polling complete");
|
return NextAction.moveToState(POLL_STATUS_SUCCESS, "Polling complete");
|
||||||
@@ -216,9 +188,7 @@ public class TransactionWorkflow extends WorkflowDefinition {
|
|||||||
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
HandlerInterface handlerInterface =
|
transaction = (Transaction) integrationHandler.process(transaction);
|
||||||
(HandlerInterface) workflowHandlerFactory.getWorkflowHandler("integration");
|
|
||||||
transaction = (Transaction) handlerInterface.process(transaction);
|
|
||||||
|
|
||||||
execution.setVariable("body", Utils.toJson(transaction));
|
execution.setVariable("body", Utils.toJson(transaction));
|
||||||
return NextAction.moveToState(INTEGRATION_STATUS_SUCCESS, "Integration request successful");
|
return NextAction.moveToState(INTEGRATION_STATUS_SUCCESS, "Integration request successful");
|
||||||
@@ -235,15 +205,7 @@ public class TransactionWorkflow extends WorkflowDefinition {
|
|||||||
byte[] json = objectMapper.writeValueAsBytes(map);
|
byte[] json = objectMapper.writeValueAsBytes(map);
|
||||||
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
||||||
|
|
||||||
var instance = workflowInstanceFactory.newWorkflowInstanceBuilder()
|
velocityPaymentService.updateSalesOrderWorkflow(transaction);
|
||||||
.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);
|
|
||||||
|
|
||||||
log.info("Transaction workflow complete");
|
log.info("Transaction workflow complete");
|
||||||
}catch (Exception e) {
|
}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.Arrays;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
@@ -6,8 +6,6 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.CompletableFuture;
|
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 io.nflow.engine.workflow.instance.WorkflowInstanceAction;
|
||||||
import org.springframework.stereotype.Component;
|
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 lombok.RequiredArgsConstructor;
|
||||||
import org.slf4j.Logger;
|
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.ChargeCondition;
|
||||||
import zw.qantra.tm.domain.models.Transaction;
|
import zw.qantra.tm.domain.models.Transaction;
|
||||||
import zw.qantra.tm.domain.services.ChargeService;
|
import zw.qantra.tm.domain.services.ChargeService;
|
||||||
import zw.qantra.tm.domain.services.processors.handlers.LogicHandler;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.RoundingMode;
|
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.NumberParseException;
|
||||||
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||||
import com.google.i18n.phonenumbers.Phonenumber;
|
import com.google.i18n.phonenumbers.Phonenumber;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.aspectj.apache.bcel.generic.RET;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.stereotype.Service;
|
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 lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import zw.qantra.tm.domain.enums.RequestType;
|
|
||||||
import zw.qantra.tm.domain.enums.Status;
|
import zw.qantra.tm.domain.enums.Status;
|
||||||
import zw.qantra.tm.domain.models.AdditionalData;
|
|
||||||
import zw.qantra.tm.domain.models.Transaction;
|
import zw.qantra.tm.domain.models.Transaction;
|
||||||
import zw.qantra.tm.domain.models.TransactionEvent;
|
import zw.qantra.tm.domain.models.TransactionEvent;
|
||||||
import zw.qantra.tm.domain.services.AdditionalDataService;
|
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.TransactionService;
|
||||||
import zw.qantra.tm.domain.services.factories.PaymentProcessorFactory;
|
import zw.qantra.tm.domain.services.factories.PaymentProcessorFactory;
|
||||||
import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface;
|
import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface;
|
||||||
import zw.qantra.tm.domain.services.processors.handlers.*;
|
|
||||||
import zw.qantra.tm.utils.Utils;
|
import zw.qantra.tm.utils.Utils;
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Service("confirm")
|
@Service("confirm")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class ConfirmHandler implements HandlerInterface {
|
public class ConfirmHandler implements HandlerInterface {
|
||||||
@@ -43,11 +36,11 @@ public class ConfirmHandler implements HandlerInterface {
|
|||||||
// provider.getLabel() + "_" +
|
// provider.getLabel() + "_" +
|
||||||
// provider.getIntegrationProcessorLabel();
|
// provider.getIntegrationProcessorLabel();
|
||||||
|
|
||||||
TransactionProcessorInterface transactionProcessorInterface =
|
|
||||||
(TransactionProcessorInterface) paymentProcessorFactory.getPaymentProcessor(
|
|
||||||
transaction.getConfirmationProcessorLabel());
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
TransactionProcessorInterface transactionProcessorInterface =
|
||||||
|
(TransactionProcessorInterface) paymentProcessorFactory.getPaymentProcessor(
|
||||||
|
transaction.getConfirmationProcessorLabel());
|
||||||
|
|
||||||
// MAIN TRANSACTION LOGIC!
|
// MAIN TRANSACTION LOGIC!
|
||||||
transaction = (Transaction) transactionProcessorInterface.process(transaction);
|
transaction = (Transaction) transactionProcessorInterface.process(transaction);
|
||||||
transaction.setConfirmationStatus(transaction.getStatus());
|
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 lombok.RequiredArgsConstructor;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -14,7 +14,7 @@ import zw.qantra.tm.exceptions.ApiException;
|
|||||||
|
|
||||||
@Service("gatewayPayment")
|
@Service("gatewayPayment")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class PaymentHandler implements HandlerInterface {
|
public class GatewayPaymentHandler implements HandlerInterface {
|
||||||
Logger logger = LoggerFactory.getLogger(TransactionService.class);
|
Logger logger = LoggerFactory.getLogger(TransactionService.class);
|
||||||
|
|
||||||
private final PaymentProcessorFactory paymentProcessorFactory;
|
private final PaymentProcessorFactory paymentProcessorFactory;
|
||||||
@@ -26,6 +26,10 @@ public class PaymentHandler implements HandlerInterface {
|
|||||||
@Override
|
@Override
|
||||||
public Object process(Transaction incomingTransaction) throws Exception {
|
public Object process(Transaction incomingTransaction) throws Exception {
|
||||||
Transaction transaction = incomingTransaction;
|
Transaction transaction = incomingTransaction;
|
||||||
|
|
||||||
|
if(incomingTransaction == null) {
|
||||||
|
throw new ApiException("Transaction not found");
|
||||||
|
}
|
||||||
if(incomingTransaction.getId() != null){
|
if(incomingTransaction.getId() != null){
|
||||||
transaction = transactionService.getTransactionRepository().findById(incomingTransaction.getId()).orElseThrow(() ->
|
transaction = transactionService.getTransactionRepository().findById(incomingTransaction.getId()).orElseThrow(() ->
|
||||||
new ApiException("Transaction not found for ID: " + incomingTransaction.getId()));
|
new ApiException("Transaction not found for ID: " + incomingTransaction.getId()));
|
||||||
@@ -56,6 +60,7 @@ public class PaymentHandler implements HandlerInterface {
|
|||||||
// MAIN TRANSACTION LOGIC!
|
// MAIN TRANSACTION LOGIC!
|
||||||
transactionProcessorInterface.process(transaction);
|
transactionProcessorInterface.process(transaction);
|
||||||
transaction.setPaymentStatus(transaction.getStatus());
|
transaction.setPaymentStatus(transaction.getStatus());
|
||||||
|
transaction.setPollingStatus(Status.PENDING);
|
||||||
|
|
||||||
}catch (Exception e){
|
}catch (Exception e){
|
||||||
e.printStackTrace();
|
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;
|
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 lombok.RequiredArgsConstructor;
|
||||||
import org.slf4j.Logger;
|
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 lombok.RequiredArgsConstructor;
|
||||||
import org.slf4j.Logger;
|
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 lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
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.Recipient;
|
||||||
import zw.qantra.tm.domain.models.Transaction;
|
import zw.qantra.tm.domain.models.Transaction;
|
||||||
import zw.qantra.tm.domain.services.RecipientService;
|
import zw.qantra.tm.domain.services.RecipientService;
|
||||||
import zw.qantra.tm.domain.services.processors.handlers.LogicHandler;
|
|
||||||
|
|
||||||
import java.util.List;
|
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 lombok.RequiredArgsConstructor;
|
||||||
import org.slf4j.Logger;
|
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);
|
return this.restTemplate.getForObject(path, clazz);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> ResponseEntity<?> exchangeGetNoAuth(String path, HttpEntity<?> httpEntity, Class<?> clazz, String pathVariable) {
|
public ResponseEntity exchange(String path, HttpMethod method, HttpEntity<Object> objectHttpEntity, Class jsonNodeClass) {
|
||||||
return this.restTemplate.exchange(path, HttpMethod.GET, httpEntity, clazz, pathVariable);
|
return this.restTemplate.exchange(path, method, objectHttpEntity, jsonNodeClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String prettyPrintJson(String json) {
|
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.annotation.JsonInclude;
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
import com.fasterxml.jackson.databind.util.StdDateFormat;
|
import com.fasterxml.jackson.databind.util.StdDateFormat;
|
||||||
@@ -47,6 +48,7 @@ public class Utils {
|
|||||||
public static <T> T fromJson(String json, Class<T> clazz){
|
public static <T> T fromJson(String json, Class<T> clazz){
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||||
|
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||||
mapper.setDateFormat(new StdDateFormat().withColonInTimeZone(true));
|
mapper.setDateFormat(new StdDateFormat().withColonInTimeZone(true));
|
||||||
mapper.registerModule(new JavaTimeModule());
|
mapper.registerModule(new JavaTimeModule());
|
||||||
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ server.servlet.context-path=/api
|
|||||||
spring.jpa.hibernate.ddl-auto=update
|
spring.jpa.hibernate.ddl-auto=update
|
||||||
spring.datasource.driver-class-name=org.postgresql.Driver
|
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.username=postgres
|
||||||
spring.datasource.password=zdDZMzq6F4B4L1IUl
|
spring.datasource.password=example
|
||||||
|
|
||||||
|
|
||||||
# sbz.merchant.url=http://192.168.16.29:24050/v1
|
# 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}
|
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
|
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.username=postgres
|
||||||
spring.datasource.nflow.password=zdDZMzq6F4B4L1IUl
|
spring.datasource.nflow.password=example
|
||||||
|
|
||||||
powertel.clientid=powertel_zesa
|
powertel.clientid=powertel_zesa
|
||||||
|
|
||||||
ecocash.apikey=OUzGezwTgktLg_9v0I48I6WUM4LVLGFl
|
ecocash.apikey=OUzGezwTgktLg_9v0I48I6WUM4LVLGFl
|
||||||
ecocash.url=https://developers.ecocash.co.zw/api/ecocash_pay/api/v2/payment/instant/c2b/sandbox
|
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
|
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 Configuration
|
||||||
jwt.secret=your-super-secret-jwt-key-here-make-it-very-long-and-secure-in-production
|
jwt.secret=your-super-secret-jwt-key-here-make-it-very-long-and-secure-in-production
|
||||||
jwt.expiration=86400000
|
jwt.expiration=86400000
|
||||||
@@ -57,10 +40,6 @@ spring.datasource.nflow.username=postgres
|
|||||||
spring.datasource.nflow.password=zdDZMzq6F4B4L1IUla
|
spring.datasource.nflow.password=zdDZMzq6F4B4L1IUla
|
||||||
spring.main.allow-bean-definition-overriding=true
|
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.host=mail.zimscape.com
|
||||||
spring.mail.port=587
|
spring.mail.port=587
|
||||||
spring.mail.username=notifications@qantra.co.zw
|
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.auth.url=https://ssl.hot.co.zw/api/v3/identity/login
|
||||||
hot.access.code=vusumuzi@qantra.co.zw
|
hot.access.code=vusumuzi@qantra.co.zw
|
||||||
hot.password=Stikbanch@50
|
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
|
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
|
username=postgres
|
||||||
password=zdDZMzq6F4B4L1IUl
|
password=zdDZMzq6F4B4L1IUl
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user