Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[13] 일반 사용자 : 결제 #35

Merged
merged 6 commits into from
Oct 6, 2024
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.example.commerce_site.application.order;

import org.example.commerce_site.application.order.dto.OrderRequestDto;
import org.example.commerce_site.common.exception.CustomException;
import org.example.commerce_site.common.exception.ErrorCode;
import org.example.commerce_site.domain.Order;
import org.example.commerce_site.infrastructure.order.OrderRepository;
import org.springframework.stereotype.Service;
Expand All @@ -17,4 +19,11 @@ public class OrderService {
public Order createOrder(OrderRequestDto.Create dto) {
return orderRepository.save(OrderRequestDto.Create.toEntity(dto));
}

@Transactional(readOnly = true)
public Order getOrder(Long orderId, Long userId) {
return orderRepository.findByIdAndUserId(orderId, userId).orElseThrow(
() -> new CustomException(ErrorCode.ORDER_NOT_FOUND)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.example.commerce_site.application.payment;

import org.example.commerce_site.application.order.OrderService;
import org.example.commerce_site.application.payment.dto.PaymentRequestDto;
import org.example.commerce_site.application.user.UserService;
import org.example.commerce_site.domain.Order;
import org.example.commerce_site.domain.User;
import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class PaymentFacade {
private final PaymentService paymentService;
private final UserService userService;
private final OrderService orderService;

public void createPayment(PaymentRequestDto.Create dto) {
User user = userService.getUser(dto.getUserAuthId());
Order order = orderService.getOrder(dto.getOrderId(), user.getId());
paymentService.create(PaymentRequestDto.Create.toEntity(dto, order, user.getId()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.example.commerce_site.application.payment;

import org.example.commerce_site.domain.Payment;
import org.example.commerce_site.infrastructure.payment.PaymentRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class PaymentService {
private final PaymentRepository paymentRepository;

@Transactional
public Payment create(Payment order) {
return paymentRepository.save(order);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.example.commerce_site.application.payment.dto;

import java.math.BigDecimal;

import org.example.commerce_site.attribute.PaymentMethod;
import org.example.commerce_site.attribute.PaymentStatus;
import org.example.commerce_site.domain.Order;
import org.example.commerce_site.domain.Payment;

import lombok.Builder;
import lombok.Getter;

public class PaymentRequestDto {
@Builder
@Getter
public static class Create {
public PaymentMethod paymentMethod;
public BigDecimal amount;
public String userAuthId;
public Long orderId;

public static Payment toEntity(Create dto, Order order, Long userId) {
return Payment.builder()
.amount(dto.getAmount())
.status(PaymentStatus.PENDING)
.paymentMethod(dto.getPaymentMethod())
.userId(userId)
.order(order)
.build();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ public User getUser(Long userId) {
);
}

@Transactional(readOnly = true)
public User getUser(String userAuthId) {
return userRepository.findByAuthId(userAuthId).orElseThrow(
() -> new CustomException(ErrorCode.USER_NOT_FOUND)
);
}

@Transactional
public void create(UserRequestDto.Create dto) {
userRepository.save(UserRequestDto.Create.toEntity(dto));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.example.commerce_site.attribute;

public enum PaymentMethod {
CARD,
CASH,
POINT
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.example.commerce_site.attribute;

public enum PaymentStatus {
PENDING, // 결제 대기 중
COMPLETED, // 결제 완료
FAILED, // 결제 실패
CANCELLED, // 결제 취소
REFUNDED; // 환불 처리 완료
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ public enum ErrorCode {
CREATE_KEYCLOAK_USER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 500, "인가 서버 유저 등록에 실패했습니다."),
ADD_USER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 500, "API 서버 유저 등록에 실패했습니다."),

//order
ORDER_NOT_FOUND(HttpStatus.NOT_FOUND, 404, "주문 정보를 찾을 수 없습니다."),

//partner
PARTNER_NOT_FOUND(HttpStatus.NOT_FOUND, 404, "파트너 회원 정보를 찾을 수 없습니다."),

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.example.commerce_site.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "auth.exclude")
public class AuthExcludeProperties {
private String[] post;
private String[] get;
private String[] web;
}

26 changes: 22 additions & 4 deletions src/main/java/org/example/commerce_site/config/OpenApiConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;

@Configuration
Expand All @@ -28,14 +31,23 @@ public OpenAPI openAPI() {
.version("1.0")
.description("Commerce Site API Documentation");

Components components = new Components();
components.addSecuritySchemes("bearerAuth", new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
.description("Enter your Bearer token in the format: **Bearer <token>**"));

return new OpenAPI()
.components(components)
.addSecurityItem(new SecurityRequirement().addList("bearerAuth"))
.info(info)
.servers(serverList);
}

@Bean
public GroupedOpenApi userOpenApi() {
String[] paths = {"/user/**"};
String[] paths = {"/users/**"};
return GroupedOpenApi.builder().group("USER API").pathsToMatch(paths).build();
}

Expand All @@ -47,7 +59,7 @@ public GroupedOpenApi partnerOpenApi() {

@Bean
public GroupedOpenApi productOpenApi() {
String[] paths = {"/product/**"};
String[] paths = {"/products/**"};
return GroupedOpenApi.builder().group("PRODUCT API").pathsToMatch(paths).build();
}

Expand All @@ -65,13 +77,19 @@ public GroupedOpenApi addressOpenApi() {

@Bean
public GroupedOpenApi cartOpenApi() {
String[] paths = {"/cart/**"};
String[] paths = {"/carts/**"};
return GroupedOpenApi.builder().group("CART API").pathsToMatch(paths).build();
}

@Bean
public GroupedOpenApi orderOpenApi() {
String[] paths = {"/order/**"};
String[] paths = {"/orders/**"};
return GroupedOpenApi.builder().group("ORDER API").pathsToMatch(paths).build();
}

@Bean
public GroupedOpenApi paymentsOpenApi() {
String[] paths = {"/payments/**"};
return GroupedOpenApi.builder().group("PAYMENT API").pathsToMatch(paths).build();
}
}
23 changes: 16 additions & 7 deletions src/main/java/org/example/commerce_site/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.example.commerce_site.config;

import org.example.commerce_site.config.filter.UserIdFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
Expand All @@ -11,15 +12,22 @@
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
private static final String[] AUTH_EXCLUDE_POST_API_LIST = {"/user/keycloak/webhook"};
private static final String[] AUTH_EXCLUDE_GET_API_LIST = {"/auth/**"};
private static final String[] AUTH_EXCLUDE_WEB_LIST = {"/swagger-ui/**", "/api-docs/**"};
// private static final String[] AUTH_EXCLUDE_POST_API_LIST = {"/users/keycloak/webhook"};
// private static final String[] AUTH_EXCLUDE_GET_API_LIST = {"/auth/**"};
// private static final String[] AUTH_EXCLUDE_WEB_LIST = {"/swagger-ui/**", "/api-docs/**"};

private final AuthExcludeProperties authExcludeProperties;

public SecurityConfig(AuthExcludeProperties authExcludeProperties) {
this.authExcludeProperties = authExcludeProperties;
}

@Bean
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
Expand All @@ -34,15 +42,16 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
.csrf(AbstractHttpConfigurer::disable)
.cors(Customizer.withDefaults())
.authorizeHttpRequests(requests -> requests
.requestMatchers(HttpMethod.POST, AUTH_EXCLUDE_POST_API_LIST).permitAll()
.requestMatchers(HttpMethod.GET, AUTH_EXCLUDE_GET_API_LIST).permitAll()
.requestMatchers(AUTH_EXCLUDE_WEB_LIST).permitAll()
.requestMatchers(HttpMethod.POST, authExcludeProperties.getPost()).permitAll()
.requestMatchers(HttpMethod.GET, authExcludeProperties.getGet()).permitAll()
.requestMatchers(authExcludeProperties.getWeb()).permitAll()
.anyRequest().authenticated()
)
.addFilterAfter(new UserIdFilter(authExcludeProperties), UsernamePasswordAuthenticationFilter.class)
.oauth2ResourceServer(
oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter)))
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin));

return http.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package org.example.commerce_site.config.filter;

import java.io.IOException;
import java.util.Arrays;
import java.util.stream.Stream;

import org.example.commerce_site.config.AuthExcludeProperties;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class UserIdFilter extends OncePerRequestFilter {
private final AuthExcludeProperties excludeProperties;

public UserIdFilter(AuthExcludeProperties excludeProperties) {
this.excludeProperties = excludeProperties;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {

String requestURI = request.getRequestURI();
boolean isExcluded = Stream.of(excludeProperties.getPost(), excludeProperties.getGet(),
excludeProperties.getWeb())
.flatMap(Arrays::stream)
.anyMatch(path -> requestURI.equals(path) || requestURI.startsWith(path.replace("**", "")));

if (isExcluded) {
filterChain.doFilter(request, response);
return;
}

var authentication = SecurityContextHolder.getContext().getAuthentication();

if (authentication != null && authentication.isAuthenticated()) {
String userId = ((JwtAuthenticationToken)authentication).getToken().getSubject();
request.setAttribute("userId", userId);
}

filterChain.doFilter(request, response);
}

}
40 changes: 40 additions & 0 deletions src/main/java/org/example/commerce_site/domain/Payment.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.example.commerce_site.domain;

import java.math.BigDecimal;

import org.example.commerce_site.attribute.PaymentMethod;
import org.example.commerce_site.attribute.PaymentStatus;
import org.example.commerce_site.common.domain.BaseTimeEntity;

import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;

@Entity
@Getter
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "payments")
public class Payment extends BaseTimeEntity {
private Long userId;

@OneToOne
@JoinColumn(name = "order_id")
private Order order;

@Enumerated(EnumType.STRING)
private PaymentMethod paymentMethod;

@Enumerated(EnumType.STRING)
private PaymentStatus status;

private BigDecimal amount;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import java.sql.Timestamp;
import java.util.List;

import org.example.commerce_site.application.order.dto.OrderDetailResponseDto;
import org.example.commerce_site.domain.OrderDetail;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package org.example.commerce_site.infrastructure.order;

import java.util.Optional;

import org.example.commerce_site.domain.Order;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
Optional<Order> findByIdAndUserId(Long orderId, Long userId);
}
Loading
Loading