Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
bbc1e8c
작성중..
SongHanSol1140 Feb 24, 2026
069485b
TicketJpaRepository 공부(주석)
SongHanSol1140 Feb 25, 2026
596fdcb
Ticket 유효기간 체크 init으로 수정
SongHanSol1140 Feb 25, 2026
1f7205d
applySellerOfferPrice 구현
SongHanSol1140 Feb 26, 2026
c51a664
각 ApiClient에 type 바코드 인자 필요없어서 삭제
SongHanSol1140 Feb 26, 2026
2f64668
ApiClient 선택 로직TicketApiClientResolver로 분리
SongHanSol1140 Feb 26, 2026
d6db88c
1. 티켓 모델에 SellerName추가
SongHanSol1140 Feb 26, 2026
de78689
getTicketDeadLine함수 수정
SongHanSol1140 Feb 26, 2026
275d716
deal 도메인 생성, 거래시작(RESERVED) 메서드 만들기
SongHanSol1140 Feb 26, 2026
ad06979
어플리케이션 패키징경로 상위로 이동
SongHanSol1140 Feb 26, 2026
0089c12
deal 레퍼지토리 저장/조회 기능 추가,
SongHanSol1140 Feb 26, 2026
80fe488
deal paymentGateway 추가
SongHanSol1140 Feb 27, 2026
6569cfa
딜 테스트 추가
SongHanSol1140 Feb 27, 2026
a59c624
티켓 컨트롤러 추가
SongHanSol1140 Feb 27, 2026
8291c66
딜 컨트롤러 추가
SongHanSol1140 Feb 27, 2026
af45a9e
PR
SongHanSol1140 Mar 1, 2026
cab7a30
PR
SongHanSol1140 Mar 4, 2026
50575a6
오타수정
SongHanSol1140 Mar 9, 2026
48df322
오타수정
SongHanSol1140 Mar 9, 2026
76e1576
1
SongHanSol1140 Mar 9, 2026
66b6f00
refactor(ticket): 바코드 체크 로직을 init 블록으로 이동
SongHanSol1140 Mar 10, 2026
badd252
refactor(deal): getDealStatus 함수 삭제 후 private set으로 대체
SongHanSol1140 Mar 12, 2026
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,7 @@
package org.example.deal.application.dto

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

data class DealEndDto(
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,73 @@
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()
val savedDeal = dealRepository.save(deal)
return DealResponseDto(
barcode = savedDeal.barcode,
sellerName = savedDeal.sellerName,
buyerName = savedDeal.buyerName,
sellingPrice = savedDeal.sellingPrice,
reservedDateTime = savedDeal.reservedDateTime,
dealStatus = savedDeal.dealStatus
)
}
@Transactional
fun dealEnd(barcode:String, dealEndDto: DealEndDto): DealResponseDto {
val deal = requireNotNull(dealRepository.findByBarcode(barcode)) { "존재하지 않는 거래입니다." }
val ticket = requireNotNull(ticketRepository.findByBarcode(barcode)) { "존재하지 않는 티켓입니다." }
val dealExpiredCheck = deal.reservedTimeExpiredCheck()
if (dealExpiredCheck) {
deal.dealCancel()
ticket.ticketOnSale()
return DealResponseDto(
barcode = deal.barcode,
sellerName = deal.sellerName,
buyerName = deal.buyerName,
sellingPrice = deal.sellingPrice,
reservedDateTime = deal.reservedDateTime,
dealStatus = deal.dealStatus
)
}
val paymentGateway = paymentGatewayResolver.resolve(dealEndDto.paymentType)
paymentGateway.pay(deal.buyerName, deal.sellerName, deal.sellingPrice)
deal.dealComplete()
ticket.ticketSold()
return DealResponseDto(
barcode = deal.barcode,
sellerName = deal.sellerName,
buyerName = deal.buyerName,
sellingPrice = deal.sellingPrice,
reservedDateTime = deal.reservedDateTime,
dealStatus = deal.dealStatus
)
}

}
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
}
46 changes: 46 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,46 @@
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 reservedDateTime: LocalDateTime = LocalDateTime.now(),
val sellingPrice: BigDecimal,
) {
var dealStatus: DealStatus = DealStatus.RESERVED
private set


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())
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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(
private val userJpaRepository: UserJpaRepository
) : 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,22 @@
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(
private val userJpaRepository: UserJpaRepository
) : 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,31 @@
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.PatchMapping
import org.springframework.web.bind.annotation.PathVariable
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 = ["/deals"])
class DealController(
private val dealService: DealService
) {
@PostMapping("")
fun dealStart(@RequestBody dealStartDto: DealStartDto): DealResponseDto {
return dealService.dealStart(dealStartDto)
}

@PatchMapping("/{barcode}")
fun dealEnd(
@PathVariable("barcode") barcode: String,
@RequestBody dealEndDto: DealEndDto
): DealResponseDto {
return dealService.dealEnd(barcode, 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,8 @@
package org.example.ticket.application.dto

import java.math.BigDecimal

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