diff --git a/.env.example b/.env.example index b9c19ef2..157a370e 100644 --- a/.env.example +++ b/.env.example @@ -4,4 +4,8 @@ TWILIO_VERIFY_SERVICE_SID=valor-twilio #Constantes para api de NSFW X_RAPIDAPI_KEY=chave -X_RAPIDAPI_HOST=nsfw-image-classification1.p.rapidapi.com \ No newline at end of file +X_RAPIDAPI_HOST=nsfw-image-classification1.p.rapidapi.com + +#chaves API Mercado Pago +MP_PUBLIC_KEY=chave +MP_ACCESS_TOKEN=chave \ No newline at end of file diff --git a/src/main/java/br/edu/utfpr/servicebook/controller/ClientController.java b/src/main/java/br/edu/utfpr/servicebook/controller/ClientController.java index 441c1085..066ae2ef 100644 --- a/src/main/java/br/edu/utfpr/servicebook/controller/ClientController.java +++ b/src/main/java/br/edu/utfpr/servicebook/controller/ClientController.java @@ -22,6 +22,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; +import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; @@ -34,6 +35,7 @@ import java.io.IOException; import java.util.Date; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -104,6 +106,12 @@ public class ClientController { @Autowired private ProfessionalMapper professionalMapper; + @Autowired + private PaymentService paymentService; + + @Autowired + private PaymentMapper paymentMapper; + /** * Método que apresenta a tela inicial do cliente @@ -786,4 +794,52 @@ public String markAsHided(@PathVariable Long jobId, @PathVariable Long individua return "redirect:/minha-conta/cliente/meus-pedidos/"+jobId; } + + /** + * Responsável por realizar o pagamento via API Mercado Pago. + */ + @PostMapping("/pagamento") + @RolesAllowed({RoleType.USER}) + public ResponseEntity createPayment(@RequestBody Map paymentData){ + ResponseDTO response = new ResponseDTO(); + + try { + if (paymentData == null || paymentData.isEmpty()) { + response.setMessage("Erro ao enviar dados. Verifique os campos e tente novamente!"); + return ResponseEntity.status(400).body(response); + } + + Optional oUser = (userService.findByEmail(authentication.getEmail())); + + if (!oUser.isPresent()) { + response.setMessage("Usuário não autenticado! Por favor, realize sua autenticação no sistema."); + return ResponseEntity.status(401).body(response); + } + + ResponseEntity paymentResponse = paymentService.pay(paymentData); + + if(!paymentResponse.getStatusCode().is2xxSuccessful()){ + response.setMessage("Erro ao processar pagamento. Tente novamente"); + return ResponseEntity.status(paymentResponse.getStatusCode()).body(response); + } + + Object responseBody = paymentResponse.getBody(); + + Map responseMap = (Map) responseBody; + Integer paymentId = (Integer) responseMap.get("id"); + String status = (String) responseMap.get("status"); + + PaymentDTO paymentDTO = new PaymentDTO(paymentId, status); + Payment payment = paymentMapper.toEntity(paymentDTO); + + paymentService.save(payment); + + response.setData(paymentResponse.getBody()); + return ResponseEntity.ok(response); + + } catch (Exception e) { + response.setMessage("Erro ao fazer pagamento. Por favor, tente novamente."); + return ResponseEntity.status(400).body(response); + } + } } \ No newline at end of file diff --git a/src/main/java/br/edu/utfpr/servicebook/model/dto/PaymentDTO.java b/src/main/java/br/edu/utfpr/servicebook/model/dto/PaymentDTO.java new file mode 100644 index 00000000..a1db2629 --- /dev/null +++ b/src/main/java/br/edu/utfpr/servicebook/model/dto/PaymentDTO.java @@ -0,0 +1,20 @@ +package br.edu.utfpr.servicebook.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotEmpty; + +@Data +@NoArgsConstructor +@AllArgsConstructor + +public class PaymentDTO { + + @NotEmpty(message = "O identificador do pagamento é obrigatório") + private Integer paymentId; + + @NotEmpty(message = "O status do pagamento é obrigatório") + private String status; +} diff --git a/src/main/java/br/edu/utfpr/servicebook/model/entity/Payment.java b/src/main/java/br/edu/utfpr/servicebook/model/entity/Payment.java new file mode 100644 index 00000000..00b1a000 --- /dev/null +++ b/src/main/java/br/edu/utfpr/servicebook/model/entity/Payment.java @@ -0,0 +1,24 @@ +package br.edu.utfpr.servicebook.model.entity; + +import lombok.*; + +import javax.persistence.*; + +@Data +@NoArgsConstructor +@RequiredArgsConstructor +@Table(name = "payments") +@Entity +public class Payment { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @NonNull + @Column(unique = true) + private Integer paymentId; + + @NonNull + @Column() + private String status; +} \ No newline at end of file diff --git a/src/main/java/br/edu/utfpr/servicebook/model/mapper/PaymentMapper.java b/src/main/java/br/edu/utfpr/servicebook/model/mapper/PaymentMapper.java new file mode 100644 index 00000000..a961716c --- /dev/null +++ b/src/main/java/br/edu/utfpr/servicebook/model/mapper/PaymentMapper.java @@ -0,0 +1,27 @@ +package br.edu.utfpr.servicebook.model.mapper; + + +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import br.edu.utfpr.servicebook.model.dto.PaymentDTO; +import br.edu.utfpr.servicebook.model.entity.Payment; + +@Component +public class PaymentMapper { + + @Autowired + private ModelMapper mapper; + + public PaymentDTO toDto(Payment entity) { + PaymentDTO dto = mapper.map(entity, PaymentDTO.class); + return dto; + } + + public Payment toEntity(PaymentDTO dto) { + Payment entity = mapper.map(dto, Payment.class); + return entity; + } + +} diff --git a/src/main/java/br/edu/utfpr/servicebook/model/repository/PaymentRepository.java b/src/main/java/br/edu/utfpr/servicebook/model/repository/PaymentRepository.java new file mode 100644 index 00000000..ebcec317 --- /dev/null +++ b/src/main/java/br/edu/utfpr/servicebook/model/repository/PaymentRepository.java @@ -0,0 +1,13 @@ +package br.edu.utfpr.servicebook.model.repository; + +import br.edu.utfpr.servicebook.model.entity.Payment; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface PaymentRepository extends JpaRepository{ + + +} + diff --git a/src/main/java/br/edu/utfpr/servicebook/service/PaymentService.java b/src/main/java/br/edu/utfpr/servicebook/service/PaymentService.java new file mode 100644 index 00000000..688179a6 --- /dev/null +++ b/src/main/java/br/edu/utfpr/servicebook/service/PaymentService.java @@ -0,0 +1,50 @@ +package br.edu.utfpr.servicebook.service; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import br.edu.utfpr.servicebook.model.entity.Payment; +import br.edu.utfpr.servicebook.model.repository.PaymentRepository; + + +@Service +public class PaymentService { + + private final Environment environment; + + @Autowired + private PaymentRepository paymentRepository; + + @Autowired + public PaymentService(Environment environment){ + this.environment = environment; + } + + public ResponseEntity pay(Map paymentData){ + RestTemplate restTemplate = new RestTemplate(); + String apiUrl = "https://api.mercadopago.com/v1/payments"; + + try { + String accessToken = environment.getProperty("MP_ACCESS_TOKEN"); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(accessToken); + + HttpEntity> requestEntity = new HttpEntity<>(paymentData, headers); + + ResponseEntity response = restTemplate.exchange(apiUrl, HttpMethod.POST, requestEntity, Object.class); + return response; + + } catch (Exception e) { + throw new RuntimeException("Algo deu errado! Tente novamente."); + } + + } + + public Payment save(Payment entity){ return paymentRepository.save(entity); } +} diff --git a/src/main/webapp/WEB-INF/view/client/details-request.jsp b/src/main/webapp/WEB-INF/view/client/details-request.jsp index 45363557..cd5b2168 100644 --- a/src/main/webapp/WEB-INF/view/client/details-request.jsp +++ b/src/main/webapp/WEB-INF/view/client/details-request.jsp @@ -1,279 +1,346 @@ -<%@page contentType="text/html" pageEncoding="UTF-8"%> +<%@page contentType="text/html" pageEncoding="UTF-8" %> <%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %> -<%@taglib prefix="t" tagdir="/WEB-INF/tags"%> +<%@taglib prefix="t" tagdir="/WEB-INF/tags" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + - -
-
- -
- - compare_arrows - -
-
-
-
-
- -
- sentiment_dissatisfied -

