2
\$\begingroup\$

This is order, orderitems and payment linked by foreign key.

Controller:

    @PatchMapping("/orders/buy/{salesJourneyId}")
        @Operation(summary = "To create either card or cash order in cash-hub for BUY flow", description = "To create either card or cash order in cash-hub for BUY flow")
        public ResponseEntity newOrderBuyFlow(
                @RequestHeader("retailerId") String retailerId,
                @RequestHeader("source") String source,
                @RequestHeader("trackingId") String trackingId,
                @PathVariable("salesJourneyId") String salesJourneyId,
                @RequestBody @Valid SalesOrderSubmissionCardRequest orderRequest
        ) {
            validator.validateDealerLoginHeaders();
            ResponseEnvelop<Map<String, Object>> response = salesOrderService.processOrder(salesJourneyId, orderRequest,
                    source, trackingId, retailerId);
            return ResponseEntity.ok(response);
        }

Service:

public CashHubResponseEnvelop<Map<String, Object>> processOrder(String salesJourneyId, SalesOrderSubmissionCardRequest orderRequest,
                                                                    String source, String trackingId, String retailerId) {
        logger.info("Processing order for Sales Journey ID: {}", salesJourneyId);
        validateRequest(orderRequest);

        Orders order = Orders.builder()
                .orderId(Long.valueOf(orderRequest.getOrderSummary().getId()))
                .orderType(orderRequest.getOrderSummary().getType())
                .description(orderRequest.getOrderSummary().getDescription())
                .amount(BigDecimal.valueOf(orderRequest.getOrderSummary().getTotal()))
                .phone(Long.valueOf(orderRequest.getCustomer().getPhone()))
                .zip(orderRequest.getCustomer().getZip())
                .customerRelationshipId(orderRequest.getCustomer().getCustomerRelationshipId())
                .salesJourneyId(salesJourneyId)
                .build();
        salesJourneyDao.saveOrder(order, source, trackingId, retailerId);

        for (PricePlan pricePlan : orderRequest.getPricePlans()) {
            for (Plan plan : pricePlan.getPlans()) {
                OrderItems orderItem = OrderItems.builder()
                        .ordersObjId(order.getOrderId())
                        .lob(pricePlan.getLob())
                        .planId(plan.getId())
                        .unitPrice(BigDecimal.valueOf(plan.getUnitPrice()))
                        .quantity(plan.getQuantity())
                        .build();
                salesJourneyDao.saveOrderItem(orderItem, source, trackingId);
            }
        }

        String paymentStatus = orderRequest.getBilling().getPaymentMethod().equals("CARD") ? Constants.COMPLETE : Constants.PENDING_POS;
        Payments payment = Payments.builder()
                .orderItemsObjId(order.getOrderId())    // Foreign key to OrderItems
                .cashhubRef(orderRequest.getBilling().getCashhubRef())
                .paymentMethod(orderRequest.getBilling().getPaymentMethod())
                .paymentType(orderRequest.getBilling().getPaymentType())
                .paymentStatus(paymentStatus)
                .build();
        salesJourneyDao.savePayment(payment, source, trackingId);

        Map<String, Object> responseData = new HashMap<>();
        responseData.put("hubConfNumber", orderRequest.getBilling().getCashhubRef());

        logger.info("Response prepared successfully for Order ID: {}", order.getOrderId());

        return ResponseEnvelop.<Map<String, Object>>builder()
                .data(responseData)
                .message("Order created successfully")
                .error(null)
                .build();
    }

private void validateRequest(SalesOrderSubmissionCardRequest orderRequest) {
        if (orderRequest.getCustomer() == null || orderRequest.getPricePlans() == null || orderRequest.getOrderSummary() == null || orderRequest.getBilling() == null) {
            throw new InvalidRequestException("Missing required fields");
        }
    }

Repository


@Repository
public class SalesJourneyDaoImpl implements SalesJourneyDao {
    private final JdbcTemplate jdbcTemplate;

    @Value("${insert.salesJourney}")
    private String insertSalesJourneyQuery;

    @Value("${select.byId}")
    private String selectByIdQuery;

    @Value("${select.bySalesJourneyId}")
    private String selectBySalesJourneyIdQuery;

    @Value("${exists.bySalesJourneyId}")
    private String existsBySalesJourneyIdQuery;

    @Value("${insert.order}")
    private String insertOrderQuery;

    @Value("${insert.orderItem}")
    private String insertOrderItemQuery;

    @Value("${insert.payment}")
    private String insertPaymentQuery;

