코인 충전, 코인 내역 API 추가

This commit is contained in:
Klaus 2023-07-29 05:37:06 +09:00
parent c06de5f9f6
commit 7c8084bdd4
25 changed files with 846 additions and 2 deletions

View File

@ -51,6 +51,9 @@ dependencies {
// bootpay // bootpay
implementation("io.github.bootpay:backend:2.2.1") implementation("io.github.bootpay:backend:2.2.1")
implementation("com.squareup.okhttp3:okhttp:4.9.3")
implementation("org.json:json:20230227")
developmentOnly("org.springframework.boot:spring-boot-devtools") developmentOnly("org.springframework.boot:spring-boot-devtools")
runtimeOnly("com.h2database:h2") runtimeOnly("com.h2database:h2")
runtimeOnly("com.mysql:mysql-connector-j") runtimeOnly("com.mysql:mysql-connector-j")

View File

@ -0,0 +1,20 @@
package kr.co.vividnext.sodalive.can
import kr.co.vividnext.sodalive.common.BaseEntity
import javax.persistence.Entity
import javax.persistence.EnumType
import javax.persistence.Enumerated
@Entity
data class Can(
var title: String,
var can: Int,
var rewardCan: Int,
var price: Int,
@Enumerated(value = EnumType.STRING)
var status: CanStatus
) : BaseEntity()
enum class CanStatus {
SALE, END_OF_SALE
}

View File

@ -0,0 +1,60 @@
package kr.co.vividnext.sodalive.can
import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.member.Member
import org.springframework.data.domain.Pageable
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/can")
class CanController(private val service: CanService) {
@GetMapping
fun getCans(): ApiResponse<List<CanResponse>> {
return ApiResponse.ok(service.getCans())
}
@GetMapping("/status")
fun getCanStatus(
@RequestParam container: String,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.")
}
ApiResponse.ok(service.getCanStatus(member, container))
}
@GetMapping("/status/use")
fun getCanUseStatus(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
@RequestParam("timezone") timezone: String,
@RequestParam("container") container: String,
pageable: Pageable
) = run {
if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.")
}
ApiResponse.ok(service.getCanUseStatus(member, pageable, timezone, container))
}
@GetMapping("/status/charge")
fun getCanChargeStatus(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
@RequestParam("timezone") timezone: String,
@RequestParam("container") container: String,
pageable: Pageable
) = run {
if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.")
}
ApiResponse.ok(service.getCanChargeStatus(member, pageable, timezone, container))
}
}

View File

@ -0,0 +1,96 @@
package kr.co.vividnext.sodalive.can
import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.can.QCan.can1
import kr.co.vividnext.sodalive.can.charge.Charge
import kr.co.vividnext.sodalive.can.charge.ChargeStatus
import kr.co.vividnext.sodalive.can.charge.QCharge.charge
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
import kr.co.vividnext.sodalive.can.payment.PaymentStatus
import kr.co.vividnext.sodalive.can.payment.QPayment.payment
import kr.co.vividnext.sodalive.can.use.QUseCan.useCan
import kr.co.vividnext.sodalive.can.use.UseCan
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.QMember
import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
@Repository
interface CanRepository : JpaRepository<Can, Long>, CanQueryRepository
interface CanQueryRepository {
fun findAllByStatus(status: CanStatus): List<CanResponse>
fun getCanUseStatus(member: Member, pageable: Pageable): List<UseCan>
fun getCanChargeStatus(member: Member, pageable: Pageable, container: String): List<Charge>
}
@Repository
class CanQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : CanQueryRepository {
override fun findAllByStatus(status: CanStatus): List<CanResponse> {
return queryFactory
.select(
QCanResponse(
can1.id,
can1.title,
can1.can,
can1.rewardCan,
can1.price
)
)
.from(can1)
.where(can1.status.eq(status))
.orderBy(can1.can.asc())
.fetch()
}
override fun getCanUseStatus(member: Member, pageable: Pageable): List<UseCan> {
return queryFactory
.selectFrom(useCan)
.where(useCan.member.id.eq(member.id))
.offset(pageable.offset)
.limit(pageable.pageSize.toLong())
.orderBy(useCan.id.desc())
.fetch()
}
override fun getCanChargeStatus(member: Member, pageable: Pageable, container: String): List<Charge> {
val qMember = QMember.member
val chargeStatusCondition = when (container) {
"aos" -> {
charge.payment.paymentGateway.eq(PaymentGateway.PG)
.or(charge.payment.paymentGateway.eq(PaymentGateway.GOOGLE_IAP))
}
"ios" -> {
charge.payment.paymentGateway.eq(PaymentGateway.PG)
.or(charge.payment.paymentGateway.eq(PaymentGateway.APPLE_IAP))
}
else -> charge.payment.paymentGateway.eq(PaymentGateway.PG)
}
return queryFactory
.selectFrom(charge)
.innerJoin(charge.member, qMember)
.innerJoin(charge.useCan, useCan)
.leftJoin(charge.payment, payment)
.where(
qMember.id.eq(member.id)
.and(
payment.status.eq(PaymentStatus.COMPLETE)
.or(
charge.status.eq(ChargeStatus.REFUND_CHARGE)
.and(useCan.isNotNull)
)
.or(charge.status.eq(ChargeStatus.EVENT))
.or(charge.status.eq(ChargeStatus.ADMIN))
)
.and(chargeStatusCondition)
)
.offset(pageable.offset)
.limit(pageable.pageSize.toLong())
.orderBy(charge.id.desc())
.fetch()
}
}