Não há nenhum candidato.

-
-
- -
-

Escolha um ${expertise.name}!

-
-
-
-

${jobRequest.description}

-

Pedido expedido em ${jobRequest.dateTarget}

-
+ +
+
- - - + +
+ +
- - +
+
+
+
+
+
+ +
+ sentiment_dissatisfied +

Não há nenhum candidato.

+
+
+ +
+

+ Escolha um ${expertise.name}!

+
+
+
+

${jobRequest.description}

+

Pedido expedido em ${jobRequest.dateTarget}

+
- + -
-
- -

Entre em contato com um ou mais profissionais que se interessaram em realizar o serviço para marcar um orçamento.

- -

${candidates.size()} profissionais responderam a sua solicitação:

-
- -

${candidates.size()} profissional respondeu a sua solicitação:

-
-
- -

Solicite e analise o(s) orçamento(s) para escolher o profissional que melhor atende a sua necessidade.

-
- -

Aguarde a confirmação do profissional para realizar o serviço.

-
- -

O profissional confirmou que realizará o serviço. Você poderá avaliar o profissional após a expiração da data combinada.

-
- -

Conforme a data de agendamento, este é o profissional que está realizando o serviço. - Quando ele finalizar o serviço, informe o término e também faça a avaliação do profissional.

-
- -