    @Autowired
    public SalesJourneyDaoImpl(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public void save(Orders salesJourney) {
        jdbcTemplate.update(
                insertSalesJourneyQuery,
                salesJourney.getSalesJourneyId(),
                salesJourney.getRetailerId(),
                salesJourney.getSource(),
                salesJourney.getTrackingId(),
                Timestamp.valueOf(LocalDateTime.now(ZoneId.of("America/New_York")))
        );
    }

    @Override
    public Optional<Orders> findById(Long id) {
        try {
            Orders salesJourney = jdbcTemplate.queryForObject(
                    selectByIdQuery,
                    salesJourneyRowMapper(),
                    id
            );
            return Optional.ofNullable(salesJourney);
        } catch (EmptyResultDataAccessException e) {
            return Optional.empty();
        }
    }

    @Override
    public Optional<Orders> findBySalesJourneyId(String salesJourneyId) {
        try {
            Orders salesJourney = jdbcTemplate.queryForObject(
                    selectBySalesJourneyIdQuery,
                    salesJourneyRowMapper(),
                    salesJourneyId
            );
            return Optional.ofNullable(salesJourney);
        } catch (EmptyResultDataAccessException e) {
            return Optional.empty();
        }
    }

    @Override
    public boolean existsBySalesJourneyId(String salesJourneyId) {
        Integer count = jdbcTemplate.queryForObject(
                existsBySalesJourneyIdQuery,
                Integer.class,
                salesJourneyId
        );
        return count != null && count > 0;
    }

    private RowMapper<Orders> salesJourneyRowMapper() {
        return new RowMapper<Orders>() {
            @Override
            public Orders mapRow(ResultSet rs, int rowNum) throws SQLException {
                Orders salesJourney = new Orders();
                salesJourney.setId(rs.getLong("id"));
                salesJourney.setSalesJourneyId(rs.getString("sales_journey_id"));
                salesJourney.setRetailerId(rs.getString("retailer_id"));
                salesJourney.setSource(rs.getString("source"));
                salesJourney.setTrackingId(rs.getString("tracking_id"));
                salesJourney.setCreateDate(rs.getTimestamp("created_at").toLocalDateTime());
                return salesJourney;
            }
        };
    }

    public void saveOrder(Orders order, String source, String trackingId, String retailerId) {
        jdbcTemplate.update(insertOrderQuery, order.getOrderId(), order.getOrderType(), order.getDescription(), order.getAmount(),
                order.getPhone(), order.getZip(), order.getCustomerRelationshipId(), order.getSalesJourneyId(), source, trackingId, retailerId,
                Timestamp.valueOf(LocalDateTime.now(ZoneId.of("America/New_York"))));
    }

    public void saveOrderItem(OrderItems orderItem, String source, String trackingId) {
        jdbcTemplate.update(insertOrderItemQuery, orderItem.getOrdersObjId(), orderItem.getLob(), orderItem.getPlanId(), orderItem.getUnitPrice(),
                orderItem.getQuantity(), source, trackingId, Timestamp.valueOf(LocalDateTime.now(ZoneId.of("America/New_York"))));
    }

    public void savePayment(Payments payment, String source, String trackingId) {
        jdbcTemplate.update(insertPaymentQuery, payment.getOrderItemsObjId(), payment.getPaymentMethod(), payment.getPaymentStatus(),
                payment.getCashhubRef(), source, trackingId, Timestamp.valueOf(LocalDateTime.now(ZoneId.of("America/New_York"))));
    }
}

controller advice

@RestControllerAdvice
public class GlobalControllerAdvice extends ResponseEntityExceptionHandler {
    private static final Logger LOGGER = LoggerFactory.getLogger(GlobalControllerAdvice.class);

    @Autowired
    @Qualifier("MessageSource")
    private ResourceBundleMessageSource messageSource;

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException exception,
                                                                  HttpHeaders httpHeaders, HttpStatus httpStatus, WebRequest webRequest) {
        // Map validation errors to the Error class
        List<ErrorCashhub> errors = exception.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(fieldError -> ErrorCashhub.builder()
                        .code("ERR_VALIDATION")
                        .message(fieldError.getDefaultMessage())
                        .details("Field: " + fieldError.getField())
                        .build())
                .collect(Collectors.toList());

        return new ResponseEntity<>(errors, httpHeaders, HttpStatus.BAD_REQUEST);
    }


    @ExceptionHandler(CashHubException.class)
    public ResponseEntity<CashHubResponseEnvelop<Void>> handleDealerPortalException(CashHubException ex) {
        return ResponseEntity
                .status(ex.getHttpStatus())
                .body(new CashHubResponseEnvelop<>(ex.getApiError()));
    }

