generated from pricelees/issue-pr-template
test: 결제 API 테스트 추가
This commit is contained in:
parent
865026aff2
commit
b847e59d6f
361
src/test/kotlin/roomescape/payment/PaymentAPITest.kt
Normal file
361
src/test/kotlin/roomescape/payment/PaymentAPITest.kt
Normal file
@ -0,0 +1,361 @@
|
||||
package roomescape.payment
|
||||
|
||||
import com.ninjasquad.springmockk.MockkBean
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.every
|
||||
import org.hamcrest.CoreMatchers.equalTo
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.http.HttpStatus
|
||||
import roomescape.payment.business.PaymentService
|
||||
import roomescape.payment.exception.PaymentErrorCode
|
||||
import roomescape.payment.infrastructure.client.CardDetail
|
||||
import roomescape.payment.infrastructure.client.EasyPayDetail
|
||||
import roomescape.payment.infrastructure.client.TosspayClient
|
||||
import roomescape.payment.infrastructure.client.TransferDetail
|
||||
import roomescape.payment.infrastructure.common.*
|
||||
import roomescape.payment.infrastructure.persistence.*
|
||||
import roomescape.payment.web.PaymentConfirmRequest
|
||||
import roomescape.payment.web.PaymentCreateResponse
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||
import roomescape.util.FunSpecSpringbootTest
|
||||
import roomescape.util.PaymentFixture
|
||||
import roomescape.util.runTest
|
||||
|
||||
class PaymentAPITest(
|
||||
@MockkBean
|
||||
private val tosspayClient: TosspayClient,
|
||||
private val paymentService: PaymentService,
|
||||
private val paymentRepository: PaymentRepository,
|
||||
private val paymentDetailRepository: PaymentDetailRepository,
|
||||
private val canceledPaymentRepository: CanceledPaymentRepository
|
||||
) : FunSpecSpringbootTest() {
|
||||
init {
|
||||
context("결제를 승인한다.") {
|
||||
val amount = 100_000
|
||||
context("간편결제 + 카드로 ${amount}원을 결제한다.") {
|
||||
context("일시불") {
|
||||
test("토스페이 + 토스뱅크카드(신용)") {
|
||||
runConfirmTest(
|
||||
amount = amount,
|
||||
cardDetail = PaymentFixture.cardDetail(
|
||||
amount = amount,
|
||||
issuerCode = CardIssuerCode.TOSS_BANK,
|
||||
cardType = CardType.CREDIT,
|
||||
),
|
||||
easyPayDetail = PaymentFixture.easypayDetail(
|
||||
amount = 0,
|
||||
provider = EasyPayCompanyCode.TOSSPAY
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
test("삼성페이 + 삼성카드(법인)") {
|
||||
runConfirmTest(
|
||||
amount = amount,
|
||||
cardDetail = PaymentFixture.cardDetail(
|
||||
amount = amount,
|
||||
issuerCode = CardIssuerCode.SAMSUNG,
|
||||
cardType = CardType.CREDIT,
|
||||
ownerType = CardOwnerType.CORPORATE
|
||||
),
|
||||
easyPayDetail = PaymentFixture.easypayDetail(
|
||||
amount = 0,
|
||||
provider = EasyPayCompanyCode.SAMSUNGPAY
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
context("할부") {
|
||||
val installmentPlanMonths = 12
|
||||
test("네이버페이 + 신한카드 / 12개월") {
|
||||
runConfirmTest(
|
||||
amount = amount,
|
||||
cardDetail = PaymentFixture.cardDetail(
|
||||
amount = amount,
|
||||
issuerCode = CardIssuerCode.SHINHAN,
|
||||
installmentPlanMonths = installmentPlanMonths
|
||||
),
|
||||
easyPayDetail = PaymentFixture.easypayDetail(
|
||||
amount = 0,
|
||||
provider = EasyPayCompanyCode.NAVERPAY
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
context("간편결제사 포인트 일부 사용") {
|
||||
val point = (amount * 0.1).toInt()
|
||||
test("토스페이 + 국민카드(체크) / 일시불 / $point 포인트 사용") {
|
||||
runConfirmTest(
|
||||
amount = amount,
|
||||
cardDetail = PaymentFixture.cardDetail(
|
||||
amount = (amount - point),
|
||||
issuerCode = CardIssuerCode.KOOKMIN,
|
||||
cardType = CardType.CHECK
|
||||
),
|
||||
easyPayDetail = PaymentFixture.easypayDetail(
|
||||
amount = 0,
|
||||
provider = EasyPayCompanyCode.TOSSPAY,
|
||||
discountAmount = point
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("간편결제사의 선불 충전금액으로 ${amount}원을 결제한다.") {
|
||||
test("토스페이 + 토스페이머니 / 전액") {
|
||||
runConfirmTest(
|
||||
easyPayDetail = PaymentFixture.easypayDetail(
|
||||
amount = amount,
|
||||
provider = EasyPayCompanyCode.TOSSPAY
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val point = (amount * 0.05).toInt()
|
||||
|
||||
test("카카오페이 + 카카오페이머니 / $point 사용") {
|
||||
runConfirmTest(
|
||||
easyPayDetail = PaymentFixture.easypayDetail(
|
||||
amount = (amount - point),
|
||||
provider = EasyPayCompanyCode.KAKAOPAY,
|
||||
discountAmount = point
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
context("계좌이체로 결제한다.") {
|
||||
test("토스뱅크") {
|
||||
runConfirmTest(
|
||||
transferDetail = PaymentFixture.transferDetail(bankCode = BankCode.TOSS_BANK)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
context("지원하지 않는 결제수단으로 요청시 실패한다.") {
|
||||
val supportedMethod = listOf(
|
||||
PaymentMethod.CARD,
|
||||
PaymentMethod.EASY_PAY,
|
||||
PaymentMethod.TRANSFER,
|
||||
)
|
||||
|
||||
PaymentMethod.entries.filter { it !in supportedMethod }.forEach {
|
||||
test("결제 수단: ${it.koreanName}") {
|
||||
val reservation = dummyInitializer.createConfirmReservation(
|
||||
adminToken = loginUtil.loginAsAdmin(),
|
||||
reserverToken = loginUtil.loginAsUser()
|
||||
)
|
||||
|
||||
val request = PaymentFixture.confirmRequest
|
||||
|
||||
every {
|
||||
tosspayClient.confirm(request.paymentKey, request.orderId, request.amount)
|
||||
} returns PaymentFixture.confirmResponse(
|
||||
paymentKey = request.paymentKey,
|
||||
amount = request.amount,
|
||||
method = it,
|
||||
cardDetail = null,
|
||||
easyPayDetail = null,
|
||||
transferDetail = null,
|
||||
)
|
||||
|
||||
runTest(
|
||||
token = loginUtil.loginAsUser(),
|
||||
using = {
|
||||
body(PaymentFixture.confirmRequest)
|
||||
},
|
||||
on = {
|
||||
post("/payments?reservationId=${reservation.id}")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.BAD_REQUEST.value())
|
||||
body("code", equalTo(PaymentErrorCode.NOT_SUPPORTED_PAYMENT_TYPE.errorCode))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("결제를 취소한다.") {
|
||||
test("정상 취소") {
|
||||
val token = loginUtil.loginAsAdmin()
|
||||
val confirmRequest = PaymentFixture.confirmRequest
|
||||
val reservation = dummyInitializer.createConfirmReservation(
|
||||
adminToken = token,
|
||||
reserverToken = token
|
||||
)
|
||||
|
||||
val paymentCreateResponse = createPayment(
|
||||
request = confirmRequest,
|
||||
reservationId = reservation.id
|
||||
)
|
||||
|
||||
every {
|
||||
tosspayClient.cancel(
|
||||
confirmRequest.paymentKey,
|
||||
confirmRequest.amount,
|
||||
cancelReason = "cancelReason"
|
||||
)
|
||||
} returns PaymentFixture.cancelResponse(confirmRequest.amount)
|
||||
|
||||
runTest(
|
||||
token = token,
|
||||
using = {
|
||||
val cancelRequest = PaymentFixture.cancelRequest.copy(
|
||||
reservationId = reservation.id
|
||||
)
|
||||
body(cancelRequest)
|
||||
},
|
||||
on = {
|
||||
post("/payments/cancel")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.OK.value())
|
||||
}
|
||||
).also {
|
||||
val payment = paymentRepository.findByIdOrNull(paymentCreateResponse.paymentId)
|
||||
?: throw AssertionError("Unexpected Exception Occurred.")
|
||||
val canceledPayment =
|
||||
canceledPaymentRepository.findByPaymentId(paymentCreateResponse.paymentId)
|
||||
?: throw AssertionError("Unexpected Exception Occurred.")
|
||||
|
||||
payment.status shouldBe PaymentStatus.CANCELED
|
||||
canceledPayment.paymentId shouldBe payment.id
|
||||
canceledPayment.cancelAmount shouldBe payment.totalAmount
|
||||
}
|
||||
}
|
||||
|
||||
test("예약에 대한 결제 정보가 없으면 실패한다.") {
|
||||
val token = loginUtil.loginAsAdmin()
|
||||
val reservation = dummyInitializer.createConfirmReservation(
|
||||
adminToken = token,
|
||||
reserverToken = token,
|
||||
)
|
||||
|
||||
runTest(
|
||||
token = token,
|
||||
using = {
|
||||
body(PaymentFixture.cancelRequest.copy(reservationId = reservation.id))
|
||||
},
|
||||
on = {
|
||||
post("/payments/cancel")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.NOT_FOUND.value())
|
||||
body("code", equalTo(PaymentErrorCode.PAYMENT_NOT_FOUND.errorCode))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createPayment(
|
||||
request: PaymentConfirmRequest,
|
||||
reservationId: Long,
|
||||
): PaymentCreateResponse {
|
||||
every {
|
||||
tosspayClient.confirm(request.paymentKey, request.orderId, request.amount)
|
||||
} returns PaymentFixture.confirmResponse(
|
||||
request.paymentKey,
|
||||
request.amount,
|
||||
method = PaymentMethod.CARD,
|
||||
cardDetail = PaymentFixture.cardDetail(request.amount),
|
||||
easyPayDetail = null,
|
||||
transferDetail = null,
|
||||
)
|
||||
|
||||
return paymentService.confirm(reservationId, request)
|
||||
}
|
||||
|
||||
fun runConfirmTest(
|
||||
cardDetail: CardDetail? = null,
|
||||
easyPayDetail: EasyPayDetail? = null,
|
||||
transferDetail: TransferDetail? = null,
|
||||
paymentKey: String = "paymentKey",
|
||||
amount: Int = 10000,
|
||||
) {
|
||||
|
||||
val request = PaymentFixture.confirmRequest.copy(paymentKey = paymentKey, amount = amount)
|
||||
|
||||
val reservation: ReservationEntity = dummyInitializer.createPendingReservation(
|
||||
adminToken = loginUtil.loginAsAdmin(),
|
||||
reserverToken = loginUtil.loginAsUser(),
|
||||
)
|
||||
|
||||
val method = if (easyPayDetail != null) {
|
||||
PaymentMethod.EASY_PAY
|
||||
} else if (cardDetail != null) {
|
||||
PaymentMethod.CARD
|
||||
} else if (transferDetail != null) {
|
||||
PaymentMethod.TRANSFER
|
||||
} else {
|
||||
throw AssertionError("결제타입 확인 필요.")
|
||||
}
|
||||
|
||||
val clientResponse = PaymentFixture.confirmResponse(
|
||||
paymentKey, amount, method, cardDetail, easyPayDetail, transferDetail
|
||||
)
|
||||
|
||||
every {
|
||||
tosspayClient.confirm(request.paymentKey, request.orderId, request.amount)
|
||||
} returns clientResponse
|
||||
|
||||
runTest(
|
||||
token = loginUtil.loginAsUser(),
|
||||
using = {
|
||||
body(request)
|
||||
},
|
||||
on = {
|
||||
post("/payments?reservationId=${reservation.id}")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.OK.value())
|
||||
}
|
||||
).also {
|
||||
val createdPayment = paymentRepository.findByIdOrNull(it.extract().path("data.paymentId"))
|
||||
?: throw AssertionError("Unexpected Exception Occurred.")
|
||||
val createdPaymentDetail =
|
||||
paymentDetailRepository.findByIdOrNull(it.extract().path("data.detailId"))
|
||||
?: throw AssertionError("Unexpected Exception Occurred.")
|
||||
|
||||
createdPayment.status shouldBe clientResponse.status
|
||||
createdPayment.method shouldBe clientResponse.method
|
||||
createdPayment.reservationId shouldBe reservation.id
|
||||
|
||||
when (createdPaymentDetail) {
|
||||
is PaymentCardDetailEntity -> {
|
||||
createdPaymentDetail.issuerCode shouldBe clientResponse.card!!.issuerCode
|
||||
createdPaymentDetail.cardType shouldBe clientResponse.card.cardType
|
||||
createdPaymentDetail.cardNumber shouldBe clientResponse.card.number
|
||||
createdPaymentDetail.suppliedAmount shouldBe clientResponse.suppliedAmount
|
||||
createdPaymentDetail.vat shouldBe clientResponse.vat
|
||||
createdPaymentDetail.amount shouldBe (clientResponse.totalAmount - (clientResponse.easyPay?.discountAmount
|
||||
?: 0))
|
||||
clientResponse.easyPay?.let { easypay ->
|
||||
createdPaymentDetail.easypayProviderCode shouldBe easypay.provider
|
||||
createdPaymentDetail.easypayDiscountAmount shouldBe easypay.discountAmount
|
||||
}
|
||||
}
|
||||
|
||||
is PaymentBankTransferDetailEntity -> {
|
||||
createdPaymentDetail.suppliedAmount shouldBe clientResponse.suppliedAmount
|
||||
createdPaymentDetail.vat shouldBe clientResponse.vat
|
||||
createdPaymentDetail.bankCode shouldBe clientResponse.transfer!!.bankCode
|
||||
createdPaymentDetail.settlementStatus shouldBe clientResponse.transfer.settlementStatus
|
||||
}
|
||||
|
||||
is PaymentEasypayPrepaidDetailEntity -> {
|
||||
createdPaymentDetail.suppliedAmount shouldBe clientResponse.suppliedAmount
|
||||
createdPaymentDetail.vat shouldBe clientResponse.vat
|
||||
createdPaymentDetail.easypayProviderCode shouldBe clientResponse.easyPay!!.provider
|
||||
createdPaymentDetail.amount shouldBe clientResponse.easyPay.amount
|
||||
createdPaymentDetail.discountAmount shouldBe clientResponse.easyPay.discountAmount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,11 +3,17 @@ package roomescape.util
|
||||
import com.github.f4b6a3.tsid.TsidFactory
|
||||
import roomescape.member.infrastructure.persistence.MemberEntity
|
||||
import roomescape.member.infrastructure.persistence.Role
|
||||
import roomescape.payment.infrastructure.client.*
|
||||
import roomescape.payment.infrastructure.common.*
|
||||
import roomescape.payment.web.PaymentCancelRequest
|
||||
import roomescape.payment.web.PaymentConfirmRequest
|
||||
import roomescape.reservation.web.PendingReservationCreateRequest
|
||||
import roomescape.schedule.web.ScheduleCreateRequest
|
||||
import roomescape.theme.infrastructure.persistence.Difficulty
|
||||
import roomescape.theme.web.ThemeCreateRequest
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
import java.time.OffsetDateTime
|
||||
|
||||
const val INVALID_PK: Long = 9999L
|
||||
val tsidFactory = TsidFactory(0)
|
||||
@ -53,3 +59,97 @@ object ScheduleFixture {
|
||||
themeId = 1L
|
||||
)
|
||||
}
|
||||
|
||||
object PaymentFixture {
|
||||
val confirmRequest: PaymentConfirmRequest = PaymentConfirmRequest(
|
||||
paymentKey = "paymentKey",
|
||||
orderId = "orderId",
|
||||
amount = 10000,
|
||||
paymentType = PaymentType.NORMAL
|
||||
)
|
||||
|
||||
val cancelRequest: PaymentCancelRequest = PaymentCancelRequest(
|
||||
reservationId = 1L,
|
||||
cancelReason = "cancelReason",
|
||||
)
|
||||
|
||||
fun cardDetail(
|
||||
amount: Int,
|
||||
issuerCode: CardIssuerCode = CardIssuerCode.SHINHAN,
|
||||
cardType: CardType = CardType.CREDIT,
|
||||
ownerType: CardOwnerType = CardOwnerType.PERSONAL,
|
||||
installmentPlanMonths: Int = 0,
|
||||
): CardDetail = CardDetail(
|
||||
issuerCode = issuerCode,
|
||||
number = "429335*********",
|
||||
amount = amount,
|
||||
cardType = cardType,
|
||||
ownerType = ownerType,
|
||||
isInterestFree = false,
|
||||
approveNo = "1828382",
|
||||
installmentPlanMonths = installmentPlanMonths
|
||||
)
|
||||
|
||||
fun easypayDetail(
|
||||
amount: Int,
|
||||
provider: EasyPayCompanyCode = EasyPayCompanyCode.TOSSPAY,
|
||||
discountAmount: Int = 0
|
||||
): EasyPayDetail = EasyPayDetail(provider, amount, discountAmount)
|
||||
|
||||
fun transferDetail(
|
||||
bankCode: BankCode = BankCode.SHINHAN,
|
||||
settlementStatus: String = "COMPLETED"
|
||||
): TransferDetail = TransferDetail(
|
||||
bankCode = bankCode,
|
||||
settlementStatus = settlementStatus
|
||||
)
|
||||
|
||||
fun confirmResponse(
|
||||
paymentKey: String,
|
||||
amount: Int,
|
||||
method: PaymentMethod,
|
||||
cardDetail: CardDetail?,
|
||||
easyPayDetail: EasyPayDetail?,
|
||||
transferDetail: TransferDetail?
|
||||
) = PaymentClientConfirmResponse(
|
||||
paymentKey = paymentKey,
|
||||
status = PaymentStatus.DONE,
|
||||
totalAmount = amount,
|
||||
vat = (amount * 0.1).toInt(),
|
||||
suppliedAmount = (amount * 0.9).toInt(),
|
||||
method = method,
|
||||
card = cardDetail,
|
||||
easyPay = easyPayDetail,
|
||||
transfer = transferDetail,
|
||||
requestedAt = OffsetDateTime.now(),
|
||||
approvedAt = OffsetDateTime.now().plusSeconds(5)
|
||||
)
|
||||
|
||||
fun cancelResponse(
|
||||
amount: Int,
|
||||
cardDiscountAmount: Int = 0,
|
||||
transferDiscountAmount: Int = 0,
|
||||
easypayDiscountAmount: Int = 0,
|
||||
cancelReason: String = "cancelReason"
|
||||
) = PaymentClientCancelResponse(
|
||||
status = PaymentStatus.CANCELED,
|
||||
cancels = CancelDetail(
|
||||
cancelAmount = amount,
|
||||
cardDiscountAmount = cardDiscountAmount,
|
||||
transferDiscountAmount = transferDiscountAmount,
|
||||
easyPayDiscountAmount = easypayDiscountAmount,
|
||||
canceledAt = OffsetDateTime.now().plusSeconds(5),
|
||||
cancelReason = cancelReason
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
object ReservationFixture {
|
||||
val pendingCreateRequest: PendingReservationCreateRequest = PendingReservationCreateRequest(
|
||||
scheduleId = 1L,
|
||||
reserverName = "Wilbur Stuart",
|
||||
reserverContact = "wilbur@example.com",
|
||||
participantCount = 5,
|
||||
requirement = "Hello, Nice to meet you!"
|
||||
)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user