payverse 적용 #344
@@ -39,7 +39,10 @@ class AdminCalculateQueryRepository(private val queryFactory: JPAQueryFactory) {
|
|||||||
.innerJoin(useCan.room, liveRoom)
|
.innerJoin(useCan.room, liveRoom)
|
||||||
.innerJoin(liveRoom.member, member)
|
.innerJoin(liveRoom.member, member)
|
||||||
.leftJoin(creatorSettlementRatio)
|
.leftJoin(creatorSettlementRatio)
|
||||||
.on(member.id.eq(creatorSettlementRatio.member.id))
|
.on(
|
||||||
|
member.id.eq(creatorSettlementRatio.member.id)
|
||||||
|
.and(creatorSettlementRatio.deletedAt.isNull)
|
||||||
|
)
|
||||||
.where(
|
.where(
|
||||||
useCan.isRefund.isFalse
|
useCan.isRefund.isFalse
|
||||||
.and(useCan.createdAt.goe(startDate))
|
.and(useCan.createdAt.goe(startDate))
|
||||||
@@ -75,7 +78,10 @@ class AdminCalculateQueryRepository(private val queryFactory: JPAQueryFactory) {
|
|||||||
.innerJoin(order.audioContent, audioContent)
|
.innerJoin(order.audioContent, audioContent)
|
||||||
.innerJoin(audioContent.member, member)
|
.innerJoin(audioContent.member, member)
|
||||||
.leftJoin(creatorSettlementRatio)
|
.leftJoin(creatorSettlementRatio)
|
||||||
.on(member.id.eq(creatorSettlementRatio.member.id))
|
.on(
|
||||||
|
member.id.eq(creatorSettlementRatio.member.id)
|
||||||
|
.and(creatorSettlementRatio.deletedAt.isNull)
|
||||||
|
)
|
||||||
.where(
|
.where(
|
||||||
order.createdAt.goe(startDate)
|
order.createdAt.goe(startDate)
|
||||||
.and(order.createdAt.loe(endDate))
|
.and(order.createdAt.loe(endDate))
|
||||||
@@ -142,7 +148,10 @@ class AdminCalculateQueryRepository(private val queryFactory: JPAQueryFactory) {
|
|||||||
.innerJoin(order.audioContent, audioContent)
|
.innerJoin(order.audioContent, audioContent)
|
||||||
.innerJoin(audioContent.member, member)
|
.innerJoin(audioContent.member, member)
|
||||||
.leftJoin(creatorSettlementRatio)
|
.leftJoin(creatorSettlementRatio)
|
||||||
.on(member.id.eq(creatorSettlementRatio.member.id))
|
.on(
|
||||||
|
member.id.eq(creatorSettlementRatio.member.id)
|
||||||
|
.and(creatorSettlementRatio.deletedAt.isNull)
|
||||||
|
)
|
||||||
.where(order.isActive.isTrue)
|
.where(order.isActive.isTrue)
|
||||||
.groupBy(
|
.groupBy(
|
||||||
member.id,
|
member.id,
|
||||||
@@ -230,7 +239,10 @@ class AdminCalculateQueryRepository(private val queryFactory: JPAQueryFactory) {
|
|||||||
.innerJoin(useCan.communityPost, creatorCommunity)
|
.innerJoin(useCan.communityPost, creatorCommunity)
|
||||||
.innerJoin(creatorCommunity.member, member)
|
.innerJoin(creatorCommunity.member, member)
|
||||||
.leftJoin(creatorSettlementRatio)
|
.leftJoin(creatorSettlementRatio)
|
||||||
.on(member.id.eq(creatorSettlementRatio.member.id))
|
.on(
|
||||||
|
member.id.eq(creatorSettlementRatio.member.id)
|
||||||
|
.and(creatorSettlementRatio.deletedAt.isNull)
|
||||||
|
)
|
||||||
.where(
|
.where(
|
||||||
useCan.isRefund.isFalse
|
useCan.isRefund.isFalse
|
||||||
.and(useCan.canUsage.eq(CanUsage.PAID_COMMUNITY_POST))
|
.and(useCan.canUsage.eq(CanUsage.PAID_COMMUNITY_POST))
|
||||||
@@ -251,7 +263,10 @@ class AdminCalculateQueryRepository(private val queryFactory: JPAQueryFactory) {
|
|||||||
.innerJoin(useCan.room, liveRoom)
|
.innerJoin(useCan.room, liveRoom)
|
||||||
.innerJoin(liveRoom.member, member)
|
.innerJoin(liveRoom.member, member)
|
||||||
.leftJoin(creatorSettlementRatio)
|
.leftJoin(creatorSettlementRatio)
|
||||||
.on(member.id.eq(creatorSettlementRatio.member.id))
|
.on(
|
||||||
|
member.id.eq(creatorSettlementRatio.member.id)
|
||||||
|
.and(creatorSettlementRatio.deletedAt.isNull)
|
||||||
|
)
|
||||||
.where(
|
.where(
|
||||||
useCan.isRefund.isFalse
|
useCan.isRefund.isFalse
|
||||||
.and(useCan.createdAt.goe(startDate))
|
.and(useCan.createdAt.goe(startDate))
|
||||||
@@ -281,7 +296,10 @@ class AdminCalculateQueryRepository(private val queryFactory: JPAQueryFactory) {
|
|||||||
.innerJoin(useCan.room, liveRoom)
|
.innerJoin(useCan.room, liveRoom)
|
||||||
.innerJoin(liveRoom.member, member)
|
.innerJoin(liveRoom.member, member)
|
||||||
.leftJoin(creatorSettlementRatio)
|
.leftJoin(creatorSettlementRatio)
|
||||||
.on(member.id.eq(creatorSettlementRatio.member.id))
|
.on(
|
||||||
|
member.id.eq(creatorSettlementRatio.member.id)
|
||||||
|
.and(creatorSettlementRatio.deletedAt.isNull)
|
||||||
|
)
|
||||||
.where(
|
.where(
|
||||||
useCan.isRefund.isFalse
|
useCan.isRefund.isFalse
|
||||||
.and(useCan.createdAt.goe(startDate))
|
.and(useCan.createdAt.goe(startDate))
|
||||||
@@ -301,7 +319,10 @@ class AdminCalculateQueryRepository(private val queryFactory: JPAQueryFactory) {
|
|||||||
.innerJoin(order.audioContent, audioContent)
|
.innerJoin(order.audioContent, audioContent)
|
||||||
.innerJoin(audioContent.member, member)
|
.innerJoin(audioContent.member, member)
|
||||||
.leftJoin(creatorSettlementRatio)
|
.leftJoin(creatorSettlementRatio)
|
||||||
.on(member.id.eq(creatorSettlementRatio.member.id))
|
.on(
|
||||||
|
member.id.eq(creatorSettlementRatio.member.id)
|
||||||
|
.and(creatorSettlementRatio.deletedAt.isNull)
|
||||||
|
)
|
||||||
.where(
|
.where(
|
||||||
order.createdAt.goe(startDate)
|
order.createdAt.goe(startDate)
|
||||||
.and(order.createdAt.loe(endDate))
|
.and(order.createdAt.loe(endDate))
|
||||||
@@ -331,7 +352,10 @@ class AdminCalculateQueryRepository(private val queryFactory: JPAQueryFactory) {
|
|||||||
.innerJoin(order.audioContent, audioContent)
|
.innerJoin(order.audioContent, audioContent)
|
||||||
.innerJoin(audioContent.member, member)
|
.innerJoin(audioContent.member, member)
|
||||||
.leftJoin(creatorSettlementRatio)
|
.leftJoin(creatorSettlementRatio)
|
||||||
.on(member.id.eq(creatorSettlementRatio.member.id))
|
.on(
|
||||||
|
member.id.eq(creatorSettlementRatio.member.id)
|
||||||
|
.and(creatorSettlementRatio.deletedAt.isNull)
|
||||||
|
)
|
||||||
.where(
|
.where(
|
||||||
order.createdAt.goe(startDate)
|
order.createdAt.goe(startDate)
|
||||||
.and(order.createdAt.loe(endDate))
|
.and(order.createdAt.loe(endDate))
|
||||||
@@ -351,7 +375,10 @@ class AdminCalculateQueryRepository(private val queryFactory: JPAQueryFactory) {
|
|||||||
.innerJoin(useCan.communityPost, creatorCommunity)
|
.innerJoin(useCan.communityPost, creatorCommunity)
|
||||||
.innerJoin(creatorCommunity.member, member)
|
.innerJoin(creatorCommunity.member, member)
|
||||||
.leftJoin(creatorSettlementRatio)
|
.leftJoin(creatorSettlementRatio)
|
||||||
.on(member.id.eq(creatorSettlementRatio.member.id))
|
.on(
|
||||||
|
member.id.eq(creatorSettlementRatio.member.id)
|
||||||
|
.and(creatorSettlementRatio.deletedAt.isNull)
|
||||||
|
)
|
||||||
.where(
|
.where(
|
||||||
useCan.isRefund.isFalse
|
useCan.isRefund.isFalse
|
||||||
.and(useCan.canUsage.eq(CanUsage.PAID_COMMUNITY_POST))
|
.and(useCan.canUsage.eq(CanUsage.PAID_COMMUNITY_POST))
|
||||||
@@ -382,7 +409,10 @@ class AdminCalculateQueryRepository(private val queryFactory: JPAQueryFactory) {
|
|||||||
.innerJoin(useCan.communityPost, creatorCommunity)
|
.innerJoin(useCan.communityPost, creatorCommunity)
|
||||||
.innerJoin(creatorCommunity.member, member)
|
.innerJoin(creatorCommunity.member, member)
|
||||||
.leftJoin(creatorSettlementRatio)
|
.leftJoin(creatorSettlementRatio)
|
||||||
.on(member.id.eq(creatorSettlementRatio.member.id))
|
.on(
|
||||||
|
member.id.eq(creatorSettlementRatio.member.id)
|
||||||
|
.and(creatorSettlementRatio.deletedAt.isNull)
|
||||||
|
)
|
||||||
.where(
|
.where(
|
||||||
useCan.isRefund.isFalse
|
useCan.isRefund.isFalse
|
||||||
.and(useCan.canUsage.eq(CanUsage.PAID_COMMUNITY_POST))
|
.and(useCan.canUsage.eq(CanUsage.PAID_COMMUNITY_POST))
|
||||||
|
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.admin.calculate.ratio
|
|||||||
|
|
||||||
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 javax.persistence.Entity
|
import javax.persistence.Entity
|
||||||
import javax.persistence.FetchType
|
import javax.persistence.FetchType
|
||||||
import javax.persistence.JoinColumn
|
import javax.persistence.JoinColumn
|
||||||
@@ -9,12 +10,29 @@ import javax.persistence.OneToOne
|
|||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
data class CreatorSettlementRatio(
|
data class CreatorSettlementRatio(
|
||||||
val subsidy: Int,
|
var subsidy: Int,
|
||||||
val liveSettlementRatio: Int,
|
var liveSettlementRatio: Int,
|
||||||
val contentSettlementRatio: Int,
|
var contentSettlementRatio: Int,
|
||||||
val communitySettlementRatio: Int
|
var communitySettlementRatio: Int
|
||||||
) : BaseEntity() {
|
) : BaseEntity() {
|
||||||
@OneToOne(fetch = FetchType.LAZY)
|
@OneToOne(fetch = FetchType.LAZY)
|
||||||
@JoinColumn(name = "member_id", nullable = false)
|
@JoinColumn(name = "member_id", nullable = false)
|
||||||
var member: Member? = null
|
var member: Member? = null
|
||||||
|
|
||||||
|
var deletedAt: LocalDateTime? = null
|
||||||
|
|
||||||
|
fun softDelete() {
|
||||||
|
this.deletedAt = LocalDateTime.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun restore() {
|
||||||
|
this.deletedAt = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateValues(subsidy: Int, live: Int, content: Int, community: Int) {
|
||||||
|
this.subsidy = subsidy
|
||||||
|
this.liveSettlementRatio = live
|
||||||
|
this.contentSettlementRatio = content
|
||||||
|
this.communitySettlementRatio = community
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ import kr.co.vividnext.sodalive.common.ApiResponse
|
|||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
import org.springframework.security.access.prepost.PreAuthorize
|
import org.springframework.security.access.prepost.PreAuthorize
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable
|
||||||
import org.springframework.web.bind.annotation.PostMapping
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
import org.springframework.web.bind.annotation.RequestBody
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
import org.springframework.web.bind.annotation.RequestMapping
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
@@ -27,4 +28,14 @@ class CreatorSettlementRatioController(private val service: CreatorSettlementRat
|
|||||||
limit = pageable.pageSize.toLong()
|
limit = pageable.pageSize.toLong()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@PostMapping("/update")
|
||||||
|
fun updateCreatorSettlementRatio(
|
||||||
|
@RequestBody request: CreateCreatorSettlementRatioRequest
|
||||||
|
) = ApiResponse.ok(service.updateCreatorSettlementRatio(request))
|
||||||
|
|
||||||
|
@PostMapping("/delete/{memberId}")
|
||||||
|
fun deleteCreatorSettlementRatio(
|
||||||
|
@PathVariable memberId: Long
|
||||||
|
) = ApiResponse.ok(service.deleteCreatorSettlementRatio(memberId))
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,9 @@ import org.springframework.data.jpa.repository.JpaRepository
|
|||||||
|
|
||||||
interface CreatorSettlementRatioRepository :
|
interface CreatorSettlementRatioRepository :
|
||||||
JpaRepository<CreatorSettlementRatio, Long>,
|
JpaRepository<CreatorSettlementRatio, Long>,
|
||||||
CreatorSettlementRatioQueryRepository
|
CreatorSettlementRatioQueryRepository {
|
||||||
|
fun findByMemberId(memberId: Long): CreatorSettlementRatio?
|
||||||
|
}
|
||||||
|
|
||||||
interface CreatorSettlementRatioQueryRepository {
|
interface CreatorSettlementRatioQueryRepository {
|
||||||
fun getCreatorSettlementRatio(offset: Long, limit: Long): List<GetCreatorSettlementRatioItem>
|
fun getCreatorSettlementRatio(offset: Long, limit: Long): List<GetCreatorSettlementRatioItem>
|
||||||
@@ -21,6 +23,7 @@ class CreatorSettlementRatioQueryRepositoryImpl(
|
|||||||
return queryFactory
|
return queryFactory
|
||||||
.select(
|
.select(
|
||||||
QGetCreatorSettlementRatioItem(
|
QGetCreatorSettlementRatioItem(
|
||||||
|
member.id,
|
||||||
member.nickname,
|
member.nickname,
|
||||||
creatorSettlementRatio.subsidy,
|
creatorSettlementRatio.subsidy,
|
||||||
creatorSettlementRatio.liveSettlementRatio,
|
creatorSettlementRatio.liveSettlementRatio,
|
||||||
@@ -30,6 +33,7 @@ class CreatorSettlementRatioQueryRepositoryImpl(
|
|||||||
)
|
)
|
||||||
.from(creatorSettlementRatio)
|
.from(creatorSettlementRatio)
|
||||||
.innerJoin(creatorSettlementRatio.member, member)
|
.innerJoin(creatorSettlementRatio.member, member)
|
||||||
|
.where(creatorSettlementRatio.deletedAt.isNull)
|
||||||
.orderBy(creatorSettlementRatio.id.asc())
|
.orderBy(creatorSettlementRatio.id.asc())
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
@@ -40,6 +44,7 @@ class CreatorSettlementRatioQueryRepositoryImpl(
|
|||||||
return queryFactory
|
return queryFactory
|
||||||
.select(creatorSettlementRatio.id)
|
.select(creatorSettlementRatio.id)
|
||||||
.from(creatorSettlementRatio)
|
.from(creatorSettlementRatio)
|
||||||
|
.where(creatorSettlementRatio.deletedAt.isNull)
|
||||||
.fetch()
|
.fetch()
|
||||||
.size
|
.size
|
||||||
}
|
}
|
||||||
|
@@ -14,8 +14,6 @@ class CreatorSettlementRatioService(
|
|||||||
) {
|
) {
|
||||||
@Transactional
|
@Transactional
|
||||||
fun createCreatorSettlementRatio(request: CreateCreatorSettlementRatioRequest) {
|
fun createCreatorSettlementRatio(request: CreateCreatorSettlementRatioRequest) {
|
||||||
val creatorSettlementRatio = request.toEntity()
|
|
||||||
|
|
||||||
val creator = memberRepository.findByIdOrNull(request.memberId)
|
val creator = memberRepository.findByIdOrNull(request.memberId)
|
||||||
?: throw SodaException("잘못된 크리에이터 입니다.")
|
?: throw SodaException("잘못된 크리에이터 입니다.")
|
||||||
|
|
||||||
@@ -23,10 +21,52 @@ class CreatorSettlementRatioService(
|
|||||||
throw SodaException("잘못된 크리에이터 입니다.")
|
throw SodaException("잘못된 크리에이터 입니다.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val existing = repository.findByMemberId(request.memberId)
|
||||||
|
if (existing != null) {
|
||||||
|
// revive if soft-deleted, then update values
|
||||||
|
existing.restore()
|
||||||
|
existing.updateValues(
|
||||||
|
request.subsidy,
|
||||||
|
request.liveSettlementRatio,
|
||||||
|
request.contentSettlementRatio,
|
||||||
|
request.communitySettlementRatio
|
||||||
|
)
|
||||||
|
repository.save(existing)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val creatorSettlementRatio = request.toEntity()
|
||||||
creatorSettlementRatio.member = creator
|
creatorSettlementRatio.member = creator
|
||||||
repository.save(creatorSettlementRatio)
|
repository.save(creatorSettlementRatio)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun updateCreatorSettlementRatio(request: CreateCreatorSettlementRatioRequest) {
|
||||||
|
val creator = memberRepository.findByIdOrNull(request.memberId)
|
||||||
|
?: throw SodaException("잘못된 크리에이터 입니다.")
|
||||||
|
if (creator.role != MemberRole.CREATOR) {
|
||||||
|
throw SodaException("잘못된 크리에이터 입니다.")
|
||||||
|
}
|
||||||
|
val existing = repository.findByMemberId(request.memberId)
|
||||||
|
?: throw SodaException("해당 크리에이터의 정산 비율 설정이 없습니다.")
|
||||||
|
existing.restore()
|
||||||
|
existing.updateValues(
|
||||||
|
request.subsidy,
|
||||||
|
request.liveSettlementRatio,
|
||||||
|
request.contentSettlementRatio,
|
||||||
|
request.communitySettlementRatio
|
||||||
|
)
|
||||||
|
repository.save(existing)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun deleteCreatorSettlementRatio(memberId: Long) {
|
||||||
|
val existing = repository.findByMemberId(memberId)
|
||||||
|
?: throw SodaException("해당 크리에이터의 정산 비율 설정이 없습니다.")
|
||||||
|
existing.softDelete()
|
||||||
|
repository.save(existing)
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun getCreatorSettlementRatio(offset: Long, limit: Long): GetCreatorSettlementRatioResponse {
|
fun getCreatorSettlementRatio(offset: Long, limit: Long): GetCreatorSettlementRatioResponse {
|
||||||
val totalCount = repository.getCreatorSettlementRatioTotalCount()
|
val totalCount = repository.getCreatorSettlementRatioTotalCount()
|
||||||
|
@@ -8,6 +8,7 @@ data class GetCreatorSettlementRatioResponse(
|
|||||||
)
|
)
|
||||||
|
|
||||||
data class GetCreatorSettlementRatioItem @QueryProjection constructor(
|
data class GetCreatorSettlementRatioItem @QueryProjection constructor(
|
||||||
|
val memberId: Long,
|
||||||
val nickname: String,
|
val nickname: String,
|
||||||
val subsidy: Int,
|
val subsidy: Int,
|
||||||
val liveSettlementRatio: Int,
|
val liveSettlementRatio: Int,
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
package kr.co.vividnext.sodalive.admin.can
|
package kr.co.vividnext.sodalive.admin.can
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.can.CanResponse
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
import org.springframework.security.access.prepost.PreAuthorize
|
import org.springframework.security.access.prepost.PreAuthorize
|
||||||
import org.springframework.web.bind.annotation.DeleteMapping
|
import org.springframework.web.bind.annotation.DeleteMapping
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
import org.springframework.web.bind.annotation.PathVariable
|
import org.springframework.web.bind.annotation.PathVariable
|
||||||
import org.springframework.web.bind.annotation.PostMapping
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
import org.springframework.web.bind.annotation.RequestBody
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
@@ -13,6 +15,11 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
@RequestMapping("/admin/can")
|
@RequestMapping("/admin/can")
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
class AdminCanController(private val service: AdminCanService) {
|
class AdminCanController(private val service: AdminCanService) {
|
||||||
|
@GetMapping
|
||||||
|
fun getCans(): ApiResponse<List<CanResponse>> {
|
||||||
|
return ApiResponse.ok(service.getCans())
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
fun insertCan(@RequestBody request: AdminCanRequest) = ApiResponse.ok(service.saveCan(request))
|
fun insertCan(@RequestBody request: AdminCanRequest) = ApiResponse.ok(service.saveCan(request))
|
||||||
|
|
||||||
|
@@ -1,6 +1,38 @@
|
|||||||
package kr.co.vividnext.sodalive.admin.can
|
package kr.co.vividnext.sodalive.admin.can
|
||||||
|
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
import kr.co.vividnext.sodalive.can.Can
|
import kr.co.vividnext.sodalive.can.Can
|
||||||
|
import kr.co.vividnext.sodalive.can.CanResponse
|
||||||
|
import kr.co.vividnext.sodalive.can.CanStatus
|
||||||
|
import kr.co.vividnext.sodalive.can.QCan.can1
|
||||||
|
import kr.co.vividnext.sodalive.can.QCanResponse
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
interface AdminCanRepository : JpaRepository<Can, Long>
|
interface AdminCanRepository : JpaRepository<Can, Long>, AdminCanQueryRepository
|
||||||
|
|
||||||
|
interface AdminCanQueryRepository {
|
||||||
|
fun findAllByStatus(status: CanStatus): List<CanResponse>
|
||||||
|
}
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
class AdminCanQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : AdminCanQueryRepository {
|
||||||
|
override fun findAllByStatus(status: CanStatus): List<CanResponse> {
|
||||||
|
return queryFactory
|
||||||
|
.select(
|
||||||
|
QCanResponse(
|
||||||
|
can1.id,
|
||||||
|
can1.title,
|
||||||
|
can1.can,
|
||||||
|
can1.rewardCan,
|
||||||
|
can1.price.intValue(),
|
||||||
|
can1.currency,
|
||||||
|
can1.price.stringValue()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.from(can1)
|
||||||
|
.where(can1.status.eq(status))
|
||||||
|
.orderBy(can1.currency.asc(), can1.price.asc())
|
||||||
|
.fetch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -3,11 +3,13 @@ package kr.co.vividnext.sodalive.admin.can
|
|||||||
import kr.co.vividnext.sodalive.can.Can
|
import kr.co.vividnext.sodalive.can.Can
|
||||||
import kr.co.vividnext.sodalive.can.CanStatus
|
import kr.co.vividnext.sodalive.can.CanStatus
|
||||||
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
data class AdminCanRequest(
|
data class AdminCanRequest(
|
||||||
val can: Int,
|
val can: Int,
|
||||||
val rewardCan: Int,
|
val rewardCan: Int,
|
||||||
val price: Int
|
val price: BigDecimal,
|
||||||
|
val currency: String
|
||||||
) {
|
) {
|
||||||
fun toEntity(): Can {
|
fun toEntity(): Can {
|
||||||
var title = "${can.moneyFormat()} 캔"
|
var title = "${can.moneyFormat()} 캔"
|
||||||
@@ -20,6 +22,7 @@ data class AdminCanRequest(
|
|||||||
can = can,
|
can = can,
|
||||||
rewardCan = rewardCan,
|
rewardCan = rewardCan,
|
||||||
price = price,
|
price = price,
|
||||||
|
currency = currency,
|
||||||
status = CanStatus.SALE
|
status = CanStatus.SALE
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.admin.can
|
package kr.co.vividnext.sodalive.admin.can
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.admin.member.AdminMemberRepository
|
import kr.co.vividnext.sodalive.admin.member.AdminMemberRepository
|
||||||
|
import kr.co.vividnext.sodalive.can.CanResponse
|
||||||
import kr.co.vividnext.sodalive.can.CanStatus
|
import kr.co.vividnext.sodalive.can.CanStatus
|
||||||
import kr.co.vividnext.sodalive.can.charge.Charge
|
import kr.co.vividnext.sodalive.can.charge.Charge
|
||||||
import kr.co.vividnext.sodalive.can.charge.ChargeRepository
|
import kr.co.vividnext.sodalive.can.charge.ChargeRepository
|
||||||
@@ -20,6 +21,10 @@ class AdminCanService(
|
|||||||
private val chargeRepository: ChargeRepository,
|
private val chargeRepository: ChargeRepository,
|
||||||
private val memberRepository: AdminMemberRepository
|
private val memberRepository: AdminMemberRepository
|
||||||
) {
|
) {
|
||||||
|
fun getCans(): List<CanResponse> {
|
||||||
|
return repository.findAllByStatus(status = CanStatus.SALE)
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun saveCan(request: AdminCanRequest) {
|
fun saveCan(request: AdminCanRequest) {
|
||||||
repository.save(request.toEntity())
|
repository.save(request.toEntity())
|
||||||
|
@@ -20,16 +20,15 @@ class AdminChargeStatusService(val repository: AdminChargeStatusQueryRepository)
|
|||||||
.withZoneSameInstant(ZoneId.of("UTC"))
|
.withZoneSameInstant(ZoneId.of("UTC"))
|
||||||
.toLocalDateTime()
|
.toLocalDateTime()
|
||||||
|
|
||||||
var totalChargeAmount = 0
|
var totalChargeAmount = 0.toBigDecimal()
|
||||||
var totalChargeCount = 0L
|
var totalChargeCount = 0L
|
||||||
|
|
||||||
val chargeStatusList = repository.getChargeStatus(startDate, endDate)
|
val chargeStatusList = repository.getChargeStatus(startDate, endDate)
|
||||||
.asSequence()
|
|
||||||
.map {
|
.map {
|
||||||
val chargeAmount = if (it.paymentGateWay == PaymentGateway.PG) {
|
val chargeAmount = if (it.paymentGateWay == PaymentGateway.PG) {
|
||||||
it.pgChargeAmount
|
it.pgChargeAmount
|
||||||
} else {
|
} else {
|
||||||
it.appleChargeAmount.toInt()
|
it.appleChargeAmount
|
||||||
}
|
}
|
||||||
|
|
||||||
val chargeCount = it.chargeCount
|
val chargeCount = it.chargeCount
|
||||||
|
@@ -1,13 +1,14 @@
|
|||||||
package kr.co.vividnext.sodalive.admin.charge
|
package kr.co.vividnext.sodalive.admin.charge
|
||||||
|
|
||||||
import com.querydsl.core.annotations.QueryProjection
|
import com.querydsl.core.annotations.QueryProjection
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
data class GetChargeStatusDetailQueryDto @QueryProjection constructor(
|
data class GetChargeStatusDetailQueryDto @QueryProjection constructor(
|
||||||
val memberId: Long,
|
val memberId: Long,
|
||||||
val nickname: String,
|
val nickname: String,
|
||||||
val method: String,
|
val method: String,
|
||||||
val appleChargeAmount: Double,
|
val appleChargeAmount: BigDecimal,
|
||||||
val pgChargeAmount: Int,
|
val pgChargeAmount: BigDecimal,
|
||||||
val locale: String,
|
val locale: String,
|
||||||
val datetime: String
|
val datetime: String
|
||||||
)
|
)
|
||||||
|
@@ -2,11 +2,12 @@ package kr.co.vividnext.sodalive.admin.charge
|
|||||||
|
|
||||||
import com.querydsl.core.annotations.QueryProjection
|
import com.querydsl.core.annotations.QueryProjection
|
||||||
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
|
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
data class GetChargeStatusQueryDto @QueryProjection constructor(
|
data class GetChargeStatusQueryDto @QueryProjection constructor(
|
||||||
val date: String,
|
val date: String,
|
||||||
val appleChargeAmount: Double,
|
val appleChargeAmount: BigDecimal,
|
||||||
val pgChargeAmount: Int,
|
val pgChargeAmount: BigDecimal,
|
||||||
val chargeCount: Long,
|
val chargeCount: Long,
|
||||||
val paymentGateWay: PaymentGateway
|
val paymentGateWay: PaymentGateway
|
||||||
)
|
)
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
package kr.co.vividnext.sodalive.admin.charge
|
package kr.co.vividnext.sodalive.admin.charge
|
||||||
|
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
data class GetChargeStatusResponse(
|
data class GetChargeStatusResponse(
|
||||||
val date: String,
|
val date: String,
|
||||||
val chargeAmount: Int,
|
val chargeAmount: BigDecimal,
|
||||||
val chargeCount: Long,
|
val chargeCount: Long,
|
||||||
val pg: String
|
val pg: String
|
||||||
)
|
)
|
||||||
|
@@ -3,7 +3,6 @@ package kr.co.vividnext.sodalive.admin.statistics.ad
|
|||||||
import com.querydsl.core.types.dsl.CaseBuilder
|
import com.querydsl.core.types.dsl.CaseBuilder
|
||||||
import com.querydsl.core.types.dsl.DateTimePath
|
import com.querydsl.core.types.dsl.DateTimePath
|
||||||
import com.querydsl.core.types.dsl.Expressions
|
import com.querydsl.core.types.dsl.Expressions
|
||||||
import com.querydsl.core.types.dsl.NumberExpression
|
|
||||||
import com.querydsl.core.types.dsl.StringTemplate
|
import com.querydsl.core.types.dsl.StringTemplate
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
import kr.co.vividnext.sodalive.marketing.AdTrackingHistoryType
|
import kr.co.vividnext.sodalive.marketing.AdTrackingHistoryType
|
||||||
@@ -67,7 +66,7 @@ class AdminAdStatisticsRepository(private val queryFactory: JPAQueryFactory) {
|
|||||||
val firstPaymentTotalAmount = CaseBuilder()
|
val firstPaymentTotalAmount = CaseBuilder()
|
||||||
.`when`(adTrackingHistory.type.eq(AdTrackingHistoryType.FIRST_PAYMENT))
|
.`when`(adTrackingHistory.type.eq(AdTrackingHistoryType.FIRST_PAYMENT))
|
||||||
.then(adTrackingHistory.price)
|
.then(adTrackingHistory.price)
|
||||||
.otherwise(Expressions.constant(0.0))
|
.otherwise(0.toBigDecimal())
|
||||||
.sum()
|
.sum()
|
||||||
|
|
||||||
val repeatPaymentCount = CaseBuilder()
|
val repeatPaymentCount = CaseBuilder()
|
||||||
@@ -79,7 +78,7 @@ class AdminAdStatisticsRepository(private val queryFactory: JPAQueryFactory) {
|
|||||||
val repeatPaymentTotalAmount = CaseBuilder()
|
val repeatPaymentTotalAmount = CaseBuilder()
|
||||||
.`when`(adTrackingHistory.type.eq(AdTrackingHistoryType.REPEAT_PAYMENT))
|
.`when`(adTrackingHistory.type.eq(AdTrackingHistoryType.REPEAT_PAYMENT))
|
||||||
.then(adTrackingHistory.price)
|
.then(adTrackingHistory.price)
|
||||||
.otherwise(Expressions.constant(0.0))
|
.otherwise(0.toBigDecimal())
|
||||||
.sum()
|
.sum()
|
||||||
|
|
||||||
val allPaymentCount = CaseBuilder()
|
val allPaymentCount = CaseBuilder()
|
||||||
@@ -97,7 +96,7 @@ class AdminAdStatisticsRepository(private val queryFactory: JPAQueryFactory) {
|
|||||||
.or(adTrackingHistory.type.eq(AdTrackingHistoryType.REPEAT_PAYMENT))
|
.or(adTrackingHistory.type.eq(AdTrackingHistoryType.REPEAT_PAYMENT))
|
||||||
)
|
)
|
||||||
.then(adTrackingHistory.price)
|
.then(adTrackingHistory.price)
|
||||||
.otherwise(Expressions.constant(0.0))
|
.otherwise(0.toBigDecimal())
|
||||||
.sum()
|
.sum()
|
||||||
|
|
||||||
return queryFactory
|
return queryFactory
|
||||||
@@ -111,11 +110,11 @@ class AdminAdStatisticsRepository(private val queryFactory: JPAQueryFactory) {
|
|||||||
loginCount,
|
loginCount,
|
||||||
signUpCount,
|
signUpCount,
|
||||||
firstPaymentCount,
|
firstPaymentCount,
|
||||||
roundedValueDecimalPlaces2(firstPaymentTotalAmount),
|
firstPaymentTotalAmount,
|
||||||
repeatPaymentCount,
|
repeatPaymentCount,
|
||||||
roundedValueDecimalPlaces2(repeatPaymentTotalAmount),
|
repeatPaymentTotalAmount,
|
||||||
allPaymentCount,
|
allPaymentCount,
|
||||||
roundedValueDecimalPlaces2(allPaymentTotalAmount)
|
allPaymentTotalAmount
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(adTrackingHistory)
|
.from(adTrackingHistory)
|
||||||
@@ -148,13 +147,4 @@ class AdminAdStatisticsRepository(private val queryFactory: JPAQueryFactory) {
|
|||||||
"%Y-%m-%d"
|
"%Y-%m-%d"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun roundedValueDecimalPlaces2(valueExpression: NumberExpression<Double>): NumberExpression<Double> {
|
|
||||||
return Expressions.numberTemplate(
|
|
||||||
Double::class.java,
|
|
||||||
"ROUND({0}, {1})",
|
|
||||||
valueExpression,
|
|
||||||
2
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.admin.statistics.ad
|
package kr.co.vividnext.sodalive.admin.statistics.ad
|
||||||
|
|
||||||
import com.querydsl.core.annotations.QueryProjection
|
import com.querydsl.core.annotations.QueryProjection
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
data class GetAdminAdStatisticsResponse(
|
data class GetAdminAdStatisticsResponse(
|
||||||
val totalCount: Int,
|
val totalCount: Int,
|
||||||
@@ -16,9 +17,9 @@ data class GetAdminAdStatisticsItem @QueryProjection constructor(
|
|||||||
val loginCount: Int,
|
val loginCount: Int,
|
||||||
val signUpCount: Int,
|
val signUpCount: Int,
|
||||||
val firstPaymentCount: Int,
|
val firstPaymentCount: Int,
|
||||||
val firstPaymentTotalAmount: Double,
|
val firstPaymentTotalAmount: BigDecimal,
|
||||||
val repeatPaymentCount: Int,
|
val repeatPaymentCount: Int,
|
||||||
val repeatPaymentTotalAmount: Double,
|
val repeatPaymentTotalAmount: BigDecimal,
|
||||||
val allPaymentCount: Int,
|
val allPaymentCount: Int,
|
||||||
val allPaymentTotalAmount: Double
|
val allPaymentTotalAmount: BigDecimal
|
||||||
)
|
)
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
package kr.co.vividnext.sodalive.can
|
package kr.co.vividnext.sodalive.can
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import javax.persistence.Column
|
||||||
import javax.persistence.Entity
|
import javax.persistence.Entity
|
||||||
import javax.persistence.EnumType
|
import javax.persistence.EnumType
|
||||||
import javax.persistence.Enumerated
|
import javax.persistence.Enumerated
|
||||||
@@ -10,7 +12,10 @@ data class Can(
|
|||||||
var title: String,
|
var title: String,
|
||||||
var can: Int,
|
var can: Int,
|
||||||
var rewardCan: Int,
|
var rewardCan: Int,
|
||||||
var price: Int,
|
@Column(precision = 10, scale = 4, nullable = false)
|
||||||
|
var price: BigDecimal,
|
||||||
|
@Column(length = 3, nullable = false, columnDefinition = "CHAR(3)")
|
||||||
|
var currency: String,
|
||||||
@Enumerated(value = EnumType.STRING)
|
@Enumerated(value = EnumType.STRING)
|
||||||
var status: CanStatus
|
var status: CanStatus
|
||||||
) : BaseEntity()
|
) : BaseEntity()
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.can
|
package kr.co.vividnext.sodalive.can
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
|
import kr.co.vividnext.sodalive.common.GeoCountry
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
@@ -9,13 +10,15 @@ import org.springframework.web.bind.annotation.GetMapping
|
|||||||
import org.springframework.web.bind.annotation.RequestMapping
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
import org.springframework.web.bind.annotation.RequestParam
|
import org.springframework.web.bind.annotation.RequestParam
|
||||||
import org.springframework.web.bind.annotation.RestController
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
import javax.servlet.http.HttpServletRequest
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/can")
|
@RequestMapping("/can")
|
||||||
class CanController(private val service: CanService) {
|
class CanController(private val service: CanService) {
|
||||||
@GetMapping
|
@GetMapping
|
||||||
fun getCans(): ApiResponse<List<CanResponse>> {
|
fun getCans(request: HttpServletRequest): ApiResponse<List<CanResponse>> {
|
||||||
return ApiResponse.ok(service.getCans())
|
val geoCountry = request.getAttribute("geoCountry") as? GeoCountry ?: GeoCountry.OTHER
|
||||||
|
return ApiResponse.ok(service.getCans(geoCountry))
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/status")
|
@GetMapping("/status")
|
||||||
|
@@ -23,7 +23,7 @@ import org.springframework.stereotype.Repository
|
|||||||
interface CanRepository : JpaRepository<Can, Long>, CanQueryRepository
|
interface CanRepository : JpaRepository<Can, Long>, CanQueryRepository
|
||||||
|
|
||||||
interface CanQueryRepository {
|
interface CanQueryRepository {
|
||||||
fun findAllByStatus(status: CanStatus): List<CanResponse>
|
fun findAllByStatusAndCurrency(status: CanStatus, currency: String): List<CanResponse>
|
||||||
fun getCanUseStatus(member: Member, pageable: Pageable): List<UseCan>
|
fun getCanUseStatus(member: Member, pageable: Pageable): List<UseCan>
|
||||||
fun getCanChargeStatus(member: Member, pageable: Pageable, container: String): List<Charge>
|
fun getCanChargeStatus(member: Member, pageable: Pageable, container: String): List<Charge>
|
||||||
fun isExistPaidLiveRoom(memberId: Long, roomId: Long): UseCan?
|
fun isExistPaidLiveRoom(memberId: Long, roomId: Long): UseCan?
|
||||||
@@ -32,7 +32,7 @@ interface CanQueryRepository {
|
|||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
class CanQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : CanQueryRepository {
|
class CanQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : CanQueryRepository {
|
||||||
override fun findAllByStatus(status: CanStatus): List<CanResponse> {
|
override fun findAllByStatusAndCurrency(status: CanStatus, currency: String): List<CanResponse> {
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(
|
.select(
|
||||||
QCanResponse(
|
QCanResponse(
|
||||||
@@ -40,11 +40,16 @@ class CanQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : CanQue
|
|||||||
can1.title,
|
can1.title,
|
||||||
can1.can,
|
can1.can,
|
||||||
can1.rewardCan,
|
can1.rewardCan,
|
||||||
can1.price
|
can1.price.intValue(),
|
||||||
|
can1.currency,
|
||||||
|
can1.price.stringValue()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(can1)
|
.from(can1)
|
||||||
.where(can1.status.eq(status))
|
.where(
|
||||||
|
can1.status.eq(status),
|
||||||
|
can1.currency.eq(currency)
|
||||||
|
)
|
||||||
.orderBy(can1.can.asc())
|
.orderBy(can1.can.asc())
|
||||||
.fetch()
|
.fetch()
|
||||||
}
|
}
|
||||||
@@ -64,11 +69,13 @@ class CanQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : CanQue
|
|||||||
val chargeStatusCondition = when (container) {
|
val chargeStatusCondition = when (container) {
|
||||||
"aos" -> {
|
"aos" -> {
|
||||||
charge.payment.paymentGateway.eq(PaymentGateway.PG)
|
charge.payment.paymentGateway.eq(PaymentGateway.PG)
|
||||||
|
.or(charge.payment.paymentGateway.eq(PaymentGateway.PAYVERSE))
|
||||||
.or(charge.payment.paymentGateway.eq(PaymentGateway.GOOGLE_IAP))
|
.or(charge.payment.paymentGateway.eq(PaymentGateway.GOOGLE_IAP))
|
||||||
}
|
}
|
||||||
|
|
||||||
"ios" -> {
|
"ios" -> {
|
||||||
charge.payment.paymentGateway.eq(PaymentGateway.PG)
|
charge.payment.paymentGateway.eq(PaymentGateway.PG)
|
||||||
|
.or(charge.payment.paymentGateway.eq(PaymentGateway.PAYVERSE))
|
||||||
.or(charge.payment.paymentGateway.eq(PaymentGateway.APPLE_IAP))
|
.or(charge.payment.paymentGateway.eq(PaymentGateway.APPLE_IAP))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,5 +7,7 @@ data class CanResponse @QueryProjection constructor(
|
|||||||
val title: String,
|
val title: String,
|
||||||
val can: Int,
|
val can: Int,
|
||||||
val rewardCan: Int,
|
val rewardCan: Int,
|
||||||
val price: Int
|
val price: Int,
|
||||||
|
val currency: String,
|
||||||
|
val priceStr: String
|
||||||
)
|
)
|
||||||
|
@@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.can
|
|||||||
import kr.co.vividnext.sodalive.can.charge.ChargeStatus
|
import kr.co.vividnext.sodalive.can.charge.ChargeStatus
|
||||||
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
|
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
|
||||||
import kr.co.vividnext.sodalive.can.use.CanUsage
|
import kr.co.vividnext.sodalive.can.use.CanUsage
|
||||||
|
import kr.co.vividnext.sodalive.common.GeoCountry
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
@@ -11,8 +12,12 @@ import java.time.format.DateTimeFormatter
|
|||||||
|
|
||||||
@Service
|
@Service
|
||||||
class CanService(private val repository: CanRepository) {
|
class CanService(private val repository: CanRepository) {
|
||||||
fun getCans(): List<CanResponse> {
|
fun getCans(geoCountry: GeoCountry): List<CanResponse> {
|
||||||
return repository.findAllByStatus(status = CanStatus.SALE)
|
val currency = when (geoCountry) {
|
||||||
|
GeoCountry.KR -> "KRW"
|
||||||
|
else -> "USD"
|
||||||
|
}
|
||||||
|
return repository.findAllByStatusAndCurrency(status = CanStatus.SALE, currency = currency)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCanStatus(member: Member, container: String): GetCanStatusResponse {
|
fun getCanStatus(member: Member, container: String): GetCanStatusResponse {
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
package kr.co.vividnext.sodalive.can.charge
|
package kr.co.vividnext.sodalive.can.charge
|
||||||
|
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
data class ChargeCompleteResponse(
|
data class ChargeCompleteResponse(
|
||||||
val price: Double,
|
val price: BigDecimal,
|
||||||
val currencyCode: String,
|
val currencyCode: String,
|
||||||
val isFirstCharged: Boolean
|
val isFirstCharged: Boolean
|
||||||
)
|
)
|
||||||
|
@@ -6,20 +6,77 @@ import kr.co.vividnext.sodalive.common.SodaException
|
|||||||
import kr.co.vividnext.sodalive.marketing.AdTrackingHistoryType
|
import kr.co.vividnext.sodalive.marketing.AdTrackingHistoryType
|
||||||
import kr.co.vividnext.sodalive.marketing.AdTrackingService
|
import kr.co.vividnext.sodalive.marketing.AdTrackingService
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import org.springframework.beans.factory.annotation.Value
|
||||||
|
import org.springframework.http.HttpStatus
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||||
import org.springframework.web.bind.annotation.PostMapping
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
import org.springframework.web.bind.annotation.RequestBody
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
import org.springframework.web.bind.annotation.RequestMapping
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
import org.springframework.web.bind.annotation.RestController
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
import org.springframework.web.server.ResponseStatusException
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
import javax.servlet.http.HttpServletRequest
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/charge")
|
@RequestMapping("/charge")
|
||||||
class ChargeController(
|
class ChargeController(
|
||||||
private val service: ChargeService,
|
private val service: ChargeService,
|
||||||
private val trackingService: AdTrackingService
|
private val trackingService: AdTrackingService,
|
||||||
|
|
||||||
|
@Value("\${payverse.inbound-ip}")
|
||||||
|
private val payverseInboundIp: String
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@PostMapping("/payverse")
|
||||||
|
fun payverseCharge(
|
||||||
|
@RequestBody request: PayverseChargeRequest,
|
||||||
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
|
) = run {
|
||||||
|
if (member == null) {
|
||||||
|
throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiResponse.ok(service.payverseCharge(member, request))
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/payverse/verify")
|
||||||
|
fun payverseVerify(
|
||||||
|
@RequestBody verifyRequest: PayverseVerifyRequest,
|
||||||
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
|
) = run {
|
||||||
|
if (member == null) {
|
||||||
|
throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
}
|
||||||
|
|
||||||
|
val response = service.payverseVerify(memberId = member.id!!, verifyRequest)
|
||||||
|
trackingCharge(member, response)
|
||||||
|
ApiResponse.ok(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Payverse Webhook 엔드포인트 (payverseVerify 아래)
|
||||||
|
@PostMapping("/payverse/webhook")
|
||||||
|
fun payverseWebhook(
|
||||||
|
@RequestBody request: PayverseWebhookRequest,
|
||||||
|
servletRequest: HttpServletRequest
|
||||||
|
): PayverseWebhookResponse {
|
||||||
|
val header = servletRequest.getHeader("X-Forwarded-For")
|
||||||
|
val remoteIp = if (header.isNullOrEmpty()) {
|
||||||
|
servletRequest.remoteAddr
|
||||||
|
} else {
|
||||||
|
header.split(",")[0].trim() // 첫 번째 값이 클라이언트 IP
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remoteIp != payverseInboundIp) {
|
||||||
|
throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||||
|
}
|
||||||
|
|
||||||
|
val success = service.payverseWebhook(request)
|
||||||
|
if (!success) {
|
||||||
|
throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||||
|
}
|
||||||
|
return PayverseWebhookResponse(receiveResult = "SUCCESS")
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
fun charge(
|
fun charge(
|
||||||
@RequestBody chargeRequest: ChargeRequest,
|
@RequestBody chargeRequest: ChargeRequest,
|
||||||
@@ -111,8 +168,7 @@ class ChargeController(
|
|||||||
memberId = member.id!!,
|
memberId = member.id!!,
|
||||||
chargeId = chargeId,
|
chargeId = chargeId,
|
||||||
productId = request.productId,
|
productId = request.productId,
|
||||||
purchaseToken = request.purchaseToken,
|
purchaseToken = request.purchaseToken
|
||||||
paymentGateway = request.paymentGateway
|
|
||||||
)
|
)
|
||||||
|
|
||||||
trackingCharge(member, response)
|
trackingCharge(member, response)
|
||||||
|
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.can.charge
|
|||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
|
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
data class ChargeRequest(val canId: Long, val paymentGateway: PaymentGateway)
|
data class ChargeRequest(val canId: Long, val paymentGateway: PaymentGateway)
|
||||||
|
|
||||||
@@ -20,14 +21,14 @@ data class VerifyResult(
|
|||||||
val method: String,
|
val method: String,
|
||||||
val pg: String,
|
val pg: String,
|
||||||
val status: Int,
|
val status: Int,
|
||||||
val price: Int
|
val price: BigDecimal
|
||||||
)
|
)
|
||||||
|
|
||||||
data class AppleChargeRequest(
|
data class AppleChargeRequest(
|
||||||
val title: String,
|
val title: String,
|
||||||
val chargeCan: Int,
|
val chargeCan: Int,
|
||||||
val paymentGateway: PaymentGateway,
|
val paymentGateway: PaymentGateway,
|
||||||
var price: Double? = null,
|
var price: BigDecimal? = null,
|
||||||
var locale: String? = null
|
var locale: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -38,9 +39,53 @@ data class AppleVerifyResponse(val status: Int)
|
|||||||
data class GoogleChargeRequest(
|
data class GoogleChargeRequest(
|
||||||
val title: String,
|
val title: String,
|
||||||
val chargeCan: Int,
|
val chargeCan: Int,
|
||||||
val price: Double,
|
val price: BigDecimal,
|
||||||
val currencyCode: String,
|
val currencyCode: String,
|
||||||
val productId: String,
|
val productId: String,
|
||||||
val purchaseToken: String,
|
val purchaseToken: String,
|
||||||
val paymentGateway: PaymentGateway
|
val paymentGateway: PaymentGateway
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class PayverseChargeRequest(
|
||||||
|
val canId: Long
|
||||||
|
)
|
||||||
|
|
||||||
|
data class PayverseChargeResponse(
|
||||||
|
val chargeId: Long,
|
||||||
|
val payloadJson: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class PayverseVerifyRequest(
|
||||||
|
val transactionId: String,
|
||||||
|
val orderId: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class PayverseVerifyResponse(
|
||||||
|
val resultStatus: String,
|
||||||
|
val tid: String,
|
||||||
|
val schemeGroup: String,
|
||||||
|
val schemeCode: String,
|
||||||
|
val transactionType: String,
|
||||||
|
val transactionStatus: String,
|
||||||
|
val transactionMessage: String,
|
||||||
|
val orderId: String,
|
||||||
|
val customerId: String,
|
||||||
|
val requestCurrency: String,
|
||||||
|
val requestAmount: BigDecimal
|
||||||
|
)
|
||||||
|
|
||||||
|
data class PayverseWebhookRequest(
|
||||||
|
val type: String,
|
||||||
|
val mid: String,
|
||||||
|
val tid: String,
|
||||||
|
val schemeGroup: String,
|
||||||
|
val schemeCode: String,
|
||||||
|
val orderId: String,
|
||||||
|
val requestCurrency: String,
|
||||||
|
val requestAmount: BigDecimal,
|
||||||
|
val resultStatus: String,
|
||||||
|
val approvalDay: String,
|
||||||
|
val sign: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class PayverseWebhookResponse(val receiveResult: String)
|
||||||
|
@@ -22,6 +22,7 @@ import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
|||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import org.apache.commons.codec.digest.DigestUtils
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.context.ApplicationEventPublisher
|
import org.springframework.context.ApplicationEventPublisher
|
||||||
@@ -34,6 +35,7 @@ import org.springframework.transaction.annotation.Transactional
|
|||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.math.RoundingMode
|
import java.math.RoundingMode
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
@@ -63,9 +65,112 @@ class ChargeService(
|
|||||||
@Value("\${apple.iap-verify-sandbox-url}")
|
@Value("\${apple.iap-verify-sandbox-url}")
|
||||||
private val appleInAppVerifySandBoxUrl: String,
|
private val appleInAppVerifySandBoxUrl: String,
|
||||||
@Value("\${apple.iap-verify-url}")
|
@Value("\${apple.iap-verify-url}")
|
||||||
private val appleInAppVerifyUrl: String
|
private val appleInAppVerifyUrl: String,
|
||||||
|
|
||||||
|
@Value("\${payverse.mid}")
|
||||||
|
private val payverseMid: String,
|
||||||
|
@Value("\${payverse.client-key}")
|
||||||
|
private val payverseClientKey: String,
|
||||||
|
@Value("\${payverse.secret-key}")
|
||||||
|
private val payverseSecretKey: String,
|
||||||
|
|
||||||
|
@Value("\${payverse.usd-mid}")
|
||||||
|
private val payverseUsdMid: String,
|
||||||
|
@Value("\${payverse.usd-client-key}")
|
||||||
|
private val payverseUsdClientKey: String,
|
||||||
|
@Value("\${payverse.usd-secret-key}")
|
||||||
|
private val payverseUsdSecretKey: String,
|
||||||
|
|
||||||
|
@Value("\${payverse.host}")
|
||||||
|
private val payverseHost: String,
|
||||||
|
|
||||||
|
@Value("\${server.env}")
|
||||||
|
private val serverEnv: String
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun payverseWebhook(request: PayverseWebhookRequest): Boolean {
|
||||||
|
val chargeId = request.orderId.toLongOrNull() ?: return false
|
||||||
|
val charge = chargeRepository.findByIdOrNull(chargeId) ?: return false
|
||||||
|
|
||||||
|
// 결제수단 확인
|
||||||
|
if (charge.payment?.paymentGateway != PaymentGateway.PAYVERSE) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 결제 상태 분기 처리
|
||||||
|
return when (charge.payment?.status) {
|
||||||
|
PaymentStatus.REQUEST -> {
|
||||||
|
// 성공 조건 검증
|
||||||
|
val mid = if (request.requestCurrency == "KRW") {
|
||||||
|
payverseMid
|
||||||
|
} else {
|
||||||
|
payverseUsdMid
|
||||||
|
}
|
||||||
|
val expectedSign = DigestUtils.sha512Hex(
|
||||||
|
String.format(
|
||||||
|
"||%s||%s||%s||%s||%s||",
|
||||||
|
if (request.requestCurrency == "KRW") {
|
||||||
|
payverseSecretKey
|
||||||
|
} else {
|
||||||
|
payverseUsdSecretKey
|
||||||
|
},
|
||||||
|
mid,
|
||||||
|
request.orderId,
|
||||||
|
request.requestAmount,
|
||||||
|
request.approvalDay
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val isAmountMatch = request.requestAmount.compareTo(
|
||||||
|
charge.payment!!.price
|
||||||
|
) == 0
|
||||||
|
|
||||||
|
val isSuccess = request.resultStatus == "SUCCESS" &&
|
||||||
|
request.mid == mid &&
|
||||||
|
request.orderId.toLongOrNull() == charge.id &&
|
||||||
|
isAmountMatch &&
|
||||||
|
request.sign == expectedSign
|
||||||
|
|
||||||
|
if (isSuccess) {
|
||||||
|
// payverseVerify의 226~246 라인과 동일 처리
|
||||||
|
charge.payment?.receiptId = request.tid
|
||||||
|
val mappedMethod = if (request.schemeGroup == "PVKR") {
|
||||||
|
mapPayverseSchemeToMethodByCode(request.schemeCode)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
charge.payment?.method = mappedMethod ?: request.schemeCode
|
||||||
|
charge.payment?.status = PaymentStatus.COMPLETE
|
||||||
|
charge.payment?.locale = request.requestCurrency
|
||||||
|
|
||||||
|
val member = charge.member!!
|
||||||
|
member.charge(charge.chargeCan, charge.rewardCan, "pg")
|
||||||
|
|
||||||
|
applicationEventPublisher.publishEvent(
|
||||||
|
ChargeSpringEvent(
|
||||||
|
chargeId = charge.id!!,
|
||||||
|
memberId = member.id!!
|
||||||
|
)
|
||||||
|
)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PaymentStatus.COMPLETE -> {
|
||||||
|
// 이미 결제가 완료된 경우 성공 처리(idempotent)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
// 그 외 상태는 404
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun chargeByCoupon(couponNumber: String, member: Member): String {
|
fun chargeByCoupon(couponNumber: String, member: Member): String {
|
||||||
val canCouponNumber = couponNumberRepository.findByCouponNumber(couponNumber = couponNumber)
|
val canCouponNumber = couponNumberRepository.findByCouponNumber(couponNumber = couponNumber)
|
||||||
@@ -126,6 +231,177 @@ class ChargeService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun payverseCharge(member: Member, request: PayverseChargeRequest): PayverseChargeResponse {
|
||||||
|
val can = canRepository.findByIdOrNull(request.canId)
|
||||||
|
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.")
|
||||||
|
|
||||||
|
val requestCurrency = can.currency
|
||||||
|
val isKrw = requestCurrency == "KRW"
|
||||||
|
val mid = if (isKrw) {
|
||||||
|
payverseMid
|
||||||
|
} else {
|
||||||
|
payverseUsdMid
|
||||||
|
}
|
||||||
|
val clientKey = if (isKrw) {
|
||||||
|
payverseClientKey
|
||||||
|
} else {
|
||||||
|
payverseUsdClientKey
|
||||||
|
}
|
||||||
|
val secretKey = if (isKrw) {
|
||||||
|
payverseSecretKey
|
||||||
|
} else {
|
||||||
|
payverseUsdSecretKey
|
||||||
|
}
|
||||||
|
|
||||||
|
val charge = Charge(can.can, can.rewardCan)
|
||||||
|
charge.title = can.title
|
||||||
|
charge.member = member
|
||||||
|
charge.can = can
|
||||||
|
|
||||||
|
val payment = Payment(paymentGateway = PaymentGateway.PAYVERSE)
|
||||||
|
payment.price = can.price
|
||||||
|
charge.payment = payment
|
||||||
|
|
||||||
|
val savedCharge = chargeRepository.save(charge)
|
||||||
|
|
||||||
|
val chargeId = savedCharge.id!!
|
||||||
|
val amount = BigDecimal(
|
||||||
|
savedCharge.payment!!.price
|
||||||
|
.setScale(4, RoundingMode.HALF_UP)
|
||||||
|
.stripTrailingZeros()
|
||||||
|
.toPlainString()
|
||||||
|
)
|
||||||
|
val reqDate = savedCharge.createdAt!!.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
|
||||||
|
val sign = DigestUtils.sha512Hex(
|
||||||
|
String.format(
|
||||||
|
"||%s||%s||%s||%s||%s||",
|
||||||
|
secretKey,
|
||||||
|
mid,
|
||||||
|
chargeId,
|
||||||
|
amount,
|
||||||
|
reqDate
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val customerId = "${serverEnv}_user_${member.id!!}"
|
||||||
|
|
||||||
|
val payload = linkedMapOf(
|
||||||
|
"mid" to mid,
|
||||||
|
"clientKey" to clientKey,
|
||||||
|
"orderId" to chargeId.toString(),
|
||||||
|
"customerId" to customerId,
|
||||||
|
"productName" to can.title,
|
||||||
|
"requestCurrency" to requestCurrency,
|
||||||
|
"requestAmount" to amount,
|
||||||
|
"reqDate" to reqDate,
|
||||||
|
"sign" to sign
|
||||||
|
)
|
||||||
|
val payloadJson = objectMapper.writeValueAsString(payload)
|
||||||
|
|
||||||
|
return PayverseChargeResponse(chargeId = charge.id!!, payloadJson = payloadJson)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun payverseVerify(memberId: Long, verifyRequest: PayverseVerifyRequest): ChargeCompleteResponse {
|
||||||
|
val charge = chargeRepository.findByIdOrNull(verifyRequest.orderId.toLong())
|
||||||
|
?: throw SodaException("결제정보에 오류가 있습니다.")
|
||||||
|
val member = memberRepository.findByIdOrNull(memberId)
|
||||||
|
?: throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
|
||||||
|
val isKrw = charge.can?.currency == "KRW"
|
||||||
|
val mid = if (isKrw) {
|
||||||
|
payverseMid
|
||||||
|
} else {
|
||||||
|
payverseUsdMid
|
||||||
|
}
|
||||||
|
val clientKey = if (isKrw) {
|
||||||
|
payverseClientKey
|
||||||
|
} else {
|
||||||
|
payverseUsdClientKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// 결제수단 확인
|
||||||
|
if (charge.payment?.paymentGateway != PaymentGateway.PAYVERSE) {
|
||||||
|
throw SodaException("결제정보에 오류가 있습니다.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 결제 상태에 따른 분기 처리
|
||||||
|
when (charge.payment?.status) {
|
||||||
|
PaymentStatus.REQUEST -> {
|
||||||
|
try {
|
||||||
|
val url = "$payverseHost/payment/search/transaction/${verifyRequest.transactionId}"
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.addHeader("mid", mid)
|
||||||
|
.addHeader("clientKey", clientKey)
|
||||||
|
.get()
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val response = okHttpClient.newCall(request).execute()
|
||||||
|
if (!response.isSuccessful) {
|
||||||
|
throw SodaException("결제정보에 오류가 있습니다.")
|
||||||
|
}
|
||||||
|
|
||||||
|
val body = response.body?.string() ?: throw SodaException("결제정보에 오류가 있습니다.")
|
||||||
|
val verifyResponse = objectMapper.readValue(body, PayverseVerifyResponse::class.java)
|
||||||
|
|
||||||
|
val customerId = "${serverEnv}_user_${member.id!!}"
|
||||||
|
val isSuccess = verifyResponse.resultStatus == "SUCCESS" &&
|
||||||
|
verifyResponse.transactionStatus == "SUCCESS" &&
|
||||||
|
verifyResponse.orderId.toLongOrNull() == charge.id &&
|
||||||
|
verifyResponse.customerId == customerId &&
|
||||||
|
verifyResponse.requestAmount.compareTo(charge.can!!.price) == 0
|
||||||
|
|
||||||
|
if (isSuccess) {
|
||||||
|
// verify 함수의 232~248 라인과 동일 처리
|
||||||
|
charge.payment?.receiptId = verifyResponse.tid
|
||||||
|
val mappedMethod = if (verifyResponse.schemeGroup == "PVKR") {
|
||||||
|
mapPayverseSchemeToMethodByCode(verifyResponse.schemeCode)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
charge.payment?.method = mappedMethod ?: verifyResponse.schemeCode
|
||||||
|
charge.payment?.status = PaymentStatus.COMPLETE
|
||||||
|
// 통화코드 설정
|
||||||
|
charge.payment?.locale = verifyResponse.requestCurrency
|
||||||
|
|
||||||
|
member.charge(charge.chargeCan, charge.rewardCan, "pg")
|
||||||
|
|
||||||
|
applicationEventPublisher.publishEvent(
|
||||||
|
ChargeSpringEvent(
|
||||||
|
chargeId = charge.id!!,
|
||||||
|
memberId = member.id!!
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return ChargeCompleteResponse(
|
||||||
|
price = charge.payment!!.price,
|
||||||
|
currencyCode = charge.payment!!.locale?.takeLast(3) ?: "KRW",
|
||||||
|
isFirstCharged = chargeRepository.isFirstCharged(memberId)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
throw SodaException("결제정보에 오류가 있습니다.")
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
throw SodaException("결제정보에 오류가 있습니다.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PaymentStatus.COMPLETE -> {
|
||||||
|
// 이미 결제가 완료된 경우, 동일한 데이터로 즉시 반환
|
||||||
|
return ChargeCompleteResponse(
|
||||||
|
price = charge.payment!!.price,
|
||||||
|
currencyCode = charge.payment!!.locale?.takeLast(3) ?: "KRW",
|
||||||
|
isFirstCharged = chargeRepository.isFirstCharged(memberId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
throw SodaException("결제정보에 오류가 있습니다.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun charge(member: Member, request: ChargeRequest): ChargeResponse {
|
fun charge(member: Member, request: ChargeRequest): ChargeResponse {
|
||||||
val can = canRepository.findByIdOrNull(request.canId)
|
val can = canRepository.findByIdOrNull(request.canId)
|
||||||
@@ -137,7 +413,7 @@ class ChargeService(
|
|||||||
charge.can = can
|
charge.can = can
|
||||||
|
|
||||||
val payment = Payment(paymentGateway = request.paymentGateway)
|
val payment = Payment(paymentGateway = request.paymentGateway)
|
||||||
payment.price = can.price.toDouble()
|
payment.price = can.price
|
||||||
charge.payment = payment
|
charge.payment = payment
|
||||||
|
|
||||||
chargeRepository.save(charge)
|
chargeRepository.save(charge)
|
||||||
@@ -176,14 +452,14 @@ class ChargeService(
|
|||||||
)
|
)
|
||||||
|
|
||||||
return ChargeCompleteResponse(
|
return ChargeCompleteResponse(
|
||||||
price = BigDecimal(charge.payment!!.price).setScale(2, RoundingMode.HALF_UP).toDouble(),
|
price = charge.payment!!.price,
|
||||||
currencyCode = charge.payment!!.locale?.takeLast(3) ?: "KRW",
|
currencyCode = charge.payment!!.locale?.takeLast(3) ?: "KRW",
|
||||||
isFirstCharged = chargeRepository.isFirstCharged(memberId)
|
isFirstCharged = chargeRepository.isFirstCharged(memberId)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
throw SodaException("결제정보에 오류가 있습니다.")
|
throw SodaException("결제정보에 오류가 있습니다.")
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
throw SodaException("결제정보에 오류가 있습니다.")
|
throw SodaException("결제정보에 오류가 있습니다.")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -226,14 +502,14 @@ class ChargeService(
|
|||||||
)
|
)
|
||||||
|
|
||||||
return ChargeCompleteResponse(
|
return ChargeCompleteResponse(
|
||||||
price = BigDecimal(charge.payment!!.price).setScale(2, RoundingMode.HALF_UP).toDouble(),
|
price = charge.payment!!.price,
|
||||||
currencyCode = charge.payment!!.locale?.takeLast(3) ?: "KRW",
|
currencyCode = charge.payment!!.locale?.takeLast(3) ?: "KRW",
|
||||||
isFirstCharged = chargeRepository.isFirstCharged(memberId)
|
isFirstCharged = chargeRepository.isFirstCharged(memberId)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
throw SodaException("결제정보에 오류가 있습니다.")
|
throw SodaException("결제정보에 오류가 있습니다.")
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
throw SodaException("결제정보에 오류가 있습니다.")
|
throw SodaException("결제정보에 오류가 있습니다.")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -251,7 +527,7 @@ class ChargeService(
|
|||||||
payment.price = if (request.price != null) {
|
payment.price = if (request.price != null) {
|
||||||
request.price!!
|
request.price!!
|
||||||
} else {
|
} else {
|
||||||
0.toDouble()
|
0.toBigDecimal()
|
||||||
}
|
}
|
||||||
|
|
||||||
payment.locale = request.locale
|
payment.locale = request.locale
|
||||||
@@ -286,7 +562,7 @@ class ChargeService(
|
|||||||
)
|
)
|
||||||
|
|
||||||
return ChargeCompleteResponse(
|
return ChargeCompleteResponse(
|
||||||
price = BigDecimal(charge.payment!!.price).setScale(2, RoundingMode.HALF_UP).toDouble(),
|
price = charge.payment!!.price,
|
||||||
currencyCode = charge.payment!!.locale?.takeLast(3) ?: "KRW",
|
currencyCode = charge.payment!!.locale?.takeLast(3) ?: "KRW",
|
||||||
isFirstCharged = chargeRepository.isFirstCharged(memberId)
|
isFirstCharged = chargeRepository.isFirstCharged(memberId)
|
||||||
)
|
)
|
||||||
@@ -303,7 +579,7 @@ class ChargeService(
|
|||||||
member: Member,
|
member: Member,
|
||||||
title: String,
|
title: String,
|
||||||
chargeCan: Int,
|
chargeCan: Int,
|
||||||
price: Double,
|
price: BigDecimal,
|
||||||
currencyCode: String,
|
currencyCode: String,
|
||||||
productId: String,
|
productId: String,
|
||||||
purchaseToken: String,
|
purchaseToken: String,
|
||||||
@@ -331,8 +607,7 @@ class ChargeService(
|
|||||||
memberId: Long,
|
memberId: Long,
|
||||||
chargeId: Long,
|
chargeId: Long,
|
||||||
productId: String,
|
productId: String,
|
||||||
purchaseToken: String,
|
purchaseToken: String
|
||||||
paymentGateway: PaymentGateway
|
|
||||||
): ChargeCompleteResponse {
|
): ChargeCompleteResponse {
|
||||||
val charge = chargeRepository.findByIdOrNull(id = chargeId)
|
val charge = chargeRepository.findByIdOrNull(id = chargeId)
|
||||||
?: throw SodaException("결제정보에 오류가 있습니다.")
|
?: throw SodaException("결제정보에 오류가 있습니다.")
|
||||||
@@ -354,7 +629,7 @@ class ChargeService(
|
|||||||
)
|
)
|
||||||
|
|
||||||
return ChargeCompleteResponse(
|
return ChargeCompleteResponse(
|
||||||
price = BigDecimal(charge.payment!!.price).setScale(2, RoundingMode.HALF_UP).toDouble(),
|
price = charge.payment!!.price,
|
||||||
currencyCode = charge.payment!!.locale?.takeLast(3) ?: "KRW",
|
currencyCode = charge.payment!!.locale?.takeLast(3) ?: "KRW",
|
||||||
isFirstCharged = chargeRepository.isFirstCharged(memberId)
|
isFirstCharged = chargeRepository.isFirstCharged(memberId)
|
||||||
)
|
)
|
||||||
@@ -436,4 +711,13 @@ class ChargeService(
|
|||||||
throw SodaException("결제를 완료하지 못했습니다.")
|
throw SodaException("결제를 완료하지 못했습니다.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Payverse 결제수단 매핑: 특정 schemeCode는 "카드"로 표기, 아니면 null 반환
|
||||||
|
private fun mapPayverseSchemeToMethodByCode(schemeCode: String?): String? {
|
||||||
|
val cardCodes = setOf(
|
||||||
|
"041", "044", "361", "364", "365", "366", "367", "368", "369", "370", "371", "372", "373", "374", "381",
|
||||||
|
"218", "071", "002", "089", "045", "050", "048", "090", "092"
|
||||||
|
)
|
||||||
|
return if (schemeCode != null && cardCodes.contains(schemeCode)) "카드" else null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
package kr.co.vividnext.sodalive.can.charge.temp
|
package kr.co.vividnext.sodalive.can.charge.temp
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
|
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
data class ChargeTempRequest(
|
data class ChargeTempRequest(
|
||||||
val can: Int,
|
val can: Int,
|
||||||
val price: Int,
|
val price: BigDecimal,
|
||||||
val paymentGateway: PaymentGateway
|
val paymentGateway: PaymentGateway
|
||||||
)
|
)
|
||||||
|
@@ -41,7 +41,7 @@ class ChargeTempService(
|
|||||||
charge.member = member
|
charge.member = member
|
||||||
|
|
||||||
val payment = Payment(paymentGateway = request.paymentGateway)
|
val payment = Payment(paymentGateway = request.paymentGateway)
|
||||||
payment.price = request.price.toDouble()
|
payment.price = request.price
|
||||||
charge.payment = payment
|
charge.payment = payment
|
||||||
|
|
||||||
chargeRepository.save(charge)
|
chargeRepository.save(charge)
|
||||||
@@ -66,7 +66,7 @@ class ChargeTempService(
|
|||||||
VerifyResult::class.java
|
VerifyResult::class.java
|
||||||
)
|
)
|
||||||
|
|
||||||
if (verifyResult.status == 1 && verifyResult.price == charge.payment!!.price.toInt()) {
|
if (verifyResult.status == 1 && verifyResult.price == charge.payment!!.price) {
|
||||||
charge.payment?.receiptId = verifyResult.receiptId
|
charge.payment?.receiptId = verifyResult.receiptId
|
||||||
charge.payment?.method = verifyResult.method
|
charge.payment?.method = verifyResult.method
|
||||||
charge.payment?.status = PaymentStatus.COMPLETE
|
charge.payment?.status = PaymentStatus.COMPLETE
|
||||||
@@ -74,7 +74,7 @@ class ChargeTempService(
|
|||||||
} else {
|
} else {
|
||||||
throw SodaException("결제정보에 오류가 있습니다.")
|
throw SodaException("결제정보에 오류가 있습니다.")
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
throw SodaException("결제정보에 오류가 있습니다.")
|
throw SodaException("결제정보에 오류가 있습니다.")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.can.payment
|
|||||||
|
|
||||||
import kr.co.vividnext.sodalive.can.charge.Charge
|
import kr.co.vividnext.sodalive.can.charge.Charge
|
||||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||||
|
import java.math.BigDecimal
|
||||||
import javax.persistence.Column
|
import javax.persistence.Column
|
||||||
import javax.persistence.Entity
|
import javax.persistence.Entity
|
||||||
import javax.persistence.EnumType
|
import javax.persistence.EnumType
|
||||||
@@ -25,7 +26,8 @@ data class Payment(
|
|||||||
var receiptId: String? = null
|
var receiptId: String? = null
|
||||||
var method: String? = null
|
var method: String? = null
|
||||||
|
|
||||||
var price: Double = 0.toDouble()
|
@Column(precision = 10, scale = 4, nullable = false)
|
||||||
|
var price: BigDecimal = 0.toBigDecimal()
|
||||||
var locale: String? = null
|
var locale: String? = null
|
||||||
var orderId: String? = null
|
var orderId: String? = null
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
package kr.co.vividnext.sodalive.can.payment
|
package kr.co.vividnext.sodalive.can.payment
|
||||||
|
|
||||||
enum class PaymentGateway {
|
enum class PaymentGateway {
|
||||||
PG, GOOGLE_IAP, APPLE_IAP, POINT_CLICK_AD
|
PG, PAYVERSE, GOOGLE_IAP, APPLE_IAP, POINT_CLICK_AD
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.chat.original.controller
|
package kr.co.vividnext.sodalive.chat.original.controller
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.chat.character.dto.Character
|
import kr.co.vividnext.sodalive.chat.character.dto.Character
|
||||||
import kr.co.vividnext.sodalive.chat.original.dto.OriginalWorkCharactersPageResponse
|
|
||||||
import kr.co.vividnext.sodalive.chat.original.dto.OriginalWorkDetailResponse
|
import kr.co.vividnext.sodalive.chat.original.dto.OriginalWorkDetailResponse
|
||||||
import kr.co.vividnext.sodalive.chat.original.dto.OriginalWorkListItemResponse
|
import kr.co.vividnext.sodalive.chat.original.dto.OriginalWorkListItemResponse
|
||||||
import kr.co.vividnext.sodalive.chat.original.dto.OriginalWorkListResponse
|
import kr.co.vividnext.sodalive.chat.original.dto.OriginalWorkListResponse
|
||||||
@@ -79,37 +78,4 @@ class OriginalWorkController(
|
|||||||
val response = OriginalWorkDetailResponse.from(ow, imageHost, characters)
|
val response = OriginalWorkDetailResponse.from(ow, imageHost, characters)
|
||||||
ApiResponse.ok(response)
|
ApiResponse.ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 지정 원작에 속한 활성 캐릭터 목록 조회 (페이징)
|
|
||||||
* - 로그인 및 본인인증 필수
|
|
||||||
* - 기본 페이지 사이즈 20
|
|
||||||
*/
|
|
||||||
@GetMapping("/{id}/characters")
|
|
||||||
fun listCharacters(
|
|
||||||
@PathVariable id: Long,
|
|
||||||
@RequestParam(defaultValue = "0") page: Int,
|
|
||||||
@RequestParam(defaultValue = "20") size: Int,
|
|
||||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
|
||||||
) = run {
|
|
||||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
|
||||||
if (member.auth == null) throw SodaException("본인인증을 하셔야 합니다.")
|
|
||||||
|
|
||||||
val pageRes = queryService.getActiveCharactersPage(id, page, size)
|
|
||||||
val content = pageRes.content.map {
|
|
||||||
val path = it.imagePath ?: "profile/default-profile.png"
|
|
||||||
Character(
|
|
||||||
characterId = it.id!!,
|
|
||||||
name = it.name,
|
|
||||||
description = it.description,
|
|
||||||
imageUrl = "$imageHost/$path"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
ApiResponse.ok(
|
|
||||||
OriginalWorkCharactersPageResponse(
|
|
||||||
totalCount = pageRes.totalElements,
|
|
||||||
content = content
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,8 @@
|
|||||||
|
package kr.co.vividnext.sodalive.common
|
||||||
|
|
||||||
|
const val WAF_GEO_HEADER = "x-amzn-waf-geo-country"
|
||||||
|
|
||||||
|
enum class GeoCountry { KR, OTHER }
|
||||||
|
|
||||||
|
fun parseGeo(headerValue: String?): GeoCountry =
|
||||||
|
if (headerValue?.trim()?.uppercase() == "KR") GeoCountry.KR else GeoCountry.OTHER
|
@@ -0,0 +1,20 @@
|
|||||||
|
package kr.co.vividnext.sodalive.common
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter
|
||||||
|
import javax.servlet.FilterChain
|
||||||
|
import javax.servlet.http.HttpServletRequest
|
||||||
|
import javax.servlet.http.HttpServletResponse
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class GeoCountryFilter : OncePerRequestFilter() {
|
||||||
|
override fun doFilterInternal(
|
||||||
|
request: HttpServletRequest,
|
||||||
|
response: HttpServletResponse,
|
||||||
|
filterChain: FilterChain
|
||||||
|
) {
|
||||||
|
val country = parseGeo(request.getHeader(WAF_GEO_HEADER))
|
||||||
|
request.setAttribute("geoCountry", country)
|
||||||
|
filterChain.doFilter(request, response)
|
||||||
|
}
|
||||||
|
}
|
@@ -10,6 +10,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler
|
|||||||
import org.springframework.web.bind.annotation.ResponseStatus
|
import org.springframework.web.bind.annotation.ResponseStatus
|
||||||
import org.springframework.web.bind.annotation.RestControllerAdvice
|
import org.springframework.web.bind.annotation.RestControllerAdvice
|
||||||
import org.springframework.web.multipart.MaxUploadSizeExceededException
|
import org.springframework.web.multipart.MaxUploadSizeExceededException
|
||||||
|
import org.springframework.web.server.ResponseStatusException
|
||||||
|
|
||||||
@RestControllerAdvice
|
@RestControllerAdvice
|
||||||
class SodaExceptionHandler {
|
class SodaExceptionHandler {
|
||||||
@@ -63,6 +64,7 @@ class SodaExceptionHandler {
|
|||||||
|
|
||||||
@ExceptionHandler(Exception::class)
|
@ExceptionHandler(Exception::class)
|
||||||
fun handleException(e: Exception) = run {
|
fun handleException(e: Exception) = run {
|
||||||
|
if (e is ResponseStatusException) throw e
|
||||||
logger.error("API error", e)
|
logger.error("API error", e)
|
||||||
ApiResponse.error("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
ApiResponse.error("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||||
}
|
}
|
||||||
|
@@ -96,6 +96,7 @@ class SecurityConfig(
|
|||||||
.antMatchers(HttpMethod.GET, "/api/chat/character/main").permitAll()
|
.antMatchers(HttpMethod.GET, "/api/chat/character/main").permitAll()
|
||||||
.antMatchers(HttpMethod.GET, "/api/chat/room/list").permitAll()
|
.antMatchers(HttpMethod.GET, "/api/chat/room/list").permitAll()
|
||||||
.antMatchers(HttpMethod.GET, "/api/chat/original/list").permitAll()
|
.antMatchers(HttpMethod.GET, "/api/chat/original/list").permitAll()
|
||||||
|
.antMatchers(HttpMethod.POST, "/charge/payverse/webhook").permitAll()
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
.and()
|
.and()
|
||||||
.build()
|
.build()
|
||||||
|
@@ -53,7 +53,10 @@ class CreatorAdminCalculateQueryRepository(private val queryFactory: JPAQueryFac
|
|||||||
.innerJoin(useCan.room, liveRoom)
|
.innerJoin(useCan.room, liveRoom)
|
||||||
.innerJoin(liveRoom.member, member)
|
.innerJoin(liveRoom.member, member)
|
||||||
.leftJoin(creatorSettlementRatio)
|
.leftJoin(creatorSettlementRatio)
|
||||||
.on(member.id.eq(creatorSettlementRatio.member.id))
|
.on(
|
||||||
|
member.id.eq(creatorSettlementRatio.member.id)
|
||||||
|
.and(creatorSettlementRatio.deletedAt.isNull)
|
||||||
|
)
|
||||||
.where(
|
.where(
|
||||||
useCan.isRefund.isFalse
|
useCan.isRefund.isFalse
|
||||||
.and(useCan.createdAt.goe(startDate))
|
.and(useCan.createdAt.goe(startDate))
|
||||||
@@ -119,7 +122,10 @@ class CreatorAdminCalculateQueryRepository(private val queryFactory: JPAQueryFac
|
|||||||
.innerJoin(order.audioContent, audioContent)
|
.innerJoin(order.audioContent, audioContent)
|
||||||
.innerJoin(audioContent.member, member)
|
.innerJoin(audioContent.member, member)
|
||||||
.leftJoin(creatorSettlementRatio)
|
.leftJoin(creatorSettlementRatio)
|
||||||
.on(member.id.eq(creatorSettlementRatio.member.id))
|
.on(
|
||||||
|
member.id.eq(creatorSettlementRatio.member.id)
|
||||||
|
.and(creatorSettlementRatio.deletedAt.isNull)
|
||||||
|
)
|
||||||
.where(
|
.where(
|
||||||
order.createdAt.goe(startDate)
|
order.createdAt.goe(startDate)
|
||||||
.and(order.createdAt.loe(endDate))
|
.and(order.createdAt.loe(endDate))
|
||||||
@@ -196,7 +202,10 @@ class CreatorAdminCalculateQueryRepository(private val queryFactory: JPAQueryFac
|
|||||||
.innerJoin(order.audioContent, audioContent)
|
.innerJoin(order.audioContent, audioContent)
|
||||||
.innerJoin(audioContent.member, member)
|
.innerJoin(audioContent.member, member)
|
||||||
.leftJoin(creatorSettlementRatio)
|
.leftJoin(creatorSettlementRatio)
|
||||||
.on(member.id.eq(creatorSettlementRatio.member.id))
|
.on(
|
||||||
|
member.id.eq(creatorSettlementRatio.member.id)
|
||||||
|
.and(creatorSettlementRatio.deletedAt.isNull)
|
||||||
|
)
|
||||||
.where(
|
.where(
|
||||||
audioContent.member.id.eq(memberId)
|
audioContent.member.id.eq(memberId)
|
||||||
.and(order.isActive.isTrue)
|
.and(order.isActive.isTrue)
|
||||||
@@ -318,7 +327,10 @@ class CreatorAdminCalculateQueryRepository(private val queryFactory: JPAQueryFac
|
|||||||
.innerJoin(useCan.communityPost, creatorCommunity)
|
.innerJoin(useCan.communityPost, creatorCommunity)
|
||||||
.innerJoin(creatorCommunity.member, member)
|
.innerJoin(creatorCommunity.member, member)
|
||||||
.leftJoin(creatorSettlementRatio)
|
.leftJoin(creatorSettlementRatio)
|
||||||
.on(member.id.eq(creatorSettlementRatio.member.id))
|
.on(
|
||||||
|
member.id.eq(creatorSettlementRatio.member.id)
|
||||||
|
.and(creatorSettlementRatio.deletedAt.isNull)
|
||||||
|
)
|
||||||
.where(
|
.where(
|
||||||
useCan.isRefund.isFalse
|
useCan.isRefund.isFalse
|
||||||
.and(useCan.canUsage.eq(CanUsage.PAID_COMMUNITY_POST))
|
.and(useCan.canUsage.eq(CanUsage.PAID_COMMUNITY_POST))
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
package kr.co.vividnext.sodalive.marketing
|
package kr.co.vividnext.sodalive.marketing
|
||||||
|
|
||||||
|
import java.math.BigDecimal
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
import javax.persistence.Column
|
||||||
import javax.persistence.Entity
|
import javax.persistence.Entity
|
||||||
import javax.persistence.EnumType
|
import javax.persistence.EnumType
|
||||||
import javax.persistence.Enumerated
|
import javax.persistence.Enumerated
|
||||||
@@ -19,7 +21,8 @@ data class AdTrackingHistory(
|
|||||||
val pidName: String,
|
val pidName: String,
|
||||||
@Enumerated(value = EnumType.STRING)
|
@Enumerated(value = EnumType.STRING)
|
||||||
val type: AdTrackingHistoryType,
|
val type: AdTrackingHistoryType,
|
||||||
val price: Double = 0.toDouble(),
|
@Column(precision = 10, scale = 4, nullable = false)
|
||||||
|
val price: BigDecimal = 0.toBigDecimal(),
|
||||||
val locale: String? = null,
|
val locale: String? = null,
|
||||||
val memberId: Long,
|
val memberId: Long,
|
||||||
val createdAt: LocalDateTime = LocalDateTime.now(),
|
val createdAt: LocalDateTime = LocalDateTime.now(),
|
||||||
|
@@ -4,6 +4,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class AdTrackingService(
|
class AdTrackingService(
|
||||||
@@ -17,7 +18,7 @@ class AdTrackingService(
|
|||||||
pid: String,
|
pid: String,
|
||||||
type: AdTrackingHistoryType,
|
type: AdTrackingHistoryType,
|
||||||
memberId: Long,
|
memberId: Long,
|
||||||
price: Double? = null,
|
price: BigDecimal? = null,
|
||||||
locale: String? = null
|
locale: String? = null
|
||||||
) {
|
) {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
@@ -30,7 +31,7 @@ class AdTrackingService(
|
|||||||
pid = pid,
|
pid = pid,
|
||||||
pidName = mediaPartner.pidName,
|
pidName = mediaPartner.pidName,
|
||||||
type = type,
|
type = type,
|
||||||
price = price ?: 0.toDouble(),
|
price = price ?: 0.toBigDecimal(),
|
||||||
locale = locale,
|
locale = locale,
|
||||||
memberId = memberId
|
memberId = memberId
|
||||||
)
|
)
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
server:
|
server:
|
||||||
shutdown: graceful
|
shutdown: graceful
|
||||||
env: ${SERVER_ENV}
|
env: ${SERVER_ENV}
|
||||||
|
forward-headers-strategy: framework
|
||||||
|
|
||||||
logging:
|
logging:
|
||||||
level:
|
level:
|
||||||
@@ -13,6 +14,16 @@ weraser:
|
|||||||
apiUrl: ${WERASER_API_URL}
|
apiUrl: ${WERASER_API_URL}
|
||||||
apiKey: ${WERASER_API_KEY}
|
apiKey: ${WERASER_API_KEY}
|
||||||
|
|
||||||
|
payverse:
|
||||||
|
host: ${PAYVERSE_HOST}
|
||||||
|
inboundIp: ${PAYVERSE_INBOUND_IP}
|
||||||
|
mid: ${PAYVERSE_MID}
|
||||||
|
clientKey: ${PAYVERSE_CLIENT_KEY}
|
||||||
|
secretKey: ${PAYVERSE_SECRET_KEY}
|
||||||
|
usdMid: ${PAYVERSE_USD_MID}
|
||||||
|
usdClientKey: ${PAYVERSE_USD_CLIENT_KEY}
|
||||||
|
usdSecretKey: ${PAYVERSE_USD_SECRET_KEY}
|
||||||
|
|
||||||
bootpay:
|
bootpay:
|
||||||
applicationId: ${BOOTPAY_APPLICATION_ID}
|
applicationId: ${BOOTPAY_APPLICATION_ID}
|
||||||
privateKey: ${BOOTPAY_PRIVATE_KEY}
|
privateKey: ${BOOTPAY_PRIVATE_KEY}
|
||||||
|
Reference in New Issue
Block a user