View File

@ -0,0 +1,11 @@
package kr.co.vividnext.sodalive.can
import com.querydsl.core.annotations.QueryProjection
data class CanResponse @QueryProjection constructor(
val id: Long,
val title: String,
val can: Int,
val rewardCan: Int,
val price: Int
)

View File

@ -0,0 +1,118 @@
package kr.co.vividnext.sodalive.can
import kr.co.vividnext.sodalive.can.charge.ChargeStatus
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
import kr.co.vividnext.sodalive.can.use.CanUsage
import kr.co.vividnext.sodalive.member.Member
import org.springframework.data.domain.Pageable
import org.springframework.stereotype.Service
import java.time.ZoneId
import java.time.format.DateTimeFormatter
@Service
class CanService(private val repository: CanRepository) {
fun getCans(): List<CanResponse> {
return repository.findAllByStatus(status = CanStatus.SALE)
}
fun getCanStatus(member: Member, container: String): GetCanStatusResponse {
return GetCanStatusResponse(
chargeCan = member.getChargeCan(container),
rewardCan = member.getRewardCan(container)
)
}
fun getCanUseStatus(
member: Member,
pageable: Pageable,
timezone: String,
container: String
): List<GetCanUseStatusResponseItem> {
return repository.getCanUseStatus(member, pageable)
.filter { (it.can + it.rewardCan) > 0 }
.filter {
when (container) {
"aos" -> {
it.useCanCalculates.any { useCanCalculate ->
useCanCalculate.paymentGateway == PaymentGateway.PG ||
useCanCalculate.paymentGateway == PaymentGateway.GOOGLE_IAP
}
}
"ios" -> {
it.useCanCalculates.any { useCanCalculate ->
useCanCalculate.paymentGateway == PaymentGateway.PG ||
useCanCalculate.paymentGateway == PaymentGateway.APPLE_IAP
}
}
else -> it.useCanCalculates.any { useCanCalculate ->
useCanCalculate.paymentGateway == PaymentGateway.PG
}
}
}
.map {
val title: String = when (it.canUsage) {
CanUsage.DONATION -> {
"[후원] ${it.room!!.member!!.nickname}"
}
CanUsage.LIVE -> {
"[라이브] ${it.room!!.title}"
}
CanUsage.CHANGE_NICKNAME -> "닉네임 변경"
CanUsage.ORDER_CONTENT -> "콘텐츠 구매"
}
val createdAt = it.createdAt!!
.atZone(ZoneId.of("UTC"))
.withZoneSameInstant(ZoneId.of(timezone))
GetCanUseStatusResponseItem(
title = title,
date = createdAt.format(
DateTimeFormatter.ofPattern("yyyy-MM-dd | HH:mm:ss")
),
can = it.can + it.rewardCan
)
}
}
fun getCanChargeStatus(
member: Member,
pageable: Pageable,
timezone: String,
container: String
): List<GetCanChargeStatusResponseItem> {
return repository.getCanChargeStatus(member, pageable, container)
.map {
val canTitle = it.title ?: ""
val chargeMethod = when (it.status) {
ChargeStatus.CHARGE, ChargeStatus.EVENT -> {
it.payment!!.method ?: ""
}
ChargeStatus.REFUND_CHARGE -> {
"환불"
}
else -> {
"환불"
}
}
val createdAt = it.createdAt!!
.atZone(ZoneId.of("UTC"))
.withZoneSameInstant(ZoneId.of(timezone))
GetCanChargeStatusResponseItem(
canTitle = canTitle,
date = createdAt.format(
DateTimeFormatter.ofPattern("yyyy-MM-dd | HH:mm:ss")
),
chargeMethod = chargeMethod
)
}
}
}

