improvements to workflows
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
package zw.qantra.tm.configs;
|
||||
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@Configuration
|
||||
public class DatabaseConfiguration {
|
||||
|
||||
// Application DataSource Properties
|
||||
@Bean
|
||||
@Primary
|
||||
@ConfigurationProperties(prefix = "spring.datasource")
|
||||
public DataSourceProperties applicationDataSourceProperties() {
|
||||
return new DataSourceProperties();
|
||||
}
|
||||
|
||||
// Application DataSource
|
||||
@Bean
|
||||
@Primary
|
||||
public DataSource applicationDataSource() {
|
||||
return applicationDataSourceProperties()
|
||||
.initializeDataSourceBuilder()
|
||||
.build();
|
||||
}
|
||||
}
|
||||
34
src/main/java/zw/qantra/tm/configs/NflowDatabaseConfig.java
Normal file
34
src/main/java/zw/qantra/tm/configs/NflowDatabaseConfig.java
Normal file
@@ -0,0 +1,34 @@
|
||||
package zw.qantra.tm.configs;
|
||||
|
||||
import io.nflow.engine.config.NFlow;
|
||||
import io.nflow.engine.config.db.PgDatabaseConfiguration;
|
||||
import jakarta.annotation.Nonnull;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@Configuration
|
||||
public class NflowDatabaseConfig extends PgDatabaseConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties(prefix = "spring.datasource.nflow")
|
||||
public DataSourceProperties nflowDataSourceProperties() {
|
||||
return new DataSourceProperties();
|
||||
}
|
||||
// Nflow DataSource - must override parent method and use @NFlow annotation
|
||||
// This tells nflow to use this datasource instead of creating its own
|
||||
@Override
|
||||
@Bean
|
||||
@NFlow
|
||||
@Nonnull
|
||||
public DataSource nflowDatasource(Environment env, BeanFactory appCtx) {
|
||||
return nflowDataSourceProperties()
|
||||
.initializeDataSourceBuilder()
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package zw.qantra.tm.configs;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.filter.CommonsRequestLoggingFilter;
|
||||
|
||||
@Configuration
|
||||
public class RequestLoggingFilterConfig {
|
||||
|
||||
@Bean
|
||||
public CommonsRequestLoggingFilter logFilter() {
|
||||
CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter();
|
||||
filter.setIncludeClientInfo(true);
|
||||
filter.setIncludeQueryString(true);
|
||||
filter.setIncludePayload(true);
|
||||
filter.setMaxPayloadLength(128000); // Set max payload length to log
|
||||
filter.setIncludeHeaders(false); // Can be set to true if headers are needed
|
||||
filter.setAfterMessagePrefix("REQUEST DATA: ");
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import java.security.NoSuchAlgorithmException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -16,6 +17,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import zw.qantra.tm.domain.dtos.SdkActionDto;
|
||||
import zw.qantra.tm.domain.enums.Status;
|
||||
import zw.qantra.tm.domain.models.Transaction;
|
||||
import zw.qantra.tm.domain.services.SettingService;
|
||||
import zw.qantra.tm.domain.services.TransactionService;
|
||||
import zw.qantra.tm.domain.services.processors.TransactionProcessorInterface;
|
||||
import zw.qantra.tm.rest.RestService;
|
||||
@@ -26,6 +28,7 @@ public class EcocashRemotePaymentProcessor implements TransactionProcessorInterf
|
||||
private final Logger logger = LoggerFactory.getLogger(EcocashRemotePaymentProcessor.class);
|
||||
private final RestService restService;
|
||||
private final TransactionService transactionService;
|
||||
private final SettingService settingService;
|
||||
|
||||
@Value("${sbz.aggregator.encryption-key}")
|
||||
private String encryptionKey;
|
||||
@@ -79,7 +82,16 @@ public class EcocashRemotePaymentProcessor implements TransactionProcessorInterf
|
||||
|
||||
LinkedHashMap body = (LinkedHashMap) response.get("body");
|
||||
transaction.setSdkActionId((String) body.get("uid"));
|
||||
|
||||
|
||||
// todo: REMOVE THIS
|
||||
if(settingService.getBooleanSetting("SIMULATE_ECOCASH_SUCCESS")){
|
||||
body.put("status", "SUCCESS");
|
||||
LinkedHashMap hashMap = new LinkedHashMap();
|
||||
hashMap.put("debitReference", RandomStringUtils.randomNumeric(10));
|
||||
body.put("transaction", hashMap);
|
||||
body.put("targetUrl", "/home");
|
||||
}
|
||||
|
||||
if(body.get("status").equals("SUCCESS")){
|
||||
transaction.setStatus(Status.SUCCESS);
|
||||
transaction.setResponseCode("00");
|
||||
@@ -90,8 +102,12 @@ public class EcocashRemotePaymentProcessor implements TransactionProcessorInterf
|
||||
}else{
|
||||
transaction.setStatus(Status.FAILED);
|
||||
transaction.setResponseCode("01");
|
||||
LinkedHashMap transactionData = (LinkedHashMap) body.get("transaction");
|
||||
transaction.setErrorMessage((String) transactionData.get("errorMessage"));
|
||||
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) {
|
||||
logger.error("Network error during payment processing: {}", e.getMessage(), e);
|
||||
|
||||
@@ -9,6 +9,7 @@ import io.nflow.engine.workflow.definition.WorkflowStateType;
|
||||
import io.nflow.engine.workflow.instance.WorkflowInstance;
|
||||
import io.nflow.engine.workflow.instance.WorkflowInstanceAction;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.LocalDateTime;
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -19,9 +20,11 @@ import zw.qantra.tm.domain.services.TransactionService;
|
||||
import zw.qantra.tm.utils.LogUtils;
|
||||
import zw.qantra.tm.utils.Utils;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@Component
|
||||
@@ -51,7 +54,10 @@ public class PollCronWorkflow extends WorkflowDefinition {
|
||||
if (!transactions.isEmpty())
|
||||
log.info("Transaction count {}", transactions.size());
|
||||
|
||||
Integer attempts = 0;
|
||||
Integer success = 0;
|
||||
for (Transaction transaction: transactions) {
|
||||
attempts++;
|
||||
try {
|
||||
// polling logic
|
||||
WorkflowInstance update = new WorkflowInstance.Builder()
|
||||
@@ -105,6 +111,8 @@ public class PollCronWorkflow extends WorkflowDefinition {
|
||||
15000, 50);
|
||||
Object response1 = future1.get();
|
||||
LogUtils.logPrettyJson(log, "integration result", response1);
|
||||
|
||||
success++;
|
||||
}else {
|
||||
transaction.setPollingStatus(Status.FAILED);
|
||||
transaction.setIntegrationStatus(Status.FAILED);
|
||||
@@ -116,8 +124,13 @@ public class PollCronWorkflow extends WorkflowDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
return NextAction.moveToStateAfter(
|
||||
PROCESS, DateTime.now().plusMinutes(2),
|
||||
log.info("Polling complete. {} transactions processed, {} successful", attempts, success);
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("attempts", attempts.toString());
|
||||
map.put("success", success.toString());
|
||||
execution.setVariable(LocalDateTime.now().toString(), map);
|
||||
|
||||
return NextAction.retryAfter(DateTime.now().plusMinutes(2),
|
||||
String.format("Cron complete on %s transactions", transactions.size()));
|
||||
}
|
||||
|
||||
|
||||
@@ -64,15 +64,15 @@ public class TransactionWorkflow extends WorkflowDefinition {
|
||||
permit(RECIPIENTS_UPDATE, CONFIRM_SUCCESS);
|
||||
permit(CONFIRM_SUCCESS, GATEWAY_PAYMENT);
|
||||
permit(GATEWAY_PAYMENT, GATEWAY_PAYMENT_SUCCESS, TRAN_FAILED);
|
||||
permit(GATEWAY_PAYMENT_SUCCESS, INTEGRATION);
|
||||
permit(INTEGRATION, PURCHASE_INVOICE, TRAN_FAILED);
|
||||
permit(PURCHASE_INVOICE, PURCHASE_INVOICE_PAYMENT);
|
||||
permit(PURCHASE_INVOICE_PAYMENT, PURCHASE_INVOICE_PAYMENT_SUCCESS, DONE);
|
||||
permit(PURCHASE_INVOICE_PAYMENT_SUCCESS, POLL_STATUS);
|
||||
permit(GATEWAY_PAYMENT_SUCCESS, POLL_STATUS);
|
||||
permit(POLL_STATUS, SALES_INVOICE, TRAN_FAILED);
|
||||
permit(SALES_INVOICE, JOURNAL_ENTRY);
|
||||
permit(JOURNAL_ENTRY, SALES_PAYMENT);
|
||||
permit(SALES_PAYMENT, SALES_PAYMENT_SUCCESS, DONE);
|
||||
permit(SALES_PAYMENT, SALES_PAYMENT_SUCCESS);
|
||||
permit(SALES_PAYMENT_SUCCESS, INTEGRATION);
|
||||
permit(INTEGRATION, PURCHASE_INVOICE, TRAN_FAILED);
|
||||
permit(PURCHASE_INVOICE, PURCHASE_INVOICE_PAYMENT);
|
||||
permit(PURCHASE_INVOICE_PAYMENT, PURCHASE_INVOICE_PAYMENT_SUCCESS, DONE);
|
||||
}
|
||||
|
||||
public NextAction calculateCharges(StateExecution execution) {
|
||||
@@ -170,76 +170,6 @@ public class TransactionWorkflow extends WorkflowDefinition {
|
||||
log.info("Gateway payment initiation complete");
|
||||
}
|
||||
|
||||
public NextAction integration(StateExecution execution) throws IOException {
|
||||
LinkedHashMap map = execution.getVariable("body", LinkedHashMap.class);
|
||||
byte[] json = objectMapper.writeValueAsBytes(map);
|
||||
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
||||
|
||||
try {
|
||||
HandlerInterface handlerInterface =
|
||||
(HandlerInterface) workflowHandlerFactory.getWorkflowHandler("integration");
|
||||
transaction = (Transaction) handlerInterface.process(transaction);
|
||||
|
||||
execution.setVariable("body", Utils.toJson(transaction));
|
||||
return NextAction.moveToState(PURCHASE_INVOICE, "Integration request successful");
|
||||
} catch (Exception e) {
|
||||
log.info(e.getMessage());
|
||||
execution.setVariable("body", Utils.toJson(transaction));
|
||||
return NextAction.moveToState(TRAN_FAILED, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public NextAction purchaseInvoice(StateExecution execution) throws Exception {
|
||||
try {
|
||||
LinkedHashMap map = execution.getVariable("body", LinkedHashMap.class);
|
||||
byte[] json = objectMapper.writeValueAsBytes(map);
|
||||
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
||||
|
||||
HandlerInterface handlerInterface =
|
||||
(HandlerInterface) workflowHandlerFactory.getWorkflowHandler("purchaseInvoice");
|
||||
transaction = (Transaction) handlerInterface.process(transaction);
|
||||
|
||||
execution.setVariable("body", Utils.toJson(transaction));
|
||||
return NextAction.moveToState(PURCHASE_INVOICE_PAYMENT,
|
||||
"Purchase invoice successful - id: " + transaction.getErpPurchaseRef());
|
||||
|
||||
}catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return NextAction.retryAfter(new DateTime().plusMinutes(1), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public NextAction purchaseInvoicePayment(StateExecution execution) {
|
||||
try {
|
||||
LinkedHashMap map = execution.getVariable("body", LinkedHashMap.class);
|
||||
byte[] json = objectMapper.writeValueAsBytes(map);
|
||||
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
||||
|
||||
HandlerInterface handlerInterface =
|
||||
(HandlerInterface) workflowHandlerFactory.getWorkflowHandler("purchaseInvoicePayment");
|
||||
transaction = (Transaction) handlerInterface.process(transaction);
|
||||
|
||||
execution.setVariable("body", Utils.toJson(transaction));
|
||||
|
||||
// it's possible to have a flow that ends up event though
|
||||
// polling has already happened
|
||||
if(transaction.getPollingStatus() == Status.SUCCESS){
|
||||
return NextAction.moveToState(DONE,
|
||||
"Purchase invoice payment generated - id: " + transaction.getErpPurchasePaymentRef() + ". " +
|
||||
"Not proceeding because polling already complete");
|
||||
}
|
||||
return NextAction.moveToState(PURCHASE_INVOICE_PAYMENT_SUCCESS,
|
||||
"Purchase invoice payment generated - id: " + transaction.getErpPurchasePaymentRef());
|
||||
}catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return NextAction.retryAfter(new DateTime().plusMinutes(1), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void purchaseInvoicePaymentSuccess(StateExecution execution) {
|
||||
log.info("Purchase invoice payment complete");
|
||||
}
|
||||
|
||||
public NextAction pollStatus(StateExecution execution) throws IOException {
|
||||
// cannot cast direct to Transaction object because of some jackson mapper hullabaloo :(
|
||||
LinkedHashMap map = execution.getVariable("body", LinkedHashMap.class);
|
||||
@@ -329,4 +259,74 @@ public class TransactionWorkflow extends WorkflowDefinition {
|
||||
public void salesPaymentSuccess(StateExecution execution) {
|
||||
log.info("Sales payment completed successfully");
|
||||
}
|
||||
|
||||
public NextAction integration(StateExecution execution) throws IOException {
|
||||
LinkedHashMap map = execution.getVariable("body", LinkedHashMap.class);
|
||||
byte[] json = objectMapper.writeValueAsBytes(map);
|
||||
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
||||
|
||||
try {
|
||||
HandlerInterface handlerInterface =
|
||||
(HandlerInterface) workflowHandlerFactory.getWorkflowHandler("integration");
|
||||
transaction = (Transaction) handlerInterface.process(transaction);
|
||||
|
||||
execution.setVariable("body", Utils.toJson(transaction));
|
||||
return NextAction.moveToState(PURCHASE_INVOICE, "Integration request successful");
|
||||
} catch (Exception e) {
|
||||
log.info(e.getMessage());
|
||||
execution.setVariable("body", Utils.toJson(transaction));
|
||||
return NextAction.moveToState(TRAN_FAILED, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public NextAction purchaseInvoice(StateExecution execution) throws Exception {
|
||||
try {
|
||||
LinkedHashMap map = execution.getVariable("body", LinkedHashMap.class);
|
||||
byte[] json = objectMapper.writeValueAsBytes(map);
|
||||
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
||||
|
||||
HandlerInterface handlerInterface =
|
||||
(HandlerInterface) workflowHandlerFactory.getWorkflowHandler("purchaseInvoice");
|
||||
transaction = (Transaction) handlerInterface.process(transaction);
|
||||
|
||||
execution.setVariable("body", Utils.toJson(transaction));
|
||||
return NextAction.moveToState(PURCHASE_INVOICE_PAYMENT,
|
||||
"Purchase invoice successful - id: " + transaction.getErpPurchaseRef());
|
||||
|
||||
}catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return NextAction.retryAfter(new DateTime().plusMinutes(1), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public NextAction purchaseInvoicePayment(StateExecution execution) {
|
||||
try {
|
||||
LinkedHashMap map = execution.getVariable("body", LinkedHashMap.class);
|
||||
byte[] json = objectMapper.writeValueAsBytes(map);
|
||||
Transaction transaction = objectMapper.readValue(json, Transaction.class);
|
||||
|
||||
HandlerInterface handlerInterface =
|
||||
(HandlerInterface) workflowHandlerFactory.getWorkflowHandler("purchaseInvoicePayment");
|
||||
transaction = (Transaction) handlerInterface.process(transaction);
|
||||
|
||||
execution.setVariable("body", Utils.toJson(transaction));
|
||||
|
||||
// it's possible to have a flow that ends up event though
|
||||
// polling has already happened
|
||||
if(transaction.getPollingStatus() == Status.SUCCESS){
|
||||
return NextAction.moveToState(DONE,
|
||||
"Purchase invoice payment generated - id: " + transaction.getErpPurchasePaymentRef() + ". " +
|
||||
"Not proceeding because polling already complete");
|
||||
}
|
||||
return NextAction.moveToState(PURCHASE_INVOICE_PAYMENT_SUCCESS,
|
||||
"Purchase invoice payment generated - id: " + transaction.getErpPurchasePaymentRef());
|
||||
}catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return NextAction.retryAfter(new DateTime().plusMinutes(1), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void purchaseInvoicePaymentSuccess(StateExecution execution) {
|
||||
log.info("Purchase invoice payment complete");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user