From 095683133fa153b281a99b1dd279f0aaca834c04 Mon Sep 17 00:00:00 2001 From: Murilo Kakazu Date: Sun, 21 Jul 2024 18:33:33 -0300 Subject: [PATCH 1/4] feat: add payment option with qr code --- Makefile | 8 +- build.gradle | 1 + docker-compose.yml | 2 +- init.sql | 2 + scripts/create-mercado-pago-point-of-sale.sh | 12 +++ scripts/create-mercado-pago-store.sh | 61 +++++++++++++ .../adapters/in/rest/PaymentResource.java | 33 +++++++ .../out/mercadopago/MercadoPagoAdapter.java | 78 +++++++++++++++++ .../MercadoPagoRequestBuilder.java | 53 ++++++++++++ .../mercadopago/model/MercadoPagoCashOut.java | 14 +++ ...eateQrCodeForPaymentCollectionRequest.java | 40 +++++++++ ...ateQrCodeForPaymentCollectionResponse.java | 15 ++++ .../model/MercadoPagoOrderItem.java | 31 +++++++ .../mercadopago/model/MercadoPagoSponsor.java | 14 +++ .../fastfood/application/dto/OrderDTO.java | 1 + .../fastfood/application/dto/PaymentDTO.java | 12 +++ .../application/dto/PaymentQrCodeDTO.java | 10 +++ .../PaymentProcessingFailedException.java | 11 +++ .../{Mapper.java => BiDirectionalMapper.java} | 2 +- .../mapper/UniDirectionalMapper.java | 5 ++ .../mapper/impl/CategoryMapper.java | 4 +- .../mapper/impl/CustomerDTOMapper.java | 4 +- .../mapper/impl/CustomerMapper.java | 4 +- .../mapper/impl/PaymentQrCodeDTOMapper.java | 18 ++++ .../mapper/impl/ProductDTOMapper.java | 4 +- .../mapper/impl/ProductMapper.java | 4 +- .../payment/GeneratePaymentQrCodeUseCase.java | 50 +++++++++++ .../persistence/jpa/entities/OrderEntity.java | 21 ++++- .../jpa/entities/PaymentEntity.java | 85 +++++++++++++++++++ .../jpa/entities/PaymentStatus.java | 8 ++ .../application-dev-docker.properties | 22 +++++ src/main/resources/application-dev.properties | 12 ++- .../resources/application-prod.properties | 10 ++- .../resources/application-test.properties | 10 ++- 34 files changed, 643 insertions(+), 18 deletions(-) create mode 100755 scripts/create-mercado-pago-point-of-sale.sh create mode 100755 scripts/create-mercado-pago-store.sh create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/PaymentResource.java create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/MercadoPagoAdapter.java create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/MercadoPagoRequestBuilder.java create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoCashOut.java create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoCreateQrCodeForPaymentCollectionRequest.java create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoCreateQrCodeForPaymentCollectionResponse.java create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoOrderItem.java create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoSponsor.java create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/application/dto/PaymentDTO.java create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/application/dto/PaymentQrCodeDTO.java create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/application/exceptions/PaymentProcessingFailedException.java rename src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/{Mapper.java => BiDirectionalMapper.java} (70%) create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/UniDirectionalMapper.java create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/PaymentQrCodeDTOMapper.java create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/domain/usecases/payment/GeneratePaymentQrCodeUseCase.java create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/infrastructure/out/persistence/jpa/entities/PaymentEntity.java create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/infrastructure/out/persistence/jpa/entities/PaymentStatus.java create mode 100644 src/main/resources/application-dev-docker.properties diff --git a/Makefile b/Makefile index 4eb05e0..5daadc1 100644 --- a/Makefile +++ b/Makefile @@ -9,11 +9,17 @@ down: test: ./gradlew test +install: + ./gradlew build --refresh-dependencies + build: ./gradlew build +run/docker: + docker-compose up --build api + run: - docker-compose up api + ./gradlew bootRun debug: ./gradlew bootRun --debug-jvm diff --git a/build.gradle b/build.gradle index bf0bc7d..1e55bfb 100644 --- a/build.gradle +++ b/build.gradle @@ -58,6 +58,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0' + implementation 'org.apache.httpcomponents:httpclient:4.5' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' runtimeOnly 'org.postgresql:postgresql' diff --git a/docker-compose.yml b/docker-compose.yml index 877d3d0..5eac2e8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,7 +21,7 @@ services: - database build: . environment: - SPRING_PROFILES_ACTIVE: "dev" + SPRING_PROFILES_ACTIVE: "dev-docker" ports: - "8080:8080" networks: diff --git a/init.sql b/init.sql index 692f301..980caf3 100644 --- a/init.sql +++ b/init.sql @@ -2,10 +2,12 @@ create table tb_category (created_at TIMESTAMP WITHOUT TIME ZONE, deleted_at TIM create table tb_customer (created_at TIMESTAMP WITHOUT TIME ZONE, deleted_at TIMESTAMP WITHOUT TIME ZONE, id bigserial not null, updated_at TIMESTAMP WITHOUT TIME ZONE, cpf varchar(255) not null unique, email varchar(255) not null, name varchar(255) not null, primary key (id)); create table tb_order (created_at TIMESTAMP WITHOUT TIME ZONE, customer_id bigint, deleted_at TIMESTAMP WITHOUT TIME ZONE, id bigserial not null, updated_at TIMESTAMP WITHOUT TIME ZONE, status varchar(255) check (status in ('DRAFT','SUBMITTED','PREPARING','READY','DELIVERED','CANCELED')), primary key (id)); create table tb_order_item (total_price float(53) not null check (total_price>=0), id bigserial not null, order_id bigint not null, product_id bigint not null, quantity bigint not null check (quantity>=1), primary key (id)); +create table tb_payment (amount float(53) not null check (amount>=0), created_at TIMESTAMP WITHOUT TIME ZONE, deleted_at TIMESTAMP WITHOUT TIME ZONE, id bigserial not null, order_id bigint not null unique, updated_at TIMESTAMP WITHOUT TIME ZONE, status varchar(255) check (status in ('NOT_SUBMITTED','PROCESSING','REJECTED','COLLECTED')), primary key (id)); create table tb_product (price float(53) not null, category_id bigint not null, created_at TIMESTAMP WITHOUT TIME ZONE, deleted_at TIMESTAMP WITHOUT TIME ZONE, id bigserial not null, updated_at TIMESTAMP WITHOUT TIME ZONE, description TEXT not null, img_url varchar(255) not null, name varchar(255) not null, primary key (id)); alter table if exists tb_order add constraint FKqcp43jdylvf2riad5s1x1i2dn foreign key (customer_id) references tb_customer; alter table if exists tb_order_item add constraint FKgeobgl2xu916he8vhljktwxnx foreign key (order_id) references tb_order; alter table if exists tb_order_item add constraint FK4h5xid5qehset7qwe5l9c997x foreign key (product_id) references tb_product; +alter table if exists tb_payment add constraint FKokaf4il2cwit4h780c25dv04r foreign key (order_id) references tb_order; alter table if exists tb_product add constraint FK8i0sq9mfbpsrabrm2pum9fspo foreign key (category_id) references tb_category; INSERT INTO tb_category (name, created_at) VALUES ('Snacks', NOW()); INSERT INTO tb_category (name, created_at) VALUES ('Drinks', NOW()); diff --git a/scripts/create-mercado-pago-point-of-sale.sh b/scripts/create-mercado-pago-point-of-sale.sh new file mode 100755 index 0000000..3655d63 --- /dev/null +++ b/scripts/create-mercado-pago-point-of-sale.sh @@ -0,0 +1,12 @@ +curl -X POST \ + 'https://api.mercadopago.com/pos'\ + -H 'Content-Type: application/json' \ + -H "Authorization: Bearer $MERCADOPAGO_PRIVATE_ACCESS_TOKEN" \ + -d '{ + "category": 5611203, + "external_id": "STORE00001POS001", + "external_store_id": "STORE00001", + "fixed_amount": false, + "name": "First POS", + "store_id": 62210177 +}' \ No newline at end of file diff --git a/scripts/create-mercado-pago-store.sh b/scripts/create-mercado-pago-store.sh new file mode 100755 index 0000000..6d3323a --- /dev/null +++ b/scripts/create-mercado-pago-store.sh @@ -0,0 +1,61 @@ +curl -X POST \ + 'https://api.mercadopago.com/users/356453274/stores'\ + -H 'Content-Type: application/json' \ + -H "Authorization: Bearer $MERCADOPAGO_PRIVATE_ACCESS_TOKEN" \ + -d '{ + "business_hours": { + "monday": [ + { + "open": "08:00", + "close": "23:00" + } + ], + "tuesday": [ + { + "open": "08:00", + "close": "23:00" + } + ], + "wednesday": [ + { + "open": "08:00", + "close": "23:00" + } + ], + "thursday": [ + { + "open": "08:00", + "close": "23:00" + } + ], + "friday": [ + { + "open": "08:00", + "close": "23:00" + } + ], + "saturday": [ + { + "open": "13:00", + "close": "22:00" + } + ], + "sunday": [ + { + "open": "13:00", + "close": "22:00" + } + ] + }, + "external_id": "STORE00001", + "location": { + "street_number": "1106", + "street_name": "Av. Paulista", + "city_name": "São Paulo", + "state_name": "São Paulo", + "latitude": -23.5640485, + "longitude": -46.6526571, + "reference": "" + }, + "name": "Fastfood FIAP" +}' \ No newline at end of file diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/PaymentResource.java b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/PaymentResource.java new file mode 100644 index 0000000..5574635 --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/PaymentResource.java @@ -0,0 +1,33 @@ +package br.com.fiap.grupo30.fastfood.adapters.in.rest; + +import br.com.fiap.grupo30.fastfood.application.dto.PaymentQrCodeDTO; +import br.com.fiap.grupo30.fastfood.domain.usecases.payment.GeneratePaymentQrCodeUseCase; +import io.swagger.v3.oas.annotations.Operation; +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 = "Payments Resource", description = "RESTful API for managing payments.") +public class PaymentResource { + + private final GeneratePaymentQrCodeUseCase generatePaymentQrCodeUseCase; + + @Autowired + public PaymentResource(GeneratePaymentQrCodeUseCase generatePaymentQrCodeUseCase) { + this.generatePaymentQrCodeUseCase = generatePaymentQrCodeUseCase; + } + + @PostMapping(value = "/{orderId}/qrcode") + @Operation(summary = "Generate qrcode for order payment collection") + public ResponseEntity generateQrCodeForPaymentCollection( + @PathVariable Long orderId) { + PaymentQrCodeDTO qrCode = this.generatePaymentQrCodeUseCase.execute(orderId); + return ResponseEntity.ok().body(qrCode); + } +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/MercadoPagoAdapter.java b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/MercadoPagoAdapter.java new file mode 100644 index 0000000..8d5308c --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/MercadoPagoAdapter.java @@ -0,0 +1,78 @@ +package br.com.fiap.grupo30.fastfood.adapters.out.mercadopago; + +import br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model.MercadoPagoCreateQrCodeForPaymentCollectionResponse; +import br.com.fiap.grupo30.fastfood.application.dto.OrderDTO; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class MercadoPagoAdapter { + + @Value("${integrations.mercadopago.base-url}") + private String baseUrl; + + @Value("${integrations.mercadopago.public-key}") + private String publicKey; + + @Value("${integrations.mercadopago.access-token}") + private String privateAccessToken; + + @Value("${integrations.mercadopago.app-user-id}") + private Long appUserId; + + @Value("${integrations.mercadopago.point-of-sale-id}") + private String pointOfSaleId; + + @Value("${integrations.mercadopago.payment-collection.user-id}") + private String userIdForPaymentCollection; + + private final MercadoPagoRequestBuilder requestBuilder; + + private static final Logger LOGGER = LoggerFactory.getLogger(MercadoPagoAdapter.class); + + public MercadoPagoAdapter(MercadoPagoRequestBuilder requestBuilder) { + this.requestBuilder = requestBuilder; + } + + public MercadoPagoCreateQrCodeForPaymentCollectionResponse createQrCodeForPaymentCollection( + OrderDTO order) throws Exception { + String resourceUrlTemplate = + "https://api.mercadopago.com/instore/orders/qr/seller/collectors/%s/pos/%s/qrs"; + String resourceUrl = String.format(resourceUrlTemplate, appUserId, pointOfSaleId); + + LOGGER.info(resourceUrl); + + var requestBody = this.requestBuilder.buildQrCodePaymentCollectionRequest(order, appUserId); + + ObjectMapper mapper = new ObjectMapper(); + String serializedRequestBody = mapper.writeValueAsString(requestBody); + + LOGGER.info(serializedRequestBody); + + try (HttpClient client = HttpClient.newHttpClient()) { + HttpRequest request = + HttpRequest.newBuilder() + .uri(URI.create(resourceUrl)) + .header("Authorization", String.format("Bearer %s", privateAccessToken)) + .header("Conten-type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(serializedRequestBody)) + .build(); + + HttpResponse response = + client.send(request, HttpResponse.BodyHandlers.ofString()); + MercadoPagoCreateQrCodeForPaymentCollectionResponse result = + mapper.readValue( + response.body(), + MercadoPagoCreateQrCodeForPaymentCollectionResponse.class); + + return result; + } + } +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/MercadoPagoRequestBuilder.java b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/MercadoPagoRequestBuilder.java new file mode 100644 index 0000000..b90f989 --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/MercadoPagoRequestBuilder.java @@ -0,0 +1,53 @@ +package br.com.fiap.grupo30.fastfood.adapters.out.mercadopago; + +import br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model.MercadoPagoCashOut; +import br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model.MercadoPagoCreateQrCodeForPaymentCollectionRequest; +import br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model.MercadoPagoOrderItem; +import br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model.MercadoPagoSponsor; +import br.com.fiap.grupo30.fastfood.application.dto.OrderDTO; +import java.time.Instant; +import java.util.Date; +import java.util.stream.Stream; +import org.springframework.stereotype.Component; + +@Component +public class MercadoPagoRequestBuilder { + public MercadoPagoCreateQrCodeForPaymentCollectionRequest buildQrCodePaymentCollectionRequest( + OrderDTO order, Long mercadoPagoAppUserId) { + String orderId = order.getOrderId().toString(); + MercadoPagoCreateQrCodeForPaymentCollectionRequest requestBody = + new MercadoPagoCreateQrCodeForPaymentCollectionRequest(); + requestBody.setTitle(String.format("Pedido %s", orderId)); + requestBody.setDescription(String.format("Pedido da lanchonete fastfood")); + requestBody.setExpirationDate(Date.from(Instant.now().plusSeconds(3600))); + requestBody.setExternalReference(orderId); + requestBody.setTotalAmount(order.getTotalPrice()); + + MercadoPagoCashOut cashOut = new MercadoPagoCashOut(); + cashOut.setAmount(order.getTotalPrice()); + + MercadoPagoOrderItem[] items = + Stream.of(order.getItems()) + .map( + (ourOrderItem) -> { + MercadoPagoOrderItem theirOrderItem = + new MercadoPagoOrderItem(); + theirOrderItem.setTitle(ourOrderItem.getProduct().getName()); + theirOrderItem.setDescription( + ourOrderItem.getProduct().getDescription()); + theirOrderItem.setCategory( + ourOrderItem.getProduct().getCategory().getName()); + theirOrderItem.setQuantity(ourOrderItem.getQuantity()); + theirOrderItem.setUnitPrice( + ourOrderItem.getProduct().getPrice()); + theirOrderItem.setUnitMeasure("unit"); + theirOrderItem.setTotalAmount( + ourOrderItem.getTotalPrice()); + return theirOrderItem; + }) + .toArray(MercadoPagoOrderItem[]::new); + requestBody.setItems(items); + + return requestBody; + } +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoCashOut.java b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoCashOut.java new file mode 100644 index 0000000..6527de5 --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoCashOut.java @@ -0,0 +1,14 @@ +package br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +public class MercadoPagoCashOut { + private Double amount; +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoCreateQrCodeForPaymentCollectionRequest.java b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoCreateQrCodeForPaymentCollectionRequest.java new file mode 100644 index 0000000..060f428 --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoCreateQrCodeForPaymentCollectionRequest.java @@ -0,0 +1,40 @@ +package br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Date; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +public class MercadoPagoCreateQrCodeForPaymentCollectionRequest { + private String title; + private String description; + + @JsonProperty("external_reference") + private String externalReference; + + @JsonProperty("expiration_date") + @JsonFormat( + shape = JsonFormat.Shape.STRING, + pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", + timezone = "GMT") + private Date expirationDate; + + private MercadoPagoSponsor sponsor; + private MercadoPagoOrderItem[] items; + + @JsonProperty("cash_out") + private MercadoPagoCashOut cashOut; + + @JsonProperty("total_amount") + private Double totalAmount; + + @JsonProperty("notification_url") + private String notificationUrl; +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoCreateQrCodeForPaymentCollectionResponse.java b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoCreateQrCodeForPaymentCollectionResponse.java new file mode 100644 index 0000000..710729c --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoCreateQrCodeForPaymentCollectionResponse.java @@ -0,0 +1,15 @@ +package br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +public class MercadoPagoCreateQrCodeForPaymentCollectionResponse { + private String qr_data; + private String in_store_order_id; +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoOrderItem.java b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoOrderItem.java new file mode 100644 index 0000000..d9671c6 --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoOrderItem.java @@ -0,0 +1,31 @@ +package br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +public class MercadoPagoOrderItem { + @JsonProperty("sku_number") + private String skuNumber; + + private String category; + private String title; + private String description; + + @JsonProperty("unit_price") + private Double unitPrice; + + private Long quantity; + + @JsonProperty("unit_measure") + private String unitMeasure; + + @JsonProperty("total_amount") + private Double totalAmount; +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoSponsor.java b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoSponsor.java new file mode 100644 index 0000000..15d52b1 --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoSponsor.java @@ -0,0 +1,14 @@ +package br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +public class MercadoPagoSponsor { + private Long id; +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/OrderDTO.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/OrderDTO.java index 20951b2..d912d3b 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/OrderDTO.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/OrderDTO.java @@ -12,4 +12,5 @@ public class OrderDTO { private OrderItemDTO[] items; private Double totalPrice; private CustomerDTO customer; + private PaymentDTO payment; } diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/PaymentDTO.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/PaymentDTO.java new file mode 100644 index 0000000..a68126a --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/PaymentDTO.java @@ -0,0 +1,12 @@ +package br.com.fiap.grupo30.fastfood.application.dto; + +import br.com.fiap.grupo30.fastfood.infrastructure.out.persistence.jpa.entities.PaymentStatus; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class PaymentDTO { + private PaymentStatus status; + private Double amount; +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/PaymentQrCodeDTO.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/PaymentQrCodeDTO.java new file mode 100644 index 0000000..1f24361 --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/PaymentQrCodeDTO.java @@ -0,0 +1,10 @@ +package br.com.fiap.grupo30.fastfood.application.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class PaymentQrCodeDTO { + public String qrCodeData; +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/exceptions/PaymentProcessingFailedException.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/exceptions/PaymentProcessingFailedException.java new file mode 100644 index 0000000..431b06f --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/exceptions/PaymentProcessingFailedException.java @@ -0,0 +1,11 @@ +package br.com.fiap.grupo30.fastfood.application.exceptions; + +import java.io.Serial; + +public class PaymentProcessingFailedException extends RuntimeException { + @Serial private static final long serialVersionUID = 1L; + + public PaymentProcessingFailedException(String msg, Throwable exception) { + super(msg, exception); + } +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/Mapper.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/BiDirectionalMapper.java similarity index 70% rename from src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/Mapper.java rename to src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/BiDirectionalMapper.java index 974e6e5..42168e8 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/Mapper.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/BiDirectionalMapper.java @@ -1,6 +1,6 @@ package br.com.fiap.grupo30.fastfood.application.mapper; -public interface Mapper { +public interface BiDirectionalMapper { T mapTo(S source); S mapFrom(T target); diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/UniDirectionalMapper.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/UniDirectionalMapper.java new file mode 100644 index 0000000..7ceaeb7 --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/UniDirectionalMapper.java @@ -0,0 +1,5 @@ +package br.com.fiap.grupo30.fastfood.application.mapper; + +public interface UniDirectionalMapper { + T map(S source); +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/CategoryMapper.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/CategoryMapper.java index b7b0e42..0d2797f 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/CategoryMapper.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/CategoryMapper.java @@ -1,12 +1,12 @@ package br.com.fiap.grupo30.fastfood.application.mapper.impl; -import br.com.fiap.grupo30.fastfood.application.mapper.Mapper; +import br.com.fiap.grupo30.fastfood.application.mapper.BiDirectionalMapper; import br.com.fiap.grupo30.fastfood.domain.Category; import br.com.fiap.grupo30.fastfood.infrastructure.out.persistence.jpa.entities.CategoryEntity; import org.springframework.stereotype.Component; @Component -public class CategoryMapper implements Mapper { +public class CategoryMapper implements BiDirectionalMapper { @Override public CategoryEntity mapTo(Category category) { diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/CustomerDTOMapper.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/CustomerDTOMapper.java index 66a50d3..438d073 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/CustomerDTOMapper.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/CustomerDTOMapper.java @@ -1,12 +1,12 @@ package br.com.fiap.grupo30.fastfood.application.mapper.impl; import br.com.fiap.grupo30.fastfood.application.dto.CustomerDTO; -import br.com.fiap.grupo30.fastfood.application.mapper.Mapper; +import br.com.fiap.grupo30.fastfood.application.mapper.BiDirectionalMapper; import br.com.fiap.grupo30.fastfood.infrastructure.out.persistence.jpa.entities.CustomerEntity; import org.springframework.stereotype.Component; @Component -public final class CustomerDTOMapper implements Mapper { +public final class CustomerDTOMapper implements BiDirectionalMapper { @Override public CustomerEntity mapTo(CustomerDTO dto) { diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/CustomerMapper.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/CustomerMapper.java index 03942a9..76ede35 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/CustomerMapper.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/CustomerMapper.java @@ -1,13 +1,13 @@ package br.com.fiap.grupo30.fastfood.application.mapper.impl; -import br.com.fiap.grupo30.fastfood.application.mapper.Mapper; +import br.com.fiap.grupo30.fastfood.application.mapper.BiDirectionalMapper; import br.com.fiap.grupo30.fastfood.domain.Customer; import br.com.fiap.grupo30.fastfood.domain.vo.CPF; import br.com.fiap.grupo30.fastfood.infrastructure.out.persistence.jpa.entities.CustomerEntity; import org.springframework.stereotype.Component; @Component -public final class CustomerMapper implements Mapper { +public final class CustomerMapper implements BiDirectionalMapper { @Override public CustomerEntity mapTo(Customer customer) { diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/PaymentQrCodeDTOMapper.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/PaymentQrCodeDTOMapper.java new file mode 100644 index 0000000..2e52830 --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/PaymentQrCodeDTOMapper.java @@ -0,0 +1,18 @@ +package br.com.fiap.grupo30.fastfood.application.mapper.impl; + +import br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model.MercadoPagoCreateQrCodeForPaymentCollectionResponse; +import br.com.fiap.grupo30.fastfood.application.dto.PaymentQrCodeDTO; +import br.com.fiap.grupo30.fastfood.application.mapper.UniDirectionalMapper; +import org.springframework.stereotype.Component; + +@Component +public class PaymentQrCodeDTOMapper + implements UniDirectionalMapper< + MercadoPagoCreateQrCodeForPaymentCollectionResponse, PaymentQrCodeDTO> { + @Override + public PaymentQrCodeDTO map( + MercadoPagoCreateQrCodeForPaymentCollectionResponse mercadoPagoData) { + PaymentQrCodeDTO dto = new PaymentQrCodeDTO(mercadoPagoData.getQr_data()); + return dto; + } +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/ProductDTOMapper.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/ProductDTOMapper.java index d826ac4..2adbe75 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/ProductDTOMapper.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/ProductDTOMapper.java @@ -2,7 +2,7 @@ import br.com.fiap.grupo30.fastfood.application.dto.CategoryDTO; import br.com.fiap.grupo30.fastfood.application.dto.ProductDTO; -import br.com.fiap.grupo30.fastfood.application.mapper.Mapper; +import br.com.fiap.grupo30.fastfood.application.mapper.BiDirectionalMapper; import br.com.fiap.grupo30.fastfood.domain.Category; import br.com.fiap.grupo30.fastfood.infrastructure.out.persistence.jpa.entities.CategoryEntity; import br.com.fiap.grupo30.fastfood.infrastructure.out.persistence.jpa.entities.ProductEntity; @@ -11,7 +11,7 @@ import org.springframework.stereotype.Component; @Component -public final class ProductDTOMapper implements Mapper { +public final class ProductDTOMapper implements BiDirectionalMapper { private final CategoryRepository categoryRepository; private final CategoryMapper categoryMapper; diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/ProductMapper.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/ProductMapper.java index 5d55014..70236ed 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/ProductMapper.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/ProductMapper.java @@ -1,13 +1,13 @@ package br.com.fiap.grupo30.fastfood.application.mapper.impl; -import br.com.fiap.grupo30.fastfood.application.mapper.Mapper; +import br.com.fiap.grupo30.fastfood.application.mapper.BiDirectionalMapper; import br.com.fiap.grupo30.fastfood.domain.Product; import br.com.fiap.grupo30.fastfood.infrastructure.out.persistence.jpa.entities.ProductEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component -public final class ProductMapper implements Mapper { +public final class ProductMapper implements BiDirectionalMapper { private final CategoryMapper categoryMapper; diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/domain/usecases/payment/GeneratePaymentQrCodeUseCase.java b/src/main/java/br/com/fiap/grupo30/fastfood/domain/usecases/payment/GeneratePaymentQrCodeUseCase.java new file mode 100644 index 0000000..85d3ffe --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/domain/usecases/payment/GeneratePaymentQrCodeUseCase.java @@ -0,0 +1,50 @@ +package br.com.fiap.grupo30.fastfood.domain.usecases.payment; + +import br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.MercadoPagoAdapter; +import br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model.MercadoPagoCreateQrCodeForPaymentCollectionResponse; +import br.com.fiap.grupo30.fastfood.application.dto.PaymentQrCodeDTO; +import br.com.fiap.grupo30.fastfood.application.exceptions.PaymentProcessingFailedException; +import br.com.fiap.grupo30.fastfood.application.exceptions.ResourceNotFoundException; +import br.com.fiap.grupo30.fastfood.application.mapper.impl.PaymentQrCodeDTOMapper; +import br.com.fiap.grupo30.fastfood.infrastructure.out.persistence.jpa.entities.OrderEntity; +import br.com.fiap.grupo30.fastfood.infrastructure.out.persistence.jpa.entities.PaymentStatus; +import br.com.fiap.grupo30.fastfood.infrastructure.out.persistence.jpa.repositories.OrderRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class GeneratePaymentQrCodeUseCase { + private final OrderRepository orderRepository; + private final MercadoPagoAdapter mercadoPagoAdapter; + private final PaymentQrCodeDTOMapper qrCodeMapper; + + @Autowired + public GeneratePaymentQrCodeUseCase( + OrderRepository orderRepository, + MercadoPagoAdapter mercadoPagoAdapter, + PaymentQrCodeDTOMapper qrCodeMapper) { + this.orderRepository = orderRepository; + this.mercadoPagoAdapter = mercadoPagoAdapter; + this.qrCodeMapper = qrCodeMapper; + } + + public PaymentQrCodeDTO execute(Long orderId) { + OrderEntity order = + this.orderRepository + .findById(orderId) + .orElseThrow(() -> new ResourceNotFoundException("Order not found")); + + MercadoPagoCreateQrCodeForPaymentCollectionResponse qrCodeResponse; + try { + qrCodeResponse = + this.mercadoPagoAdapter.createQrCodeForPaymentCollection(order.toDTO()); + } catch (Exception e) { + throw new PaymentProcessingFailedException("Could not start payment processing", e); + } + + order.updatePaymentStatus(PaymentStatus.PROCESSING); + this.orderRepository.save(order); + + return qrCodeMapper.map(qrCodeResponse); + } +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/infrastructure/out/persistence/jpa/entities/OrderEntity.java b/src/main/java/br/com/fiap/grupo30/fastfood/infrastructure/out/persistence/jpa/entities/OrderEntity.java index d191b9a..18d9f8d 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/infrastructure/out/persistence/jpa/entities/OrderEntity.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/infrastructure/out/persistence/jpa/entities/OrderEntity.java @@ -31,6 +31,13 @@ public class OrderEntity { @JoinColumn(name = "customer_id") private CustomerEntity customer; + @OneToOne( + mappedBy = "order", + fetch = FetchType.EAGER, + cascade = CascadeType.ALL, + orphanRemoval = true) + private PaymentEntity payment; + @OneToMany( mappedBy = "order", fetch = FetchType.EAGER, @@ -102,6 +109,11 @@ public void validate() { errors.add("Can not submit order without products"); } + if (this.status == OrderStatus.PREPARING + && !PaymentStatus.COLLECTED.equals(this.getPayment().getStatus())) { + errors.add("Can not start peparing order without collecting payment"); + } + if (!errors.isEmpty()) { throw new CompositeDomainValidationException(errors); } @@ -132,6 +144,7 @@ protected void preRemove() { public static OrderEntity create() { OrderEntity order = new OrderEntity(); order.status = OrderStatus.DRAFT; + order.payment = PaymentEntity.create(order); return order; } @@ -139,6 +152,11 @@ public void setCustomer(CustomerEntity customer) { this.customer = customer; } + public void updatePaymentStatus(PaymentStatus status) { + this.payment.setStatus(status); + this.payment.setAmount(this.totalPrice); + } + public OrderDTO toDTO() { OrderDTO orderDto = new OrderDTO( @@ -146,7 +164,8 @@ public OrderDTO toDTO() { this.status, this.items.stream().map(item -> item.toDTO()).toArray(OrderItemDTO[]::new), this.getTotalPrice(), - this.customer.toDTO()); + this.customer.toDTO(), + this.payment.toDTO()); return orderDto; } } diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/infrastructure/out/persistence/jpa/entities/PaymentEntity.java b/src/main/java/br/com/fiap/grupo30/fastfood/infrastructure/out/persistence/jpa/entities/PaymentEntity.java new file mode 100644 index 0000000..cf34c2c --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/infrastructure/out/persistence/jpa/entities/PaymentEntity.java @@ -0,0 +1,85 @@ +package br.com.fiap.grupo30.fastfood.infrastructure.out.persistence.jpa.entities; + +import br.com.fiap.grupo30.fastfood.application.dto.PaymentDTO; +import jakarta.persistence.*; +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 + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @OneToOne() + @JoinColumn(name = "order_id", nullable = false, updatable = true) + private OrderEntity order; + + @Enumerated(EnumType.STRING) + private PaymentStatus status; + + @Column(nullable = false) + @Min(0) + private Double amount; + + @Column(columnDefinition = "TIMESTAMP WITHOUT TIME ZONE") + private Instant createdAt; + + @Column(columnDefinition = "TIMESTAMP WITHOUT TIME ZONE") + private Instant updatedAt; + + @Column(columnDefinition = "TIMESTAMP WITHOUT TIME ZONE") + private Instant deletedAt; + + @PrePersist + protected void prePersist() { + createdAt = Instant.now(); + } + + @PreUpdate + protected void preUpdate() { + updatedAt = Instant.now(); + } + + @PreRemove + protected void preRemove() { + deletedAt = Instant.now(); + } + + public PaymentStatus getStatus() { + return this.status; + } + + public void setStatus(PaymentStatus status) { + this.status = status; + } + + public Double getAmount() { + return this.amount; + } + + public void setAmount(Double amount) { + this.amount = amount; + } + + public static PaymentEntity create(OrderEntity order) { + PaymentEntity payment = new PaymentEntity(); + payment.order = order; + payment.status = PaymentStatus.NOT_SUBMITTED; + payment.amount = 0.0; + return payment; + } + + public PaymentDTO toDTO() { + PaymentDTO dto = new PaymentDTO(this.status, this.amount); + return dto; + } +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/infrastructure/out/persistence/jpa/entities/PaymentStatus.java b/src/main/java/br/com/fiap/grupo30/fastfood/infrastructure/out/persistence/jpa/entities/PaymentStatus.java new file mode 100644 index 0000000..9b80c92 --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/infrastructure/out/persistence/jpa/entities/PaymentStatus.java @@ -0,0 +1,8 @@ +package br.com.fiap.grupo30.fastfood.infrastructure.out.persistence.jpa.entities; + +public enum PaymentStatus { + NOT_SUBMITTED, + PROCESSING, + REJECTED, + COLLECTED, +} diff --git a/src/main/resources/application-dev-docker.properties b/src/main/resources/application-dev-docker.properties new file mode 100644 index 0000000..d789485 --- /dev/null +++ b/src/main/resources/application-dev-docker.properties @@ -0,0 +1,22 @@ +# DATASOURCE +spring.datasource.url=jdbc:postgresql://database:5432/fastfood +spring.datasource.username=postgres +spring.datasource.password=123456 + +# JPA, SQL +spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect +spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true +spring.jpa.hibernate.ddl-auto=none +# spring.jpa.properties.jakarta.persistence.schema-generation.create-source=metadata +# spring.jpa.properties.jakarta.persistence.schema-generation.scripts.action=create +# spring.jpa.properties.jakarta.persistence.schema-generation.scripts.create-target=init.sql +spring.jpa.properties.hibernate.hbm2ddl.delimiter=; +spring.datasource.hikari.connection-timeout=60000 + +# app +integrations.mercadopago.base-url=https://api.mercadopago.com +integrations.mercadopago.public-key=TEST-78f422d3-0d6f-4a39-b7e7-57ef547df9b5 +integrations.mercadopago.access-token=TEST-2593682679309925-072110-2bda203330ae23737187e79862410ee6-356453274 +integrations.mercadopago.app-user-id=356453274 +integrations.mercadopago.point-of-sale-id=STORE00001POS001 +integrations.mercadopago.payment-collection.user-id=TESTUSER269524473 \ No newline at end of file diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index d7c8ff1..64b6c85 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -1,5 +1,5 @@ # DATASOURCE -spring.datasource.url=jdbc:postgresql://database:5432/fastfood +spring.datasource.url=jdbc:postgresql://localhost:5432/fastfood spring.datasource.username=postgres spring.datasource.password=123456 @@ -11,4 +11,12 @@ spring.jpa.hibernate.ddl-auto=none # spring.jpa.properties.jakarta.persistence.schema-generation.scripts.action=create # spring.jpa.properties.jakarta.persistence.schema-generation.scripts.create-target=init.sql spring.jpa.properties.hibernate.hbm2ddl.delimiter=; -spring.datasource.hikari.connection-timeout=60000 \ No newline at end of file +spring.datasource.hikari.connection-timeout=60000 + +# app +integrations.mercadopago.base-url=https://api.mercadopago.com +integrations.mercadopago.public-key=TEST-78f422d3-0d6f-4a39-b7e7-57ef547df9b5 +integrations.mercadopago.access-token=TEST-2593682679309925-072110-2bda203330ae23737187e79862410ee6-356453274 +integrations.mercadopago.app-user-id=356453274 +integrations.mercadopago.point-of-sale-id=STORE00001POS001 +integrations.mercadopago.payment-collection.user-id=TESTUSER269524473 \ No newline at end of file diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index b7d87f2..8e7f387 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -6,4 +6,12 @@ spring.datasource.password=${DB_PASSWORD:123456} # JPA, SQL spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true -spring.jpa.hibernate.ddl-auto=none \ No newline at end of file +spring.jpa.hibernate.ddl-auto=none + +# app +integrations.mercadopago.base-url=https://api.mercadopago.com +integrations.mercadopago.public-key=${MERCADOPAGO_PUBLIC_KEY} +integrations.mercadopago.access-token=${MERCADOPAGO_PRIVATE_ACCESS_TOKEN} +integrations.mercadopago.app-user-id=${MERCADOPAGO_APP_USER_ID} +integrations.mercadopago.point-of-sale-id=${MERCADOPAGO_POINT_OF_SALE_ID} +integrations.mercadopago.payment-collection.user-id=${MERCADOPAGO_PAYMENT_COLLECTION_USER_ID} \ No newline at end of file diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties index 6c3334f..c6bc4a6 100644 --- a/src/main/resources/application-test.properties +++ b/src/main/resources/application-test.properties @@ -12,4 +12,12 @@ spring.h2.console.path=/h2-console spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.jpa.defer-datasource-initialization=true spring.jpa.show-sql=true -spring.jpa.properties.hibernate.format_sql=true \ No newline at end of file +spring.jpa.properties.hibernate.format_sql=true + +# app +integrations.mercadopago.base-url=https://api.mercadopago.com +integrations.mercadopago.public-key=TEST-78f422d3-0d6f-4a39-b7e7-57ef547df9b5 +integrations.mercadopago.access-token=TEST-2593682679309925-072110-2bda203330ae23737187e79862410ee6-356453274 +integrations.mercadopago.app-user-id=356453274 +integrations.mercadopago.point-of-sale-id=STORE00001POS001 +integrations.mercadopago.payment-collection.user-id=TESTUSER269524473 \ No newline at end of file From 652c098b4c5722d517d543f8860cb2e6fd14f1ce Mon Sep 17 00:00:00 2001 From: Murilo Kakazu Date: Sun, 21 Jul 2024 20:35:21 -0300 Subject: [PATCH 2/4] feat: introducing some mercadopago webhook stuff, set credentials of test payment collector user --- scripts/create-mercado-pago-point-of-sale.sh | 4 +-- scripts/create-mercado-pago-store.sh | 2 +- .../adapters/in/rest/WebhookResource.java | 32 +++++++++++++++++++ .../out/mercadopago/MercadoPagoAdapter.java | 18 ++++------- .../MercadoPagoRequestBuilder.java | 7 ++-- .../dto/MercadoPagoNotificationDTO.java | 22 +++++++++++++ .../application-dev-docker.properties | 9 +++--- src/main/resources/application-dev.properties | 9 +++--- .../resources/application-prod.properties | 1 + .../resources/application-test.properties | 9 +++--- 10 files changed, 83 insertions(+), 30 deletions(-) create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/WebhookResource.java create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/application/dto/MercadoPagoNotificationDTO.java diff --git a/scripts/create-mercado-pago-point-of-sale.sh b/scripts/create-mercado-pago-point-of-sale.sh index 3655d63..31070e9 100755 --- a/scripts/create-mercado-pago-point-of-sale.sh +++ b/scripts/create-mercado-pago-point-of-sale.sh @@ -6,7 +6,7 @@ curl -X POST \ "category": 5611203, "external_id": "STORE00001POS001", "external_store_id": "STORE00001", - "fixed_amount": false, + "fixed_amount": true, "name": "First POS", - "store_id": 62210177 + "store_id": 62212711 }' \ No newline at end of file diff --git a/scripts/create-mercado-pago-store.sh b/scripts/create-mercado-pago-store.sh index 6d3323a..8262914 100755 --- a/scripts/create-mercado-pago-store.sh +++ b/scripts/create-mercado-pago-store.sh @@ -1,5 +1,5 @@ curl -X POST \ - 'https://api.mercadopago.com/users/356453274/stores'\ + 'https://api.mercadopago.com/users/1910105219/stores'\ -H 'Content-Type: application/json' \ -H "Authorization: Bearer $MERCADOPAGO_PRIVATE_ACCESS_TOKEN" \ -d '{ diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/WebhookResource.java b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/WebhookResource.java new file mode 100644 index 0000000..158b28e --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/WebhookResource.java @@ -0,0 +1,32 @@ +package br.com.fiap.grupo30.fastfood.adapters.in.rest; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import java.util.HashMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(value = "/webhooks") +@Tag(name = "Wehooks Resource", description = "RESTful API for webhook events.") +public class WebhookResource { + + private static final Logger LOGGER = LoggerFactory.getLogger(WebhookResource.class); + + @PostMapping(value = "/mercadopago") + @Operation(summary = "Handle mercadopago events") + public ResponseEntity createCustomer(@RequestBody @Valid HashMap notification) + throws Exception { + ObjectMapper mapper = new ObjectMapper(); + String serializedRequestBody = mapper.writeValueAsString(notification); + LOGGER.info(serializedRequestBody); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/MercadoPagoAdapter.java b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/MercadoPagoAdapter.java index 8d5308c..19adcce 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/MercadoPagoAdapter.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/MercadoPagoAdapter.java @@ -7,8 +7,6 @@ import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -30,13 +28,14 @@ public class MercadoPagoAdapter { @Value("${integrations.mercadopago.point-of-sale-id}") private String pointOfSaleId; + @Value("${integrations.mercadopago.notifications-url}") + private String notificationsUrl; + @Value("${integrations.mercadopago.payment-collection.user-id}") private String userIdForPaymentCollection; private final MercadoPagoRequestBuilder requestBuilder; - private static final Logger LOGGER = LoggerFactory.getLogger(MercadoPagoAdapter.class); - public MercadoPagoAdapter(MercadoPagoRequestBuilder requestBuilder) { this.requestBuilder = requestBuilder; } @@ -47,22 +46,19 @@ public MercadoPagoCreateQrCodeForPaymentCollectionResponse createQrCodeForPaymen "https://api.mercadopago.com/instore/orders/qr/seller/collectors/%s/pos/%s/qrs"; String resourceUrl = String.format(resourceUrlTemplate, appUserId, pointOfSaleId); - LOGGER.info(resourceUrl); - - var requestBody = this.requestBuilder.buildQrCodePaymentCollectionRequest(order, appUserId); + var requestBody = + this.requestBuilder.buildQrCodePaymentCollectionRequest(order, notificationsUrl); ObjectMapper mapper = new ObjectMapper(); String serializedRequestBody = mapper.writeValueAsString(requestBody); - LOGGER.info(serializedRequestBody); - try (HttpClient client = HttpClient.newHttpClient()) { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(resourceUrl)) .header("Authorization", String.format("Bearer %s", privateAccessToken)) - .header("Conten-type", "application/json") - .POST(HttpRequest.BodyPublishers.ofString(serializedRequestBody)) + .header("Content-type", "application/json") + .PUT(HttpRequest.BodyPublishers.ofString(serializedRequestBody)) .build(); HttpResponse response = diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/MercadoPagoRequestBuilder.java b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/MercadoPagoRequestBuilder.java index b90f989..12e4bdd 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/MercadoPagoRequestBuilder.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/MercadoPagoRequestBuilder.java @@ -3,7 +3,6 @@ import br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model.MercadoPagoCashOut; import br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model.MercadoPagoCreateQrCodeForPaymentCollectionRequest; import br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model.MercadoPagoOrderItem; -import br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model.MercadoPagoSponsor; import br.com.fiap.grupo30.fastfood.application.dto.OrderDTO; import java.time.Instant; import java.util.Date; @@ -13,7 +12,7 @@ @Component public class MercadoPagoRequestBuilder { public MercadoPagoCreateQrCodeForPaymentCollectionRequest buildQrCodePaymentCollectionRequest( - OrderDTO order, Long mercadoPagoAppUserId) { + OrderDTO order, String notificationsUrl) { String orderId = order.getOrderId().toString(); MercadoPagoCreateQrCodeForPaymentCollectionRequest requestBody = new MercadoPagoCreateQrCodeForPaymentCollectionRequest(); @@ -22,6 +21,7 @@ public MercadoPagoCreateQrCodeForPaymentCollectionRequest buildQrCodePaymentColl requestBody.setExpirationDate(Date.from(Instant.now().plusSeconds(3600))); requestBody.setExternalReference(orderId); requestBody.setTotalAmount(order.getTotalPrice()); + requestBody.setNotificationUrl(notificationsUrl); MercadoPagoCashOut cashOut = new MercadoPagoCashOut(); cashOut.setAmount(order.getTotalPrice()); @@ -41,8 +41,7 @@ public MercadoPagoCreateQrCodeForPaymentCollectionRequest buildQrCodePaymentColl theirOrderItem.setUnitPrice( ourOrderItem.getProduct().getPrice()); theirOrderItem.setUnitMeasure("unit"); - theirOrderItem.setTotalAmount( - ourOrderItem.getTotalPrice()); + theirOrderItem.setTotalAmount(ourOrderItem.getTotalPrice()); return theirOrderItem; }) .toArray(MercadoPagoOrderItem[]::new); diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/MercadoPagoNotificationDTO.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/MercadoPagoNotificationDTO.java new file mode 100644 index 0000000..600553f --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/MercadoPagoNotificationDTO.java @@ -0,0 +1,22 @@ +package br.com.fiap.grupo30.fastfood.application.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Date; +import java.util.HashMap; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class MercadoPagoNotificationDTO { + private String action; + private String type; + + @JsonProperty("date_created") + private Date dateCreated; + + private String id; + private HashMap data; +} diff --git a/src/main/resources/application-dev-docker.properties b/src/main/resources/application-dev-docker.properties index d789485..3b16849 100644 --- a/src/main/resources/application-dev-docker.properties +++ b/src/main/resources/application-dev-docker.properties @@ -15,8 +15,9 @@ spring.datasource.hikari.connection-timeout=60000 # app integrations.mercadopago.base-url=https://api.mercadopago.com -integrations.mercadopago.public-key=TEST-78f422d3-0d6f-4a39-b7e7-57ef547df9b5 -integrations.mercadopago.access-token=TEST-2593682679309925-072110-2bda203330ae23737187e79862410ee6-356453274 -integrations.mercadopago.app-user-id=356453274 +integrations.mercadopago.public-key=APP_USR-f597bfbc-e6dd-43db-9061-64652759b605 +integrations.mercadopago.access-token=APP_USR-5669469328830836-072119-fbf214281e7130fae8bbf302da74c439-1910105219 +integrations.mercadopago.app-user-id=1910105219 integrations.mercadopago.point-of-sale-id=STORE00001POS001 -integrations.mercadopago.payment-collection.user-id=TESTUSER269524473 \ No newline at end of file +integrations.mercadopago.notifications-url=https://e916-189-29-149-200.ngrok-free.app/webhooks/mercadopago +integrations.mercadopago.payment-collection.user-id=1910105219 \ No newline at end of file diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index 64b6c85..262ef2b 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -15,8 +15,9 @@ spring.datasource.hikari.connection-timeout=60000 # app integrations.mercadopago.base-url=https://api.mercadopago.com -integrations.mercadopago.public-key=TEST-78f422d3-0d6f-4a39-b7e7-57ef547df9b5 -integrations.mercadopago.access-token=TEST-2593682679309925-072110-2bda203330ae23737187e79862410ee6-356453274 -integrations.mercadopago.app-user-id=356453274 +integrations.mercadopago.public-key=APP_USR-f597bfbc-e6dd-43db-9061-64652759b605 +integrations.mercadopago.access-token=APP_USR-5669469328830836-072119-fbf214281e7130fae8bbf302da74c439-1910105219 +integrations.mercadopago.app-user-id=1910105219 integrations.mercadopago.point-of-sale-id=STORE00001POS001 -integrations.mercadopago.payment-collection.user-id=TESTUSER269524473 \ No newline at end of file +integrations.mercadopago.notifications-url=https://e916-189-29-149-200.ngrok-free.app/webhooks/mercadopago +integrations.mercadopago.payment-collection.user-id=1910105219 \ No newline at end of file diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 8e7f387..44c3413 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -14,4 +14,5 @@ integrations.mercadopago.public-key=${MERCADOPAGO_PUBLIC_KEY} integrations.mercadopago.access-token=${MERCADOPAGO_PRIVATE_ACCESS_TOKEN} integrations.mercadopago.app-user-id=${MERCADOPAGO_APP_USER_ID} integrations.mercadopago.point-of-sale-id=${MERCADOPAGO_POINT_OF_SALE_ID} +integrations.mercadopago.notifications-url=${MERCADOPAGO_NOTIFICATIONS_URL} integrations.mercadopago.payment-collection.user-id=${MERCADOPAGO_PAYMENT_COLLECTION_USER_ID} \ No newline at end of file diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties index c6bc4a6..afdbb8c 100644 --- a/src/main/resources/application-test.properties +++ b/src/main/resources/application-test.properties @@ -16,8 +16,9 @@ spring.jpa.properties.hibernate.format_sql=true # app integrations.mercadopago.base-url=https://api.mercadopago.com -integrations.mercadopago.public-key=TEST-78f422d3-0d6f-4a39-b7e7-57ef547df9b5 -integrations.mercadopago.access-token=TEST-2593682679309925-072110-2bda203330ae23737187e79862410ee6-356453274 -integrations.mercadopago.app-user-id=356453274 +integrations.mercadopago.public-key=APP_USR-f597bfbc-e6dd-43db-9061-64652759b605 +integrations.mercadopago.access-token=APP_USR-5669469328830836-072119-fbf214281e7130fae8bbf302da74c439-1910105219 +integrations.mercadopago.app-user-id=1910105219 integrations.mercadopago.point-of-sale-id=STORE00001POS001 -integrations.mercadopago.payment-collection.user-id=TESTUSER269524473 \ No newline at end of file +integrations.mercadopago.notifications-url=https://e916-189-29-149-200.ngrok-free.app/webhooks/mercadopago +integrations.mercadopago.payment-collection.user-id=1910105219 \ No newline at end of file From 4eb388635dfcdc80e7ffde69914d2757ffb3c6bc Mon Sep 17 00:00:00 2001 From: Murilo Kakazu Date: Sun, 21 Jul 2024 20:38:49 -0300 Subject: [PATCH 3/4] fix: make the linter happy... --- .../grupo30/fastfood/adapters/in/rest/WebhookResource.java | 4 +--- .../fastfood/application/dto/MercadoPagoNotificationDTO.java | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/WebhookResource.java b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/WebhookResource.java index 158b28e..3870c73 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/WebhookResource.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/WebhookResource.java @@ -4,7 +4,6 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; -import java.util.HashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; @@ -22,8 +21,7 @@ public class WebhookResource { @PostMapping(value = "/mercadopago") @Operation(summary = "Handle mercadopago events") - public ResponseEntity createCustomer(@RequestBody @Valid HashMap notification) - throws Exception { + public ResponseEntity createCustomer(@RequestBody @Valid Object notification) throws Exception { ObjectMapper mapper = new ObjectMapper(); String serializedRequestBody = mapper.writeValueAsString(notification); LOGGER.info(serializedRequestBody); diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/MercadoPagoNotificationDTO.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/MercadoPagoNotificationDTO.java index 600553f..9c431b8 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/MercadoPagoNotificationDTO.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/MercadoPagoNotificationDTO.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Date; -import java.util.HashMap; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -18,5 +17,5 @@ public class MercadoPagoNotificationDTO { private Date dateCreated; private String id; - private HashMap data; + private Object data; } From 3627bb64414508292e65f4edc8fcae85bb34738c Mon Sep 17 00:00:00 2001 From: Murilo Kakazu Date: Wed, 24 Jul 2024 03:57:31 -0300 Subject: [PATCH 4/4] feat: add mercadopago payment webhook --- .../adapters/in/rest/PaymentResource.java | 18 ++++- .../adapters/in/rest/WebhookResource.java | 65 ++++++++++++++++-- .../out/mercadopago/MercadoPagoAdapter.java | 34 ++++++++-- .../MercadoPagoRequestBuilder.java | 24 +++---- ...ateQrCodeForPaymentCollectionResponse.java | 15 ---- .../dto/CollectPaymentViaCashRequest.java | 12 ++++ .../mercadopago/MercadoPagoCashOutDTO.java} | 4 +- ...QrCodeForPaymentCollectionRequestDTO.java} | 10 +-- .../mercadopago/MercadoPagoOrderItemDTO.java} | 4 +- .../mercadopago/MercadoPagoPaymentDTO.java | 68 +++++++++++++++++++ .../mercadopago/MercadoPagoPaymentStatus.java | 15 ++++ .../dto/mercadopago/MercadoPagoQrCodeDTO.java | 19 ++++++ .../mercadopago/MercadoPagoSponsorDTO.java} | 4 +- .../mercadopago/events/MercadoPagoAction.java | 15 ++++ .../events/MercadoPagoActionEventDTO.java | 31 +++++++++ .../events/MercadoPagoActionEventDataDTO.java | 14 ++++ .../mapper/impl/PaymentQrCodeDTOMapper.java | 10 ++- .../CollectOrderPaymentViaCashUseCase.java | 30 ++++++++ ...lectOrderPaymentViaMercadoPagoUseCase.java | 48 +++++++++++++ .../payment/GeneratePaymentQrCodeUseCase.java | 7 +- .../persistence/jpa/entities/OrderEntity.java | 16 ++++- 21 files changed, 397 insertions(+), 66 deletions(-) delete mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoCreateQrCodeForPaymentCollectionResponse.java create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/application/dto/CollectPaymentViaCashRequest.java rename src/main/java/br/com/fiap/grupo30/fastfood/{adapters/out/mercadopago/model/MercadoPagoCashOut.java => application/dto/mercadopago/MercadoPagoCashOutDTO.java} (67%) rename src/main/java/br/com/fiap/grupo30/fastfood/{adapters/out/mercadopago/model/MercadoPagoCreateQrCodeForPaymentCollectionRequest.java => application/dto/mercadopago/MercadoPagoCreateQrCodeForPaymentCollectionRequestDTO.java} (76%) rename src/main/java/br/com/fiap/grupo30/fastfood/{adapters/out/mercadopago/model/MercadoPagoOrderItem.java => application/dto/mercadopago/MercadoPagoOrderItemDTO.java} (85%) create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/MercadoPagoPaymentDTO.java create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/MercadoPagoPaymentStatus.java create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/MercadoPagoQrCodeDTO.java rename src/main/java/br/com/fiap/grupo30/fastfood/{adapters/out/mercadopago/model/MercadoPagoSponsor.java => application/dto/mercadopago/MercadoPagoSponsorDTO.java} (66%) create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/events/MercadoPagoAction.java create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/events/MercadoPagoActionEventDTO.java create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/events/MercadoPagoActionEventDataDTO.java create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/domain/usecases/payment/CollectOrderPaymentViaCashUseCase.java create mode 100644 src/main/java/br/com/fiap/grupo30/fastfood/domain/usecases/payment/CollectOrderPaymentViaMercadoPagoUseCase.java diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/PaymentResource.java b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/PaymentResource.java index 5574635..4da0d2b 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/PaymentResource.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/PaymentResource.java @@ -1,6 +1,9 @@ package br.com.fiap.grupo30.fastfood.adapters.in.rest; +import br.com.fiap.grupo30.fastfood.application.dto.CollectPaymentViaCashRequest; +import br.com.fiap.grupo30.fastfood.application.dto.OrderDTO; import br.com.fiap.grupo30.fastfood.application.dto.PaymentQrCodeDTO; +import br.com.fiap.grupo30.fastfood.domain.usecases.payment.CollectOrderPaymentViaCashUseCase; import br.com.fiap.grupo30.fastfood.domain.usecases.payment.GeneratePaymentQrCodeUseCase; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -8,6 +11,7 @@ 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.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -17,10 +21,14 @@ public class PaymentResource { private final GeneratePaymentQrCodeUseCase generatePaymentQrCodeUseCase; + private final CollectOrderPaymentViaCashUseCase collectOrderPaymentViaCashUseCase; @Autowired - public PaymentResource(GeneratePaymentQrCodeUseCase generatePaymentQrCodeUseCase) { + public PaymentResource( + GeneratePaymentQrCodeUseCase generatePaymentQrCodeUseCase, + CollectOrderPaymentViaCashUseCase collectOrderPaymentViaCashUseCase) { this.generatePaymentQrCodeUseCase = generatePaymentQrCodeUseCase; + this.collectOrderPaymentViaCashUseCase = collectOrderPaymentViaCashUseCase; } @PostMapping(value = "/{orderId}/qrcode") @@ -30,4 +38,12 @@ public ResponseEntity generateQrCodeForPaymentCollection( PaymentQrCodeDTO qrCode = this.generatePaymentQrCodeUseCase.execute(orderId); return ResponseEntity.ok().body(qrCode); } + + @PostMapping(value = "/{orderId}/collect") + @Operation(summary = "Collect payment by cash") + public ResponseEntity collectPaymentByBash( + @PathVariable Long orderId, @RequestBody CollectPaymentViaCashRequest request) { + var order = this.collectOrderPaymentViaCashUseCase.execute(orderId, request); + return ResponseEntity.ok().body(order); + } } diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/WebhookResource.java b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/WebhookResource.java index 3870c73..be4549a 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/WebhookResource.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/WebhookResource.java @@ -1,11 +1,16 @@ package br.com.fiap.grupo30.fastfood.adapters.in.rest; +import br.com.fiap.grupo30.fastfood.application.dto.mercadopago.events.MercadoPagoAction; +import br.com.fiap.grupo30.fastfood.application.dto.mercadopago.events.MercadoPagoActionEventDTO; +import br.com.fiap.grupo30.fastfood.domain.usecases.payment.CollectOrderPaymentViaMercadoPagoUseCase; import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; +import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -15,16 +20,62 @@ @RestController @RequestMapping(value = "/webhooks") @Tag(name = "Wehooks Resource", description = "RESTful API for webhook events.") +@SuppressWarnings("PMD.InvalidLogMessageFormat") public class WebhookResource { - private static final Logger LOGGER = LoggerFactory.getLogger(WebhookResource.class); + private static final Logger LOG = LoggerFactory.getLogger(WebhookResource.class); + + private final CollectOrderPaymentViaMercadoPagoUseCase collectOrderPaymentMercadoPago; + + @Autowired + public WebhookResource( + CollectOrderPaymentViaMercadoPagoUseCase collectOrderPaymentMercadoPago) { + this.collectOrderPaymentMercadoPago = collectOrderPaymentMercadoPago; + } @PostMapping(value = "/mercadopago") @Operation(summary = "Handle mercadopago events") - public ResponseEntity createCustomer(@RequestBody @Valid Object notification) throws Exception { - ObjectMapper mapper = new ObjectMapper(); - String serializedRequestBody = mapper.writeValueAsString(notification); - LOGGER.info(serializedRequestBody); - return ResponseEntity.noContent().build(); + public ResponseEntity handleMercadoPagoEvent(@RequestBody Map payload) + throws Exception { + + // TODO: validate request authenticity + + try { + // topic event + if (payload.containsKey("topic") && !payload.containsKey("action")) { + LOG.info("Ignoring topic event from mercadopago", payload); + } + // action event + else if (payload.containsKey("action") && !payload.containsKey("topic")) { + LOG.info("Received action event from mercadopago", payload); + MercadoPagoActionEventDTO actionEvent = + new ObjectMapper().convertValue(payload, MercadoPagoActionEventDTO.class); + if (!"payment".equals(actionEvent.getType())) { + LOG.info("Ignoring action event from mercadopago", payload); + return ResponseEntity.noContent().build(); + } + + this.handlePaymentEvent(actionEvent); + } + // unknown event + else { + LOG.warn("Ignoring unknown event from mercadopago", payload); + return ResponseEntity.badRequest().body("Unsupported mercadopago event"); + } + + return ResponseEntity.noContent().build(); + } catch (Exception e) { + LOG.error("Failed to consume mercadopago event", e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body("Error processing event"); + } + } + + private void handlePaymentEvent(MercadoPagoActionEventDTO event) { + if (MercadoPagoAction.PAYMENT_CREATED.getValue().equals(event.getAction())) { + collectOrderPaymentMercadoPago.execute(event); + } else { + LOG.warn("Ignoring unimplemented payment event", event); + } } } diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/MercadoPagoAdapter.java b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/MercadoPagoAdapter.java index 19adcce..e831b71 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/MercadoPagoAdapter.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/MercadoPagoAdapter.java @@ -1,7 +1,8 @@ package br.com.fiap.grupo30.fastfood.adapters.out.mercadopago; -import br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model.MercadoPagoCreateQrCodeForPaymentCollectionResponse; import br.com.fiap.grupo30.fastfood.application.dto.OrderDTO; +import br.com.fiap.grupo30.fastfood.application.dto.mercadopago.MercadoPagoPaymentDTO; +import br.com.fiap.grupo30.fastfood.application.dto.mercadopago.MercadoPagoQrCodeDTO; import com.fasterxml.jackson.databind.ObjectMapper; import java.net.URI; import java.net.http.HttpClient; @@ -40,8 +41,7 @@ public MercadoPagoAdapter(MercadoPagoRequestBuilder requestBuilder) { this.requestBuilder = requestBuilder; } - public MercadoPagoCreateQrCodeForPaymentCollectionResponse createQrCodeForPaymentCollection( - OrderDTO order) throws Exception { + public MercadoPagoQrCodeDTO createQrCodeForPaymentCollection(OrderDTO order) throws Exception { String resourceUrlTemplate = "https://api.mercadopago.com/instore/orders/qr/seller/collectors/%s/pos/%s/qrs"; String resourceUrl = String.format(resourceUrlTemplate, appUserId, pointOfSaleId); @@ -63,10 +63,30 @@ public MercadoPagoCreateQrCodeForPaymentCollectionResponse createQrCodeForPaymen HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - MercadoPagoCreateQrCodeForPaymentCollectionResponse result = - mapper.readValue( - response.body(), - MercadoPagoCreateQrCodeForPaymentCollectionResponse.class); + MercadoPagoQrCodeDTO result = + mapper.readValue(response.body(), MercadoPagoQrCodeDTO.class); + + return result; + } + } + + public MercadoPagoPaymentDTO getPayment(String paymentId) throws Exception { + String resourceUrlTemplate = "https://api.mercadopago.com/v1/payments/%s"; + String resourceUrl = String.format(resourceUrlTemplate, paymentId); + + try (HttpClient client = HttpClient.newHttpClient()) { + HttpRequest request = + HttpRequest.newBuilder() + .uri(URI.create(resourceUrl)) + .header("Authorization", String.format("Bearer %s", privateAccessToken)) + .header("Content-type", "application/json") + .GET() + .build(); + + HttpResponse response = + client.send(request, HttpResponse.BodyHandlers.ofString()); + MercadoPagoPaymentDTO result = + new ObjectMapper().readValue(response.body(), MercadoPagoPaymentDTO.class); return result; } diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/MercadoPagoRequestBuilder.java b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/MercadoPagoRequestBuilder.java index 12e4bdd..b60f6ca 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/MercadoPagoRequestBuilder.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/MercadoPagoRequestBuilder.java @@ -1,9 +1,9 @@ package br.com.fiap.grupo30.fastfood.adapters.out.mercadopago; -import br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model.MercadoPagoCashOut; -import br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model.MercadoPagoCreateQrCodeForPaymentCollectionRequest; -import br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model.MercadoPagoOrderItem; import br.com.fiap.grupo30.fastfood.application.dto.OrderDTO; +import br.com.fiap.grupo30.fastfood.application.dto.mercadopago.MercadoPagoCashOutDTO; +import br.com.fiap.grupo30.fastfood.application.dto.mercadopago.MercadoPagoCreateQrCodeForPaymentCollectionRequestDTO; +import br.com.fiap.grupo30.fastfood.application.dto.mercadopago.MercadoPagoOrderItemDTO; import java.time.Instant; import java.util.Date; import java.util.stream.Stream; @@ -11,11 +11,11 @@ @Component public class MercadoPagoRequestBuilder { - public MercadoPagoCreateQrCodeForPaymentCollectionRequest buildQrCodePaymentCollectionRequest( - OrderDTO order, String notificationsUrl) { + public MercadoPagoCreateQrCodeForPaymentCollectionRequestDTO + buildQrCodePaymentCollectionRequest(OrderDTO order, String notificationsUrl) { String orderId = order.getOrderId().toString(); - MercadoPagoCreateQrCodeForPaymentCollectionRequest requestBody = - new MercadoPagoCreateQrCodeForPaymentCollectionRequest(); + MercadoPagoCreateQrCodeForPaymentCollectionRequestDTO requestBody = + new MercadoPagoCreateQrCodeForPaymentCollectionRequestDTO(); requestBody.setTitle(String.format("Pedido %s", orderId)); requestBody.setDescription(String.format("Pedido da lanchonete fastfood")); requestBody.setExpirationDate(Date.from(Instant.now().plusSeconds(3600))); @@ -23,15 +23,15 @@ public MercadoPagoCreateQrCodeForPaymentCollectionRequest buildQrCodePaymentColl requestBody.setTotalAmount(order.getTotalPrice()); requestBody.setNotificationUrl(notificationsUrl); - MercadoPagoCashOut cashOut = new MercadoPagoCashOut(); + MercadoPagoCashOutDTO cashOut = new MercadoPagoCashOutDTO(); cashOut.setAmount(order.getTotalPrice()); - MercadoPagoOrderItem[] items = + MercadoPagoOrderItemDTO[] items = Stream.of(order.getItems()) .map( (ourOrderItem) -> { - MercadoPagoOrderItem theirOrderItem = - new MercadoPagoOrderItem(); + MercadoPagoOrderItemDTO theirOrderItem = + new MercadoPagoOrderItemDTO(); theirOrderItem.setTitle(ourOrderItem.getProduct().getName()); theirOrderItem.setDescription( ourOrderItem.getProduct().getDescription()); @@ -44,7 +44,7 @@ public MercadoPagoCreateQrCodeForPaymentCollectionRequest buildQrCodePaymentColl theirOrderItem.setTotalAmount(ourOrderItem.getTotalPrice()); return theirOrderItem; }) - .toArray(MercadoPagoOrderItem[]::new); + .toArray(MercadoPagoOrderItemDTO[]::new); requestBody.setItems(items); return requestBody; diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoCreateQrCodeForPaymentCollectionResponse.java b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoCreateQrCodeForPaymentCollectionResponse.java deleted file mode 100644 index 710729c..0000000 --- a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoCreateQrCodeForPaymentCollectionResponse.java +++ /dev/null @@ -1,15 +0,0 @@ -package br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; - -@Data -@NoArgsConstructor -@AllArgsConstructor -@EqualsAndHashCode -public class MercadoPagoCreateQrCodeForPaymentCollectionResponse { - private String qr_data; - private String in_store_order_id; -} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/CollectPaymentViaCashRequest.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/CollectPaymentViaCashRequest.java new file mode 100644 index 0000000..1dc4f55 --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/CollectPaymentViaCashRequest.java @@ -0,0 +1,12 @@ +package br.com.fiap.grupo30.fastfood.application.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CollectPaymentViaCashRequest { + private Double amount; +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoCashOut.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/MercadoPagoCashOutDTO.java similarity index 67% rename from src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoCashOut.java rename to src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/MercadoPagoCashOutDTO.java index 6527de5..21baf42 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoCashOut.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/MercadoPagoCashOutDTO.java @@ -1,4 +1,4 @@ -package br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model; +package br.com.fiap.grupo30.fastfood.application.dto.mercadopago; import lombok.AllArgsConstructor; import lombok.Data; @@ -9,6 +9,6 @@ @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode -public class MercadoPagoCashOut { +public class MercadoPagoCashOutDTO { private Double amount; } diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoCreateQrCodeForPaymentCollectionRequest.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/MercadoPagoCreateQrCodeForPaymentCollectionRequestDTO.java similarity index 76% rename from src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoCreateQrCodeForPaymentCollectionRequest.java rename to src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/MercadoPagoCreateQrCodeForPaymentCollectionRequestDTO.java index 060f428..107d50c 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoCreateQrCodeForPaymentCollectionRequest.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/MercadoPagoCreateQrCodeForPaymentCollectionRequestDTO.java @@ -1,4 +1,4 @@ -package br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model; +package br.com.fiap.grupo30.fastfood.application.dto.mercadopago; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; @@ -12,7 +12,7 @@ @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode -public class MercadoPagoCreateQrCodeForPaymentCollectionRequest { +public class MercadoPagoCreateQrCodeForPaymentCollectionRequestDTO { private String title; private String description; @@ -26,11 +26,11 @@ public class MercadoPagoCreateQrCodeForPaymentCollectionRequest { timezone = "GMT") private Date expirationDate; - private MercadoPagoSponsor sponsor; - private MercadoPagoOrderItem[] items; + private MercadoPagoSponsorDTO sponsor; + private MercadoPagoOrderItemDTO[] items; @JsonProperty("cash_out") - private MercadoPagoCashOut cashOut; + private MercadoPagoCashOutDTO cashOut; @JsonProperty("total_amount") private Double totalAmount; diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoOrderItem.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/MercadoPagoOrderItemDTO.java similarity index 85% rename from src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoOrderItem.java rename to src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/MercadoPagoOrderItemDTO.java index d9671c6..2e27c0f 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoOrderItem.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/MercadoPagoOrderItemDTO.java @@ -1,4 +1,4 @@ -package br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model; +package br.com.fiap.grupo30.fastfood.application.dto.mercadopago; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; @@ -10,7 +10,7 @@ @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode -public class MercadoPagoOrderItem { +public class MercadoPagoOrderItemDTO { @JsonProperty("sku_number") private String skuNumber; diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/MercadoPagoPaymentDTO.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/MercadoPagoPaymentDTO.java new file mode 100644 index 0000000..df41598 --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/MercadoPagoPaymentDTO.java @@ -0,0 +1,68 @@ +package br.com.fiap.grupo30.fastfood.application.dto.mercadopago; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Date; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +@JsonIgnoreProperties(ignoreUnknown = true) +public class MercadoPagoPaymentDTO { + private Long id; + + @JsonProperty("date_created") + private Date dateCreated; + + @JsonProperty("date_approved") + private Date dateApproved; + + @JsonProperty("date_last_updated") + private Date dateLastUpdated; + + @JsonProperty("money_release_date") + private Date moneyReleaseDate; + + @JsonProperty("payment_method_id") + private String paymentMethodId; + + @JsonProperty("payment_type_id") + private String paymentTypeId; + + private String status; + + @JsonProperty("status_detail") + private String statusDetail; + + @JsonProperty("currency_id") + private String currencyId; + + private String description; + + @JsonProperty("collector_id") + private Long collectorId; + + @JsonProperty("external_reference") + private String externalReference; + + @JsonProperty("transaction_amount") + private Double transactionAmount; + + @JsonProperty("transaction_amount_refunded") + private Double transactionAmountRefunded; + + @JsonProperty("coupon_amount") + private Double couponAmount; + + private Long installments; + // private Map transaction_details; + // private Map payer; + // private Map metadata; + // private Map additional_info; + // private Map card; +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/MercadoPagoPaymentStatus.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/MercadoPagoPaymentStatus.java new file mode 100644 index 0000000..a55ffde --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/MercadoPagoPaymentStatus.java @@ -0,0 +1,15 @@ +package br.com.fiap.grupo30.fastfood.application.dto.mercadopago; + +public enum MercadoPagoPaymentStatus { + APPROVED("approved"); + + private String value; + + MercadoPagoPaymentStatus(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/MercadoPagoQrCodeDTO.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/MercadoPagoQrCodeDTO.java new file mode 100644 index 0000000..435a7fd --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/MercadoPagoQrCodeDTO.java @@ -0,0 +1,19 @@ +package br.com.fiap.grupo30.fastfood.application.dto.mercadopago; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +public class MercadoPagoQrCodeDTO { + @JsonProperty("qr_data") + private String qrData; + + @JsonProperty("in_store_order_id") + private String inStoreOrderId; +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoSponsor.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/MercadoPagoSponsorDTO.java similarity index 66% rename from src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoSponsor.java rename to src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/MercadoPagoSponsorDTO.java index 15d52b1..529bd73 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/out/mercadopago/model/MercadoPagoSponsor.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/MercadoPagoSponsorDTO.java @@ -1,4 +1,4 @@ -package br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model; +package br.com.fiap.grupo30.fastfood.application.dto.mercadopago; import lombok.AllArgsConstructor; import lombok.Data; @@ -9,6 +9,6 @@ @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode -public class MercadoPagoSponsor { +public class MercadoPagoSponsorDTO { private Long id; } diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/events/MercadoPagoAction.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/events/MercadoPagoAction.java new file mode 100644 index 0000000..4515302 --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/events/MercadoPagoAction.java @@ -0,0 +1,15 @@ +package br.com.fiap.grupo30.fastfood.application.dto.mercadopago.events; + +public enum MercadoPagoAction { + PAYMENT_CREATED("payment.created"); + + private String value; + + MercadoPagoAction(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/events/MercadoPagoActionEventDTO.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/events/MercadoPagoActionEventDTO.java new file mode 100644 index 0000000..e26fe00 --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/events/MercadoPagoActionEventDTO.java @@ -0,0 +1,31 @@ +package br.com.fiap.grupo30.fastfood.application.dto.mercadopago.events; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Date; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +public class MercadoPagoActionEventDTO { + private String id; + private String action; + private String type; + private MercadoPagoActionEventDataDTO data; + + @JsonProperty("user_id") + private String userId; + + @JsonProperty("api_version") + private String apiVersion; + + @JsonProperty("date_created") + private Date dateCreated; + + @JsonProperty("live_mode") + private Boolean liveMode; +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/events/MercadoPagoActionEventDataDTO.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/events/MercadoPagoActionEventDataDTO.java new file mode 100644 index 0000000..368caaf --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/mercadopago/events/MercadoPagoActionEventDataDTO.java @@ -0,0 +1,14 @@ +package br.com.fiap.grupo30.fastfood.application.dto.mercadopago.events; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +public class MercadoPagoActionEventDataDTO { + private String id; +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/PaymentQrCodeDTOMapper.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/PaymentQrCodeDTOMapper.java index 2e52830..6e7db62 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/PaymentQrCodeDTOMapper.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/mapper/impl/PaymentQrCodeDTOMapper.java @@ -1,18 +1,16 @@ package br.com.fiap.grupo30.fastfood.application.mapper.impl; -import br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model.MercadoPagoCreateQrCodeForPaymentCollectionResponse; import br.com.fiap.grupo30.fastfood.application.dto.PaymentQrCodeDTO; +import br.com.fiap.grupo30.fastfood.application.dto.mercadopago.MercadoPagoQrCodeDTO; import br.com.fiap.grupo30.fastfood.application.mapper.UniDirectionalMapper; import org.springframework.stereotype.Component; @Component public class PaymentQrCodeDTOMapper - implements UniDirectionalMapper< - MercadoPagoCreateQrCodeForPaymentCollectionResponse, PaymentQrCodeDTO> { + implements UniDirectionalMapper { @Override - public PaymentQrCodeDTO map( - MercadoPagoCreateQrCodeForPaymentCollectionResponse mercadoPagoData) { - PaymentQrCodeDTO dto = new PaymentQrCodeDTO(mercadoPagoData.getQr_data()); + public PaymentQrCodeDTO map(MercadoPagoQrCodeDTO mercadoPagoData) { + PaymentQrCodeDTO dto = new PaymentQrCodeDTO(mercadoPagoData.getQrData()); return dto; } } diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/domain/usecases/payment/CollectOrderPaymentViaCashUseCase.java b/src/main/java/br/com/fiap/grupo30/fastfood/domain/usecases/payment/CollectOrderPaymentViaCashUseCase.java new file mode 100644 index 0000000..fdf24b2 --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/domain/usecases/payment/CollectOrderPaymentViaCashUseCase.java @@ -0,0 +1,30 @@ +package br.com.fiap.grupo30.fastfood.domain.usecases.payment; + +import br.com.fiap.grupo30.fastfood.application.dto.CollectPaymentViaCashRequest; +import br.com.fiap.grupo30.fastfood.application.dto.OrderDTO; +import br.com.fiap.grupo30.fastfood.application.exceptions.ResourceNotFoundException; +import br.com.fiap.grupo30.fastfood.infrastructure.out.persistence.jpa.entities.OrderEntity; +import br.com.fiap.grupo30.fastfood.infrastructure.out.persistence.jpa.repositories.OrderRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class CollectOrderPaymentViaCashUseCase { + private final OrderRepository orderRepository; + + @Autowired + public CollectOrderPaymentViaCashUseCase(OrderRepository orderRepository) { + this.orderRepository = orderRepository; + } + + public OrderDTO execute(Long orderId, CollectPaymentViaCashRequest payment) { + OrderEntity order = + this.orderRepository + .findById(orderId) + .orElseThrow(() -> new ResourceNotFoundException("Order not found")); + + order.setPaymentCollected(payment.getAmount()); + + return this.orderRepository.save(order).toDTO(); + } +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/domain/usecases/payment/CollectOrderPaymentViaMercadoPagoUseCase.java b/src/main/java/br/com/fiap/grupo30/fastfood/domain/usecases/payment/CollectOrderPaymentViaMercadoPagoUseCase.java new file mode 100644 index 0000000..07f6545 --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/domain/usecases/payment/CollectOrderPaymentViaMercadoPagoUseCase.java @@ -0,0 +1,48 @@ +package br.com.fiap.grupo30.fastfood.domain.usecases.payment; + +import br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.MercadoPagoAdapter; +import br.com.fiap.grupo30.fastfood.application.dto.mercadopago.MercadoPagoPaymentDTO; +import br.com.fiap.grupo30.fastfood.application.dto.mercadopago.MercadoPagoPaymentStatus; +import br.com.fiap.grupo30.fastfood.application.dto.mercadopago.events.MercadoPagoActionEventDTO; +import br.com.fiap.grupo30.fastfood.application.exceptions.PaymentProcessingFailedException; +import br.com.fiap.grupo30.fastfood.application.exceptions.ResourceNotFoundException; +import br.com.fiap.grupo30.fastfood.infrastructure.out.persistence.jpa.entities.OrderEntity; +import br.com.fiap.grupo30.fastfood.infrastructure.out.persistence.jpa.repositories.OrderRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class CollectOrderPaymentViaMercadoPagoUseCase { + private final OrderRepository orderRepository; + private final MercadoPagoAdapter mercadoPagoAdapter; + + @Autowired + public CollectOrderPaymentViaMercadoPagoUseCase( + OrderRepository orderRepository, MercadoPagoAdapter mercadoPagoAdapter) { + this.orderRepository = orderRepository; + this.mercadoPagoAdapter = mercadoPagoAdapter; + } + + public void execute(MercadoPagoActionEventDTO mercadoPagoPaymentEvent) { + try { + MercadoPagoPaymentDTO payment = + this.mercadoPagoAdapter.getPayment(mercadoPagoPaymentEvent.getData().getId()); + + OrderEntity order = + this.orderRepository + .findById(Long.parseLong(payment.getExternalReference())) + .orElseThrow(() -> new ResourceNotFoundException("Order not found")); + + if (MercadoPagoPaymentStatus.APPROVED.getValue().equals(payment.getStatus())) { + order.setPaymentCollected(payment.getTransactionAmount()); + } else { + order.setPaymentRejected(); + } + + this.orderRepository.save(order); + + } catch (Exception e) { + throw new PaymentProcessingFailedException("Could not process payment collection", e); + } + } +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/domain/usecases/payment/GeneratePaymentQrCodeUseCase.java b/src/main/java/br/com/fiap/grupo30/fastfood/domain/usecases/payment/GeneratePaymentQrCodeUseCase.java index 85d3ffe..f689b36 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/domain/usecases/payment/GeneratePaymentQrCodeUseCase.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/domain/usecases/payment/GeneratePaymentQrCodeUseCase.java @@ -1,13 +1,12 @@ package br.com.fiap.grupo30.fastfood.domain.usecases.payment; import br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.MercadoPagoAdapter; -import br.com.fiap.grupo30.fastfood.adapters.out.mercadopago.model.MercadoPagoCreateQrCodeForPaymentCollectionResponse; import br.com.fiap.grupo30.fastfood.application.dto.PaymentQrCodeDTO; +import br.com.fiap.grupo30.fastfood.application.dto.mercadopago.MercadoPagoQrCodeDTO; import br.com.fiap.grupo30.fastfood.application.exceptions.PaymentProcessingFailedException; import br.com.fiap.grupo30.fastfood.application.exceptions.ResourceNotFoundException; import br.com.fiap.grupo30.fastfood.application.mapper.impl.PaymentQrCodeDTOMapper; import br.com.fiap.grupo30.fastfood.infrastructure.out.persistence.jpa.entities.OrderEntity; -import br.com.fiap.grupo30.fastfood.infrastructure.out.persistence.jpa.entities.PaymentStatus; import br.com.fiap.grupo30.fastfood.infrastructure.out.persistence.jpa.repositories.OrderRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -34,7 +33,7 @@ public PaymentQrCodeDTO execute(Long orderId) { .findById(orderId) .orElseThrow(() -> new ResourceNotFoundException("Order not found")); - MercadoPagoCreateQrCodeForPaymentCollectionResponse qrCodeResponse; + MercadoPagoQrCodeDTO qrCodeResponse; try { qrCodeResponse = this.mercadoPagoAdapter.createQrCodeForPaymentCollection(order.toDTO()); @@ -42,7 +41,7 @@ public PaymentQrCodeDTO execute(Long orderId) { throw new PaymentProcessingFailedException("Could not start payment processing", e); } - order.updatePaymentStatus(PaymentStatus.PROCESSING); + order.setPaymentProcessing(); this.orderRepository.save(order); return qrCodeMapper.map(qrCodeResponse); diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/infrastructure/out/persistence/jpa/entities/OrderEntity.java b/src/main/java/br/com/fiap/grupo30/fastfood/infrastructure/out/persistence/jpa/entities/OrderEntity.java index 18d9f8d..5af5227 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/infrastructure/out/persistence/jpa/entities/OrderEntity.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/infrastructure/out/persistence/jpa/entities/OrderEntity.java @@ -152,9 +152,19 @@ public void setCustomer(CustomerEntity customer) { this.customer = customer; } - public void updatePaymentStatus(PaymentStatus status) { - this.payment.setStatus(status); - this.payment.setAmount(this.totalPrice); + public void setPaymentProcessing() { + this.payment.setStatus(PaymentStatus.PROCESSING); + this.payment.setAmount(this.getTotalPrice()); + } + + public void setPaymentCollected(Double paymentCollectedAmount) { + this.payment.setStatus(PaymentStatus.COLLECTED); + this.payment.setAmount(paymentCollectedAmount); + } + + public void setPaymentRejected() { + this.payment.setStatus(PaymentStatus.REJECTED); + this.payment.setAmount(this.getTotalPrice()); } public OrderDTO toDTO() {