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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
kotlin("jvm") version "2.2.21"
kotlin("plugin.spring") version "2.2.21"
kotlin("plugin.jpa") version "2.2.21"
id("org.springframework.boot") version "4.0.2"
id("io.spring.dependency-management") version "1.1.7"
id("com.google.devtools.ksp") version "2.3.4" apply false
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package org.example.ticket
package org.example

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class TicketApplication
class SecondChanceApplication

fun main(args: Array<String>) {
runApplication<TicketApplication>(*args)
runApplication<SecondChanceApplication>(*args)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.example.deal.application.dto

import org.example.deal.domain.enum.PaymentType

data class DealEndDto(
val barcode: String,
val paymentType: PaymentType
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.example.deal.application.dto

import org.example.deal.domain.enum.DealStatus
import java.math.BigDecimal
import java.time.LocalDateTime

data class DealResponseDto(
val barcode: String,
val sellerName: String,
val buyerName: String,
val sellingPrice: BigDecimal,
val reservedDateTime: LocalDateTime,
val dealStatus: DealStatus
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.example.deal.application.dto

data class DealStartDto(
val barcode: String,
val buyerName: String,
)

Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package org.example.deal.application.service

import jakarta.transaction.Transactional
import org.example.deal.application.dto.DealEndDto
import org.example.deal.application.dto.DealResponseDto
import org.example.deal.application.dto.DealStartDto
import org.example.deal.domain.model.Deal
import org.example.deal.infrastructure.api.PaymentGatewayResolver
import org.example.deal.infrastructure.repository.DealJpaRepository
import org.example.ticket.infrastructure.repository.TicketJpaRepository
import org.springframework.stereotype.Service

@Service
class DealService(
private val dealRepository: DealJpaRepository,
private val ticketRepository: TicketJpaRepository,
private val paymentGatewayResolver: PaymentGatewayResolver
) {
@Transactional
fun dealStart(dealStartDto: DealStartDto): DealResponseDto {
val ticket = requireNotNull(ticketRepository.findByBarcode(dealStartDto.barcode)) { "존재하지 않는 티켓입니다." }
val sellingPrice = requireNotNull(ticket.sellingPrice) { "판매가가 설정되지 않았습니다." }

val deal = Deal(
barcode = ticket.barcode,
sellerName = ticket.sellerName,
buyerName = dealStartDto.buyerName,
sellingPrice = sellingPrice
)

ticket.ticketReserve()
ticketRepository.save(ticket)
val savedDeal = dealRepository.save(deal)
return DealResponseDto(
barcode = savedDeal.barcode,
sellerName = savedDeal.sellerName,
buyerName = savedDeal.buyerName,
sellingPrice = savedDeal.sellingPrice,
reservedDateTime = savedDeal.reservedDateTime,
dealStatus = savedDeal.getDealStatus()
)
}
@Transactional
fun dealEnd(dealEndDto: DealEndDto): DealResponseDto {
val deal = requireNotNull(dealRepository.findByBarcode(dealEndDto.barcode)) { "존재하지 않는 거래입니다." }
val ticket = requireNotNull(ticketRepository.findByBarcode(dealEndDto.barcode)) { "존재하지 않는 티켓입니다." }
val dealExpiredCheck = deal.reservedTimeExpiredCheck()
if (dealExpiredCheck) {
deal.dealCancel()
ticket.ticketOnSale()
ticketRepository.save(ticket)
dealRepository.save(deal)
throw IllegalArgumentException("10분 이내 입금이 되지않아 결제가 취소되었습니다.")
}
val paymentGateway = paymentGatewayResolver.resolve(dealEndDto.paymentType)
paymentGateway.pay(deal.buyerName, deal.sellerName, deal.sellingPrice)
deal.dealComplete()
ticket.ticketSold()
ticketRepository.save(ticket)
val savedDeal = dealRepository.save(deal)
return DealResponseDto(
barcode = savedDeal.barcode,
sellerName = savedDeal.sellerName,
buyerName = savedDeal.buyerName,
sellingPrice = savedDeal.sellingPrice,
reservedDateTime = savedDeal.reservedDateTime,
dealStatus = savedDeal.getDealStatus()
)
}

}
7 changes: 7 additions & 0 deletions src/main/kotlin/org/example/deal/domain/enum/DealStatus.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.example.deal.domain.enum

enum class DealStatus {
RESERVED,
COMPLETED,
CANCELLED,
}
6 changes: 6 additions & 0 deletions src/main/kotlin/org/example/deal/domain/enum/PaymentType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.example.deal.domain.enum

enum class PaymentType {
KAKAO,
TOSS
}
48 changes: 48 additions & 0 deletions src/main/kotlin/org/example/deal/domain/model/Deal.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.example.deal.domain.model

import jakarta.persistence.Entity
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import org.example.deal.domain.enum.DealStatus
import java.math.BigDecimal
import java.time.LocalDateTime

@Entity
class Deal(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
val barcode: String,
val sellerName: String,
val buyerName: String,
val sellingPrice: BigDecimal,
val reservedDateTime: LocalDateTime = LocalDateTime.now(),
private var dealStatus: DealStatus = DealStatus.RESERVED
) {


companion object {}

init {
require(sellerName != buyerName) { "본인이 등록한 티켓은 구매할 수 없습니다." }
}

fun dealComplete() {
require(dealStatus == DealStatus.RESERVED) { "예약 상태인 거래만 완료할 수 있습니다." }
dealStatus = DealStatus.COMPLETED

}

fun dealCancel() {
require(dealStatus == DealStatus.RESERVED) { "예약 상태인 거래만 취소 할 수 있습니다." }
dealStatus = DealStatus.CANCELLED
}

fun reservedTimeExpiredCheck(): Boolean {
return dealStatus == DealStatus.RESERVED && reservedDateTime.plusMinutes(10).isBefore(LocalDateTime.now())
}

fun getDealStatus(): DealStatus {
return dealStatus
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.example.deal.infrastructure.api

import org.example.deal.domain.enum.PaymentType
import org.springframework.stereotype.Component
import org.example.user.infrastructure.repository.UserJpaRepository
import java.math.BigDecimal


@Component
class KakaoPaymentGateway : PaymentGateway {
override fun pay(buyerName: String, sellerName: String, price: BigDecimal) {
val buyer = requireNotNull(UserJpaRepository.findByName(buyerName)) { "구매자 정보를 찾을 수 없습니다." }
val seller = requireNotNull(UserJpaRepository.findByName(sellerName)) { "판매자 정보를 찾을 수 없습니다." }
buyer.withdraw(price)
seller.deposit(price)
}

override fun type(): PaymentType {
return PaymentType.KAKAO
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.example.deal.infrastructure.api

import org.example.deal.domain.enum.PaymentType
import java.math.BigDecimal

interface PaymentGateway {
fun pay(buyerName: String, sellerName: String, price: BigDecimal)
fun type(): PaymentType
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.example.deal.infrastructure.api

import org.example.deal.domain.enum.PaymentType
import org.springframework.stereotype.Component

@Component
class PaymentGatewayResolver(
private val gateways: List<PaymentGateway>
) {
fun resolve(offerPaymentType: PaymentType): PaymentGateway {
val paymentType = gateways.find { it.type() == offerPaymentType }
return paymentType ?: throw IllegalArgumentException("지원하지 않는 결제 수단입니다.")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.example.deal.infrastructure.api

import org.example.deal.domain.enum.PaymentType
import org.example.user.infrastructure.repository.UserJpaRepository
import org.springframework.stereotype.Component
import java.math.BigDecimal

@Component
class TossPaymentGateway : PaymentGateway {
override fun pay(buyerName: String, sellerName: String, price: BigDecimal) {
val buyer = requireNotNull(UserJpaRepository.findByName(buyerName)) { "구매자 정보를 찾을 수 없습니다." }
val seller = requireNotNull(UserJpaRepository.findByName(sellerName)) { "판매자 정보를 찾을 수 없습니다." }
buyer.withdraw(price)
seller.deposit(price)
}

override fun type(): PaymentType {
return PaymentType.TOSS
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.example.deal.infrastructure.repository

import org.example.deal.domain.model.Deal
import org.springframework.data.jpa.repository.JpaRepository

interface DealJpaRepository : JpaRepository<Deal, Long> {
fun findByBarcode(barcode: String): Deal?
fun findBySellerName(sellerName: String): Deal?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.example.deal.presentation.controller

import org.example.deal.application.dto.DealEndDto
import org.example.deal.application.dto.DealResponseDto
import org.example.deal.application.dto.DealStartDto
import org.example.deal.application.service.DealService
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping(value = ["/deal"])
class DealController(
private val dealService: DealService
) {
@PostMapping("/start")
fun dealStart(@RequestBody dealStartDto: DealStartDto): DealResponseDto {
return dealService.dealStart(dealStartDto)
}

@PostMapping("/end")
fun dealEnd(@RequestBody dealEndDto: DealEndDto): DealResponseDto {
return dealService.dealEnd(dealEndDto)
}
}
6 changes: 6 additions & 0 deletions src/main/kotlin/org/example/error/controller/ErrorResponse.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.example.error.controller

data class ErrorResponse(
val message: String,
val status: Int
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.example.error.controller

import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice

@RestControllerAdvice
class GlobalExceptionHandler {

@ExceptionHandler(IllegalArgumentException::class)
fun handleIllegalArgument(e: IllegalArgumentException): ResponseEntity<ErrorResponse> {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(
ErrorResponse(
message = e.message ?: "잘못된 요청입니다.", status = 400
)
)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.example.ticket.application.dto

data class TicketCreationDto(
val varCode: String
val sellerName: String,
val barcode: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.example.ticket.application.dto

import org.example.ticket.domain.enum.TicketStatus
import java.math.BigDecimal
import java.time.LocalDateTime

data class TicketResponseDto(
val barcode: String,
val seller: String,
val originalPrice: BigDecimal,
val sellingPrice: BigDecimal?,
val expirationDate: LocalDateTime,
val ticketStatus: TicketStatus
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.example.ticket.application.dto

import java.math.BigDecimal

data class TicketSellingPriceOfferDto(
val sellerName: String,
val barcode: String,
val sellingPrice: BigDecimal,
)
Loading