Este é o profissional que realizou o serviço.

-
+
+
+ Excluir +
+
-
- -
-
-
-
-
-
-
- - - star - - - star_border - - -
-
-
-
-
- check_circle + + +
+
+ + +
+
+ +
+
+ +

Entre em contato com um ou mais profissionais que se interessaram em + realizar o serviço para marcar um orçamento.

+ +

${candidates.size()} profissionais responderam a sua + solicitação:

+
+ +

${candidates.size()} profissional respondeu a sua + solicitação:

+
+
+ +

Solicite e analise o(s) orçamento(s) para escolher o profissional que + melhor atende a sua necessidade.

+
+ +

Aguarde a confirmação do profissional para realizar o serviço.

+
+ +

O profissional confirmou que realizará o serviço. Você poderá avaliar + o profissional após a expiração da data combinada.

+
+ +

Conforme a data de agendamento, este é o profissional que está + realizando o serviço. + Quando ele finalizar o serviço, informe o término e também faça a + avaliação do profissional.

+
+ +

Este é o profissional que realizou o serviço.

+
+ +
+ +
+
+
+
+
+
+
+ + + star + + + star_border + + +
+
+
+
+
+ check_circle +
+
+
+
${jobCandidate.individual.followsAmount != 0 ? jobCandidate.individual.followsAmount : " "}
+
thumb_up +
+
+
+
+
+
+ + + + + + +
+ Profissional - Imagem de perfil. +
+
+
+
+
+

${jobCandidate.individual.name}

+
+

+ + email ${jobCandidate.individual.email} + + + email ${jobCandidate.individual.email} + +

+

+ + phone ${jobCandidate.individual.phoneNumber} + + + phone ${jobCandidate.individual.phoneNumber} + +