View File

@ -0,0 +1,7 @@
package kr.co.vividnext.sodalive.can
data class GetCanChargeStatusResponseItem(
val canTitle: String,
val date: String,
val chargeMethod: String
)

View File

@ -0,0 +1,6 @@
package kr.co.vividnext.sodalive.can
data class GetCanStatusResponse(
val chargeCan: Int,
val rewardCan: Int
)

View File

@ -0,0 +1,7 @@
package kr.co.vividnext.sodalive.can
data class GetCanUseStatusResponseItem(
val title: String,
val date: String,
val can: Int
)

View File

@ -0,0 +1,45 @@
package kr.co.vividnext.sodalive.can.charge
import kr.co.vividnext.sodalive.can.Can
import kr.co.vividnext.sodalive.can.payment.Payment
import kr.co.vividnext.sodalive.can.use.UseCan
import kr.co.vividnext.sodalive.common.BaseEntity
import kr.co.vividnext.sodalive.member.Member
import javax.persistence.CascadeType
import javax.persistence.Entity
import javax.persistence.EnumType
import javax.persistence.Enumerated
import javax.persistence.FetchType
import javax.persistence.JoinColumn
import javax.persistence.ManyToOne
import javax.persistence.OneToOne
@Entity
data class Charge(
var chargeCan: Int,
var rewardCan: Int,
@Enumerated(value = EnumType.STRING)
var status: ChargeStatus = ChargeStatus.CHARGE
) : BaseEntity() {
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "can_id", nullable = true)
var can: Can? = null
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = false)
var member: Member? = null
@OneToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL])
@JoinColumn(name = "payment_id", nullable = true)
var payment: Payment? = null
set(value) {
value?.charge = this
field = value
}
@OneToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL])
@JoinColumn(name = "use_can_id", nullable = true)
var useCan: UseCan? = null
var title: String? = null
}

View File

@ -0,0 +1,52 @@
package kr.co.vividnext.sodalive.can.charge
import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.member.Member
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.security.core.userdetails.User
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("/charge")
class ChargeController(private val service: ChargeService) {
@PostMapping
fun charge(
@RequestBody chargeRequest: ChargeRequest,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.")
}
ApiResponse.ok(service.charge(member, chargeRequest))
}
@PostMapping("/verify")
fun verify(
@RequestBody verifyRequest: VerifyRequest,
@AuthenticationPrincipal user: User
) = ApiResponse.ok(service.verify(user, verifyRequest))
@PostMapping("/apple")
fun appleCharge(
@RequestBody chargeRequest: AppleChargeRequest,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.")
}
ApiResponse.ok(service.appleCharge(member, chargeRequest))
}
@PostMapping("/apple/verify")
fun appleVerify(
@RequestBody verifyRequest: AppleVerifyRequest,
@AuthenticationPrincipal user: User
) = ApiResponse.ok(service.appleVerify(user, verifyRequest))
}

View File

@ -0,0 +1,35 @@
package kr.co.vividnext.sodalive.can.charge
import com.fasterxml.jackson.annotation.JsonProperty
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
data class ChargeRequest(val canId: Long, val paymentGateway: PaymentGateway)
data class ChargeResponse(val chargeId: Long)
data class VerifyRequest(
@JsonProperty("receipt_id")
val receiptId: String,
@JsonProperty("order_id")
val orderId: String
)
data class VerifyResult(
@JsonProperty("receipt_id")
val receiptId: String,
val method: String,
val status: Int,
val price: Int
)
data class AppleChargeRequest(
val title: String,
val chargeCan: Int,
val paymentGateway: PaymentGateway,
var price: Double? = null,
var locale: String? = null
)
data class AppleVerifyRequest(val receiptString: String, val chargeId: Long)
data class AppleVerifyResponse(val status: Int)

View File

@ -0,0 +1,7 @@
package kr.co.vividnext.sodalive.can.charge
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
@Repository
interface ChargeRepository : JpaRepository<Charge, Long>

View File

