Skip to content

Commit

Permalink
Merge pull request #2 from 7SOATSquad30/feat/collect-payment-by-cash
Browse files Browse the repository at this point in the history
feat: add collect payment by cash resource
  • Loading branch information
MuriloKakazu authored Nov 14, 2024
2 parents a620984 + ba255b7 commit a8760ca
Show file tree
Hide file tree
Showing 11 changed files with 319 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package br.com.fiap.grupo30.fastfood.payments_api.domain.entities;

import br.com.fiap.grupo30.fastfood.payments_api.domain.enums.PaymentStatus;
import br.com.fiap.grupo30.fastfood.payments_api.infrastructure.persistence.entities.PaymentEntity;
import br.com.fiap.grupo30.fastfood.payments_api.presentation.dtos.PaymentDto;
import java.util.Objects;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter(AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Payment {
private Long id;
private PaymentStatus status;
private Double amount;

public static Payment create(Long orderId) {
return new Payment(orderId, PaymentStatus.PENDING, 0.0);
}

public void setPaymentCollected(Double paymentCollectedAmount) {
this.setStatus(PaymentStatus.COLLECTED);
this.setAmount(paymentCollectedAmount);
}

public void setPaymentRejected() {
this.setStatus(PaymentStatus.REJECTED);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Payment payment)) return false;
return Objects.equals(id, payment.id);
}

@Override
public int hashCode() {
return Objects.hash(id);
}

public PaymentDto toDto() {
return new PaymentDto(status, amount);
}

public PaymentEntity toPersistence() {
return new PaymentEntity(id, status, amount);
}