+
+

Detalhes +

+
+
+
+
+
+
-
-
-
${jobCandidate.individual.followsAmount != 0 ? jobCandidate.individual.followsAmount : " "}
-
thumb_up
-
-
-
- - - - - - -
- Profissional - Imagem de perfil. -
-
-
-
-
-

${jobCandidate.individual.name}

-
-

- - email ${jobCandidate.individual.email} - - - email ${jobCandidate.individual.email} - -

-

- - phone ${jobCandidate.individual.phoneNumber} - - - phone ${jobCandidate.individual.phoneNumber} - -

-
-

Detalhes

-
-
-
-
-
-
+
-
-
-
-
-
-
-
+ +
+ + + + diff --git a/src/main/webapp/assets/resources/scripts/mp-payment.js b/src/main/webapp/assets/resources/scripts/mp-payment.js new file mode 100644 index 00000000..3a85eedd --- /dev/null +++ b/src/main/webapp/assets/resources/scripts/mp-payment.js @@ -0,0 +1,114 @@ + +function showPayment() { + + if ($(".brick-payment").is(":hidden")) { + $('.brick-payment').fadeIn(); + $('.brick-status').fadeIn(); + + } + else{ + $('.brick-payment').fadeOut(); + $('.brick-status').fadeOut(); + return; + } + + const renderPaymentBrick = async (bricksBuilder) => { + const settings = { + initialization: { + amount: 0.5, + }, + customization: { + paymentMethods: { + ticket: "all", + bankTransfer: "all", + creditCard: "all", + debitCard: "all", + mercadoPago: "all", + }, + }, + callbacks: { + onReady: () => {}, + onSubmit: ({ + selectedPaymentMethod, + formData + }) => { + // callback chamado ao clicar no botão de submissão dos dados + return new Promise((resolve, reject) => { + fetch("minha-conta/cliente/pagamento", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(formData), + }) + .then(response => { + if (!response.ok) { + return response.json().then(responseBody => { + throw new Error(responseBody.message); + }); + } + + return response.json(); + }) + .then((response) => { + const paymentId = response.data.id; + renderStatusScreenBrick(bricksBuilder, paymentId); + resolve(); + }) + .catch((error) => { + swal({ + title: "Opss", + text: error.message, + icon: "error", + }); + reject(); + }); + }); + }, + onError: (error) => { + console.error(error); + }, + }, + }; + + bricksBuilder.create( + "payment", + "paymentBrick_container", + settings + ) + .then(paymentBrickController => { + + }) + .catch(error => { + console.error(error); + }); + }; + + renderPaymentBrick(bricksBuilder); +} + + +const renderStatusScreenBrick = async (bricksBuilder, paymentId) => { + const settings = { + initialization: { + paymentId: paymentId, + }, + callbacks: { + onReady: () => { + /* + Callback chamado quando o Brick estiver pronto. + Aqui você pode ocultar loadings do seu site, por exemplo. + */ + }, + onError: (error) => { + console.error(error); + }, + }, + }; + + window.statusScreenBrickController = await bricksBuilder.create( + 'statusScreen', + 'statusScreenBrick_container', + settings, + ); +}; \ No newline at end of file diff --git a/src/main/webapp/assets/resources/scripts/sse.js b/src/main/webapp/assets/resources/scripts/sse.js index a2c89491..8722f7d3 100644 --- a/src/main/webapp/assets/resources/scripts/sse.js +++ b/src/main/webapp/assets/resources/scripts/sse.js @@ -1,4 +1,4 @@ -const URL = 'sse/subscribe'; +const URL_SSE = 'sse/subscribe'; const SSE = 'sse-data'; /** @@ -6,7 +6,7 @@ const SSE = 'sse-data'; */ window.onload = function () { - let eventSource = new EventSource(URL) + let eventSource = new EventSource(URL_SSE) //aguarda uma mensagem global. eventSource.addEventListener("message", function (event) {