Skip to content
This repository has been archived by the owner on Dec 4, 2024. It is now read-only.

Commit

Permalink
Merge remote-tracking branch 'origin/main' into refactor/folder-struc…
Browse files Browse the repository at this point in the history
…ture-clean-architecture
  • Loading branch information
MuriloKakazu committed Jul 27, 2024
2 parents 4f07cad + 3ce5a11 commit 7133801
Show file tree
Hide file tree
Showing 44 changed files with 1,024 additions and 18 deletions.
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ services:
- database
build: .
environment:
SPRING_PROFILES_ACTIVE: "dev"
SPRING_PROFILES_ACTIVE: "dev-docker"
ports:
- "8080:8080"
networks:
Expand Down
2 changes: 2 additions & 0 deletions init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
12 changes: 12 additions & 0 deletions scripts/create-mercado-pago-point-of-sale.sh
Original file line number Diff line number Diff line change
@@ -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": true,
"name": "First POS",
"store_id": 62212711
}'
61 changes: 61 additions & 0 deletions scripts/create-mercado-pago-store.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
curl -X POST \
'https://api.mercadopago.com/users/1910105219/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"
}'
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package br.com.fiap.grupo30.fastfood.adapters.out.mercadopago;

import br.com.fiap.grupo30.fastfood.presentation.presenters.dto.OrderDTO;
import br.com.fiap.grupo30.fastfood.presentation.presenters.dto.mercadopago.MercadoPagoPaymentDTO;
import br.com.fiap.grupo30.fastfood.presentation.presenters.dto.mercadopago.MercadoPagoQrCodeDTO;
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.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.notifications-url}")
private String notificationsUrl;

@Value("${integrations.mercadopago.payment-collection.user-id}")
private String userIdForPaymentCollection;

private final MercadoPagoRequestBuilder requestBuilder;

public MercadoPagoAdapter(MercadoPagoRequestBuilder requestBuilder) {
this.requestBuilder = requestBuilder;
}

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);

var requestBody =
this.requestBuilder.buildQrCodePaymentCollectionRequest(order, notificationsUrl);

ObjectMapper mapper = new ObjectMapper();
String serializedRequestBody = mapper.writeValueAsString(requestBody);

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")
.PUT(HttpRequest.BodyPublishers.ofString(serializedRequestBody))
.build();

HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
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<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
MercadoPagoPaymentDTO result =
new ObjectMapper().readValue(response.body(), MercadoPagoPaymentDTO.class);

return result;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package br.com.fiap.grupo30.fastfood.adapters.out.mercadopago;

import br.com.fiap.grupo30.fastfood.presentation.presenters.dto.OrderDTO;
import br.com.fiap.grupo30.fastfood.presentation.presenters.dto.mercadopago.MercadoPagoCashOutDTO;
import br.com.fiap.grupo30.fastfood.presentation.presenters.dto.mercadopago.MercadoPagoCreateQrCodeForPaymentCollectionRequestDTO;
import br.com.fiap.grupo30.fastfood.presentation.presenters.dto.mercadopago.MercadoPagoOrderItemDTO;
import java.time.Instant;
import java.util.Date;
import java.util.stream.Stream;
import org.springframework.stereotype.Component;

@Component
public class MercadoPagoRequestBuilder {
public MercadoPagoCreateQrCodeForPaymentCollectionRequestDTO
buildQrCodePaymentCollectionRequest(OrderDTO order, String notificationsUrl) {
String orderId = order.getOrderId().toString();
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)));
requestBody.setExternalReference(orderId);
requestBody.setTotalAmount(order.getTotalPrice());
requestBody.setNotificationUrl(notificationsUrl);

MercadoPagoCashOutDTO cashOut = new MercadoPagoCashOutDTO();
cashOut.setAmount(order.getTotalPrice());

MercadoPagoOrderItemDTO[] items =
Stream.of(order.getItems())
.map(
(ourOrderItem) -> {
MercadoPagoOrderItemDTO theirOrderItem =
new MercadoPagoOrderItemDTO();
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(MercadoPagoOrderItemDTO[]::new);
requestBody.setItems(items);

return requestBody;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package br.com.fiap.grupo30.fastfood.domain.usecases.payment;

import br.com.fiap.grupo30.fastfood.infrastructure.persistence.entities.OrderEntity;
import br.com.fiap.grupo30.fastfood.infrastructure.persistence.repositories.JpaOrderRepository;
import br.com.fiap.grupo30.fastfood.presentation.presenters.dto.CollectPaymentViaCashRequest;
import br.com.fiap.grupo30.fastfood.presentation.presenters.dto.OrderDTO;
import br.com.fiap.grupo30.fastfood.presentation.presenters.exceptions.ResourceNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class CollectOrderPaymentViaCashUseCase {
private final JpaOrderRepository orderRepository;

@Autowired
public CollectOrderPaymentViaCashUseCase(JpaOrderRepository 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();
}
}
Original file line number Diff line number Diff line change
@@ -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.infrastructure.persistence.entities.OrderEntity;
import br.com.fiap.grupo30.fastfood.infrastructure.persistence.repositories.JpaOrderRepository;
import br.com.fiap.grupo30.fastfood.presentation.presenters.dto.mercadopago.MercadoPagoPaymentDTO;
import br.com.fiap.grupo30.fastfood.presentation.presenters.dto.mercadopago.MercadoPagoPaymentStatus;
import br.com.fiap.grupo30.fastfood.presentation.presenters.dto.mercadopago.events.MercadoPagoActionEventDTO;
import br.com.fiap.grupo30.fastfood.presentation.presenters.exceptions.PaymentProcessingFailedException;
import br.com.fiap.grupo30.fastfood.presentation.presenters.exceptions.ResourceNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class CollectOrderPaymentViaMercadoPagoUseCase {
private final JpaOrderRepository orderRepository;
private final MercadoPagoAdapter mercadoPagoAdapter;

@Autowired
public CollectOrderPaymentViaMercadoPagoUseCase(
JpaOrderRepository 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);
}
}
}
Loading

0 comments on commit 7133801

Please sign in to comment.