@ -0,0 +1,203 @@
package kr.co.vividnext.sodalive.can.charge
import com.fasterxml.jackson.databind.ObjectMapper
import kr.co.bootpay.Bootpay
import kr.co.vividnext.sodalive.can.CanRepository
import kr.co.vividnext.sodalive.can.payment.Payment
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
import kr.co.vividnext.sodalive.can.payment.PaymentStatus
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberRepository
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
import org.springframework.beans.factory.annotation.Value
import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpHeaders
import org.springframework.security.core.userdetails.User
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
@Service
@Transactional(readOnly = true)
class ChargeService(
private val chargeRepository: ChargeRepository,
private val canRepository: CanRepository,
private val memberRepository: MemberRepository,
private val objectMapper: ObjectMapper,
private val okHttpClient: OkHttpClient,
@Value("\${bootpay.application-id}")
private val bootpayApplicationId: String,
@Value("\${bootpay.private-key}")
private val bootpayPrivateKey: String,
@Value("\${apple.iap-verify-sandbox-url}")
private val appleInAppVerifySandBoxUrl: String,
@Value("\${apple.iap-verify-url}")
private val appleInAppVerifyUrl: String
) {
@Transactional
fun charge(member: Member, request: ChargeRequest): ChargeResponse {
val can = canRepository.findByIdOrNull(request.canId)
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.")
val charge = Charge(can.can, can.rewardCan)
charge.title = can.title
charge.member = member
charge.can = can
val payment = Payment(paymentGateway = request.paymentGateway)
payment.price = can.price.toDouble()
charge.payment = payment
chargeRepository.save(charge)
return ChargeResponse(chargeId = charge.id!!)
}
@Transactional
fun verify(user: User, verifyRequest: VerifyRequest) {
val charge = chargeRepository.findByIdOrNull(verifyRequest.orderId.toLong())
?: throw SodaException("결제정보에 오류가 있습니다.")
val member = memberRepository.findByEmail(user.username)
?: throw SodaException("로그인 정보를 확인해주세요.")
if (charge.payment!!.paymentGateway == PaymentGateway.PG) {
val bootpay = Bootpay(bootpayApplicationId, bootpayPrivateKey)
try {
bootpay.accessToken
val verifyResult = objectMapper.convertValue(
bootpay.getReceipt(verifyRequest.receiptId),
VerifyResult::class.java
)
if (verifyResult.status == 1 && verifyResult.price == charge.can?.price) {
charge.payment?.receiptId = verifyResult.receiptId
charge.payment?.method = verifyResult.method
charge.payment?.status = PaymentStatus.COMPLETE
member.charge(charge.chargeCan, charge.rewardCan, "pg")
} else {
throw SodaException("결제정보에 오류가 있습니다.")
}
} catch (e: Exception) {
throw SodaException("결제정보에 오류가 있습니다.")
}
} else {
throw SodaException("결제정보에 오류가 있습니다.")
}
}
@Transactional
fun appleCharge(member: Member, request: AppleChargeRequest): ChargeResponse {
val charge = Charge(request.chargeCan, 0)
charge.title = request.title
charge.member = member
val payment = Payment(paymentGateway = request.paymentGateway)
payment.price = if (request.price != null) {
request.price!!
} else {
0.toDouble()
}
payment.locale = request.locale
charge.payment = payment
chargeRepository.save(charge)
return ChargeResponse(chargeId = charge.id!!)
}
@Transactional
fun appleVerify(user: User, verifyRequest: AppleVerifyRequest) {
val charge = chargeRepository.findByIdOrNull(verifyRequest.chargeId)
?: throw SodaException("결제정보에 오류가 있습니다.")
val member = memberRepository.findByEmail(user.username)
?: throw SodaException("로그인 정보를 확인해주세요.")
if (charge.payment!!.paymentGateway == PaymentGateway.APPLE_IAP) {
// 검증로직
if (requestRealServerVerify(verifyRequest)) {
charge.payment?.receiptId = verifyRequest.receiptString
charge.payment?.method = "애플(인 앱 결제)"
charge.payment?.status = PaymentStatus.COMPLETE
member.charge(charge.chargeCan, charge.rewardCan, "ios")
} else {
throw SodaException("결제정보에 오류가 있습니다.")
}
} else {
throw SodaException("결제정보에 오류가 있습니다.")
}
}
private fun requestRealServerVerify(verifyRequest: AppleVerifyRequest): Boolean {
val body = JSONObject()
body.put("receipt-data", verifyRequest.receiptString)
val request = Request.Builder()
.url(appleInAppVerifyUrl)
.addHeader(HttpHeaders.CONTENT_TYPE, "application/json")
.post(body.toString().toRequestBody("application/json".toMediaTypeOrNull()))
.build()
val response = okHttpClient.newCall(request).execute()
if (response.isSuccessful) {
val responseString = response.body?.string()
if (responseString != null) {
val verifyResult = objectMapper.readValue(responseString, AppleVerifyResponse::class.java)
return when (verifyResult.status) {
0 -> {
true
}
21007 -> {
requestSandboxServerVerify(verifyRequest)
}
else -> {
throw SodaException("결제정보에 오류가 있습니다.")
}
}
} else {
throw SodaException("결제를 완료하지 못했습니다.")
}
} else {
throw SodaException("결제를 완료하지 못했습니다.")
}
}
private fun requestSandboxServerVerify(verifyRequest: AppleVerifyRequest): Boolean {
val body = JSONObject()
body.put("receipt-data", verifyRequest.receiptString)
val request = Request.Builder()
.url(appleInAppVerifySandBoxUrl)
.addHeader(HttpHeaders.CONTENT_TYPE, "application/json")
.post(body.toString().toRequestBody("application/json".toMediaTypeOrNull()))
.build()
val response = okHttpClient.newCall(request).execute()
if (response.isSuccessful) {
val responseString = response.body?.string()
if (responseString != null) {
val verifyResult = objectMapper.readValue(responseString, AppleVerifyResponse::class.java)
return when (verifyResult.status) {
0 -> {
true
}
else -> {
throw SodaException("결제정보에 오류가 있습니다.")
}
}
} else {
throw SodaException("결제를 완료하지 못했습니다.")
}
} else {
throw SodaException("결제를 완료하지 못했습니다.")
}
}
}