public static Payment fromPersistence(PaymentEntity payment) {
return new Payment(payment.getOrderId(), payment.getStatus(), payment.getAmount());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package br.com.fiap.grupo30.fastfood.payments_api.domain.enums;

public enum PaymentStatus {
PENDING,
REJECTED,
COLLECTED,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package br.com.fiap.grupo30.fastfood.payments_api.domain.repositories;

import br.com.fiap.grupo30.fastfood.payments_api.domain.entities.Payment;
import br.com.fiap.grupo30.fastfood.payments_api.infrastructure.persistence.repositories.PaymentJpaRepository;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

@Repository
public class PaymentRepository {

@Autowired private PaymentJpaRepository paymentJpaRepository;

@Transactional(readOnly = true)
public Optional<Payment> findByOrderId(Long orderId) {
return paymentJpaRepository
.findById(orderId)
.map(Payment::fromPersistence)
.or(() -> Optional.empty());
}

@Transactional()
public Optional<Payment> findByOrderIdForUpdate(Long orderId) {
return paymentJpaRepository
.findById(orderId)
.map(Payment::fromPersistence)
.or(() -> Optional.empty());
}

@Transactional
public Payment save(Payment payment) {
return Payment.fromPersistence(paymentJpaRepository.save(payment.toPersistence()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package br.com.fiap.grupo30.fastfood.payments_api.domain.usecases;

import br.com.fiap.grupo30.fastfood.payments_api.domain.entities.Payment;
import br.com.fiap.grupo30.fastfood.payments_api.domain.repositories.PaymentRepository;
import br.com.fiap.grupo30.fastfood.payments_api.presentation.dtos.PaymentDto;
import org.springframework.stereotype.Component;

@Component
public class CollectPaymentViaCashUseCase {

public PaymentDto execute(
PaymentRepository paymentRepository, Long orderId, Double paidAmount) {
Payment payment =
paymentRepository.findByOrderIdForUpdate(orderId).orElse(Payment.create(orderId));

payment.setPaymentCollected(paidAmount);

return paymentRepository.save(payment).toDto();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package br.com.fiap.grupo30.fastfood.payments_api.infrastructure.auth;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireAdminUserRole {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package br.com.fiap.grupo30.fastfood.payments_api.infrastructure.auth;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Base64;
import java.util.List;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Aspect
@Component
public class RequireAdminUserRoleAspect {

private final ObjectMapper objectMapper = new ObjectMapper();
private static String ADMIN_ROLE = "admin-group";
private static String BEARER_TYPE = "Bearer";
private static Integer JWT_PARTS = 3;

@Before("@annotation(RequireAdminUserRole)")
public void checkAdminRole() throws Exception {
ServletRequestAttributes attrs =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attrs.getRequest();
HttpServletResponse response = attrs.getResponse();

String authorizationHeader = request.getHeader("Authorization");

if (authorizationHeader == null || !authorizationHeader.startsWith(BEARER_TYPE)) {
response.sendError(
HttpServletResponse.SC_UNAUTHORIZED, "Missing or invalid Authorization header");
return;
}

String jwtToken = authorizationHeader.substring(7);
String[] tokenParts = jwtToken.split("\\.");
if (tokenParts.length != JWT_PARTS) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid JWT token structure");
return;
}

String payload = new String(Base64.getDecoder().decode(tokenParts[1]));
JsonNode jsonNode = objectMapper.readTree(payload);
JsonNode groupsNode = jsonNode.get("cognito:groups");
if (groupsNode != null && groupsNode.isArray()) {
List<String> groups = objectMapper.convertValue(groupsNode, List.class);
if (!groups.contains(ADMIN_ROLE)) {
response.sendError(
HttpServletResponse.SC_UNAUTHORIZED, "User does not have admin role");
return;
}
} else {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "No cognito:groups found");
return;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package br.com.fiap.grupo30.fastfood.payments_api.infrastructure.persistence.entities;

import br.com.fiap.grupo30.fastfood.payments_api.domain.enums.PaymentStatus;
import jakarta.persistence.*;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import java.time.Instant;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode
@Entity
@Table(name = "tb_payment")
public class PaymentEntity {
@Id
@Column(nullable = false)
private Long orderId;

@Enumerated(EnumType.STRING)
private PaymentStatus status;

@Column(nullable = false)
@Min(0)
@Max(10_000)
private Double amount;

@Column(columnDefinition = "TIMESTAMP WITHOUT TIME ZONE", updatable = false)
private Instant createdAt;

@Column(columnDefinition = "TIMESTAMP WITHOUT TIME ZONE")
private Instant updatedAt;

@Column(columnDefinition = "TIMESTAMP WITHOUT TIME ZONE")
private Instant deletedAt;

public PaymentEntity(Long orderId, PaymentStatus status, Double amount) {
this.orderId = orderId;
this.status = status;
this.amount = amount;
}

@PrePersist
protected void prePersist() {
createdAt = Instant.now();
}

@PreUpdate
protected void preUpdate() {
updatedAt = Instant.now();
}

@PreRemove
protected void preRemove() {
deletedAt = Instant.now();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package br.com.fiap.grupo30.fastfood.payments_api.infrastructure.persistence.repositories;

import br.com.fiap.grupo30.fastfood.payments_api.infrastructure.persistence.entities.PaymentEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface PaymentJpaRepository extends JpaRepository<PaymentEntity, Long> {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package br.com.fiap.grupo30.fastfood.payments_api.presentation.controllers;

import br.com.fiap.grupo30.fastfood.payments_api.domain.repositories.PaymentRepository;
import br.com.fiap.grupo30.fastfood.payments_api.domain.usecases.CollectPaymentViaCashUseCase;
import br.com.fiap.grupo30.fastfood.payments_api.infrastructure.auth.RequireAdminUserRole;
import br.com.fiap.grupo30.fastfood.payments_api.presentation.dtos.CollectPaymentViaCashRequestDto;
import br.com.fiap.grupo30.fastfood.payments_api.presentation.dtos.PaymentDto;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "/payments")
@Tag(name = "Payment collection controller", description = "Manage payment collection")
public class PaymentCollectionController {

@Autowired private CollectPaymentViaCashUseCase collectPaymentViaCashUseCase;
@Autowired private PaymentRepository paymentRepository;

@RequireAdminUserRole()
@PostMapping(value = "/{orderId}/collect/cash")
@Operation(summary = "Collect payment by cash")
public ResponseEntity<PaymentDto> collectPaymentByCash(
@PathVariable Long orderId, @RequestBody CollectPaymentViaCashRequestDto request) {
PaymentDto payment =
this.collectPaymentViaCashUseCase.execute(
paymentRepository, orderId, request.getAmount());
return ResponseEntity.ok().body(payment);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package br.com.fiap.grupo30.fastfood.payments_api.presentation.dtos;

import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class CollectPaymentViaCashRequestDto {
@Min(0)
@Max(10_000)
private Double amount;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package br.com.fiap.grupo30.fastfood.payments_api.presentation.dtos;

import br.com.fiap.grupo30.fastfood.payments_api.domain.enums.PaymentStatus;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class PaymentDto {
private PaymentStatus status;
private Double amount;
}

0 comments on commit a8760ca

Please sign in to comment.