diff --git a/build.gradle b/build.gradle index f066d01..df8ffea 100644 --- a/build.gradle +++ b/build.gradle @@ -45,6 +45,9 @@ dependencies { // slack Notification implementation 'com.github.maricn:logback-slack-appender:1.4.0' + // flyway + implementation 'org.flywaydb:flyway-mysql' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'com.squareup.okhttp3:mockwebserver' diff --git a/src/main/java/in/koreatech/koin/domain/order/exception/PaymentCancelException.java b/src/main/java/in/koreatech/koin/domain/order/exception/PaymentCancelException.java deleted file mode 100644 index 422cca8..0000000 --- a/src/main/java/in/koreatech/koin/domain/order/exception/PaymentCancelException.java +++ /dev/null @@ -1,22 +0,0 @@ -package in.koreatech.koin.domain.order.exception; - -import in.koreatech.payment.common.exception.custom.ExternalServiceException; -import in.koreatech.payment.exception.PaymentConfirmException; - -public class PaymentCancelException extends ExternalServiceException { - - private static final String DEFAULT_MESSAGE = "결제 취소 중 문제가 생겼습니다."; - private static final String ERROR_CODE = "PAYMENT_CANCEL_ERROR"; - - public PaymentCancelException(String message) { - super(message, ERROR_CODE); - } - - public PaymentCancelException(String message, String detail) { - super(message, detail, ERROR_CODE); - } - - public static PaymentConfirmException withDetail(String detail) { - return new PaymentConfirmException(DEFAULT_MESSAGE, detail); - } -} diff --git a/src/main/java/in/koreatech/payment/KoinPaymentApplication.java b/src/main/java/in/koreatech/payment/KoinPaymentApplication.java index a072058..0eac7d6 100644 --- a/src/main/java/in/koreatech/payment/KoinPaymentApplication.java +++ b/src/main/java/in/koreatech/payment/KoinPaymentApplication.java @@ -7,20 +7,7 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @ConfigurationPropertiesScan -@SpringBootApplication( - scanBasePackages = { - "in.koreatech.payment", - "in.koreatech.koin" - } -) -@EnableJpaRepositories(basePackages = { - "in.koreatech.payment", - "in.koreatech.koin" -}) -@EntityScan(basePackages = { - "in.koreatech.payment", - "in.koreatech.koin" -}) +@SpringBootApplication public class KoinPaymentApplication { public static void main(String[] args) { diff --git a/src/main/java/in/koreatech/payment/client/dto/request/PaymentCancelRequest.java b/src/main/java/in/koreatech/payment/client/dto/request/PaymentCancelRequest.java deleted file mode 100644 index 8e5c9e2..0000000 --- a/src/main/java/in/koreatech/payment/client/dto/request/PaymentCancelRequest.java +++ /dev/null @@ -1,7 +0,0 @@ -package in.koreatech.payment.client.dto.request; - -public record PaymentCancelRequest( - String cancelReason -) { - -} diff --git a/src/main/java/in/koreatech/payment/client/dto/request/PaymentConfirmRequest.java b/src/main/java/in/koreatech/payment/client/dto/request/PaymentConfirmRequest.java deleted file mode 100644 index 1002216..0000000 --- a/src/main/java/in/koreatech/payment/client/dto/request/PaymentConfirmRequest.java +++ /dev/null @@ -1,9 +0,0 @@ -package in.koreatech.payment.client.dto.request; - -public record PaymentConfirmRequest( - String paymentKey, - String orderId, - Integer amount -) { - -} diff --git a/src/main/java/in/koreatech/payment/client/dto/response/PaymentCancelResponse.java b/src/main/java/in/koreatech/payment/client/dto/response/PaymentCancelResponse.java deleted file mode 100644 index d12a93a..0000000 --- a/src/main/java/in/koreatech/payment/client/dto/response/PaymentCancelResponse.java +++ /dev/null @@ -1,44 +0,0 @@ -package in.koreatech.payment.client.dto.response; - -import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.util.ArrayList; -import java.util.List; - -import in.koreatech.koin.domain.order.model.Payment; -import in.koreatech.koin.domain.order.model.PaymentCancel; - -public record PaymentCancelResponse( - String paymentKey, - String orderId, - String status, - List cancels -) { - public record CancelInfo( - Integer cancelAmount, - String cancelReason, - String canceledAt, - String transactionKey - ) { - - } - - public List getPaymentCancels(Payment payment) { - List paymentCancels = new ArrayList<>(); - - for (CancelInfo cancelInfo : cancels) { - OffsetDateTime cancelOffsetDateTime = OffsetDateTime.parse(cancelInfo.canceledAt); - LocalDateTime canceled = cancelOffsetDateTime.toLocalDateTime(); - - paymentCancels.add(PaymentCancel.builder() - .transactionKey(cancelInfo.transactionKey) - .cancelReason(cancelInfo.cancelReason) - .cancelAmount(cancelInfo.cancelAmount) - .canceledAt(canceled) - .payment(payment) - .build()); - } - - return paymentCancels; - } -} diff --git a/src/main/java/in/koreatech/payment/client/dto/response/TossPaymentConfirmResponse.java b/src/main/java/in/koreatech/payment/client/dto/response/TossPaymentConfirmResponse.java deleted file mode 100644 index cbcab26..0000000 --- a/src/main/java/in/koreatech/payment/client/dto/response/TossPaymentConfirmResponse.java +++ /dev/null @@ -1,36 +0,0 @@ -package in.koreatech.payment.client.dto.response; - -import java.time.LocalDateTime; -import java.time.OffsetDateTime; - -import in.koreatech.koin.domain.order.model.Order; -import in.koreatech.koin.domain.order.model.Payment; -import in.koreatech.koin.domain.order.model.PaymentMethod; -import in.koreatech.koin.domain.order.model.PaymentStatus; - -public record TossPaymentConfirmResponse( - String paymentKey, - Integer totalAmount, - String status, - String method, - String requestedAt, - String approvedAt -) { - public Payment toEntity(Order order) { - OffsetDateTime requestedOffsetDateTime = OffsetDateTime.parse(requestedAt); - OffsetDateTime approvedOffsetDateTime = OffsetDateTime.parse(approvedAt); - - LocalDateTime requested = requestedOffsetDateTime.toLocalDateTime(); - LocalDateTime approved = approvedOffsetDateTime.toLocalDateTime(); - - return Payment.builder() - .paymentKey(paymentKey) - .amount(totalAmount) - .paymentStatus(PaymentStatus.valueOf(status)) - .paymentMethod(PaymentMethod.from(method)) - .requestedAt(requested) - .approvedAt(approved) - .order(order) - .build(); - } -} diff --git a/src/main/java/in/koreatech/payment/common/config/datasource/KoinDBProperties.java b/src/main/java/in/koreatech/payment/common/config/datasource/KoinDBProperties.java new file mode 100644 index 0000000..00246f4 --- /dev/null +++ b/src/main/java/in/koreatech/payment/common/config/datasource/KoinDBProperties.java @@ -0,0 +1,13 @@ +package in.koreatech.payment.common.config.datasource; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "spring.datasource.koin.hibernate") +public record KoinDBProperties( + String ddlAuto, + Boolean showSql, + String packagesToScan, + String formatSql +) { + +} diff --git a/src/main/java/in/koreatech/payment/common/config/datasource/KoinDataSourceConfig.java b/src/main/java/in/koreatech/payment/common/config/datasource/KoinDataSourceConfig.java new file mode 100644 index 0000000..dce81ad --- /dev/null +++ b/src/main/java/in/koreatech/payment/common/config/datasource/KoinDataSourceConfig.java @@ -0,0 +1,67 @@ +package in.koreatech.payment.common.config.datasource; + +import java.util.HashMap; +import java.util.Map; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import jakarta.persistence.EntityManagerFactory; +import lombok.RequiredArgsConstructor; + +@Configuration +@RequiredArgsConstructor +@EnableTransactionManagement +@EnableJpaRepositories( + basePackages = "in.koreatech.koin", + entityManagerFactoryRef = "koinEntityManagerFactory", + transactionManagerRef = "koinTransactionManager" +) +public class KoinDataSourceConfig { + + private final KoinDBProperties koinDBProperties; + + @Bean(name = "koinDataSource") + @ConfigurationProperties("spring.datasource.koin") + public DataSource koinDataSource() { + return DataSourceBuilder.create().build(); + } + + @Bean(name = "koinEntityManagerFactory") + public LocalContainerEntityManagerFactoryBean koinEntityManagerFactory( + @Qualifier(value = "koinDataSource") DataSource dataSource + ) { + LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); + em.setDataSource(dataSource); + em.setPackagesToScan(koinDBProperties.packagesToScan()); + em.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); + em.setJpaPropertyMap(createJpaVendorProperties()); + return em; + } + + private Map createJpaVendorProperties() { + Map properties = new HashMap<>(); + properties.put("hibernate.show_sql", koinDBProperties.showSql()); + properties.put("hibernate.hbm2ddl.auto", koinDBProperties.ddlAuto()); + properties.put("hibernate.format_sql", koinDBProperties.formatSql()); + return properties; + } + + @Bean(name = "koinTransactionManager") + public PlatformTransactionManager koinTransactionManager( + @Qualifier(value = "koinEntityManagerFactory") EntityManagerFactory entityManagerFactory + ) { + return new JpaTransactionManager(entityManagerFactory); + } +} diff --git a/src/main/java/in/koreatech/payment/common/config/datasource/KoinPaymentDBProperties.java b/src/main/java/in/koreatech/payment/common/config/datasource/KoinPaymentDBProperties.java new file mode 100644 index 0000000..2e8a04b --- /dev/null +++ b/src/main/java/in/koreatech/payment/common/config/datasource/KoinPaymentDBProperties.java @@ -0,0 +1,13 @@ +package in.koreatech.payment.common.config.datasource; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "spring.datasource.koin-payment.hibernate") +public record KoinPaymentDBProperties( + String ddlAuto, + Boolean showSql, + String packagesToScan, + String formatSql +) { + +} diff --git a/src/main/java/in/koreatech/payment/common/config/datasource/KoinPaymentDataSourceConfig.java b/src/main/java/in/koreatech/payment/common/config/datasource/KoinPaymentDataSourceConfig.java new file mode 100644 index 0000000..a1180c0 --- /dev/null +++ b/src/main/java/in/koreatech/payment/common/config/datasource/KoinPaymentDataSourceConfig.java @@ -0,0 +1,70 @@ +package in.koreatech.payment.common.config.datasource; + +import java.util.HashMap; +import java.util.Map; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import jakarta.persistence.EntityManagerFactory; +import lombok.RequiredArgsConstructor; + +@Configuration +@RequiredArgsConstructor +@EnableTransactionManagement +@EnableJpaRepositories( + basePackages = "in.koreatech.payment", + entityManagerFactoryRef = "koinPaymentEntityManagerFactory", + transactionManagerRef = "koinPaymentTransactionManager" +) +public class KoinPaymentDataSourceConfig { + + private final KoinPaymentDBProperties koinPaymentDBProperties; + + @Bean(name = "koinPaymentDataSource") + @ConfigurationProperties("spring.datasource.koin-payment") + public DataSource koinPaymentDataSource() { + return DataSourceBuilder.create().build(); + } + + @Primary + @Bean(name = "koinPaymentEntityManagerFactory") + public LocalContainerEntityManagerFactoryBean koinPaymentEntityManagerFactory( + @Qualifier(value = "koinPaymentDataSource") DataSource dataSource + ) { + LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); + em.setDataSource(dataSource); + em.setPackagesToScan(koinPaymentDBProperties.packagesToScan()); + em.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); + em.setJpaPropertyMap(createJpaVendorProperties()); + return em; + } + + private Map createJpaVendorProperties() { + Map properties = new HashMap<>(); + properties.put("hibernate.show_sql", koinPaymentDBProperties.showSql()); + properties.put("hibernate.hbm2ddl.auto", koinPaymentDBProperties.ddlAuto()); + properties.put("hibernate.format_sql", koinPaymentDBProperties.formatSql()); + return properties; + } + + @Primary + @Bean(name = "koinPaymentTransactionManager") + public PlatformTransactionManager koinPaymentTransactionManager( + @Qualifier(value = "koinPaymentEntityManagerFactory") EntityManagerFactory entityManagerFactory + ) { + return new JpaTransactionManager(entityManagerFactory); + } +} diff --git a/src/main/java/in/koreatech/payment/common/exception/GlobalExceptionHandler.java b/src/main/java/in/koreatech/payment/common/exception/GlobalExceptionHandler.java index 952833c..2e9edf0 100644 --- a/src/main/java/in/koreatech/payment/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/in/koreatech/payment/common/exception/GlobalExceptionHandler.java @@ -25,7 +25,7 @@ import org.springframework.web.util.ContentCachingRequestWrapper; import org.springframework.web.util.WebUtils; -import in.koreatech.payment.client.exception.TossPaymentException; +import in.koreatech.payment.gateway.toss.exception.TossPaymentException; import in.koreatech.payment.common.exception.custom.AuthenticationException; import in.koreatech.payment.common.exception.custom.AuthorizationException; import in.koreatech.payment.common.exception.custom.DataNotFoundException; diff --git a/src/main/java/in/koreatech/payment/controller/PaymentsController.java b/src/main/java/in/koreatech/payment/controller/PaymentsController.java index 2fa2eff..499f6fe 100644 --- a/src/main/java/in/koreatech/payment/controller/PaymentsController.java +++ b/src/main/java/in/koreatech/payment/controller/PaymentsController.java @@ -1,7 +1,5 @@ package in.koreatech.payment.controller; -import java.util.List; - import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -10,7 +8,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import in.koreatech.koin.domain.order.model.PaymentCancel; import in.koreatech.payment.common.auth.AccessToken; import in.koreatech.payment.dto.request.PaymentCancelRequest; import in.koreatech.payment.dto.request.PaymentConfirmRequest; @@ -36,8 +33,7 @@ public ResponseEntity createTemporaryDeliveryPayment( @RequestBody @Valid final TemporaryDeliveryPaymentSaveRequest request, @AccessToken final String accessToken ) { - String orderId = paymentService.createTemporaryDeliveryPayment(accessToken, request); - TemporaryPaymentResponse response = TemporaryPaymentResponse.of(orderId); + TemporaryPaymentResponse response = paymentService.createTemporaryDeliveryPayment(accessToken, request); return ResponseEntity.ok(response); } @@ -46,8 +42,7 @@ public ResponseEntity createTemporaryTakeoutPayment( @RequestBody @Valid final TemporaryTakeoutPaymentSaveRequest request, @AccessToken final String accessToken ) { - String orderId = paymentService.createTemporaryTakeoutPayment(accessToken, request); - TemporaryPaymentResponse response = TemporaryPaymentResponse.of(orderId); + TemporaryPaymentResponse response = paymentService.createTemporaryTakeoutPayment(accessToken, request); return ResponseEntity.ok(response); } @@ -56,8 +51,7 @@ public ResponseEntity confirmPayment( @RequestBody @Valid final PaymentConfirmRequest request, @AccessToken final String accessToken ) { - PaymentConfirmResponse response = paymentService.confirmPayment(accessToken, request.paymentKey(), request.orderId(), - request.amount()); + PaymentConfirmResponse response = paymentService.confirmPayment(accessToken, request); return ResponseEntity.ok(response); } @@ -67,9 +61,7 @@ public ResponseEntity cancelPayment( @RequestBody @Valid final PaymentCancelRequest request, @AccessToken final String accessToken ) { - List paymentCancels = paymentService.cancelPayment(accessToken, paymentId, - request.cancelReason()); - PaymentCancelResponse response = PaymentCancelResponse.from(paymentCancels); + PaymentCancelResponse response = paymentService.cancelPayment(accessToken, paymentId, request); return ResponseEntity.ok(response); } diff --git a/src/main/java/in/koreatech/payment/dto/response/PaymentCancelResponse.java b/src/main/java/in/koreatech/payment/dto/response/PaymentCancelResponse.java index eb3989c..84ad97d 100644 --- a/src/main/java/in/koreatech/payment/dto/response/PaymentCancelResponse.java +++ b/src/main/java/in/koreatech/payment/dto/response/PaymentCancelResponse.java @@ -10,7 +10,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.databind.annotation.JsonNaming; -import in.koreatech.koin.domain.order.model.PaymentCancel; +import in.koreatech.payment.model.entity.PaymentCancel; import io.swagger.v3.oas.annotations.media.Schema; @JsonNaming(value = SnakeCaseStrategy.class) diff --git a/src/main/java/in/koreatech/payment/dto/response/PaymentConfirmResponse.java b/src/main/java/in/koreatech/payment/dto/response/PaymentConfirmResponse.java index c995b6a..20cb3b8 100644 --- a/src/main/java/in/koreatech/payment/dto/response/PaymentConfirmResponse.java +++ b/src/main/java/in/koreatech/payment/dto/response/PaymentConfirmResponse.java @@ -1,8 +1,8 @@ package in.koreatech.payment.dto.response; import static com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; -import static in.koreatech.koin.domain.order.model.OrderType.DELIVERY; -import static in.koreatech.koin.domain.order.model.OrderType.TAKE_OUT; +import static in.koreatech.payment.model.entity.OrderType.DELIVERY; +import static in.koreatech.payment.model.entity.OrderType.TAKE_OUT; import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED; import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; @@ -13,12 +13,12 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.databind.annotation.JsonNaming; -import in.koreatech.koin.domain.order.model.Order; -import in.koreatech.koin.domain.order.model.OrderDelivery; -import in.koreatech.koin.domain.order.model.OrderMenu; -import in.koreatech.koin.domain.order.model.OrderMenuOption; -import in.koreatech.koin.domain.order.model.OrderTakeout; -import in.koreatech.koin.domain.order.model.Payment; +import in.koreatech.payment.model.entity.Order; +import in.koreatech.payment.model.entity.OrderDelivery; +import in.koreatech.payment.model.entity.OrderMenu; +import in.koreatech.payment.model.entity.OrderMenuOption; +import in.koreatech.payment.model.entity.OrderTakeout; +import in.koreatech.payment.model.entity.Payment; import in.koreatech.koin.domain.order.shop.model.entity.shop.OrderableShop; import in.koreatech.koin.domain.shop.model.shop.Shop; import io.swagger.v3.oas.annotations.media.Schema; @@ -113,9 +113,9 @@ public static InnerMenuOptionResponse from(OrderMenuOption orderMenuOption) { public static PaymentConfirmResponse of( Payment payment, Order order, + OrderableShop orderableShop, List orderMenus ) { - OrderableShop orderableShop = order.getOrderableShop(); Shop shop = orderableShop.getShop(); String deliveryAddress = null; diff --git a/src/main/java/in/koreatech/payment/dto/response/PaymentResponse.java b/src/main/java/in/koreatech/payment/dto/response/PaymentResponse.java index e68606d..b1e6626 100644 --- a/src/main/java/in/koreatech/payment/dto/response/PaymentResponse.java +++ b/src/main/java/in/koreatech/payment/dto/response/PaymentResponse.java @@ -1,8 +1,8 @@ package in.koreatech.payment.dto.response; import static com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; -import static in.koreatech.koin.domain.order.model.OrderType.DELIVERY; -import static in.koreatech.koin.domain.order.model.OrderType.TAKE_OUT; +import static in.koreatech.payment.model.entity.OrderType.DELIVERY; +import static in.koreatech.payment.model.entity.OrderType.TAKE_OUT; import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED; import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; @@ -13,12 +13,12 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.databind.annotation.JsonNaming; -import in.koreatech.koin.domain.order.model.Order; -import in.koreatech.koin.domain.order.model.OrderDelivery; -import in.koreatech.koin.domain.order.model.OrderMenu; -import in.koreatech.koin.domain.order.model.OrderMenuOption; -import in.koreatech.koin.domain.order.model.OrderTakeout; -import in.koreatech.koin.domain.order.model.Payment; +import in.koreatech.payment.model.entity.Order; +import in.koreatech.payment.model.entity.OrderDelivery; +import in.koreatech.payment.model.entity.OrderMenu; +import in.koreatech.payment.model.entity.OrderMenuOption; +import in.koreatech.payment.model.entity.OrderTakeout; +import in.koreatech.payment.model.entity.Payment; import in.koreatech.koin.domain.order.shop.model.entity.shop.OrderableShop; import in.koreatech.koin.domain.shop.model.shop.Shop; import io.swagger.v3.oas.annotations.media.Schema; @@ -113,9 +113,9 @@ public static InnerMenuOptionResponse from(OrderMenuOption orderMenuOption) { public static PaymentResponse of( Payment payment, Order order, + OrderableShop orderableShop, List orderMenus ) { - OrderableShop orderableShop = order.getOrderableShop(); Shop shop = orderableShop.getShop(); String deliveryAddress = null; diff --git a/src/main/java/in/koreatech/payment/event/TossPaymentRollBackEvent.java b/src/main/java/in/koreatech/payment/event/TossPaymentRollBackEvent.java deleted file mode 100644 index 835c0ca..0000000 --- a/src/main/java/in/koreatech/payment/event/TossPaymentRollBackEvent.java +++ /dev/null @@ -1,14 +0,0 @@ -package in.koreatech.payment.event; - -import in.koreatech.payment.client.dto.response.TossPaymentConfirmResponse; -import in.koreatech.payment.model.redis.TemporaryPayment; - -public record TossPaymentRollBackEvent( - String paymentKey, - TemporaryPayment temporaryPayment, - TossPaymentConfirmResponse tossPaymentConfirmResponse -) { - public static TossPaymentRollBackEvent from(String paymentKey, TemporaryPayment temporaryPayment, TossPaymentConfirmResponse tossPaymentConfirmResponse) { - return new TossPaymentRollBackEvent(paymentKey, temporaryPayment, tossPaymentConfirmResponse); - } -} diff --git a/src/main/java/in/koreatech/koin/domain/order/exception/PaymentAccessDeniedException.java b/src/main/java/in/koreatech/payment/exception/PaymentAccessDeniedException.java similarity index 93% rename from src/main/java/in/koreatech/koin/domain/order/exception/PaymentAccessDeniedException.java rename to src/main/java/in/koreatech/payment/exception/PaymentAccessDeniedException.java index 65ae02b..7a53ee9 100644 --- a/src/main/java/in/koreatech/koin/domain/order/exception/PaymentAccessDeniedException.java +++ b/src/main/java/in/koreatech/payment/exception/PaymentAccessDeniedException.java @@ -1,4 +1,4 @@ -package in.koreatech.koin.domain.order.exception; +package in.koreatech.payment.exception; import in.koreatech.payment.common.exception.custom.AuthorizationException; diff --git a/src/main/java/in/koreatech/payment/gateway/pg/PaymentGatewayService.java b/src/main/java/in/koreatech/payment/gateway/pg/PaymentGatewayService.java new file mode 100644 index 0000000..b7789a6 --- /dev/null +++ b/src/main/java/in/koreatech/payment/gateway/pg/PaymentGatewayService.java @@ -0,0 +1,10 @@ +package in.koreatech.payment.gateway.pg; + +import in.koreatech.payment.gateway.pg.dto.PaymentGatewayCancelResponse; +import in.koreatech.payment.gateway.pg.dto.PaymentGatewayConfirmResponse; + +public interface PaymentGatewayService { + PaymentGatewayConfirmResponse confirmPayment(String paymentKey, String pgOrderId, Integer amount); + PaymentGatewayCancelResponse cancelPayment(String paymentKey, String cancelReason, String idempotencyKey); + String generatePgOrderId(); +} diff --git a/src/main/java/in/koreatech/payment/gateway/pg/PgOrderIdGenerator.java b/src/main/java/in/koreatech/payment/gateway/pg/PgOrderIdGenerator.java new file mode 100644 index 0000000..020c5df --- /dev/null +++ b/src/main/java/in/koreatech/payment/gateway/pg/PgOrderIdGenerator.java @@ -0,0 +1,5 @@ +package in.koreatech.payment.gateway.pg; + +public interface PgOrderIdGenerator { + String generatePgOrderId(); +} diff --git a/src/main/java/in/koreatech/payment/gateway/pg/dto/PaymentGatewayCancelResponse.java b/src/main/java/in/koreatech/payment/gateway/pg/dto/PaymentGatewayCancelResponse.java new file mode 100644 index 0000000..918008f --- /dev/null +++ b/src/main/java/in/koreatech/payment/gateway/pg/dto/PaymentGatewayCancelResponse.java @@ -0,0 +1,19 @@ +package in.koreatech.payment.gateway.pg.dto; + +import java.util.List; + +public record PaymentGatewayCancelResponse( + String paymentKey, + String orderId, + String status, + List cancels +) { + public record CancelInfo( + Integer cancelAmount, + String cancelReason, + String canceledAt, + String transactionKey + ) { + + } +} diff --git a/src/main/java/in/koreatech/payment/gateway/pg/dto/PaymentGatewayConfirmResponse.java b/src/main/java/in/koreatech/payment/gateway/pg/dto/PaymentGatewayConfirmResponse.java new file mode 100644 index 0000000..1cd934b --- /dev/null +++ b/src/main/java/in/koreatech/payment/gateway/pg/dto/PaymentGatewayConfirmResponse.java @@ -0,0 +1,13 @@ +package in.koreatech.payment.gateway.pg.dto; + +public record PaymentGatewayConfirmResponse( + String paymentKey, + Integer totalAmount, + String orderId, + String status, + String method, + String requestedAt, + String approvedAt +) { + +} diff --git a/src/main/java/in/koreatech/payment/util/TossPaymentOrderIdGenerator.java b/src/main/java/in/koreatech/payment/gateway/toss/TossOrderIdGenerator.java similarity index 78% rename from src/main/java/in/koreatech/payment/util/TossPaymentOrderIdGenerator.java rename to src/main/java/in/koreatech/payment/gateway/toss/TossOrderIdGenerator.java index 3c1e324..0f6237f 100644 --- a/src/main/java/in/koreatech/payment/util/TossPaymentOrderIdGenerator.java +++ b/src/main/java/in/koreatech/payment/gateway/toss/TossOrderIdGenerator.java @@ -1,18 +1,20 @@ -package in.koreatech.payment.util; +package in.koreatech.payment.gateway.toss; import java.security.SecureRandom; import org.springframework.stereotype.Component; +import in.koreatech.payment.gateway.pg.PgOrderIdGenerator; + @Component -public class TossPaymentOrderIdGenerator implements OrderIdGenerator { +public class TossOrderIdGenerator implements PgOrderIdGenerator { private static final String ORDER_ID_CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"; private static final int ORDER_ID_MIN_LENGTH = 6; private static final int ORDER_ID_MAX_LENGTH = 64; private final SecureRandom random = new SecureRandom(); - public String generateOrderId() { + public String generatePgOrderId() { int length = ORDER_ID_MIN_LENGTH + random.nextInt(ORDER_ID_MAX_LENGTH - ORDER_ID_MIN_LENGTH + 1); StringBuilder sb = new StringBuilder(length); for (int index = 0; index < length; index++) { diff --git a/src/main/java/in/koreatech/payment/client/TossPaymentClient.java b/src/main/java/in/koreatech/payment/gateway/toss/TossPaymentClient.java similarity index 78% rename from src/main/java/in/koreatech/payment/client/TossPaymentClient.java rename to src/main/java/in/koreatech/payment/gateway/toss/TossPaymentClient.java index 17268f3..fcd7e58 100644 --- a/src/main/java/in/koreatech/payment/client/TossPaymentClient.java +++ b/src/main/java/in/koreatech/payment/gateway/toss/TossPaymentClient.java @@ -1,4 +1,4 @@ -package in.koreatech.payment.client; +package in.koreatech.payment.gateway.toss; import static java.nio.charset.StandardCharsets.UTF_8; import static org.springframework.http.HttpHeaders.AUTHORIZATION; @@ -14,14 +14,14 @@ import com.fasterxml.jackson.databind.ObjectMapper; -import in.koreatech.payment.client.dto.request.PaymentCancelRequest; -import in.koreatech.payment.client.dto.request.PaymentConfirmRequest; -import in.koreatech.payment.client.dto.response.PaymentCancelResponse; -import in.koreatech.payment.client.dto.response.TossPaymentConfirmResponse; -import in.koreatech.payment.client.exception.TossPaymentErrorCode; -import in.koreatech.payment.client.exception.TossPaymentErrorResponse; -import in.koreatech.payment.client.exception.TossPaymentException; +import in.koreatech.payment.gateway.toss.exception.TossPaymentErrorCode; +import in.koreatech.payment.gateway.toss.exception.TossPaymentErrorResponse; +import in.koreatech.payment.gateway.toss.exception.TossPaymentException; import in.koreatech.payment.common.exception.custom.KoinIllegalStateException; +import in.koreatech.payment.gateway.toss.dto.request.TossPaymentCancelRequest; +import in.koreatech.payment.gateway.toss.dto.request.TossPaymentConfirmRequest; +import in.koreatech.payment.gateway.toss.dto.response.TossPaymentCancelResponse; +import in.koreatech.payment.gateway.toss.dto.response.TossPaymentConfirmResponse; @Component public class TossPaymentClient { @@ -48,7 +48,7 @@ public TossPaymentClient( } public TossPaymentConfirmResponse requestConfirm(String paymentKey, String orderId, Integer amount) { - PaymentConfirmRequest request = new PaymentConfirmRequest(paymentKey, orderId, amount); + TossPaymentConfirmRequest request = new TossPaymentConfirmRequest(paymentKey, orderId, amount); try { return webClient.post() @@ -57,7 +57,6 @@ public TossPaymentConfirmResponse requestConfirm(String paymentKey, String order .retrieve() .bodyToMono(TossPaymentConfirmResponse.class) .block(); - } catch (WebClientResponseException e) { throw handleErrorResponse(e); } catch (Exception e) { @@ -65,8 +64,8 @@ public TossPaymentConfirmResponse requestConfirm(String paymentKey, String order } } - public PaymentCancelResponse requestCancel(String paymentKey, String cancelReason, String IdempotencyKey) { - PaymentCancelRequest request = new PaymentCancelRequest(cancelReason); + public TossPaymentCancelResponse requestCancel(String paymentKey, String cancelReason, String IdempotencyKey) { + TossPaymentCancelRequest request = new TossPaymentCancelRequest(cancelReason); try { return webClient.post() @@ -74,7 +73,7 @@ public PaymentCancelResponse requestCancel(String paymentKey, String cancelReaso .header(IDEMPOTENT_KEY, IdempotencyKey) .bodyValue(request) .retrieve() - .bodyToMono(PaymentCancelResponse.class) + .bodyToMono(TossPaymentCancelResponse.class) .block(); } catch (WebClientResponseException e) { throw handleErrorResponse(e); diff --git a/src/main/java/in/koreatech/payment/gateway/toss/TossPaymentGatewayService.java b/src/main/java/in/koreatech/payment/gateway/toss/TossPaymentGatewayService.java new file mode 100644 index 0000000..31b0690 --- /dev/null +++ b/src/main/java/in/koreatech/payment/gateway/toss/TossPaymentGatewayService.java @@ -0,0 +1,58 @@ +package in.koreatech.payment.gateway.toss; + +import static in.koreatech.payment.gateway.pg.dto.PaymentGatewayCancelResponse.CancelInfo; + +import org.springframework.stereotype.Service; + +import in.koreatech.payment.gateway.pg.PaymentGatewayService; +import in.koreatech.payment.gateway.pg.PgOrderIdGenerator; +import in.koreatech.payment.gateway.pg.dto.PaymentGatewayCancelResponse; +import in.koreatech.payment.gateway.pg.dto.PaymentGatewayConfirmResponse; +import in.koreatech.payment.gateway.toss.dto.response.TossPaymentCancelResponse; +import in.koreatech.payment.gateway.toss.dto.response.TossPaymentConfirmResponse; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class TossPaymentGatewayService implements PaymentGatewayService { + + private final TossPaymentClient tossPaymentClient; + private final PgOrderIdGenerator pgOrderIdGenerator; + + public PaymentGatewayConfirmResponse confirmPayment(String paymentKey, String pgOrderId, Integer amount) { + TossPaymentConfirmResponse tossPaymentConfirmResponse = tossPaymentClient.requestConfirm(paymentKey, pgOrderId, + amount); + + return new PaymentGatewayConfirmResponse( + tossPaymentConfirmResponse.paymentKey(), + tossPaymentConfirmResponse.totalAmount(), + tossPaymentConfirmResponse.orderId(), + tossPaymentConfirmResponse.status(), + tossPaymentConfirmResponse.method(), + tossPaymentConfirmResponse.requestedAt(), + tossPaymentConfirmResponse.approvedAt() + ); + } + + public PaymentGatewayCancelResponse cancelPayment(String paymentKey, String cancelReason, String idempotencyKey) { + TossPaymentCancelResponse tossPaymentCancelResponse = tossPaymentClient.requestCancel(paymentKey, cancelReason, idempotencyKey); + + return new PaymentGatewayCancelResponse( + tossPaymentCancelResponse.paymentKey(), + tossPaymentCancelResponse.orderId(), + tossPaymentCancelResponse.status(), + tossPaymentCancelResponse.cancels().stream() + .map(cancelInfo -> new CancelInfo( + cancelInfo.cancelAmount(), + cancelInfo.cancelReason(), + cancelInfo.canceledAt(), + cancelInfo.transactionKey() + )) + .toList() + ); + } + + public String generatePgOrderId() { + return pgOrderIdGenerator.generatePgOrderId(); + } +} diff --git a/src/main/java/in/koreatech/payment/gateway/toss/dto/request/TossPaymentCancelRequest.java b/src/main/java/in/koreatech/payment/gateway/toss/dto/request/TossPaymentCancelRequest.java new file mode 100644 index 0000000..50956a0 --- /dev/null +++ b/src/main/java/in/koreatech/payment/gateway/toss/dto/request/TossPaymentCancelRequest.java @@ -0,0 +1,7 @@ +package in.koreatech.payment.gateway.toss.dto.request; + +public record TossPaymentCancelRequest( + String cancelReason +) { + +} diff --git a/src/main/java/in/koreatech/payment/gateway/toss/dto/request/TossPaymentConfirmRequest.java b/src/main/java/in/koreatech/payment/gateway/toss/dto/request/TossPaymentConfirmRequest.java new file mode 100644 index 0000000..3a2a2bd --- /dev/null +++ b/src/main/java/in/koreatech/payment/gateway/toss/dto/request/TossPaymentConfirmRequest.java @@ -0,0 +1,9 @@ +package in.koreatech.payment.gateway.toss.dto.request; + +public record TossPaymentConfirmRequest( + String paymentKey, + String orderId, + Integer amount +) { + +} diff --git a/src/main/java/in/koreatech/payment/gateway/toss/dto/response/TossPaymentCancelResponse.java b/src/main/java/in/koreatech/payment/gateway/toss/dto/response/TossPaymentCancelResponse.java new file mode 100644 index 0000000..3a2d4ed --- /dev/null +++ b/src/main/java/in/koreatech/payment/gateway/toss/dto/response/TossPaymentCancelResponse.java @@ -0,0 +1,20 @@ +package in.koreatech.payment.gateway.toss.dto.response; + +import java.util.List; + +public record TossPaymentCancelResponse( + String paymentKey, + String orderId, + String status, + List cancels +) { + public record CancelInfo( + Integer cancelAmount, + String cancelReason, + String canceledAt, + String transactionKey + ) { + + } + +} diff --git a/src/main/java/in/koreatech/payment/gateway/toss/dto/response/TossPaymentConfirmResponse.java b/src/main/java/in/koreatech/payment/gateway/toss/dto/response/TossPaymentConfirmResponse.java new file mode 100644 index 0000000..6585b80 --- /dev/null +++ b/src/main/java/in/koreatech/payment/gateway/toss/dto/response/TossPaymentConfirmResponse.java @@ -0,0 +1,13 @@ +package in.koreatech.payment.gateway.toss.dto.response; + +public record TossPaymentConfirmResponse( + String paymentKey, + Integer totalAmount, + String orderId, + String status, + String method, + String requestedAt, + String approvedAt +) { + +} diff --git a/src/main/java/in/koreatech/payment/client/exception/TossPaymentErrorCode.java b/src/main/java/in/koreatech/payment/gateway/toss/exception/TossPaymentErrorCode.java similarity index 99% rename from src/main/java/in/koreatech/payment/client/exception/TossPaymentErrorCode.java rename to src/main/java/in/koreatech/payment/gateway/toss/exception/TossPaymentErrorCode.java index 21503f8..74170e5 100644 --- a/src/main/java/in/koreatech/payment/client/exception/TossPaymentErrorCode.java +++ b/src/main/java/in/koreatech/payment/gateway/toss/exception/TossPaymentErrorCode.java @@ -1,4 +1,4 @@ -package in.koreatech.payment.client.exception; +package in.koreatech.payment.gateway.toss.exception; import java.util.Map; import java.util.stream.Collectors; diff --git a/src/main/java/in/koreatech/payment/client/exception/TossPaymentErrorResponse.java b/src/main/java/in/koreatech/payment/gateway/toss/exception/TossPaymentErrorResponse.java similarity index 62% rename from src/main/java/in/koreatech/payment/client/exception/TossPaymentErrorResponse.java rename to src/main/java/in/koreatech/payment/gateway/toss/exception/TossPaymentErrorResponse.java index 5be3924..edea146 100644 --- a/src/main/java/in/koreatech/payment/client/exception/TossPaymentErrorResponse.java +++ b/src/main/java/in/koreatech/payment/gateway/toss/exception/TossPaymentErrorResponse.java @@ -1,4 +1,4 @@ -package in.koreatech.payment.client.exception; +package in.koreatech.payment.gateway.toss.exception; public record TossPaymentErrorResponse( String code, diff --git a/src/main/java/in/koreatech/payment/client/exception/TossPaymentException.java b/src/main/java/in/koreatech/payment/gateway/toss/exception/TossPaymentException.java similarity index 92% rename from src/main/java/in/koreatech/payment/client/exception/TossPaymentException.java rename to src/main/java/in/koreatech/payment/gateway/toss/exception/TossPaymentException.java index 3b47452..336f5c2 100644 --- a/src/main/java/in/koreatech/payment/client/exception/TossPaymentException.java +++ b/src/main/java/in/koreatech/payment/gateway/toss/exception/TossPaymentException.java @@ -1,4 +1,4 @@ -package in.koreatech.payment.client.exception; +package in.koreatech.payment.gateway.toss.exception; public class TossPaymentException extends RuntimeException { diff --git a/src/main/java/in/koreatech/payment/mapper/PaymentCancelMapper.java b/src/main/java/in/koreatech/payment/mapper/PaymentCancelMapper.java new file mode 100644 index 0000000..c3e1773 --- /dev/null +++ b/src/main/java/in/koreatech/payment/mapper/PaymentCancelMapper.java @@ -0,0 +1,36 @@ +package in.koreatech.payment.mapper; + +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.stereotype.Component; + +import in.koreatech.payment.model.entity.Payment; +import in.koreatech.payment.model.entity.PaymentCancel; +import in.koreatech.payment.gateway.pg.dto.PaymentGatewayCancelResponse; + +@Component +public class PaymentCancelMapper { + + public List toEntity(Payment payment, PaymentGatewayCancelResponse paymentGatewayCancelResponse) { + List paymentCancels = new ArrayList<>(); + List cancels = paymentGatewayCancelResponse.cancels(); + + for (PaymentGatewayCancelResponse.CancelInfo cancelInfo : cancels) { + OffsetDateTime cancelOffsetDateTime = OffsetDateTime.parse(cancelInfo.canceledAt()); + LocalDateTime canceled = cancelOffsetDateTime.toLocalDateTime(); + + paymentCancels.add(PaymentCancel.builder() + .transactionKey(cancelInfo.transactionKey()) + .cancelReason(cancelInfo.cancelReason()) + .cancelAmount(cancelInfo.cancelAmount()) + .canceledAt(canceled) + .payment(payment) + .build()); + } + + return paymentCancels; + } +} diff --git a/src/main/java/in/koreatech/payment/mapper/PaymentMapper.java b/src/main/java/in/koreatech/payment/mapper/PaymentMapper.java new file mode 100644 index 0000000..6b87033 --- /dev/null +++ b/src/main/java/in/koreatech/payment/mapper/PaymentMapper.java @@ -0,0 +1,34 @@ +package in.koreatech.payment.mapper; + +import java.time.LocalDateTime; +import java.time.OffsetDateTime; + +import org.springframework.stereotype.Component; + +import in.koreatech.payment.model.entity.Order; +import in.koreatech.payment.model.entity.Payment; +import in.koreatech.payment.model.entity.PaymentMethod; +import in.koreatech.payment.model.entity.PaymentStatus; +import in.koreatech.payment.gateway.pg.dto.PaymentGatewayConfirmResponse; + +@Component +public class PaymentMapper { + + public Payment toEntity(Order order, PaymentGatewayConfirmResponse response) { + OffsetDateTime requestedOffsetDateTime = OffsetDateTime.parse(response.requestedAt()); + OffsetDateTime approvedOffsetDateTime = OffsetDateTime.parse(response.approvedAt()); + + LocalDateTime requested = requestedOffsetDateTime.toLocalDateTime(); + LocalDateTime approved = approvedOffsetDateTime.toLocalDateTime(); + + return Payment.builder() + .paymentKey(response.paymentKey()) + .amount(response.totalAmount()) + .paymentStatus(PaymentStatus.valueOf(response.status())) + .paymentMethod(PaymentMethod.from(response.method())) + .requestedAt(requested) + .approvedAt(approved) + .order(order) + .build(); + } +} diff --git a/src/main/java/in/koreatech/payment/util/TemporaryMenuItemConverter.java b/src/main/java/in/koreatech/payment/mapper/TemporaryMenuItemsMapper.java similarity index 74% rename from src/main/java/in/koreatech/payment/util/TemporaryMenuItemConverter.java rename to src/main/java/in/koreatech/payment/mapper/TemporaryMenuItemsMapper.java index e1830f6..1d898ef 100644 --- a/src/main/java/in/koreatech/payment/util/TemporaryMenuItemConverter.java +++ b/src/main/java/in/koreatech/payment/mapper/TemporaryMenuItemsMapper.java @@ -1,7 +1,9 @@ -package in.koreatech.payment.util; +package in.koreatech.payment.mapper; import java.util.List; +import org.springframework.stereotype.Component; + import in.koreatech.koin.domain.order.cart.model.Cart; import in.koreatech.koin.domain.order.cart.model.CartMenuItem; import in.koreatech.koin.domain.order.cart.model.CartMenuItemOption; @@ -11,20 +13,18 @@ import in.koreatech.payment.model.domain.TemporaryMenuOption; import in.koreatech.payment.model.domain.TemporaryMenuPrice; -public class TemporaryMenuItemConverter { - - private TemporaryMenuItemConverter() { - } +@Component +public class TemporaryMenuItemsMapper { - public static List fromCart(Cart cart) { + public List fromCart(Cart cart) { return cart.getCartMenuItems().stream() - .map(TemporaryMenuItemConverter::fromCartMenuItem) + .map(this::fromCartMenuItem) .toList(); } - public static TemporaryMenuItems fromCartMenuItem(CartMenuItem cartMenuItem) { + private TemporaryMenuItems fromCartMenuItem(CartMenuItem cartMenuItem) { List options = cartMenuItem.getCartMenuItemOptions().stream() - .map(TemporaryMenuItemConverter::fromCartMenuItemOption) + .map(this::fromCartMenuItemOption) .toList(); OrderableShopMenuPrice price = cartMenuItem.getOrderableShopMenuPrice(); @@ -38,7 +38,7 @@ public static TemporaryMenuItems fromCartMenuItem(CartMenuItem cartMenuItem) { ); } - private static TemporaryMenuOption fromCartMenuItemOption(CartMenuItemOption option) { + private TemporaryMenuOption fromCartMenuItemOption(CartMenuItemOption option) { OrderableShopMenuOption shopOption = option.getOrderableShopMenuOption(); String optionGroupName = shopOption.getOptionGroup().getName(); diff --git a/src/main/java/in/koreatech/payment/model/domain/DeliveryPaymentInfo.java b/src/main/java/in/koreatech/payment/model/domain/DeliveryPaymentInfo.java new file mode 100644 index 0000000..c8ee1d7 --- /dev/null +++ b/src/main/java/in/koreatech/payment/model/domain/DeliveryPaymentInfo.java @@ -0,0 +1,47 @@ +package in.koreatech.payment.model.domain; + +import in.koreatech.payment.exception.OrderPriceMismatchException; + +public record DeliveryPaymentInfo( + String phoneNumber, + String address, + String toOwner, + String toRider, + Boolean provideCutlery, + Integer totalMenuPrice, + Integer deliveryTip, + Integer totalAmount +) { + public static DeliveryPaymentInfo of( + String phoneNumber, + String address, + String toOwner, + String toRider, + Boolean provideCutlery, + Integer totalMenuPrice, + Integer deliveryTip, + Integer totalAmount + ) { + return new DeliveryPaymentInfo( + phoneNumber, + address, + toOwner, + toRider, + provideCutlery, + totalMenuPrice, + deliveryTip, + totalAmount + ); + } + + public void validatePrice(Integer totalProductPrice, Integer deliveryFee, Integer finalAmount) { + if (!totalMenuPrice().equals(totalProductPrice) + || !deliveryTip().equals(deliveryFee) + || !totalAmount().equals(finalAmount) + ) { + throw OrderPriceMismatchException.withDetail( + "totalProductPrice : " + totalProductPrice + "deliveryFee : " + deliveryFee + "totalAmount : " + + totalProductPrice + "finalAmount : " + finalAmount); + } + } +} diff --git a/src/main/java/in/koreatech/payment/model/domain/PaymentCancelInfo.java b/src/main/java/in/koreatech/payment/model/domain/PaymentCancelInfo.java new file mode 100644 index 0000000..3dd59f8 --- /dev/null +++ b/src/main/java/in/koreatech/payment/model/domain/PaymentCancelInfo.java @@ -0,0 +1,9 @@ +package in.koreatech.payment.model.domain; + +public record PaymentCancelInfo( + String cancelReason +) { + public static PaymentCancelInfo of(String cancelReason) { + return new PaymentCancelInfo(cancelReason); + } +} diff --git a/src/main/java/in/koreatech/payment/model/domain/PaymentConfirmInfo.java b/src/main/java/in/koreatech/payment/model/domain/PaymentConfirmInfo.java new file mode 100644 index 0000000..c232a67 --- /dev/null +++ b/src/main/java/in/koreatech/payment/model/domain/PaymentConfirmInfo.java @@ -0,0 +1,11 @@ +package in.koreatech.payment.model.domain; + +public record PaymentConfirmInfo( + String paymentKey, + String orderId, + Integer amount +) { + public static PaymentConfirmInfo of(String paymentKey, String orderId, Integer amount) { + return new PaymentConfirmInfo(paymentKey, orderId, amount); + } +} diff --git a/src/main/java/in/koreatech/payment/model/domain/TakeoutPaymentInfo.java b/src/main/java/in/koreatech/payment/model/domain/TakeoutPaymentInfo.java new file mode 100644 index 0000000..587f788 --- /dev/null +++ b/src/main/java/in/koreatech/payment/model/domain/TakeoutPaymentInfo.java @@ -0,0 +1,27 @@ +package in.koreatech.payment.model.domain; + +import in.koreatech.payment.exception.OrderPriceMismatchException; + +public record TakeoutPaymentInfo( + String phoneNumber, + String toOwner, + Boolean provideCutlery, + Integer totalProductPrice, + Integer totalAmount +) { + public static TakeoutPaymentInfo of( + String phoneNumber, String toOwner, Boolean provideCutlery, + Integer totalMenuPrice, Integer totalAmount + ) { + return new TakeoutPaymentInfo(phoneNumber, toOwner, provideCutlery, totalMenuPrice, totalAmount); + } + + public void validatePrice(Integer totalProductPrice, Integer finalAmount) { + if (!totalProductPrice().equals(totalProductPrice) + || !totalAmount().equals(finalAmount) + ) { + throw OrderPriceMismatchException.withDetail( + "totalProductPrice : " + totalProductPrice + "finalAmount : " + finalAmount); + } + } +} diff --git a/src/main/java/in/koreatech/payment/model/domain/TemporaryMenuItems.java b/src/main/java/in/koreatech/payment/model/domain/TemporaryMenuItems.java index 3e427a3..9194c7a 100644 --- a/src/main/java/in/koreatech/payment/model/domain/TemporaryMenuItems.java +++ b/src/main/java/in/koreatech/payment/model/domain/TemporaryMenuItems.java @@ -2,9 +2,9 @@ import java.util.List; -import in.koreatech.koin.domain.order.model.Order; -import in.koreatech.koin.domain.order.model.OrderMenu; -import in.koreatech.koin.domain.order.model.OrderMenuOption; +import in.koreatech.payment.model.entity.Order; +import in.koreatech.payment.model.entity.OrderMenu; +import in.koreatech.payment.model.entity.OrderMenuOption; public record TemporaryMenuItems( String name, diff --git a/src/main/java/in/koreatech/payment/model/domain/TemporaryMenuOption.java b/src/main/java/in/koreatech/payment/model/domain/TemporaryMenuOption.java index 94f3ba8..6f8fca0 100644 --- a/src/main/java/in/koreatech/payment/model/domain/TemporaryMenuOption.java +++ b/src/main/java/in/koreatech/payment/model/domain/TemporaryMenuOption.java @@ -1,7 +1,7 @@ package in.koreatech.payment.model.domain; -import in.koreatech.koin.domain.order.model.OrderMenu; -import in.koreatech.koin.domain.order.model.OrderMenuOption; +import in.koreatech.payment.model.entity.OrderMenu; +import in.koreatech.payment.model.entity.OrderMenuOption; public record TemporaryMenuOption( String optionGroupName, diff --git a/src/main/java/in/koreatech/koin/domain/order/model/Order.java b/src/main/java/in/koreatech/payment/model/entity/Order.java similarity index 72% rename from src/main/java/in/koreatech/koin/domain/order/model/Order.java rename to src/main/java/in/koreatech/payment/model/entity/Order.java index 9d6511e..d27872e 100644 --- a/src/main/java/in/koreatech/koin/domain/order/model/Order.java +++ b/src/main/java/in/koreatech/payment/model/entity/Order.java @@ -1,8 +1,9 @@ -package in.koreatech.koin.domain.order.model; +package in.koreatech.payment.model.entity; import static jakarta.persistence.CascadeType.ALL; import static jakarta.persistence.EnumType.STRING; import static jakarta.persistence.FetchType.LAZY; +import static jakarta.persistence.GenerationType.IDENTITY; import static java.lang.Boolean.FALSE; import static lombok.AccessLevel.PROTECTED; @@ -14,6 +15,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; @@ -28,16 +30,19 @@ @Getter @Entity -@Table(schema = "koin", name = "`order`") +@Table(schema = "koin_payment", name = "`order`") @Where(clause = "is_deleted=0") @NoArgsConstructor(access = PROTECTED) public class Order extends BaseEntity { @Id + @GeneratedValue(strategy = IDENTITY) + private Integer id; + @NotBlank @Size(min = 6, max = 64) - @Column(name = "id", length = 64, nullable = false, updatable = false) - private String id; + @Column(name = "pg_order_id", length = 64, nullable = false, updatable = false) + private String pgOrderId; @NotNull @Enumerated(STRING) @@ -61,13 +66,13 @@ public class Order extends BaseEntity { @Column(name = "is_deleted", nullable = false) private Boolean isDeleted = FALSE; - @JoinColumn(name = "orderable_shop_id", nullable = false, updatable = false) - @ManyToOne(fetch = LAZY) - private OrderableShop orderableShop; + @NotNull + @Column(name = "orderable_shop_id", nullable = false, updatable = false) + private Integer orderableShopId; - @JoinColumn(name = "user_id", nullable = false, updatable = false) - @ManyToOne(fetch = LAZY) - private User user; + @NotNull + @Column(name = "user_id", nullable = false, updatable = false) + private Integer userId; @OneToOne(mappedBy = "order", fetch = LAZY, cascade = ALL) private OrderDelivery orderDelivery; @@ -76,24 +81,30 @@ public class Order extends BaseEntity { private OrderTakeout orderTakeout; @Builder - public Order( - String id, + private Order( + Integer id, + String pgOrderId, OrderType orderType, String phoneNumber, Integer totalProductPrice, Integer totalPrice, Boolean isDeleted, - OrderableShop orderableShop, - User user + Integer orderableShopId, + Integer userId, + OrderDelivery orderDelivery, + OrderTakeout orderTakeout ) { this.id = id; + this.pgOrderId = pgOrderId; this.orderType = orderType; this.phoneNumber = phoneNumber; this.totalProductPrice = totalProductPrice; this.totalPrice = totalPrice; this.isDeleted = isDeleted; - this.orderableShop = orderableShop; - this.user = user; + this.orderableShopId = orderableShopId; + this.userId = userId; + this.orderDelivery = orderDelivery; + this.orderTakeout = orderTakeout; } public void setOrderDelivery(OrderDelivery orderDelivery) { diff --git a/src/main/java/in/koreatech/koin/domain/order/model/OrderDelivery.java b/src/main/java/in/koreatech/payment/model/entity/OrderDelivery.java similarity index 93% rename from src/main/java/in/koreatech/koin/domain/order/model/OrderDelivery.java rename to src/main/java/in/koreatech/payment/model/entity/OrderDelivery.java index 27bcd78..1f4c2cc 100644 --- a/src/main/java/in/koreatech/koin/domain/order/model/OrderDelivery.java +++ b/src/main/java/in/koreatech/payment/model/entity/OrderDelivery.java @@ -1,4 +1,4 @@ -package in.koreatech.koin.domain.order.model; +package in.koreatech.payment.model.entity; import static lombok.AccessLevel.PROTECTED; @@ -18,13 +18,13 @@ @Getter @Entity -@Table(name = "order_delivery") +@Table(schema = "koin_payment", name = "order_delivery") @NoArgsConstructor(access = PROTECTED) public class OrderDelivery { @Id @Column(name = "order_id", nullable = false, updatable = false) - private String id; + private Integer id; @MapsId @OneToOne diff --git a/src/main/java/in/koreatech/koin/domain/order/model/OrderMenu.java b/src/main/java/in/koreatech/payment/model/entity/OrderMenu.java similarity index 95% rename from src/main/java/in/koreatech/koin/domain/order/model/OrderMenu.java rename to src/main/java/in/koreatech/payment/model/entity/OrderMenu.java index 6033a2c..4572c37 100644 --- a/src/main/java/in/koreatech/koin/domain/order/model/OrderMenu.java +++ b/src/main/java/in/koreatech/payment/model/entity/OrderMenu.java @@ -1,4 +1,4 @@ -package in.koreatech.koin.domain.order.model; +package in.koreatech.payment.model.entity; import static jakarta.persistence.CascadeType.ALL; import static jakarta.persistence.FetchType.LAZY; @@ -24,7 +24,7 @@ @Entity @Getter -@Table(schema = "koin", name = "order_menu") +@Table(schema = "koin_payment", name = "order_menu") @NoArgsConstructor(access = PROTECTED) public class OrderMenu { diff --git a/src/main/java/in/koreatech/koin/domain/order/model/OrderMenuOption.java b/src/main/java/in/koreatech/payment/model/entity/OrderMenuOption.java similarity index 94% rename from src/main/java/in/koreatech/koin/domain/order/model/OrderMenuOption.java rename to src/main/java/in/koreatech/payment/model/entity/OrderMenuOption.java index 733575e..7c107b5 100644 --- a/src/main/java/in/koreatech/koin/domain/order/model/OrderMenuOption.java +++ b/src/main/java/in/koreatech/payment/model/entity/OrderMenuOption.java @@ -1,4 +1,4 @@ -package in.koreatech.koin.domain.order.model; +package in.koreatech.payment.model.entity; import static jakarta.persistence.FetchType.LAZY; import static jakarta.persistence.GenerationType.IDENTITY; @@ -19,7 +19,7 @@ @Entity @Getter -@Table(schema = "koin", name = "order_menu_option") +@Table(schema = "koin_payment", name = "order_menu_option") @NoArgsConstructor(access = PROTECTED) public class OrderMenuOption { diff --git a/src/main/java/in/koreatech/koin/domain/order/model/OrderTakeout.java b/src/main/java/in/koreatech/payment/model/entity/OrderTakeout.java similarity index 91% rename from src/main/java/in/koreatech/koin/domain/order/model/OrderTakeout.java rename to src/main/java/in/koreatech/payment/model/entity/OrderTakeout.java index ed5cc7c..e68fdbc 100644 --- a/src/main/java/in/koreatech/koin/domain/order/model/OrderTakeout.java +++ b/src/main/java/in/koreatech/payment/model/entity/OrderTakeout.java @@ -1,4 +1,4 @@ -package in.koreatech.koin.domain.order.model; +package in.koreatech.payment.model.entity; import static lombok.AccessLevel.PROTECTED; @@ -17,13 +17,13 @@ @Getter @Entity -@Table(name = "order_takeout") +@Table(schema = "koin_payment", name = "order_takeout") @NoArgsConstructor(access = PROTECTED) public class OrderTakeout { @Id @Column(name = "order_id", nullable = false, updatable = false) - private String id; + private Integer id; @MapsId @OneToOne diff --git a/src/main/java/in/koreatech/koin/domain/order/model/OrderType.java b/src/main/java/in/koreatech/payment/model/entity/OrderType.java similarity index 81% rename from src/main/java/in/koreatech/koin/domain/order/model/OrderType.java rename to src/main/java/in/koreatech/payment/model/entity/OrderType.java index e82a5e9..82c5c50 100644 --- a/src/main/java/in/koreatech/koin/domain/order/model/OrderType.java +++ b/src/main/java/in/koreatech/payment/model/entity/OrderType.java @@ -1,4 +1,4 @@ -package in.koreatech.koin.domain.order.model; +package in.koreatech.payment.model.entity; import lombok.Getter; diff --git a/src/main/java/in/koreatech/koin/domain/order/model/Payment.java b/src/main/java/in/koreatech/payment/model/entity/Payment.java similarity index 89% rename from src/main/java/in/koreatech/koin/domain/order/model/Payment.java rename to src/main/java/in/koreatech/payment/model/entity/Payment.java index a4a0477..57f6f1b 100644 --- a/src/main/java/in/koreatech/koin/domain/order/model/Payment.java +++ b/src/main/java/in/koreatech/payment/model/entity/Payment.java @@ -1,6 +1,6 @@ -package in.koreatech.koin.domain.order.model; +package in.koreatech.payment.model.entity; -import static in.koreatech.koin.domain.order.model.PaymentStatus.CANCELED; +import static in.koreatech.payment.model.entity.PaymentStatus.CANCELED; import static jakarta.persistence.EnumType.STRING; import static jakarta.persistence.FetchType.LAZY; import static jakarta.persistence.GenerationType.IDENTITY; @@ -8,7 +8,7 @@ import java.time.LocalDateTime; -import in.koreatech.koin.domain.order.exception.PaymentAccessDeniedException; +import in.koreatech.payment.exception.PaymentAccessDeniedException; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Enumerated; @@ -26,7 +26,7 @@ @Entity @Getter -@Table(schema = "koin", name = "payment") +@Table(schema = "koin_payment", name = "payment") @NoArgsConstructor(access = PROTECTED) public class Payment { @@ -86,7 +86,7 @@ private Payment( } public void validateUserIdMatches(Integer userId) { - if (!userId.equals(this.order.getUser().getId())) { + if (!userId.equals(this.order.getUserId())) { throw PaymentAccessDeniedException.withDetail("userId : " + userId); } } diff --git a/src/main/java/in/koreatech/koin/domain/order/model/PaymentCancel.java b/src/main/java/in/koreatech/payment/model/entity/PaymentCancel.java similarity index 95% rename from src/main/java/in/koreatech/koin/domain/order/model/PaymentCancel.java rename to src/main/java/in/koreatech/payment/model/entity/PaymentCancel.java index a81f8ed..47463c3 100644 --- a/src/main/java/in/koreatech/koin/domain/order/model/PaymentCancel.java +++ b/src/main/java/in/koreatech/payment/model/entity/PaymentCancel.java @@ -1,4 +1,4 @@ -package in.koreatech.koin.domain.order.model; +package in.koreatech.payment.model.entity; import static jakarta.persistence.FetchType.LAZY; import static jakarta.persistence.GenerationType.IDENTITY; @@ -21,7 +21,7 @@ @Entity @Getter -@Table(name = "payment_cancel") +@Table(schema = "koin_payment", name = "payment_cancel") @NoArgsConstructor(access = PROTECTED) public class PaymentCancel { diff --git a/src/main/java/in/koreatech/koin/domain/order/model/PaymentIdempotencyKey.java b/src/main/java/in/koreatech/payment/model/entity/PaymentIdempotencyKey.java similarity index 95% rename from src/main/java/in/koreatech/koin/domain/order/model/PaymentIdempotencyKey.java rename to src/main/java/in/koreatech/payment/model/entity/PaymentIdempotencyKey.java index 4e5bf64..bdd05f0 100644 --- a/src/main/java/in/koreatech/koin/domain/order/model/PaymentIdempotencyKey.java +++ b/src/main/java/in/koreatech/payment/model/entity/PaymentIdempotencyKey.java @@ -1,4 +1,4 @@ -package in.koreatech.koin.domain.order.model; +package in.koreatech.payment.model.entity; import static jakarta.persistence.GenerationType.IDENTITY; import static lombok.AccessLevel.PROTECTED; @@ -22,6 +22,7 @@ @Getter @Entity @Table( + schema = "koin_payment", name = "payment_idempotency_key", uniqueConstraints = @UniqueConstraint(name = "uk_idempotency_key_user_id", columnNames = "user_id") ) diff --git a/src/main/java/in/koreatech/koin/domain/order/model/PaymentMethod.java b/src/main/java/in/koreatech/payment/model/entity/PaymentMethod.java similarity index 94% rename from src/main/java/in/koreatech/koin/domain/order/model/PaymentMethod.java rename to src/main/java/in/koreatech/payment/model/entity/PaymentMethod.java index 65bcfbd..1f413b1 100644 --- a/src/main/java/in/koreatech/koin/domain/order/model/PaymentMethod.java +++ b/src/main/java/in/koreatech/payment/model/entity/PaymentMethod.java @@ -1,4 +1,4 @@ -package in.koreatech.koin.domain.order.model; +package in.koreatech.payment.model.entity; import java.util.Arrays; diff --git a/src/main/java/in/koreatech/koin/domain/order/model/PaymentStatus.java b/src/main/java/in/koreatech/payment/model/entity/PaymentStatus.java similarity index 87% rename from src/main/java/in/koreatech/koin/domain/order/model/PaymentStatus.java rename to src/main/java/in/koreatech/payment/model/entity/PaymentStatus.java index f56398f..e88e9f1 100644 --- a/src/main/java/in/koreatech/koin/domain/order/model/PaymentStatus.java +++ b/src/main/java/in/koreatech/payment/model/entity/PaymentStatus.java @@ -1,4 +1,4 @@ -package in.koreatech.koin.domain.order.model; +package in.koreatech.payment.model.entity; import lombok.Getter; diff --git a/src/main/java/in/koreatech/payment/model/redis/TemporaryPayment.java b/src/main/java/in/koreatech/payment/model/redis/TemporaryPayment.java index 8ad2750..3a8de9b 100644 --- a/src/main/java/in/koreatech/payment/model/redis/TemporaryPayment.java +++ b/src/main/java/in/koreatech/payment/model/redis/TemporaryPayment.java @@ -1,7 +1,7 @@ package in.koreatech.payment.model.redis; -import static in.koreatech.koin.domain.order.model.OrderType.DELIVERY; -import static in.koreatech.koin.domain.order.model.OrderType.TAKE_OUT; +import static in.koreatech.payment.model.entity.OrderType.DELIVERY; +import static in.koreatech.payment.model.entity.OrderType.TAKE_OUT; import java.time.LocalDateTime; import java.util.List; @@ -10,10 +10,10 @@ import org.springframework.data.redis.core.RedisHash; import org.springframework.data.redis.core.TimeToLive; -import in.koreatech.koin.domain.order.model.Order; -import in.koreatech.koin.domain.order.model.OrderDelivery; -import in.koreatech.koin.domain.order.model.OrderTakeout; -import in.koreatech.koin.domain.order.model.OrderType; +import in.koreatech.payment.model.entity.Order; +import in.koreatech.payment.model.entity.OrderDelivery; +import in.koreatech.payment.model.entity.OrderTakeout; +import in.koreatech.payment.model.entity.OrderType; import in.koreatech.koin.domain.order.shop.model.entity.shop.OrderableShop; import in.koreatech.koin.domain.user.model.User; import in.koreatech.payment.exception.InvalidTemporaryPaymentException; @@ -27,7 +27,7 @@ public class TemporaryPayment { private static final Long CACHE_EXPIRE_SECOND = 60 * 10L; @Id - private String orderId; + private String pgOrderId; private Integer userId; @@ -59,7 +59,7 @@ public class TemporaryPayment { private LocalDateTime createdAt; private TemporaryPayment( - String orderId, + String pgOrderId, Integer userId, Integer orderableShopId, String phoneNumber, @@ -73,7 +73,7 @@ private TemporaryPayment( Integer totalPrice, List temporaryMenuItems ) { - this.orderId = orderId; + this.pgOrderId = pgOrderId; this.userId = userId; this.orderableShopId = orderableShopId; this.phoneNumber = phoneNumber; @@ -91,7 +91,7 @@ private TemporaryPayment( } public static TemporaryPayment toDeliveryEntity( - String orderId, + String pgOrderId, Integer userId, Integer orderableShopId, String phoneNumber, @@ -105,7 +105,7 @@ public static TemporaryPayment toDeliveryEntity( List temporaryMenuItems ) { return new TemporaryPayment( - orderId, + pgOrderId, userId, orderableShopId, phoneNumber, @@ -122,7 +122,7 @@ public static TemporaryPayment toDeliveryEntity( } public static TemporaryPayment toTakeOutEntity( - String orderId, + String pgOrderId, Integer userId, Integer orderableShopId, String phoneNumber, @@ -133,7 +133,7 @@ public static TemporaryPayment toTakeOutEntity( List temporaryMenuItems ) { return new TemporaryPayment( - orderId, + pgOrderId, userId, orderableShopId, phoneNumber, @@ -151,13 +151,13 @@ public static TemporaryPayment toTakeOutEntity( public Order toOrder(User user, OrderableShop orderableShop) { Order order = Order.builder() - .id(orderId) + .pgOrderId(pgOrderId) .orderType(orderType) .phoneNumber(phoneNumber) .totalProductPrice(totalProductPrice) .totalPrice(totalPrice) - .orderableShop(orderableShop) - .user(user) + .orderableShopId(orderableShop.getId()) + .userId(user.getId()) .isDeleted(false) .build(); @@ -181,15 +181,15 @@ public Order toOrder(User user, OrderableShop orderableShop) { return order; } - public void validateMatches(String orderId, Integer userId, Integer amount) { - validateOrderIdMatches(orderId); + public void validateMatches(String pgOrderId, Integer userId, Integer amount) { + validatePgOrderIdMatches(pgOrderId); validateUserIdMatches(userId); validateAmountMatches(amount); } - private void validateOrderIdMatches(String orderId) { - if (!orderId.equals(this.orderId)) { - throw InvalidTemporaryPaymentException.withDetail("orderId : " + orderId); + private void validatePgOrderIdMatches(String pgOrderId) { + if (!pgOrderId.equals(this.pgOrderId)) { + throw InvalidTemporaryPaymentException.withDetail("orderId : " + pgOrderId); } } diff --git a/src/main/java/in/koreatech/koin/domain/order/repository/OrderDeliveryRepository.java b/src/main/java/in/koreatech/payment/repository/mysql/OrderDeliveryRepository.java similarity index 63% rename from src/main/java/in/koreatech/koin/domain/order/repository/OrderDeliveryRepository.java rename to src/main/java/in/koreatech/payment/repository/mysql/OrderDeliveryRepository.java index 4691a83..f1e7c9a 100644 --- a/src/main/java/in/koreatech/koin/domain/order/repository/OrderDeliveryRepository.java +++ b/src/main/java/in/koreatech/payment/repository/mysql/OrderDeliveryRepository.java @@ -1,8 +1,8 @@ -package in.koreatech.koin.domain.order.repository; +package in.koreatech.payment.repository.mysql; import org.springframework.data.repository.Repository; -import in.koreatech.koin.domain.order.model.OrderDelivery; +import in.koreatech.payment.model.entity.OrderDelivery; public interface OrderDeliveryRepository extends Repository { diff --git a/src/main/java/in/koreatech/koin/domain/order/repository/OrderMenuOptionRepository.java b/src/main/java/in/koreatech/payment/repository/mysql/OrderMenuOptionRepository.java similarity index 65% rename from src/main/java/in/koreatech/koin/domain/order/repository/OrderMenuOptionRepository.java rename to src/main/java/in/koreatech/payment/repository/mysql/OrderMenuOptionRepository.java index 56653fb..8dd36ab 100644 --- a/src/main/java/in/koreatech/koin/domain/order/repository/OrderMenuOptionRepository.java +++ b/src/main/java/in/koreatech/payment/repository/mysql/OrderMenuOptionRepository.java @@ -1,8 +1,8 @@ -package in.koreatech.koin.domain.order.repository; +package in.koreatech.payment.repository.mysql; import org.springframework.data.repository.Repository; -import in.koreatech.koin.domain.order.model.OrderMenuOption; +import in.koreatech.payment.model.entity.OrderMenuOption; public interface OrderMenuOptionRepository extends Repository { diff --git a/src/main/java/in/koreatech/koin/domain/order/repository/OrderMenuRepository.java b/src/main/java/in/koreatech/payment/repository/mysql/OrderMenuRepository.java similarity index 57% rename from src/main/java/in/koreatech/koin/domain/order/repository/OrderMenuRepository.java rename to src/main/java/in/koreatech/payment/repository/mysql/OrderMenuRepository.java index 5020d5c..4bda8cc 100644 --- a/src/main/java/in/koreatech/koin/domain/order/repository/OrderMenuRepository.java +++ b/src/main/java/in/koreatech/payment/repository/mysql/OrderMenuRepository.java @@ -1,14 +1,14 @@ -package in.koreatech.koin.domain.order.repository; +package in.koreatech.payment.repository.mysql; import java.util.List; import org.springframework.data.repository.Repository; -import in.koreatech.koin.domain.order.model.OrderMenu; +import in.koreatech.payment.model.entity.OrderMenu; public interface OrderMenuRepository extends Repository { void saveAll(Iterable orderMenus); - List findAllByOrderId(String orderId); + List findAllByOrderId(Integer orderId); } diff --git a/src/main/java/in/koreatech/koin/domain/order/repository/OrderRepository.java b/src/main/java/in/koreatech/payment/repository/mysql/OrderRepository.java similarity index 60% rename from src/main/java/in/koreatech/koin/domain/order/repository/OrderRepository.java rename to src/main/java/in/koreatech/payment/repository/mysql/OrderRepository.java index 92693ae..70314b7 100644 --- a/src/main/java/in/koreatech/koin/domain/order/repository/OrderRepository.java +++ b/src/main/java/in/koreatech/payment/repository/mysql/OrderRepository.java @@ -1,8 +1,8 @@ -package in.koreatech.koin.domain.order.repository; +package in.koreatech.payment.repository.mysql; import org.springframework.data.repository.Repository; -import in.koreatech.koin.domain.order.model.Order; +import in.koreatech.payment.model.entity.Order; public interface OrderRepository extends Repository { diff --git a/src/main/java/in/koreatech/koin/domain/order/repository/OrderTakeoutRepository.java b/src/main/java/in/koreatech/payment/repository/mysql/OrderTakeoutRepository.java similarity index 64% rename from src/main/java/in/koreatech/koin/domain/order/repository/OrderTakeoutRepository.java rename to src/main/java/in/koreatech/payment/repository/mysql/OrderTakeoutRepository.java index b7dc2b1..7c9cfa9 100644 --- a/src/main/java/in/koreatech/koin/domain/order/repository/OrderTakeoutRepository.java +++ b/src/main/java/in/koreatech/payment/repository/mysql/OrderTakeoutRepository.java @@ -1,8 +1,8 @@ -package in.koreatech.koin.domain.order.repository; +package in.koreatech.payment.repository.mysql; import org.springframework.data.repository.Repository; -import in.koreatech.koin.domain.order.model.OrderTakeout; +import in.koreatech.payment.model.entity.OrderTakeout; public interface OrderTakeoutRepository extends Repository { diff --git a/src/main/java/in/koreatech/koin/domain/order/repository/PaymentCancelRepository.java b/src/main/java/in/koreatech/payment/repository/mysql/PaymentCancelRepository.java similarity index 72% rename from src/main/java/in/koreatech/koin/domain/order/repository/PaymentCancelRepository.java rename to src/main/java/in/koreatech/payment/repository/mysql/PaymentCancelRepository.java index d54dd1e..cc7c506 100644 --- a/src/main/java/in/koreatech/koin/domain/order/repository/PaymentCancelRepository.java +++ b/src/main/java/in/koreatech/payment/repository/mysql/PaymentCancelRepository.java @@ -1,10 +1,10 @@ -package in.koreatech.koin.domain.order.repository; +package in.koreatech.payment.repository.mysql; import java.util.List; import org.springframework.data.repository.Repository; -import in.koreatech.koin.domain.order.model.PaymentCancel; +import in.koreatech.payment.model.entity.PaymentCancel; public interface PaymentCancelRepository extends Repository { diff --git a/src/main/java/in/koreatech/koin/domain/order/repository/PaymentIdempotencyKeyRepository.java b/src/main/java/in/koreatech/payment/repository/mysql/PaymentIdempotencyKeyRepository.java similarity index 73% rename from src/main/java/in/koreatech/koin/domain/order/repository/PaymentIdempotencyKeyRepository.java rename to src/main/java/in/koreatech/payment/repository/mysql/PaymentIdempotencyKeyRepository.java index bfe9c3d..0d89d4d 100644 --- a/src/main/java/in/koreatech/koin/domain/order/repository/PaymentIdempotencyKeyRepository.java +++ b/src/main/java/in/koreatech/payment/repository/mysql/PaymentIdempotencyKeyRepository.java @@ -1,10 +1,10 @@ -package in.koreatech.koin.domain.order.repository; +package in.koreatech.payment.repository.mysql; import java.util.Optional; import org.springframework.data.repository.Repository; -import in.koreatech.koin.domain.order.model.PaymentIdempotencyKey; +import in.koreatech.payment.model.entity.PaymentIdempotencyKey; public interface PaymentIdempotencyKeyRepository extends Repository { diff --git a/src/main/java/in/koreatech/koin/domain/order/repository/PaymentRepository.java b/src/main/java/in/koreatech/payment/repository/mysql/PaymentRepository.java similarity index 87% rename from src/main/java/in/koreatech/koin/domain/order/repository/PaymentRepository.java rename to src/main/java/in/koreatech/payment/repository/mysql/PaymentRepository.java index 8a74e6e..a766f94 100644 --- a/src/main/java/in/koreatech/koin/domain/order/repository/PaymentRepository.java +++ b/src/main/java/in/koreatech/payment/repository/mysql/PaymentRepository.java @@ -1,10 +1,10 @@ -package in.koreatech.koin.domain.order.repository; +package in.koreatech.payment.repository.mysql; import java.util.Optional; import org.springframework.data.repository.Repository; -import in.koreatech.koin.domain.order.model.Payment; +import in.koreatech.payment.model.entity.Payment; import in.koreatech.payment.exception.PaymentNotFoundException; public interface PaymentRepository extends Repository { diff --git a/src/main/java/in/koreatech/payment/repository/redis/TemporaryPaymentRedisRepository.java b/src/main/java/in/koreatech/payment/repository/redis/TemporaryPaymentRedisRepository.java index 4ad89cd..7dc07fe 100644 --- a/src/main/java/in/koreatech/payment/repository/redis/TemporaryPaymentRedisRepository.java +++ b/src/main/java/in/koreatech/payment/repository/redis/TemporaryPaymentRedisRepository.java @@ -11,12 +11,12 @@ public interface TemporaryPaymentRedisRepository extends Repository findById(String orderId); + Optional findById(String pgOrderId); - default TemporaryPayment getById(String orderId) { - return findById(orderId) - .orElseThrow(() -> TemporaryPaymentNotFoundException.withDetail("orderId : " + orderId)); + default TemporaryPayment getById(String pgOrderId) { + return findById(pgOrderId) + .orElseThrow(() -> TemporaryPaymentNotFoundException.withDetail("pgOrderId : " + pgOrderId)); } - void deleteById(String orderId); + void deleteById(String pgOrderId); } diff --git a/src/main/java/in/koreatech/payment/service/PaymentCancelService.java b/src/main/java/in/koreatech/payment/service/PaymentCancelService.java new file mode 100644 index 0000000..2d9d173 --- /dev/null +++ b/src/main/java/in/koreatech/payment/service/PaymentCancelService.java @@ -0,0 +1,61 @@ +package in.koreatech.payment.service; + +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import in.koreatech.payment.model.entity.Payment; +import in.koreatech.payment.model.entity.PaymentCancel; +import in.koreatech.payment.model.entity.PaymentStatus; +import in.koreatech.payment.repository.mysql.PaymentCancelRepository; +import in.koreatech.payment.repository.mysql.PaymentRepository; +import in.koreatech.koin.domain.user.model.User; +import in.koreatech.payment.dto.response.PaymentCancelResponse; +import in.koreatech.payment.exception.PaymentAlreadyCanceledException; +import in.koreatech.payment.exception.PaymentCancelException; +import in.koreatech.payment.gateway.pg.PaymentGatewayService; +import in.koreatech.payment.gateway.pg.dto.PaymentGatewayCancelResponse; +import in.koreatech.payment.mapper.PaymentCancelMapper; +import in.koreatech.payment.model.domain.PaymentCancelInfo; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class PaymentCancelService { + + private final PaymentRepository paymentRepository; + private final PaymentCancelRepository paymentCancelRepository; + private final PaymentGatewayService paymentGatewayService; + private final PaymentIdempotencyKeyService paymentIdempotencyKeyService; + private final PaymentCancelMapper paymentCancelMapper; + + @Transactional(transactionManager = "koinPaymentTransactionManager") + public PaymentCancelResponse cancelPayment(User user, Integer paymentId, PaymentCancelInfo paymentCancelInfo) { + Payment payment = paymentRepository.getById(paymentId); + validatePaymentStatusIsNotCanceled(payment); + payment.validateUserIdMatches(user.getId()); + + String paymentIdempotencyKey = paymentIdempotencyKeyService.getOrCreate(user.getId()); + PaymentGatewayCancelResponse pgResponse = paymentGatewayService.cancelPayment(payment.getPaymentKey(), paymentCancelInfo.cancelReason(), paymentIdempotencyKey); + validatePaymentIsCanceled(pgResponse.status()); + + payment.cancel(); + List paymentCancels = paymentCancelMapper.toEntity(payment, pgResponse); + paymentCancelRepository.saveAll(paymentCancels); + + return PaymentCancelResponse.from(paymentCancels); + } + + private void validatePaymentStatusIsNotCanceled(Payment payment) { + if (payment.getPaymentStatus().isCanceled()) { + throw PaymentAlreadyCanceledException.withDetail("paymentId : " + payment.getId()); + } + } + + private void validatePaymentIsCanceled(String status) { + if (!PaymentStatus.valueOf(status).isCanceled()) { + throw PaymentCancelException.withDetail("paymentStatus : " + status); + } + } +} diff --git a/src/main/java/in/koreatech/payment/service/PaymentConfirmService.java b/src/main/java/in/koreatech/payment/service/PaymentConfirmService.java new file mode 100644 index 0000000..42f4f03 --- /dev/null +++ b/src/main/java/in/koreatech/payment/service/PaymentConfirmService.java @@ -0,0 +1,83 @@ +package in.koreatech.payment.service; + +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import in.koreatech.koin.domain.order.cart.repository.CartRepository; +import in.koreatech.payment.model.entity.Order; +import in.koreatech.payment.model.entity.OrderMenu; +import in.koreatech.payment.model.entity.Payment; +import in.koreatech.payment.model.entity.PaymentStatus; +import in.koreatech.payment.repository.mysql.OrderMenuRepository; +import in.koreatech.payment.repository.mysql.OrderRepository; +import in.koreatech.payment.repository.mysql.PaymentRepository; +import in.koreatech.koin.domain.order.shop.model.entity.shop.OrderableShop; +import in.koreatech.koin.domain.order.shop.repository.OrderableShopRepository; +import in.koreatech.koin.domain.user.model.User; +import in.koreatech.payment.dto.response.PaymentConfirmResponse; +import in.koreatech.payment.exception.PaymentConfirmException; +import in.koreatech.payment.gateway.pg.PaymentGatewayService; +import in.koreatech.payment.gateway.pg.dto.PaymentGatewayConfirmResponse; +import in.koreatech.payment.mapper.PaymentMapper; +import in.koreatech.payment.model.domain.PaymentConfirmInfo; +import in.koreatech.payment.model.redis.TemporaryPayment; +import in.koreatech.payment.repository.redis.TemporaryPaymentRedisRepository; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class PaymentConfirmService { + + private final PaymentGatewayService paymentGatewayService; + private final OrderableShopRepository orderableShopRepository; + private final OrderRepository orderRepository; + private final OrderMenuRepository orderMenuRepository; + private final PaymentRepository paymentRepository; + private final TemporaryPaymentRedisRepository temporaryPaymentRedisRepository; + private final CartRepository cartRepository; + private final PaymentMapper paymentMapper; + + @Transactional(transactionManager = "koinPaymentTransactionManager") + public PaymentConfirmResponse confirmPayment(User user, PaymentConfirmInfo paymentConfirmInfo) { + TemporaryPayment temporaryPayment = temporaryPaymentRedisRepository.getById(paymentConfirmInfo.orderId()); + temporaryPayment.validateMatches(paymentConfirmInfo.orderId(), user.getId(), paymentConfirmInfo.amount()); + + PaymentGatewayConfirmResponse pgResponse = paymentGatewayService.confirmPayment(paymentConfirmInfo.paymentKey(), + paymentConfirmInfo.orderId(), paymentConfirmInfo.amount()); + validatePaymentStatus(pgResponse.status()); + + OrderableShop orderableShop = orderableShopRepository.getById(temporaryPayment.getOrderableShopId()); + Order order = temporaryPayment.toOrder(user, orderableShop); + orderRepository.save(order); + + List orderMenus = createOrderMenus(temporaryPayment, order); + orderMenuRepository.saveAll(orderMenus); + + Payment payment = paymentMapper.toEntity(order, pgResponse); + paymentRepository.save(payment); + + cleanupAfterPaymentConfirm(paymentConfirmInfo.orderId(), user.getId()); + + return PaymentConfirmResponse.of(payment, order, orderableShop, orderMenus); + } + + private void validatePaymentStatus(String status) { + PaymentStatus paymentStatus = PaymentStatus.valueOf(status); + if (!paymentStatus.isDone()) { + throw PaymentConfirmException.withDetail("paymentStatus : " + status); + } + } + + private List createOrderMenus(TemporaryPayment temporaryPayment, Order order) { + return temporaryPayment.getTemporaryMenuItems().stream() + .map(temporaryMenuItems -> temporaryMenuItems.toOrderMenu(order)) + .toList(); + } + + private void cleanupAfterPaymentConfirm(String orderId, Integer userId) { + temporaryPaymentRedisRepository.deleteById(orderId); + cartRepository.deleteByUserId(userId); + } +} diff --git a/src/main/java/in/koreatech/payment/service/PaymentIdempotencyKeyService.java b/src/main/java/in/koreatech/payment/service/PaymentIdempotencyKeyService.java new file mode 100644 index 0000000..e260bd2 --- /dev/null +++ b/src/main/java/in/koreatech/payment/service/PaymentIdempotencyKeyService.java @@ -0,0 +1,35 @@ +package in.koreatech.payment.service; + +import java.util.UUID; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import in.koreatech.payment.model.entity.PaymentIdempotencyKey; +import in.koreatech.payment.repository.mysql.PaymentIdempotencyKeyRepository; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class PaymentIdempotencyKeyService { + + private final PaymentIdempotencyKeyRepository paymentIdempotencyKeyRepository; + + @Transactional(transactionManager = "koinPaymentTransactionManager") + public String getOrCreate(Integer userId) { + return paymentIdempotencyKeyRepository + .findByUserId(userId) + .map(idempotencyKey -> { + if (idempotencyKey.isOlderThanExpireDays()) { + idempotencyKey.updateIdempotencyKey(UUID.randomUUID().toString()); + } + return idempotencyKey; + }) + .orElseGet(() -> paymentIdempotencyKeyRepository.save( + PaymentIdempotencyKey.builder() + .userId(userId) + .idempotencyKey(UUID.randomUUID().toString()) + .build() + )).getIdempotencyKey(); + } +} diff --git a/src/main/java/in/koreatech/payment/service/PaymentQueryService.java b/src/main/java/in/koreatech/payment/service/PaymentQueryService.java new file mode 100644 index 0000000..b780d7e --- /dev/null +++ b/src/main/java/in/koreatech/payment/service/PaymentQueryService.java @@ -0,0 +1,37 @@ +package in.koreatech.payment.service; + +import in.koreatech.koin.domain.order.shop.model.entity.shop.OrderableShop; +import in.koreatech.koin.domain.order.shop.repository.OrderableShopRepository; +import in.koreatech.payment.model.entity.Order; +import in.koreatech.payment.model.entity.OrderMenu; +import in.koreatech.payment.model.entity.Payment; +import in.koreatech.payment.repository.mysql.OrderMenuRepository; +import in.koreatech.payment.repository.mysql.PaymentRepository; +import in.koreatech.koin.domain.user.model.User; +import in.koreatech.payment.dto.response.PaymentResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional(transactionManager = "koinPaymentTransactionManager", readOnly = true) +public class PaymentQueryService { + + private final PaymentRepository paymentRepository; + private final OrderMenuRepository orderMenuRepository; + private final OrderableShopRepository orderableShopRepository; + + public PaymentResponse getPayment(User user, Integer paymentId) { + Payment payment = paymentRepository.getById(paymentId); + payment.validateUserIdMatches(user.getId()); + + Order order = payment.getOrder(); + List orderMenus = orderMenuRepository.findAllByOrderId(order.getId()); + OrderableShop orderableShop = orderableShopRepository.getById(order.getOrderableShopId()); + + return PaymentResponse.of(payment, order, orderableShop, orderMenus); + } +} diff --git a/src/main/java/in/koreatech/payment/service/PaymentRollBackService.java b/src/main/java/in/koreatech/payment/service/PaymentRollBackService.java deleted file mode 100644 index 0b42951..0000000 --- a/src/main/java/in/koreatech/payment/service/PaymentRollBackService.java +++ /dev/null @@ -1,7 +0,0 @@ -package in.koreatech.payment.service; - -import in.koreatech.payment.event.TossPaymentRollBackEvent; - -public interface PaymentRollBackService { - void paymentRollback(TossPaymentRollBackEvent event); -} diff --git a/src/main/java/in/koreatech/payment/service/PaymentService.java b/src/main/java/in/koreatech/payment/service/PaymentService.java index 9c99a42..c698e75 100644 --- a/src/main/java/in/koreatech/payment/service/PaymentService.java +++ b/src/main/java/in/koreatech/payment/service/PaymentService.java @@ -1,17 +1,81 @@ package in.koreatech.payment.service; -import java.util.List; +import org.springframework.stereotype.Service; -import in.koreatech.koin.domain.order.model.PaymentCancel; +import in.koreatech.koin.domain.user.model.User; +import in.koreatech.payment.dto.request.PaymentCancelRequest; +import in.koreatech.payment.dto.request.PaymentConfirmRequest; import in.koreatech.payment.dto.request.TemporaryDeliveryPaymentSaveRequest; import in.koreatech.payment.dto.request.TemporaryTakeoutPaymentSaveRequest; +import in.koreatech.payment.dto.response.PaymentCancelResponse; import in.koreatech.payment.dto.response.PaymentConfirmResponse; import in.koreatech.payment.dto.response.PaymentResponse; +import in.koreatech.payment.dto.response.TemporaryPaymentResponse; +import in.koreatech.payment.model.domain.DeliveryPaymentInfo; +import in.koreatech.payment.model.domain.PaymentCancelInfo; +import in.koreatech.payment.model.domain.PaymentConfirmInfo; +import in.koreatech.payment.model.domain.TakeoutPaymentInfo; +import lombok.RequiredArgsConstructor; -public interface PaymentService { - String createTemporaryDeliveryPayment(String accessToken, TemporaryDeliveryPaymentSaveRequest request); - String createTemporaryTakeoutPayment(String accessToken, TemporaryTakeoutPaymentSaveRequest request); - PaymentConfirmResponse confirmPayment(String accessToken, String paymentKey, String orderId, Integer amount); - List cancelPayment(String accessToken, Integer paymentId, String cancelReason); - PaymentResponse getPayment(String accessToken, Integer paymentId); +@Service +@RequiredArgsConstructor +public class PaymentService { + + private final UserAuthenticationService userAuthenticationService; + private final TemporaryPaymentService temporaryPaymentService; + private final PaymentConfirmService paymentConfirmService; + private final PaymentCancelService paymentCancelService; + private final PaymentQueryService paymentQueryService; + + public TemporaryPaymentResponse createTemporaryDeliveryPayment( + String accessToken, TemporaryDeliveryPaymentSaveRequest request + ) { + User user = userAuthenticationService.authenticateUser(accessToken); + DeliveryPaymentInfo deliveryPaymentInfo = DeliveryPaymentInfo.of( + request.phoneNumber(), + request.address(), + request.toOwner(), + request.toRider(), + request.provideCutlery(), + request.totalMenuPrice(), + request.deliveryTip(), + request.totalAmount() + ); + return temporaryPaymentService.createDeliveryPayment(user, deliveryPaymentInfo); + } + + public TemporaryPaymentResponse createTemporaryTakeoutPayment( + String accessToken, TemporaryTakeoutPaymentSaveRequest request + ) { + User user = userAuthenticationService.authenticateUser(accessToken); + TakeoutPaymentInfo takeoutPaymentInfo = TakeoutPaymentInfo.of( + request.phoneNumber(), + request.toOwner(), + request.provideCutlery(), + request.totalMenuPrice(), + request.totalAmount() + ); + return temporaryPaymentService.createTakeoutPayment(user, takeoutPaymentInfo); + } + + public PaymentConfirmResponse confirmPayment(String accessToken, PaymentConfirmRequest request) { + User user = userAuthenticationService.authenticateUser(accessToken); + PaymentConfirmInfo paymentConfirmInfo = PaymentConfirmInfo.of( + request.paymentKey(), + request.orderId(), + request.amount() + ); + return paymentConfirmService.confirmPayment(user, paymentConfirmInfo); + } + + public PaymentCancelResponse cancelPayment(String accessToken, Integer paymentId, PaymentCancelRequest request) { + User user = userAuthenticationService.authenticateUser(accessToken); + PaymentCancelInfo paymentCancelInfo = PaymentCancelInfo.of(request.cancelReason()); + return paymentCancelService.cancelPayment(user, paymentId, paymentCancelInfo); + } + + public PaymentResponse getPayment(String accessToken, Integer paymentId) { + User user = userAuthenticationService.authenticateUser(accessToken); + return paymentQueryService.getPayment(user, paymentId); + } } diff --git a/src/main/java/in/koreatech/payment/service/TemporaryPaymentService.java b/src/main/java/in/koreatech/payment/service/TemporaryPaymentService.java new file mode 100644 index 0000000..ef2191a --- /dev/null +++ b/src/main/java/in/koreatech/payment/service/TemporaryPaymentService.java @@ -0,0 +1,88 @@ +package in.koreatech.payment.service; + +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import in.koreatech.koin.domain.order.cart.model.Cart; +import in.koreatech.koin.domain.order.cart.repository.CartRepository; +import in.koreatech.koin.domain.order.shop.model.entity.shop.OrderableShop; +import in.koreatech.koin.domain.user.model.User; +import in.koreatech.payment.dto.response.TemporaryPaymentResponse; +import in.koreatech.payment.gateway.pg.PaymentGatewayService; +import in.koreatech.payment.mapper.TemporaryMenuItemsMapper; +import in.koreatech.payment.model.domain.DeliveryPaymentInfo; +import in.koreatech.payment.model.domain.TakeoutPaymentInfo; +import in.koreatech.payment.model.domain.TemporaryMenuItems; +import in.koreatech.payment.model.redis.TemporaryPayment; +import in.koreatech.payment.repository.redis.TemporaryPaymentRedisRepository; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class TemporaryPaymentService { + + private final CartRepository cartRepository; + private final PaymentGatewayService paymentGatewayService; + private final TemporaryPaymentRedisRepository temporaryPaymentRedisRepository; + private final TemporaryMenuItemsMapper temporaryMenuItemsMapper; + + public TemporaryPaymentResponse createDeliveryPayment(User user, DeliveryPaymentInfo deliveryPaymentInfo) { + Cart cart = cartRepository.getCartByUserId(user.getId()); + OrderableShop orderableShop = cart.getOrderableShop(); + + List temporaryMenuItems = temporaryMenuItemsMapper.fromCart(cart); + int totalProductPrice = cart.calculateItemsAmount(); + int deliveryFee = orderableShop.calculateDeliveryFee(totalProductPrice); + int finalAmount = totalProductPrice + deliveryFee; + + deliveryPaymentInfo.validatePrice(totalProductPrice, deliveryFee, finalAmount); + + String pgOrderId = paymentGatewayService.generatePgOrderId(); + TemporaryPayment deliveryEntity = TemporaryPayment.toDeliveryEntity( + pgOrderId, + user.getId(), + orderableShop.getId(), + deliveryPaymentInfo.phoneNumber(), + deliveryPaymentInfo.address(), + deliveryPaymentInfo.toOwner(), + deliveryPaymentInfo.toRider(), + deliveryPaymentInfo.provideCutlery(), + totalProductPrice, + deliveryFee, + finalAmount, + temporaryMenuItems + ); + + temporaryPaymentRedisRepository.save(deliveryEntity); + return TemporaryPaymentResponse.of(pgOrderId); + } + + public TemporaryPaymentResponse createTakeoutPayment(User user, TakeoutPaymentInfo takeoutPaymentInfo) { + Cart cart = cartRepository.getCartByUserId(user.getId()); + OrderableShop orderableShop = cart.getOrderableShop(); + + List temporaryMenuItems = temporaryMenuItemsMapper.fromCart(cart); + int totalProductPrice = cart.calculateItemsAmount(); + int finalAmount = totalProductPrice; + + takeoutPaymentInfo.validatePrice(totalProductPrice, finalAmount); + + String pgOrderId = paymentGatewayService.generatePgOrderId(); + TemporaryPayment takeoutEntity = TemporaryPayment.toTakeOutEntity( + pgOrderId, + user.getId(), + orderableShop.getId(), + takeoutPaymentInfo.phoneNumber(), + takeoutPaymentInfo.toOwner(), + takeoutPaymentInfo.provideCutlery(), + totalProductPrice, + finalAmount, + temporaryMenuItems + ); + + temporaryPaymentRedisRepository.save(takeoutEntity); + return TemporaryPaymentResponse.of(pgOrderId); + } +} diff --git a/src/main/java/in/koreatech/payment/service/TossPaymentRollBackService.java b/src/main/java/in/koreatech/payment/service/TossPaymentRollBackService.java deleted file mode 100644 index b2d33a9..0000000 --- a/src/main/java/in/koreatech/payment/service/TossPaymentRollBackService.java +++ /dev/null @@ -1,93 +0,0 @@ -package in.koreatech.payment.service; - -import static org.springframework.transaction.annotation.Propagation.REQUIRES_NEW; -import static org.springframework.transaction.event.TransactionPhase.AFTER_ROLLBACK; - -import java.util.List; -import java.util.UUID; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.event.TransactionalEventListener; - -import in.koreatech.koin.domain.order.cart.repository.CartRepository; -import in.koreatech.koin.domain.order.model.Order; -import in.koreatech.koin.domain.order.model.Payment; -import in.koreatech.koin.domain.order.model.PaymentCancel; -import in.koreatech.koin.domain.order.model.PaymentIdempotencyKey; -import in.koreatech.koin.domain.order.repository.OrderRepository; -import in.koreatech.koin.domain.order.repository.PaymentCancelRepository; -import in.koreatech.koin.domain.order.repository.PaymentIdempotencyKeyRepository; -import in.koreatech.koin.domain.order.repository.PaymentRepository; -import in.koreatech.koin.domain.order.shop.model.entity.shop.OrderableShop; -import in.koreatech.koin.domain.order.shop.repository.OrderableShopRepository; -import in.koreatech.koin.domain.user.model.User; -import in.koreatech.koin.domain.user.repository.UserRepository; -import in.koreatech.payment.client.TossPaymentClient; -import in.koreatech.payment.client.dto.response.PaymentCancelResponse; -import in.koreatech.payment.event.TossPaymentRollBackEvent; -import in.koreatech.payment.model.redis.TemporaryPayment; -import in.koreatech.payment.repository.redis.TemporaryPaymentRedisRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@Service -@RequiredArgsConstructor -public class TossPaymentRollBackService implements PaymentRollBackService { - - private final TossPaymentClient tossPaymentClient; - private final PaymentIdempotencyKeyRepository paymentIdempotencyKeyRepository; - private final UserRepository userRepository; - private final OrderableShopRepository orderableShopRepository; - private final OrderRepository orderRepository; - private final PaymentRepository paymentRepository; - private final TemporaryPaymentRedisRepository temporaryPaymentRedisRepository; - private final CartRepository cartRepository; - private final PaymentCancelRepository paymentCancelRepository; - - private static final String PAYMENT_CANCEL_REASON = "코인 서버 오류로 인한 결제 취소"; - - @TransactionalEventListener(phase = AFTER_ROLLBACK) - @Transactional(propagation = REQUIRES_NEW) - public void paymentRollback(TossPaymentRollBackEvent event) { - TemporaryPayment temporaryPayment = event.temporaryPayment(); - User user = userRepository.getById(temporaryPayment.getUserId()); - PaymentIdempotencyKey paymentIdempotencyKey = paymentIdempotencyKeyRepository - .findByUserId(user.getId()) - .map(idempotencyKey -> { - if (idempotencyKey.isOlderThanExpireDays()) { - idempotencyKey.updateIdempotencyKey(UUID.randomUUID().toString()); - } - return idempotencyKey; - }) - .orElseGet(() -> paymentIdempotencyKeyRepository.save( - PaymentIdempotencyKey.builder() - .userId(user.getId()) - .idempotencyKey(UUID.randomUUID().toString()) - .build() - )); - - PaymentCancelResponse response = tossPaymentClient.requestCancel(event.paymentKey(), - PAYMENT_CANCEL_REASON, paymentIdempotencyKey.getIdempotencyKey()); - - try { - OrderableShop orderableShop = orderableShopRepository.getById(temporaryPayment.getOrderableShopId()); - Order order = temporaryPayment.toOrder(user, orderableShop); - orderRepository.save(order); - - Payment payment = event.tossPaymentConfirmResponse().toEntity(order); - payment.cancel(); - paymentRepository.save(payment); - - List paymentCancels = response.getPaymentCancels(payment); - paymentCancelRepository.saveAll(paymentCancels); - - temporaryPaymentRedisRepository.deleteById(order.getId()); - cartRepository.deleteByUserId(user.getId()); - } catch (Exception e) { - log.error("결제 취소 과정에서 오류 발생 - paymentId: {}, userId: {}, orderId: {}", event.paymentKey(), - temporaryPayment.getUserId(), temporaryPayment.getOrderId()); - } - } -} diff --git a/src/main/java/in/koreatech/payment/service/TossService.java b/src/main/java/in/koreatech/payment/service/TossService.java deleted file mode 100644 index 2cec10d..0000000 --- a/src/main/java/in/koreatech/payment/service/TossService.java +++ /dev/null @@ -1,227 +0,0 @@ -package in.koreatech.payment.service; - -import java.util.List; -import java.util.UUID; - -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import in.koreatech.koin.domain.order.cart.model.Cart; -import in.koreatech.koin.domain.order.cart.repository.CartRepository; -import in.koreatech.koin.domain.order.model.Order; -import in.koreatech.koin.domain.order.model.OrderMenu; -import in.koreatech.koin.domain.order.model.Payment; -import in.koreatech.koin.domain.order.model.PaymentCancel; -import in.koreatech.koin.domain.order.model.PaymentIdempotencyKey; -import in.koreatech.koin.domain.order.model.PaymentStatus; -import in.koreatech.koin.domain.order.repository.OrderMenuRepository; -import in.koreatech.koin.domain.order.repository.OrderRepository; -import in.koreatech.koin.domain.order.repository.PaymentCancelRepository; -import in.koreatech.koin.domain.order.repository.PaymentIdempotencyKeyRepository; -import in.koreatech.koin.domain.order.repository.PaymentRepository; -import in.koreatech.koin.domain.order.shop.model.entity.shop.OrderableShop; -import in.koreatech.koin.domain.order.shop.repository.OrderableShopRepository; -import in.koreatech.koin.domain.user.model.User; -import in.koreatech.koin.domain.user.repository.UserRepository; -import in.koreatech.payment.client.TossPaymentClient; -import in.koreatech.payment.client.dto.response.PaymentCancelResponse; -import in.koreatech.payment.client.dto.response.TossPaymentConfirmResponse; -import in.koreatech.payment.common.auth.JwtProvider; -import in.koreatech.payment.dto.request.TemporaryDeliveryPaymentSaveRequest; -import in.koreatech.payment.dto.request.TemporaryTakeoutPaymentSaveRequest; -import in.koreatech.payment.dto.response.PaymentConfirmResponse; -import in.koreatech.payment.dto.response.PaymentResponse; -import in.koreatech.payment.event.TossPaymentRollBackEvent; -import in.koreatech.payment.exception.OrderPriceMismatchException; -import in.koreatech.payment.exception.PaymentAlreadyCanceledException; -import in.koreatech.payment.exception.PaymentCancelException; -import in.koreatech.payment.exception.PaymentConfirmException; -import in.koreatech.payment.model.domain.TemporaryMenuItems; -import in.koreatech.payment.model.redis.TemporaryPayment; -import in.koreatech.payment.repository.redis.TemporaryPaymentRedisRepository; -import in.koreatech.payment.util.OrderIdGenerator; -import in.koreatech.payment.util.TemporaryMenuItemConverter; -import lombok.RequiredArgsConstructor; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class TossService implements PaymentService { - - private final OrderIdGenerator orderIdGenerator; - private final JwtProvider jwtProvider; - private final UserRepository userRepository; - private final TossPaymentClient tossPaymentClient; - private final PaymentRepository paymentRepository; - private final PaymentIdempotencyKeyRepository paymentIdempotencyKeyRepository; - private final PaymentCancelRepository paymentCancelRepository; - private final CartRepository cartRepository; - private final TemporaryPaymentRedisRepository temporaryPaymentRedisRepository; - private final OrderableShopRepository orderableShopRepository; - private final OrderRepository orderRepository; - private final OrderMenuRepository orderMenuRepository; - private final ApplicationEventPublisher applicationEventPublisher; - - @Transactional - public String createTemporaryDeliveryPayment(String accessToken, TemporaryDeliveryPaymentSaveRequest request) { - Integer userId = jwtProvider.getUserId(accessToken); - User user = userRepository.getById(userId); - - Cart cart = cartRepository.getCartByUserId(user.getId()); - - OrderableShop orderableShop = cart.getOrderableShop(); - List temporaryMenuItems = TemporaryMenuItemConverter.fromCart(cart); - int totalProductPrice = cart.calculateItemsAmount(); - int deliveryFee = orderableShop.calculateDeliveryFee(totalProductPrice); - int finalAmount = totalProductPrice + deliveryFee; - - if (!request.totalMenuPrice().equals(totalProductPrice) - || !request.deliveryTip().equals(deliveryFee) - || !request.totalAmount().equals(finalAmount) - ) { - throw OrderPriceMismatchException.withDetail( - "totalProductPrice : " + totalProductPrice + "deliveryFee : " + deliveryFee + "totalAmount : " - + totalProductPrice + "finalAmount : " + finalAmount); - } - - String orderId = orderIdGenerator.generateOrderId(); - - TemporaryPayment deliveryEntity = TemporaryPayment.toDeliveryEntity( - orderId, - user.getId(), - orderableShop.getId(), - request.phoneNumber(), - request.address(), - request.toOwner(), - request.toRider(), - request.provideCutlery(), - totalProductPrice, - deliveryFee, - finalAmount, - temporaryMenuItems - ); - - temporaryPaymentRedisRepository.save(deliveryEntity); - return orderId; - } - - @Transactional - public String createTemporaryTakeoutPayment(String accessToken, TemporaryTakeoutPaymentSaveRequest request) { - Integer userId = jwtProvider.getUserId(accessToken); - User user = userRepository.getById(userId); - - Cart cart = cartRepository.getCartByUserId(user.getId()); - - OrderableShop orderableShop = cart.getOrderableShop(); - List temporaryMenuItems = TemporaryMenuItemConverter.fromCart(cart); - int totalProductPrice = cart.calculateItemsAmount(); - int finalAmount = totalProductPrice; - - if (!request.totalMenuPrice().equals(totalProductPrice) - || !request.totalAmount().equals(finalAmount) - ) { - throw OrderPriceMismatchException.withDetail( - "totalProductPrice : " + totalProductPrice + "finalAmount : " + finalAmount); - } - - String orderId = orderIdGenerator.generateOrderId(); - - TemporaryPayment deliveryEntity = TemporaryPayment.toTakeOutEntity( - orderId, - user.getId(), - orderableShop.getId(), - request.phoneNumber(), - request.toOwner(), - request.provideCutlery(), - totalProductPrice, - finalAmount, - temporaryMenuItems - ); - - temporaryPaymentRedisRepository.save(deliveryEntity); - return orderId; - } - - @Transactional - public PaymentConfirmResponse confirmPayment(String accessToken, String paymentKey, String orderId, - Integer amount) { - Integer userId = jwtProvider.getUserId(accessToken); - User user = userRepository.getById(userId); - TemporaryPayment temporaryPayment = temporaryPaymentRedisRepository.getById(orderId); - temporaryPayment.validateMatches(orderId, user.getId(), amount); - - TossPaymentConfirmResponse tossPaymentResponse = tossPaymentClient.requestConfirm(paymentKey, orderId, amount); - PaymentStatus paymentStatus = PaymentStatus.valueOf(tossPaymentResponse.status()); - if (!paymentStatus.isDone()) { - throw PaymentConfirmException.withDetail("paymentStatus : " + tossPaymentResponse.status()); - } - - applicationEventPublisher.publishEvent( - TossPaymentRollBackEvent.from(paymentKey, temporaryPayment, tossPaymentResponse)); - - OrderableShop orderableShop = orderableShopRepository.getById(temporaryPayment.getOrderableShopId()); - Order order = temporaryPayment.toOrder(user, orderableShop); - orderRepository.save(order); - - List orderMenus = temporaryPayment.getTemporaryMenuItems().stream() - .map(temporaryMenuItems -> temporaryMenuItems.toOrderMenu(order)) - .toList(); - orderMenuRepository.saveAll(orderMenus); - - Payment payment = tossPaymentResponse.toEntity(order); - paymentRepository.save(payment); - temporaryPaymentRedisRepository.deleteById(orderId); - cartRepository.deleteByUserId(user.getId()); - return PaymentConfirmResponse.of(payment, order, orderMenus); - } - - @Transactional - public List cancelPayment(String accessToken, Integer paymentId, String cancelReason) { - Integer userId = jwtProvider.getUserId(accessToken); - User user = userRepository.getById(userId); - Payment payment = paymentRepository.getById(paymentId); - if (payment.getPaymentStatus().isCanceled()) { - throw PaymentAlreadyCanceledException.withDetail("paymentId : " + payment.getId()); - } - payment.validateUserIdMatches(user.getId()); - - PaymentIdempotencyKey paymentIdempotencyKey = paymentIdempotencyKeyRepository - .findByUserId(user.getId()) - .map(idempotencyKey -> { - if (idempotencyKey.isOlderThanExpireDays()) { - idempotencyKey.updateIdempotencyKey(UUID.randomUUID().toString()); - } - return idempotencyKey; - }) - .orElseGet(() -> paymentIdempotencyKeyRepository.save( - PaymentIdempotencyKey.builder() - .userId(user.getId()) - .idempotencyKey(UUID.randomUUID().toString()) - .build() - )); - - PaymentCancelResponse response = tossPaymentClient.requestCancel(payment.getPaymentKey(), cancelReason, - paymentIdempotencyKey.getIdempotencyKey()); - if (!PaymentStatus.valueOf(response.status()).isCanceled()) { - throw PaymentCancelException.withDetail("paymentStatus : " + response.status()); - } - - payment.cancel(); - List paymentCancels = response.getPaymentCancels(payment); - paymentCancelRepository.saveAll(paymentCancels); - return paymentCancels; - } - - public PaymentResponse getPayment(String accessToken, Integer paymentId) { - Integer userId = jwtProvider.getUserId(accessToken); - User user = userRepository.getById(userId); - Payment payment = paymentRepository.getById(paymentId); - payment.validateUserIdMatches(user.getId()); - - Order order = payment.getOrder(); - List orderMenus = orderMenuRepository.findAllByOrderId(order.getId()); - - return PaymentResponse.of(payment, order, orderMenus); - } -} diff --git a/src/main/java/in/koreatech/payment/service/UserAuthenticationService.java b/src/main/java/in/koreatech/payment/service/UserAuthenticationService.java new file mode 100644 index 0000000..947643f --- /dev/null +++ b/src/main/java/in/koreatech/payment/service/UserAuthenticationService.java @@ -0,0 +1,23 @@ +package in.koreatech.payment.service; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import in.koreatech.koin.domain.user.model.User; +import in.koreatech.koin.domain.user.repository.UserRepository; +import in.koreatech.payment.common.auth.JwtProvider; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@Transactional(transactionManager = "koinTransactionManager", readOnly = true) +public class UserAuthenticationService { + + private final JwtProvider jwtProvider; + private final UserRepository userRepository; + + public User authenticateUser(String accessToken) { + Integer userId = jwtProvider.getUserId(accessToken); + return userRepository.getById(userId); + } +} diff --git a/src/main/java/in/koreatech/payment/util/OrderIdGenerator.java b/src/main/java/in/koreatech/payment/util/OrderIdGenerator.java deleted file mode 100644 index b733dc9..0000000 --- a/src/main/java/in/koreatech/payment/util/OrderIdGenerator.java +++ /dev/null @@ -1,5 +0,0 @@ -package in.koreatech.payment.util; - -public interface OrderIdGenerator { - String generateOrderId(); -} diff --git a/src/main/resources/db/migration/V1__init.sql b/src/main/resources/db/migration/V1__init.sql new file mode 100644 index 0000000..2f9c25c --- /dev/null +++ b/src/main/resources/db/migration/V1__init.sql @@ -0,0 +1,103 @@ +CREATE TABLE IF NOT EXISTS `koin_payment`.`order` +( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '주문 ID', + `pg_order_id` VARCHAR(64) NOT NULL COMMENT 'pg 주문 ID', + `order_type` VARCHAR(10) NOT NULL COMMENT '주문 타입', + `phone_number` VARCHAR(20) NOT NULL COMMENT '주문자 전화번호', + `total_product_price` INT UNSIGNED NOT NULL COMMENT '상품 총 금액', + `total_price` INT UNSIGNED NOT NULL COMMENT '주문 총 금액', + `is_deleted` TINYINT(1) NOT NULL DEFAULT FALSE COMMENT '삭제 여부', + `orderable_shop_id` INT UNSIGNED NOT NULL COMMENT '주문한 상점 ID', + `user_id` INT UNSIGNED NOT NULL COMMENT '주문자 사용자 ID', + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '생성 일시', + `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정 일시', + PRIMARY KEY (`id`) +); + +CREATE INDEX idx_user_id ON `koin_payment`.`order` (user_id); +CREATE INDEX idx_orderable_shop_id ON `koin_payment`.`order` (orderable_shop_id); + +CREATE TABLE IF NOT EXISTS `koin_payment`.`order_delivery` +( + `order_id` INT UNSIGNED NOT NULL COMMENT '주문 ID', + `address` VARCHAR(100) NOT NULL COMMENT '배달 주소', + `to_owner` VARCHAR(50) NOT NULL COMMENT '사장님 전달 메시지', + `to_rider` VARCHAR(50) NOT NULL COMMENT '라이더 전달 메시지', + `delivery_tip` INT UNSIGNED NOT NULL COMMENT '배달비', + `provide_cutlery` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '수저, 포크 수령 여부', + PRIMARY KEY (order_id), + CONSTRAINT `fk_order_delivery_order` FOREIGN KEY (`order_id`) REFERENCES `koin_payment`.`order` (`id`) +); + +CREATE TABLE IF NOT EXISTS `koin_payment`.`order_takeout` +( + `order_id` INT UNSIGNED NOT NULL COMMENT '주문 ID', + `to_owner` VARCHAR(50) NOT NULL COMMENT '사장님 전달 메시지', + `provide_cutlery` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '수저, 포크 수령 여부', + PRIMARY KEY (order_id), + CONSTRAINT `fk_order_pack_order` FOREIGN KEY (`order_id`) REFERENCES `koin_payment`.`order` (`id`) +); + +CREATE TABLE IF NOT EXISTS `koin_payment`.`payment` +( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '고유 ID', + `payment_key` VARCHAR(200) NOT NULL COMMENT '결제 키', + `amount` INT UNSIGNED NOT NULL COMMENT '결제 금액', + `status` VARCHAR(30) NOT NULL COMMENT '결제 상태', + `method` VARCHAR(30) NOT NULL COMMENT '결제 수단', + `requested_at` TIMESTAMP NOT NULL COMMENT '결제 요청 일시', + `approved_at` TIMESTAMP NOT NULL COMMENT '결제 승인 일시', + `order_id` INT UNSIGNED NOT NULL COMMENT '주문 번호', + PRIMARY KEY (`id`), + UNIQUE KEY uq_payment_key (`id`), + CONSTRAINT fk_payment_order FOREIGN KEY (`order_id`) REFERENCES `koin_payment`.`order` (`id`) +); + +CREATE TABLE IF NOT EXISTS `koin_payment`.`payment_cancel` +( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '결제 취소 ID', + `transaction_key` VARCHAR(64) NOT NULL COMMENT '취소 트랜잭션 키', + `cancel_reason` VARCHAR(200) NOT NULL COMMENT '취소 사유', + `cancel_amount` INT UNSIGNED NOT NULL COMMENT '취소 금액', + `canceled_at` TIMESTAMP NOT NULL COMMENT '취소 일시', + `payment_id` INT UNSIGNED NOT NULL COMMENT '결제 ID', + PRIMARY KEY (`id`), + CONSTRAINT `fk_payment_cancel_payment` FOREIGN KEY (`payment_id`) REFERENCES `koin_payment`.`payment` (`id`) +); + +CREATE TABLE IF NOT EXISTS `koin_payment`.`order_menu` +( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '주문 메뉴 ID', + `menu_name` VARCHAR(255) NOT NULL COMMENT '메뉴 이름', + `menu_price` INT UNSIGNED NOT NULL COMMENT '메뉴 금액', + `menu_price_name` VARCHAR(255) NULL COMMENT '메뉴 가격 이름', + `quantity` INT UNSIGNED NOT NULL COMMENT '수량', + `order_id` INT UNSIGNED NOT NULL COMMENT '주문 ID', + PRIMARY KEY (`id`), + CONSTRAINT `fk_order_menu_order` FOREIGN KEY (`order_id`) REFERENCES `koin_payment`.`order` (`id`) +); + +CREATE TABLE IF NOT EXISTS `koin_payment`.`order_menu_option` +( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '메뉴 옵션 ID', + `option_group_name` VARCHAR(255) NOT NULL COMMENT '주문 메뉴 옵션 이름', + `option_name` VARCHAR(255) NOT NULL COMMENT '옵션 이름', + `option_price` INT UNSIGNED NOT NULL COMMENT '옵션 가격', + `quantity` INT UNSIGNED NOT NULL COMMENT '옵션 수량', + `order_menu_id` INT UNSIGNED NOT NULL COMMENT '주문 메뉴 ID', + PRIMARY KEY (`id`), + CONSTRAINT `fk_order_menu_option_menu` FOREIGN KEY (`order_menu_id`) REFERENCES `koin_payment`.`order_menu` (`id`) +); + +CREATE TABLE IF NOT EXISTS `koin_payment`.`payment_idempotency_key` +( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '결제 멱등키 ID', + `user_id` INT UNSIGNED NOT NULL COMMENT '유저 ID', + `idempotency_key` VARCHAR(300) NOT NULL COMMENT '결제 멱등키', + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '생성 일시', + `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정 일시', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_idempotency_key_user_id` (`user_id`) +); + +CREATE INDEX idx_user_id ON `koin_payment`.`payment_idempotency_key` (user_id); diff --git a/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java b/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java index 43a5542..bdce69d 100644 --- a/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java +++ b/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java @@ -1,6 +1,6 @@ package in.koreatech.payment.acceptance.domain; -import static in.koreatech.payment.client.dto.response.PaymentCancelResponse.CancelInfo; +import static in.koreatech.payment.gateway.toss.dto.response.TossPaymentCancelResponse.CancelInfo; import static org.assertj.core.api.SoftAssertions.assertSoftly; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.eq; @@ -21,19 +21,19 @@ import org.springframework.http.MediaType; import in.koreatech.koin.domain.order.cart.model.Cart; -import in.koreatech.koin.domain.order.model.Order; -import in.koreatech.koin.domain.order.model.OrderDelivery; -import in.koreatech.koin.domain.order.model.OrderMenu; -import in.koreatech.koin.domain.order.model.OrderTakeout; -import in.koreatech.koin.domain.order.model.OrderType; -import in.koreatech.koin.domain.order.model.Payment; -import in.koreatech.koin.domain.order.model.PaymentCancel; -import in.koreatech.koin.domain.order.model.PaymentIdempotencyKey; -import in.koreatech.koin.domain.order.model.PaymentMethod; -import in.koreatech.koin.domain.order.model.PaymentStatus; -import in.koreatech.koin.domain.order.repository.OrderMenuRepository; -import in.koreatech.koin.domain.order.repository.PaymentCancelRepository; -import in.koreatech.koin.domain.order.repository.PaymentRepository; +import in.koreatech.payment.model.entity.Order; +import in.koreatech.payment.model.entity.OrderDelivery; +import in.koreatech.payment.model.entity.OrderMenu; +import in.koreatech.payment.model.entity.OrderTakeout; +import in.koreatech.payment.model.entity.OrderType; +import in.koreatech.payment.model.entity.Payment; +import in.koreatech.payment.model.entity.PaymentCancel; +import in.koreatech.payment.model.entity.PaymentIdempotencyKey; +import in.koreatech.payment.model.entity.PaymentMethod; +import in.koreatech.payment.model.entity.PaymentStatus; +import in.koreatech.payment.repository.mysql.OrderMenuRepository; +import in.koreatech.payment.repository.mysql.PaymentCancelRepository; +import in.koreatech.payment.repository.mysql.PaymentRepository; import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenu; import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenuPrice; import in.koreatech.koin.domain.order.shop.model.entity.shop.OrderableShop; @@ -47,14 +47,13 @@ import in.koreatech.payment.acceptance.fixture.PaymentIdempotencyKeyFixture; import in.koreatech.payment.acceptance.fixture.ShopFixture; import in.koreatech.payment.acceptance.fixture.UserFixture; -import in.koreatech.payment.client.TossPaymentClient; -import in.koreatech.payment.client.dto.response.PaymentCancelResponse; -import in.koreatech.payment.client.dto.response.TossPaymentConfirmResponse; +import in.koreatech.payment.gateway.toss.TossPaymentClient; +import in.koreatech.payment.gateway.toss.dto.response.TossPaymentCancelResponse; +import in.koreatech.payment.gateway.toss.dto.response.TossPaymentConfirmResponse; import in.koreatech.payment.common.auth.JwtProvider; import in.koreatech.payment.model.redis.TemporaryPayment; import in.koreatech.payment.repository.redis.TemporaryPaymentRedisRepository; -import in.koreatech.payment.service.PaymentRollBackService; -import in.koreatech.payment.util.OrderIdGenerator; +import in.koreatech.payment.gateway.pg.PgOrderIdGenerator; public class PaymentApiTest extends AcceptanceTest { @@ -98,10 +97,7 @@ public class PaymentApiTest extends AcceptanceTest { private TossPaymentClient tossPaymentClient; @MockBean - private PaymentRollBackService paymentRollBackService; - - @MockBean - private OrderIdGenerator orderIdGenerator; + private PgOrderIdGenerator pgOrderIdGenerator; private User user; private String token; @@ -132,7 +128,7 @@ class TemporaryPaymentSuccess { @Test void 임시_배달_결제_정보_저장에_성공한다() throws Exception { - given(orderIdGenerator.generateOrderId()).willReturn("FAKE_ORDER_123"); + given(pgOrderIdGenerator.generatePgOrderId()).willReturn("FAKE_ORDER_123"); mockMvc.perform( post("/payments/delivery/temporary") @@ -162,7 +158,7 @@ class TemporaryPaymentSuccess { TemporaryPayment temporaryPayment = temporaryPaymentRedisRepository.getById("FAKE_ORDER_123"); assertSoftly( softly -> { - softly.assertThat(temporaryPayment.getOrderId()).isEqualTo("FAKE_ORDER_123"); + softly.assertThat(temporaryPayment.getPgOrderId()).isEqualTo("FAKE_ORDER_123"); softly.assertThat(temporaryPayment.getUserId()).isEqualTo(user.getId()); softly.assertThat(temporaryPayment.getOrderableShopId()).isEqualTo(orderableShop.getId()); softly.assertThat(temporaryPayment.getPhoneNumber()).isEqualTo("01012345678"); @@ -183,7 +179,7 @@ class TemporaryPaymentSuccess { @Test void 임시_포장_결제_정보_저장에_성공한다() throws Exception { - given(orderIdGenerator.generateOrderId()).willReturn("FAKE_ORDER_123"); + given(pgOrderIdGenerator.generatePgOrderId()).willReturn("FAKE_ORDER_123"); mockMvc.perform( post("/payments/takeout/temporary") @@ -209,7 +205,7 @@ class TemporaryPaymentSuccess { TemporaryPayment temporaryPayment = temporaryPaymentRedisRepository.getById("FAKE_ORDER_123"); assertSoftly( softly -> { - softly.assertThat(temporaryPayment.getOrderId()).isEqualTo("FAKE_ORDER_123"); + softly.assertThat(temporaryPayment.getPgOrderId()).isEqualTo("FAKE_ORDER_123"); softly.assertThat(temporaryPayment.getUserId()).isEqualTo(user.getId()); softly.assertThat(temporaryPayment.getOrderableShopId()).isEqualTo(orderableShop.getId()); softly.assertThat(temporaryPayment.getPhoneNumber()).isEqualTo("01012345678"); @@ -238,6 +234,7 @@ class PaymentSuccess { TossPaymentConfirmResponse confirmDto = new TossPaymentConfirmResponse( "pay_123", 24000, + "FAKE_ORDER_123", "DONE", "카드", "2024-01-01T10:00:00+09:00", @@ -245,7 +242,7 @@ class PaymentSuccess { ); when(tossPaymentClient.requestConfirm(eq("pay_123"), eq("FAKE_ORDER_123"), eq(24000))) .thenReturn(confirmDto); - given(orderIdGenerator.generateOrderId()).willReturn("FAKE_ORDER_123"); + given(pgOrderIdGenerator.generatePgOrderId()).willReturn("FAKE_ORDER_123"); mockMvc.perform( post("/payments/delivery/temporary") @@ -324,11 +321,12 @@ class PaymentSuccess { softly.assertThat(payment.getPaymentStatus()).isEqualTo(PaymentStatus.DONE); softly.assertThat(payment.getPaymentMethod()).isEqualTo(PaymentMethod.CARD); - softly.assertThat(order.getId()).isEqualTo("FAKE_ORDER_123"); + softly.assertThat(order.getId()).isEqualTo(1); softly.assertThat(order.getOrderType()).isEqualTo(OrderType.DELIVERY); softly.assertThat(order.getPhoneNumber()).isEqualTo("01012345678"); softly.assertThat(order.getTotalProductPrice()).isEqualTo(24000); softly.assertThat(order.getTotalPrice()).isEqualTo(24000); + softly.assertThat(order.getPgOrderId()).isEqualTo("FAKE_ORDER_123"); softly.assertThat(orderTakeout).isNull(); @@ -353,6 +351,7 @@ class PaymentSuccess { TossPaymentConfirmResponse confirmDto = new TossPaymentConfirmResponse( "pay_123", 24000, + "FAKE_ORDER_123", "DONE", "카드", "2024-01-01T10:00:00+09:00", @@ -360,7 +359,7 @@ class PaymentSuccess { ); when(tossPaymentClient.requestConfirm(eq("pay_123"), eq("FAKE_ORDER_123"), eq(24000))) .thenReturn(confirmDto); - given(orderIdGenerator.generateOrderId()).willReturn("FAKE_ORDER_123"); + given(pgOrderIdGenerator.generatePgOrderId()).willReturn("FAKE_ORDER_123"); mockMvc.perform( post("/payments/takeout/temporary") @@ -434,11 +433,12 @@ class PaymentSuccess { softly.assertThat(payment.getPaymentStatus()).isEqualTo(PaymentStatus.DONE); softly.assertThat(payment.getPaymentMethod()).isEqualTo(PaymentMethod.CARD); - softly.assertThat(order.getId()).isEqualTo("FAKE_ORDER_123"); + softly.assertThat(order.getId()).isEqualTo(1); softly.assertThat(order.getOrderType()).isEqualTo(OrderType.TAKE_OUT); softly.assertThat(order.getPhoneNumber()).isEqualTo("01012345678"); softly.assertThat(order.getTotalProductPrice()).isEqualTo(24000); softly.assertThat(order.getTotalPrice()).isEqualTo(24000); + softly.assertThat(order.getPgOrderId()).isEqualTo("FAKE_ORDER_123"); softly.assertThat(orderTakeout.getToOwner()).isEqualTo("리뷰 이벤트 감사합니다."); softly.assertThat(orderTakeout.getProvideCutlery()).isEqualTo(true); @@ -461,6 +461,7 @@ class PaymentSuccess { TossPaymentConfirmResponse confirmDto = new TossPaymentConfirmResponse( "pay_123", 24000, + "FAKE_ORDER_123", "DONE", "카드", "2024-01-01T10:00:00+09:00", @@ -468,9 +469,9 @@ class PaymentSuccess { ); when(tossPaymentClient.requestConfirm(eq("pay_123"), eq("FAKE_ORDER_123"), eq(24000))) .thenReturn(confirmDto); - given(orderIdGenerator.generateOrderId()).willReturn("FAKE_ORDER_123"); + given(pgOrderIdGenerator.generatePgOrderId()).willReturn("FAKE_ORDER_123"); - PaymentCancelResponse cancelDto = new PaymentCancelResponse( + TossPaymentCancelResponse cancelDto = new TossPaymentCancelResponse( "pay_123", "FAKE_ORDER_123", "CANCELED", diff --git a/src/test/java/in/koreatech/payment/acceptance/fixture/PaymentIdempotencyKeyFixture.java b/src/test/java/in/koreatech/payment/acceptance/fixture/PaymentIdempotencyKeyFixture.java index 13e0e58..1eed205 100644 --- a/src/test/java/in/koreatech/payment/acceptance/fixture/PaymentIdempotencyKeyFixture.java +++ b/src/test/java/in/koreatech/payment/acceptance/fixture/PaymentIdempotencyKeyFixture.java @@ -2,8 +2,8 @@ import org.springframework.stereotype.Component; -import in.koreatech.koin.domain.order.model.PaymentIdempotencyKey; -import in.koreatech.koin.domain.order.repository.PaymentIdempotencyKeyRepository; +import in.koreatech.payment.model.entity.PaymentIdempotencyKey; +import in.koreatech.payment.repository.mysql.PaymentIdempotencyKeyRepository; import in.koreatech.koin.domain.user.model.User; @Component diff --git a/src/test/java/in/koreatech/payment/acceptance/support/DBInitializer.java b/src/test/java/in/koreatech/payment/acceptance/support/DBInitializer.java index b826c1c..5ac311f 100644 --- a/src/test/java/in/koreatech/payment/acceptance/support/DBInitializer.java +++ b/src/test/java/in/koreatech/payment/acceptance/support/DBInitializer.java @@ -46,7 +46,7 @@ public void initIncrement() { String sql = "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'test' AND AUTO_INCREMENT >= 1"; List dirtyTables = entityManager.createNativeQuery(sql).getResultList(); for (String dirtyTable: dirtyTables) { - entityManager.createNativeQuery(String.format("ALTER TABLE %s AUTO_INCREMENT = 1", dirtyTable)).executeUpdate(); + entityManager.createNativeQuery(String.format("ALTER TABLE `%s` AUTO_INCREMENT = 1", dirtyTable)).executeUpdate(); } } diff --git a/src/test/java/in/koreatech/payment/unit/client/TossPaymentClientTest.java b/src/test/java/in/koreatech/payment/unit/client/TossPaymentClientTest.java index de11edb..bc47800 100644 --- a/src/test/java/in/koreatech/payment/unit/client/TossPaymentClientTest.java +++ b/src/test/java/in/koreatech/payment/unit/client/TossPaymentClientTest.java @@ -1,6 +1,6 @@ package in.koreatech.payment.unit.client; -import static in.koreatech.payment.client.dto.response.PaymentCancelResponse.CancelInfo; +import static in.koreatech.payment.gateway.toss.dto.response.TossPaymentCancelResponse.CancelInfo; import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; @@ -20,10 +20,10 @@ import com.fasterxml.jackson.databind.ObjectMapper; -import in.koreatech.payment.client.TossPaymentClient; -import in.koreatech.payment.client.dto.response.PaymentCancelResponse; -import in.koreatech.payment.client.dto.response.TossPaymentConfirmResponse; -import in.koreatech.payment.client.exception.TossPaymentException; +import in.koreatech.payment.gateway.toss.TossPaymentClient; +import in.koreatech.payment.gateway.toss.dto.response.TossPaymentCancelResponse; +import in.koreatech.payment.gateway.toss.dto.response.TossPaymentConfirmResponse; +import in.koreatech.payment.gateway.toss.exception.TossPaymentException; import in.koreatech.payment.unit.support.MockHttpServer; import okhttp3.mockwebserver.RecordedRequest; @@ -57,6 +57,7 @@ class PaymentConfirmSuccess { TossPaymentConfirmResponse dto = new TossPaymentConfirmResponse( "pay_123", 15000, + "FAKE_ORDER_123", "DONE", "CARD", "2024-01-01T10:00:00+09:00", @@ -143,7 +144,7 @@ class PaymentCancelSuccess { @Test void 결제_취소_요청을_성공한다() throws Exception { // given - PaymentCancelResponse dto = new PaymentCancelResponse( + TossPaymentCancelResponse dto = new TossPaymentCancelResponse( "pay_123", "a4CWyWY5m89PNh7xJwhk1", "CANCELED", @@ -157,7 +158,7 @@ class PaymentCancelSuccess { mockHttpServer.enqueueJson(objectMapper.writeValueAsString(dto), 200); // when - PaymentCancelResponse response = tossPaymentClient.requestCancel( + TossPaymentCancelResponse response = tossPaymentClient.requestCancel( "pay_123", "단순 변심이에요", "91b0343a-423d-431d-832c-031d9391afae"