    @ExceptionHandler(MissingRequestHeaderException.class)
    public ResponseEntity<CashHubResponseEnvelop<ErrorCashhub>> handleMissingRequestHeaderException(MissingRequestHeaderException ex) {
        ErrorCashhub error = ErrorCashhub.builder()
                .code(ENUMConfig.ERR_VALIDATION.toString())
                .message(ENUMConfig.ERR_VALIDATION.getValue())
                .details("Required header is missing: " + ex.getHeaderName())
                .build();

        CashHubResponseEnvelop<ErrorCashhub> response = new CashHubResponseEnvelop<>(error);

        return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(InvalidHeaderException.class)
    public ResponseEntity<CashHubResponseEnvelop<ErrorCashhub>> handleInvalidHeaderException(InvalidHeaderException ex, HttpServletRequest request) {
        ErrorCashhub error = ErrorCashhub.builder()
                .code(ENUMConfig.ERR_VALIDATION.toString())
                .message(ENUMConfig.ERR_VALIDATION.getValue())
                .details(ex.getMessage())
                .build();

        CashHubResponseEnvelop<ErrorCashhub> response = new CashHubResponseEnvelop<>(error);
        return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleException(Exception ex) {
        return new ResponseEntity<>("Internal Server Error: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(InvalidRequestException.class)
    public ResponseEntity<ErrorCashhub> handleInvalidRequestException(InvalidRequestException ex) {
        ErrorCashhub errorResponse = ErrorCashhub.builder()
                .code(ENUMConfig.ERR_VALIDATION.toString())
                .message(ENUMConfig.ERR_VALIDATION.getValue())
                .details("Invalid request parameters: " + ex.getMessage())
                .build();
        return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(DataAccessException.class)
    public ResponseEntity<ErrorCashhub> handleDatabaseException(DataAccessException ex) {

        ErrorCashhub errorResponse = ErrorCashhub.builder()
                .code(ENUMConfig.DATABASE_EXCEPTION_109.toString())
                .message(ENUMConfig.DATABASE_EXCEPTION_109.getValue())
                .details("Error while accessing the database")
                .build();

        return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

Validator:

@Component
public class Validator {

    private static final String SPLIT_REGEX = "\\s*,\\s*";
    private final Logger log = LoggerFactory.getLogger(Validator.class);
    @Autowired
    private WebRequest webRequest;

    @Value("${eligible.sources}")
    private String eligibleSourcesStr;

    @Value("${eligible.dealerLogin.sources}")
    private String eligibleDealerLoginSourcesStr;

    private List<String> eligibleSourceList;

    private List<String> eligibleDealerLoginSourceList;

    Validator() {
        log.info("Validator initialized!");
    }

    @PostConstruct
    private void initFields() {
        if (!empty(eligibleSourcesStr)) {
            eligibleSourceList = Arrays.asList(eligibleSourcesStr.toUpperCase().split(SPLIT_REGEX));
        }
        log.info("eligibleSources: {} ", eligibleSourceList);
    }

    private boolean empty(final String s) {
        return s == null || s.trim().isEmpty();
    }

    public void validateDealerLoginHeaders() {
        final String trackingId = webRequest.getHeader(Constants.HEADER_TRACKING_ID);
        final String source = webRequest.getHeader(Constants.HEADER_SOURCE);
        final String retailerId = webRequest.getHeader(Constants.HEADER_RETAILER_ID);

        if (!isInputValid(trackingId)) {
            log.error(Constants.VALIDATION_ERROR_PRECONDITION_FAILED, ENUMConfig.DEALERPORTAL_SERVICE_COMMON_002);
            throw new InvalidHeaderException("Header '" + trackingId + "' must not be null or empty.");
        } else if (!isInputValid(source)) {
            log.error(Constants.VALIDATION_ERROR_PRECONDITION_FAILED, ENUMConfig.DEALERPORTAL_SERVICE_COMMON_001);
            throw new InvalidHeaderException("Header '" + source + "' must not be null or empty.");
        } else if (!isInputValid(retailerId)) {
            log.error(Constants.VALIDATION_ERROR_PRECONDITION_FAILED, ENUMConfig.DEALERPORTAL_SERVICE_COMMON_004);
            throw new InvalidHeaderException("Header '" + retailerId + "' must not be null or empty.");
        }

        if (!isDealerLoginSourceAuthorized(source)) {
            log.error(Constants.VALIDATION_ERROR_FORBIDDEN, ENUMConfig.DEALERPORTAL_SERVICE_COMMON_003);
            throw new InvalidHeaderException("Header '" + source + "' unauthorized.");
        }
    }

    private boolean isInputValid(String input) {
        return input != null && !input.trim().equals("");
    }

    private boolean isDealerLoginSourceAuthorized(String source) {
        return CollectionUtils.isEmpty(eligibleSourceList) ||
                eligibleSourceList.contains(source.trim().toUpperCase());
    }
}

Some things jumping out to me are logging along with transaction handling. There are one to many and many to one relation between table, can I leverage something else available with jdbctemplate?

\$\endgroup\$
0

1 Answer 1

3
\$\begingroup\$

you already invoke spring validation on SalesOrderSubmissionCardRequest why do you need validateRequest() method? can be replaced with @NotNull(message = "Missing required fields") annotation on fields in the request

processOrder() should be broken down to several methods like saveOrder(), savePricePlans() etc in order to adhere to the single responsibility principle

since you already make use of apache common library, you can implement isInputValid(String input) as StringUtils.isNotBlank(input)

\$\endgroup\$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.