View File

@ -0,0 +1,8 @@
package kr.co.vividnext.sodalive.can.charge
enum class ChargeStatus {
CHARGE, REFUND_CHARGE, EVENT, CANCEL,
// 관리자 지급
ADMIN
}

View File

@ -0,0 +1,41 @@
package kr.co.vividnext.sodalive.can.payment
import kr.co.vividnext.sodalive.can.charge.Charge
import kr.co.vividnext.sodalive.common.BaseEntity
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.EnumType
import javax.persistence.Enumerated
import javax.persistence.FetchType
import javax.persistence.OneToOne
@Entity
data class Payment(
@Enumerated(value = EnumType.STRING)
var status: PaymentStatus = PaymentStatus.REQUEST,
@Column(nullable = false)
@Enumerated(value = EnumType.STRING)
val paymentGateway: PaymentGateway
) : BaseEntity() {
@OneToOne(mappedBy = "payment", fetch = FetchType.LAZY)
var charge: Charge? = null
@Column(columnDefinition = "TEXT", nullable = true)
var receiptId: String? = null
var method: String? = null
var price: Double = 0.toDouble()
var locale: String? = null
}
enum class PaymentStatus {
// 결제요청
REQUEST,
// 결제완료
COMPLETE,
// 환불
RETURN
}

View File

@ -0,0 +1,5 @@
package kr.co.vividnext.sodalive.can.payment
enum class PaymentGateway {
PG, GOOGLE_IAP, APPLE_IAP
}

View File

@ -0,0 +1,8 @@
package kr.co.vividnext.sodalive.can.use
enum class CanUsage {
LIVE,
DONATION,
CHANGE_NICKNAME,
ORDER_CONTENT
}

View File

@ -0,0 +1,40 @@
package kr.co.vividnext.sodalive.can.use
import kr.co.vividnext.sodalive.common.BaseEntity
import kr.co.vividnext.sodalive.live.room.LiveRoom
import kr.co.vividnext.sodalive.member.Member
import javax.persistence.CascadeType
import javax.persistence.Entity
import javax.persistence.EnumType
import javax.persistence.Enumerated
import javax.persistence.FetchType
import javax.persistence.JoinColumn
import javax.persistence.ManyToOne
import javax.persistence.OneToMany
@Entity
data class UseCan(
@Enumerated(value = EnumType.STRING)
val canUsage: CanUsage,
val can: Int,
val rewardCan: Int,
var isRefund: Boolean = false
) : BaseEntity() {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = false)
var member: Member? = null
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "room_id", nullable = true)
var room: LiveRoom? = null
set(value) {
value?.useCan?.add(this)
field = value
}
@OneToMany(mappedBy = "useCan", cascade = [CascadeType.ALL])
val useCanCalculates: MutableList<UseCanCalculate> = mutableListOf()
}

