Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ dependencies {
implementation 'com.github.lolgab:snunit-autowire_native0.4.0-M2_2.11:0.0.4'
implementation 'jakarta.validation:jakarta.validation-api:3.0.2'

implementation 'com.mercadopago:sdk-java:2.5.0'

// Swagger documentation
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0'

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.cuoco.adapter.in.controller;

import com.cuoco.adapter.in.controller.model.UserPaymentResponse;
import com.cuoco.application.port.in.CreateUserPaymentCommand;
import com.cuoco.application.port.in.ProcessUserPaymentCommand;
import com.cuoco.application.usecase.model.UserPayment;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@Slf4j
@RestController
@RequestMapping("/payments")
@RequiredArgsConstructor
public class UserPaymentsControllerAdapter {

private final CreateUserPaymentCommand createUserPaymentCommand;
private final ProcessUserPaymentCommand processUserPaymentCommand;

@PostMapping
public ResponseEntity<UserPaymentResponse> init() {
log.info("Executing POST for init user subscription payment");

UserPayment userPayment = createUserPaymentCommand.execute();
UserPaymentResponse response = UserPaymentResponse.fromDomain(userPayment);

log.info("User subscription payment response: {}", response);
return ResponseEntity.ok(response);
}


@PostMapping("/webhook")
public ResponseEntity<Void> processPayment(@RequestBody Map<String, Object> payload) {
log.info("Executing POST for payment webhook: {}", payload);

ProcessUserPaymentCommand.Command command = ProcessUserPaymentCommand.Command.builder()
.payload(payload)
.build();

processUserPaymentCommand.execute(command);

log.info("User payment webhook processed successfully");
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.cuoco.adapter.in.controller.model;

public class SubscriptionResponse {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.cuoco.adapter.in.controller.model;

import com.cuoco.application.usecase.model.UserPayment;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
@JsonIgnoreProperties(ignoreUnknown = true)
public class UserPaymentResponse {

private String externalId;
private String externalReference;
private String checkoutUrl;

public static UserPaymentResponse fromDomain(UserPayment userPayment) {
return UserPaymentResponse.builder()
.externalId(userPayment.getExternalId())
.externalReference(userPayment.getExternalReference())
.checkoutUrl(userPayment.getCheckoutUrl())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ protected boolean shouldNotFilter(HttpServletRequest request) {
|| matcher.match("/cook-levels", request.getRequestURI())
|| matcher.match("/meal-types", request.getRequestURI())
|| matcher.match("/preparation-times", request.getRequestURI())
|| matcher.match("/payments/webhook", request.getRequestURI())
|| matcher.match("/v3/api-docs/**", request.getRequestURI())
|| matcher.match("/swagger-ui/**", request.getRequestURI())
|| matcher.match("/swagger-ui.html", request.getRequestURI());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.cuoco.adapter.out.hibernate;

import com.cuoco.adapter.out.hibernate.model.UserHibernateModel;
import com.cuoco.adapter.out.hibernate.model.UserPaymentsHibernateModel;
import com.cuoco.adapter.out.hibernate.repository.CreateUserPaymentHibernateRepositoryAdapter;
import com.cuoco.application.port.out.CreateUserPaymentRepository;
import com.cuoco.application.usecase.model.UserPayment;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Repository;

@Slf4j
@Repository
@Qualifier("repository")
@RequiredArgsConstructor
public class CreateUserPaymentDatabaseRepositoryAdapter implements CreateUserPaymentRepository {

private final CreateUserPaymentHibernateRepositoryAdapter createUserPaymentHibernateRepositoryAdapter;

@Override
public UserPayment execute(UserPayment userPayment) {
log.info("Executing create user payment in database");

UserPaymentsHibernateModel userPaymentToSave = UserPaymentsHibernateModel.fromDomain(userPayment);

UserPaymentsHibernateModel savedUserPayment = createUserPaymentHibernateRepositoryAdapter.save(userPaymentToSave);

log.info("Saved user payment with ID {}", savedUserPayment.getId());

UserPayment response = savedUserPayment.toDomain();

response.setUser(userPayment.getUser());

return response;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.cuoco.adapter.out.hibernate;

import com.cuoco.adapter.out.hibernate.model.PaymentStatusHibernateModel;
import com.cuoco.adapter.out.hibernate.repository.GetAllPaymentStatusHibernateRepositoryAdapter;
import com.cuoco.application.port.out.GetAllPaymentStatusRepository;
import com.cuoco.application.usecase.model.PaymentStatus;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;

import java.util.List;

@Slf4j
@Repository
@RequiredArgsConstructor
public class GetAllPaymentStatusDatabaseRepositoryAdapter implements GetAllPaymentStatusRepository {

private final GetAllPaymentStatusHibernateRepositoryAdapter getAllPaymentStatusHibernateRepositoryAdapter;

@Override
public List<PaymentStatus> getAll() {
log.info("Get all payment statuses from database");

List<PaymentStatusHibernateModel> response = getAllPaymentStatusHibernateRepositoryAdapter.findAll();

return response.stream().map(PaymentStatusHibernateModel::toDomain).toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.cuoco.adapter.out.hibernate;

import com.cuoco.adapter.exception.NotFoundException;
import com.cuoco.adapter.out.hibernate.model.UserPaymentsHibernateModel;
import com.cuoco.adapter.out.hibernate.repository.GetUserPaymentByExternalReferenceHibernateRepositoryAdapter;
import com.cuoco.application.port.out.GetUserPaymentByExternalReferenceRepository;
import com.cuoco.application.usecase.model.UserPayment;
import com.cuoco.shared.model.ErrorDescription;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Slf4j
@Repository
@RequiredArgsConstructor
public class GetUserPaymentByExternalReferenceDatabaseRepositoryAdapter implements GetUserPaymentByExternalReferenceRepository {

private final GetUserPaymentByExternalReferenceHibernateRepositoryAdapter getUserPaymentByExternalReferenceHibernateRepositoryAdapter;

@Override
public UserPayment execute(String externalReference) {
log.info("Executing get user payment by external reference {}", externalReference);

Optional<UserPaymentsHibernateModel> maybeResponse = getUserPaymentByExternalReferenceHibernateRepositoryAdapter.findByExternalReference(externalReference);

if(!maybeResponse.isPresent()) {
log.info("Not found user payment with external reference {}", externalReference);
throw new NotFoundException(ErrorDescription.NOT_FOUND.getValue());
}

UserPaymentsHibernateModel response = maybeResponse.get();
return response.toDomain();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.cuoco.adapter.out.hibernate;

import com.cuoco.adapter.out.hibernate.model.UserHibernateModel;
import com.cuoco.adapter.out.hibernate.model.UserPaymentsHibernateModel;
import com.cuoco.adapter.out.hibernate.repository.CreateUserPaymentHibernateRepositoryAdapter;
import com.cuoco.application.port.out.CreateUserPaymentRepository;
import com.cuoco.application.port.out.UpdateUserPaymentRepository;
import com.cuoco.application.usecase.model.UserPayment;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Repository;

@Slf4j
@Repository
@Qualifier("repository")
@RequiredArgsConstructor
public class UpdateUserPaymentDatabaseRepositoryAdapter implements UpdateUserPaymentRepository {

private final CreateUserPaymentHibernateRepositoryAdapter createUserPaymentHibernateRepositoryAdapter;

@Override
public UserPayment execute(UserPayment userPayment) {
log.info("Executing update user payment in database");

UserPaymentsHibernateModel userPaymentToUpdate = UserPaymentsHibernateModel.fromDomain(userPayment);

userPaymentToUpdate.setUser(UserHibernateModel.builder().id(userPayment.getUser().getId()).build());

UserPaymentsHibernateModel updatedUserPayment = createUserPaymentHibernateRepositoryAdapter.save(userPaymentToUpdate);

log.info("Updated user payment with ID {}", updatedUserPayment.getId());

UserPayment response = updatedUserPayment.toDomain();

response.setUser(userPayment.getUser());

return response;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.cuoco.adapter.out.hibernate.model;

import com.cuoco.application.usecase.model.PaymentStatus;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity(name = "payment_status")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PaymentStatusHibernateModel {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String description;

public static PaymentStatusHibernateModel fromDomain(PaymentStatus paymentStatus) {
return PaymentStatusHibernateModel.builder()
.id(paymentStatus.getId())
.description(paymentStatus.getDescription())
.build();
}

public PaymentStatus toDomain() {
return PaymentStatus.builder()
.id(id)
.description(description)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.cuoco.adapter.out.hibernate.model;

import com.cuoco.application.usecase.model.PlanConfiguration;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;

@Entity(name = "plan_configuration")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PlanConfigurationHibernateModel {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String title;
private String description;
private Integer quantity;
private BigDecimal price;
private String currency;

public static PlanConfigurationHibernateModel fromDomain(PlanConfiguration planConfiguration) {
return PlanConfigurationHibernateModel.builder()
.title(planConfiguration.getTitle())
.description(planConfiguration.getDescription())
.quantity(planConfiguration.getQuantity())
.price(planConfiguration.getPrice())
.currency(planConfiguration.getCurrency())
.build();
}

public PlanConfiguration toDomain() {
return PlanConfiguration.builder()
.title(title)
.description(description)
.quantity(quantity)
.price(price)
.currency(currency)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
Expand All @@ -22,17 +24,23 @@ public class PlanHibernateModel {
private Integer id;
private String description;

@OneToOne
@JoinColumn(name = "configuration_id")
private PlanConfigurationHibernateModel configuration;

public static PlanHibernateModel fromDomain(Plan plan) {
return PlanHibernateModel.builder()
.id(plan.getId())
.description(plan.getDescription())
.configuration(plan.getConfiguration() != null ? PlanConfigurationHibernateModel.fromDomain(plan.getConfiguration()) : null)
.build();
}

public Plan toDomain() {
return Plan.builder()
.id(id)
.description(description)
.configuration(configuration != null ? configuration.toDomain() : null)
.build();
}
}
Loading