View File

@ -0,0 +1,37 @@
package kr.co.vividnext.sodalive.can.use
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
import kr.co.vividnext.sodalive.common.BaseEntity
import javax.persistence.CascadeType
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.EnumType
import javax.persistence.Enumerated
import javax.persistence.FetchType
import javax.persistence.JoinColumn
import javax.persistence.ManyToOne
@Entity
data class UseCanCalculate(
val can: Int,
@Column(nullable = false)
@Enumerated(value = EnumType.STRING)
val paymentGateway: PaymentGateway,
@Column(nullable = false)
@Enumerated(value = EnumType.STRING)
var status: UseCanCalculateStatus
) : BaseEntity() {
@ManyToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL])
@JoinColumn(name = "use_can_id", nullable = false)
var useCan: UseCan? = null
set(value) {
value?.useCanCalculates?.add(this)
field = value
}
}
enum class UseCanCalculateStatus {
RECEIVED, CALCULATE_COMPLETE, REFUND
}

View File

@ -0,0 +1,22 @@
package kr.co.vividnext.sodalive.configs
import okhttp3.OkHttpClient
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.util.concurrent.TimeUnit
@Configuration
class OkHttpConfig {
@Bean("okHttpClient")
fun okHttpClient(): OkHttpClient {
return OkHttpClient()
.newBuilder().apply {
// 서버 연결을 최대 60초 수행
connectTimeout(60, TimeUnit.SECONDS)
// 서버 요청을 최대 60초 수행
writeTimeout(60, TimeUnit.SECONDS)
// 서버 응답을 최대 60초 기다림
readTimeout(60, TimeUnit.SECONDS)
}.build()
}
}

View File

@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.live.room package kr.co.vividnext.sodalive.live.room
import kr.co.vividnext.sodalive.can.use.UseCan
import kr.co.vividnext.sodalive.common.BaseEntity import kr.co.vividnext.sodalive.common.BaseEntity
import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.Member
import java.time.LocalDateTime import java.time.LocalDateTime
@ -9,6 +10,7 @@ import javax.persistence.EnumType
import javax.persistence.Enumerated import javax.persistence.Enumerated
import javax.persistence.FetchType import javax.persistence.FetchType
import javax.persistence.JoinColumn import javax.persistence.JoinColumn
import javax.persistence.OneToMany
import javax.persistence.OneToOne import javax.persistence.OneToOne
@Entity @Entity
@ -31,6 +33,9 @@ data class LiveRoom(
@JoinColumn(name = "member_id", nullable = false) @JoinColumn(name = "member_id", nullable = false)
var member: Member? = null var member: Member? = null
@OneToMany(mappedBy = "room", fetch = FetchType.LAZY)
var useCan: MutableList<UseCan> = mutableListOf()
var channelName: String? = null var channelName: String? = null
var isActive: Boolean = true var isActive: Boolean = true
} }

View File

@ -56,8 +56,8 @@ data class Member(
private var pgRewardCan: Int = 0 private var pgRewardCan: Int = 0
private var googleChargeCan: Int = 0 private var googleChargeCan: Int = 0
private var googleRewardCan: Int = 0 private var googleRewardCan: Int = 0
private var appleChargeCan: Int = 0 var appleChargeCan: Int = 0
private var appleRewardCan: Int = 0 var appleRewardCan: Int = 0
fun getChargeCan(container: String): Int { fun getChargeCan(container: String): Int {
return when (container) { return when (container) {

View File

@ -12,6 +12,10 @@ bootpay:
applicationId: ${BOOTPAY_APPLICATION_ID} applicationId: ${BOOTPAY_APPLICATION_ID}
privateKey: ${BOOTPAY_PRIVATE_KEY} privateKey: ${BOOTPAY_PRIVATE_KEY}
apple:
iapVerifyUrl: https://buy.itunes.apple.com/verifyReceipt
iapVerifySandboxUrl: https://sandbox.itunes.apple.com/verifyReceipt
cloud: cloud:
aws: aws:
credentials: credentials:

View File

@ -5,6 +5,10 @@ logging:
util: util:
EC2MetadataUtils: error EC2MetadataUtils: error
apple:
iapVerifyUrl: https://buy.itunes.apple.com/verifyReceipt
iapVerifySandboxUrl: https://sandbox.itunes.apple.com/verifyReceipt
cloud: cloud:
aws: aws:
credentials: credentials: