commit
01a88964df
|
@ -66,6 +66,7 @@ dependencies {
|
||||||
implementation("com.google.apis:google-api-services-androidpublisher:v3-rev20240319-2.0.0")
|
implementation("com.google.apis:google-api-services-androidpublisher:v3-rev20240319-2.0.0")
|
||||||
|
|
||||||
implementation("org.apache.poi:poi-ooxml:5.2.3")
|
implementation("org.apache.poi:poi-ooxml:5.2.3")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
|
||||||
|
|
||||||
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
||||||
runtimeOnly("com.h2database:h2")
|
runtimeOnly("com.h2database:h2")
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
package kr.co.vividnext.sodalive.admin.marketing
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
|
import org.springframework.data.domain.Pageable
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
@RequestMapping("/admin/marketing/media-partner")
|
||||||
|
class AdminAdMediaPartnerController(private val service: AdminAdMediaPartnerService) {
|
||||||
|
@PostMapping
|
||||||
|
fun createMediaPartner(
|
||||||
|
@RequestBody request: CreateAdMediaPartnerRequest
|
||||||
|
) = ApiResponse.ok(service.createMediaPartner(request))
|
||||||
|
|
||||||
|
@PutMapping
|
||||||
|
fun updateMediaPartner(
|
||||||
|
@RequestBody request: UpdateAdMediaPartnerRequest
|
||||||
|
) = ApiResponse.ok(service.updateMediaPartner(request))
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
fun getMediaPartnerList(pageable: Pageable) = ApiResponse.ok(
|
||||||
|
service.getMediaPartnerList(
|
||||||
|
offset = pageable.offset,
|
||||||
|
limit = pageable.pageSize.toLong()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
package kr.co.vividnext.sodalive.admin.marketing
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
|
import kr.co.vividnext.sodalive.marketing.AdMediaPartnerRepository
|
||||||
|
import kr.co.vividnext.sodalive.marketing.AdMediaPartnerType
|
||||||
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class AdminAdMediaPartnerService(private val repository: AdMediaPartnerRepository) {
|
||||||
|
@Transactional
|
||||||
|
fun createMediaPartner(request: CreateAdMediaPartnerRequest) {
|
||||||
|
val mediaPartner = request.toEntity()
|
||||||
|
repository.save(mediaPartner)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun updateMediaPartner(request: UpdateAdMediaPartnerRequest) {
|
||||||
|
val entity = repository.findByIdOrNull(request.id)
|
||||||
|
?: throw SodaException("잘못된 접근입니다")
|
||||||
|
|
||||||
|
if (request.mediaGroup != null) {
|
||||||
|
entity.mediaGroup = request.mediaGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.pid != null) {
|
||||||
|
entity.pid = request.pid
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.pidName != null) {
|
||||||
|
entity.pidName = request.pidName
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.type != null) {
|
||||||
|
entity.type = request.type
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.utmSource != null) {
|
||||||
|
entity.utmSource = request.utmSource
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.utmMedium != null) {
|
||||||
|
entity.utmMedium = request.utmMedium
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.isActive != null) {
|
||||||
|
entity.isActive = request.isActive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMediaPartnerList(offset: Long, limit: Long): GetAdminAdMediaPartnerResponse {
|
||||||
|
val totalCount = repository.getMediaPartnerListTotalCount()
|
||||||
|
val items = repository.getMediaPartnerList(offset, limit)
|
||||||
|
.map {
|
||||||
|
val deepLinkValue = when (it.type) {
|
||||||
|
AdMediaPartnerType.SERIES -> "series"
|
||||||
|
AdMediaPartnerType.CONTENT -> "content"
|
||||||
|
AdMediaPartnerType.LIVE -> "live"
|
||||||
|
AdMediaPartnerType.CHANNEL -> "channel"
|
||||||
|
AdMediaPartnerType.MAIN -> "main"
|
||||||
|
}
|
||||||
|
|
||||||
|
val link = "$oneLinkHost?" +
|
||||||
|
"af_dp=voiceon://" +
|
||||||
|
"&deep_link_value=$deepLinkValue" +
|
||||||
|
"&deep_link_sub1=${it.pid}" +
|
||||||
|
"&deep_link_sub2=${it.utmSource}" +
|
||||||
|
"&deep_link_sub3=${it.utmMedium}" +
|
||||||
|
"&deep_link_sub4=${it.pidName}" +
|
||||||
|
"&utm_source=${it.utmSource}" +
|
||||||
|
"&utm_medium=${it.utmMedium}" +
|
||||||
|
"&utm_campaign=${it.pidName}"
|
||||||
|
|
||||||
|
it.link = link
|
||||||
|
it
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetAdminAdMediaPartnerResponse(
|
||||||
|
totalCount = totalCount,
|
||||||
|
items = items
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val oneLinkHost = "https://voiceon.onelink.me/RkTm"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package kr.co.vividnext.sodalive.admin.marketing
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.marketing.AdMediaPartner
|
||||||
|
import kr.co.vividnext.sodalive.marketing.AdMediaPartnerType
|
||||||
|
|
||||||
|
data class CreateAdMediaPartnerRequest(
|
||||||
|
val mediaGroup: String,
|
||||||
|
val pid: String,
|
||||||
|
val pidName: String,
|
||||||
|
val type: AdMediaPartnerType,
|
||||||
|
val utmSource: String,
|
||||||
|
val utmMedium: String
|
||||||
|
) {
|
||||||
|
fun toEntity(): AdMediaPartner {
|
||||||
|
return AdMediaPartner(
|
||||||
|
mediaGroup = mediaGroup,
|
||||||
|
pid = pid,
|
||||||
|
pidName = pidName,
|
||||||
|
type = type,
|
||||||
|
utmSource = utmSource,
|
||||||
|
utmMedium = utmMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package kr.co.vividnext.sodalive.admin.marketing
|
||||||
|
|
||||||
|
import com.querydsl.core.annotations.QueryProjection
|
||||||
|
import kr.co.vividnext.sodalive.marketing.AdMediaPartnerType
|
||||||
|
|
||||||
|
data class GetAdminAdMediaPartnerResponse(
|
||||||
|
val totalCount: Int,
|
||||||
|
val items: List<GetAdminAdMediaPartnerResponseItem>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class GetAdminAdMediaPartnerResponseItem @QueryProjection constructor(
|
||||||
|
val id: Long,
|
||||||
|
val mediaGroup: String,
|
||||||
|
val pid: String,
|
||||||
|
val pidName: String,
|
||||||
|
val type: AdMediaPartnerType,
|
||||||
|
val utmSource: String,
|
||||||
|
val utmMedium: String,
|
||||||
|
val isActive: Boolean,
|
||||||
|
val createdAt: String,
|
||||||
|
var link: String
|
||||||
|
)
|
|
@ -0,0 +1,14 @@
|
||||||
|
package kr.co.vividnext.sodalive.admin.marketing
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.marketing.AdMediaPartnerType
|
||||||
|
|
||||||
|
data class UpdateAdMediaPartnerRequest(
|
||||||
|
val id: Long,
|
||||||
|
val mediaGroup: String?,
|
||||||
|
val pid: String?,
|
||||||
|
val pidName: String?,
|
||||||
|
val type: AdMediaPartnerType?,
|
||||||
|
val utmSource: String?,
|
||||||
|
val utmMedium: String?,
|
||||||
|
val isActive: Boolean?
|
||||||
|
)
|
|
@ -0,0 +1,7 @@
|
||||||
|
package kr.co.vividnext.sodalive.can.charge
|
||||||
|
|
||||||
|
data class ChargeCompleteResponse(
|
||||||
|
val price: Double,
|
||||||
|
val currencyCode: String,
|
||||||
|
val isFirstCharged: Boolean
|
||||||
|
)
|
|
@ -3,17 +3,22 @@ package kr.co.vividnext.sodalive.can.charge
|
||||||
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
|
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
|
import kr.co.vividnext.sodalive.marketing.AdTrackingHistoryType
|
||||||
|
import kr.co.vividnext.sodalive.marketing.AdTrackingService
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
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.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 java.time.LocalDateTime
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/charge")
|
@RequestMapping("/charge")
|
||||||
class ChargeController(private val service: ChargeService) {
|
class ChargeController(
|
||||||
|
private val service: ChargeService,
|
||||||
|
private val trackingService: AdTrackingService
|
||||||
|
) {
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
fun charge(
|
fun charge(
|
||||||
|
@ -30,14 +35,30 @@ class ChargeController(private val service: ChargeService) {
|
||||||
@PostMapping("/verify")
|
@PostMapping("/verify")
|
||||||
fun verify(
|
fun verify(
|
||||||
@RequestBody verifyRequest: VerifyRequest,
|
@RequestBody verifyRequest: VerifyRequest,
|
||||||
@AuthenticationPrincipal user: User
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
) = ApiResponse.ok(service.verify(user, verifyRequest))
|
) = run {
|
||||||
|
if (member == null) {
|
||||||
|
throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
}
|
||||||
|
|
||||||
|
val response = service.verify(memberId = member.id!!, verifyRequest)
|
||||||
|
trackingCharge(member, response)
|
||||||
|
ApiResponse.ok(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/verify/hecto")
|
@PostMapping("/verify/hecto")
|
||||||
fun verifyHecto(
|
fun verifyHecto(
|
||||||
@RequestBody verifyRequest: VerifyRequest,
|
@RequestBody verifyRequest: VerifyRequest,
|
||||||
@AuthenticationPrincipal user: User
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
) = ApiResponse.ok(service.verifyHecto(user, verifyRequest))
|
) = run {
|
||||||
|
if (member == null) {
|
||||||
|
throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
}
|
||||||
|
|
||||||
|
val response = service.verifyHecto(memberId = member.id!!, verifyRequest)
|
||||||
|
trackingCharge(member, response)
|
||||||
|
ApiResponse.ok(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/apple")
|
@PostMapping("/apple")
|
||||||
fun appleCharge(
|
fun appleCharge(
|
||||||
|
@ -54,8 +75,16 @@ class ChargeController(private val service: ChargeService) {
|
||||||
@PostMapping("/apple/verify")
|
@PostMapping("/apple/verify")
|
||||||
fun appleVerify(
|
fun appleVerify(
|
||||||
@RequestBody verifyRequest: AppleVerifyRequest,
|
@RequestBody verifyRequest: AppleVerifyRequest,
|
||||||
@AuthenticationPrincipal user: User
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
) = ApiResponse.ok(service.appleVerify(user, verifyRequest))
|
) = run {
|
||||||
|
if (member == null) {
|
||||||
|
throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
}
|
||||||
|
|
||||||
|
val response = service.appleVerify(memberId = member.id!!, verifyRequest)
|
||||||
|
trackingCharge(member, response)
|
||||||
|
ApiResponse.ok(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/google")
|
@PostMapping("/google")
|
||||||
fun googleCharge(
|
fun googleCharge(
|
||||||
|
@ -78,17 +107,40 @@ class ChargeController(private val service: ChargeService) {
|
||||||
paymentGateway = request.paymentGateway
|
paymentGateway = request.paymentGateway
|
||||||
)
|
)
|
||||||
|
|
||||||
ApiResponse.ok(
|
val response = service.processGoogleIap(
|
||||||
service.processGoogleIap(
|
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
|
||||||
paymentGateway = request.paymentGateway
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
trackingCharge(member, response)
|
||||||
|
ApiResponse.ok(Unit)
|
||||||
} else {
|
} else {
|
||||||
throw SodaException("결제정보에 오류가 있습니다.")
|
throw SodaException("결제정보에 오류가 있습니다.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun trackingCharge(
|
||||||
|
member: Member,
|
||||||
|
response: ChargeCompleteResponse
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
!member.activePid.isNullOrBlank() &&
|
||||||
|
member.partnerExpirationDatetime?.isAfter(LocalDateTime.now()) == true
|
||||||
|
) {
|
||||||
|
trackingService.saveTrackingHistory(
|
||||||
|
pid = member.activePid!!,
|
||||||
|
type = if (response.isFirstCharged) {
|
||||||
|
AdTrackingHistoryType.FIRST_PAYMENT
|
||||||
|
} else {
|
||||||
|
AdTrackingHistoryType.REPEAT_PAYMENT
|
||||||
|
},
|
||||||
|
memberId = member.id!!,
|
||||||
|
price = response.price,
|
||||||
|
locale = response.currencyCode
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ interface ChargeQueryRepository {
|
||||||
fun getOldestChargeWhereRewardCanGreaterThan0(chargeId: Long, memberId: Long, container: String): Charge?
|
fun getOldestChargeWhereRewardCanGreaterThan0(chargeId: Long, memberId: Long, container: String): Charge?
|
||||||
fun getOldestChargeWhereChargeCanGreaterThan0(chargeId: Long, memberId: Long, container: String): Charge?
|
fun getOldestChargeWhereChargeCanGreaterThan0(chargeId: Long, memberId: Long, container: String): Charge?
|
||||||
fun getChargeCountAfterDate(memberId: Long, date: LocalDateTime): Int
|
fun getChargeCountAfterDate(memberId: Long, date: LocalDateTime): Int
|
||||||
|
fun isFirstCharged(memberId: Long): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChargeQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : ChargeQueryRepository {
|
class ChargeQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : ChargeQueryRepository {
|
||||||
|
@ -76,6 +77,21 @@ class ChargeQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : Cha
|
||||||
.size
|
.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun isFirstCharged(memberId: Long): Boolean {
|
||||||
|
return queryFactory
|
||||||
|
.select(charge.id)
|
||||||
|
.from(charge)
|
||||||
|
.innerJoin(charge.member, member)
|
||||||
|
.innerJoin(charge.payment, payment)
|
||||||
|
.where(
|
||||||
|
member.id.eq(memberId),
|
||||||
|
charge.status.eq(ChargeStatus.CHARGE),
|
||||||
|
payment.status.eq(PaymentStatus.COMPLETE)
|
||||||
|
)
|
||||||
|
.fetch()
|
||||||
|
.size <= 1
|
||||||
|
}
|
||||||
|
|
||||||
private fun getPaymentGatewayCondition(container: String): BooleanExpression? {
|
private fun getPaymentGatewayCondition(container: String): BooleanExpression? {
|
||||||
val paymentGatewayCondition = when (container) {
|
val paymentGatewayCondition = when (container) {
|
||||||
"aos" -> {
|
"aos" -> {
|
||||||
|
|
|
@ -23,9 +23,10 @@ import org.springframework.data.repository.findByIdOrNull
|
||||||
import org.springframework.http.HttpHeaders
|
import org.springframework.http.HttpHeaders
|
||||||
import org.springframework.retry.annotation.Backoff
|
import org.springframework.retry.annotation.Backoff
|
||||||
import org.springframework.retry.annotation.Retryable
|
import org.springframework.retry.annotation.Retryable
|
||||||
import org.springframework.security.core.userdetails.User
|
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.math.RoundingMode
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
|
@ -102,10 +103,10 @@ class ChargeService(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun verify(user: User, verifyRequest: VerifyRequest) {
|
fun verify(memberId: Long, verifyRequest: VerifyRequest): ChargeCompleteResponse {
|
||||||
val charge = chargeRepository.findByIdOrNull(verifyRequest.orderId.toLong())
|
val charge = chargeRepository.findByIdOrNull(verifyRequest.orderId.toLong())
|
||||||
?: throw SodaException("결제정보에 오류가 있습니다.")
|
?: throw SodaException("결제정보에 오류가 있습니다.")
|
||||||
val member = memberRepository.findByEmail(user.username)
|
val member = memberRepository.findByIdOrNull(memberId)
|
||||||
?: throw SodaException("로그인 정보를 확인해주세요.")
|
?: throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
|
||||||
if (charge.payment!!.paymentGateway == PaymentGateway.PG) {
|
if (charge.payment!!.paymentGateway == PaymentGateway.PG) {
|
||||||
|
@ -130,6 +131,12 @@ class ChargeService(
|
||||||
memberId = member.id!!
|
memberId = member.id!!
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return ChargeCompleteResponse(
|
||||||
|
price = BigDecimal(charge.payment!!.price).setScale(2, RoundingMode.HALF_UP).toDouble(),
|
||||||
|
currencyCode = charge.payment!!.locale?.takeLast(3) ?: "KRW",
|
||||||
|
isFirstCharged = chargeRepository.isFirstCharged(memberId)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
throw SodaException("결제정보에 오류가 있습니다.")
|
throw SodaException("결제정보에 오류가 있습니다.")
|
||||||
}
|
}
|
||||||
|
@ -142,10 +149,10 @@ class ChargeService(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun verifyHecto(user: User, verifyRequest: VerifyRequest) {
|
fun verifyHecto(memberId: Long, verifyRequest: VerifyRequest): ChargeCompleteResponse {
|
||||||
val charge = chargeRepository.findByIdOrNull(verifyRequest.orderId.toLong())
|
val charge = chargeRepository.findByIdOrNull(verifyRequest.orderId.toLong())
|
||||||
?: throw SodaException("결제정보에 오류가 있습니다.")
|
?: throw SodaException("결제정보에 오류가 있습니다.")
|
||||||
val member = memberRepository.findByEmail(user.username)
|
val member = memberRepository.findByIdOrNull(memberId)
|
||||||
?: throw SodaException("로그인 정보를 확인해주세요.")
|
?: throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
|
||||||
if (charge.payment!!.paymentGateway == PaymentGateway.PG) {
|
if (charge.payment!!.paymentGateway == PaymentGateway.PG) {
|
||||||
|
@ -174,6 +181,12 @@ class ChargeService(
|
||||||
memberId = member.id!!
|
memberId = member.id!!
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return ChargeCompleteResponse(
|
||||||
|
price = BigDecimal(charge.payment!!.price).setScale(2, RoundingMode.HALF_UP).toDouble(),
|
||||||
|
currencyCode = charge.payment!!.locale?.takeLast(3) ?: "KRW",
|
||||||
|
isFirstCharged = chargeRepository.isFirstCharged(memberId)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
throw SodaException("결제정보에 오류가 있습니다.")
|
throw SodaException("결제정보에 오류가 있습니다.")
|
||||||
}
|
}
|
||||||
|
@ -208,10 +221,10 @@ class ChargeService(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun appleVerify(user: User, verifyRequest: AppleVerifyRequest) {
|
fun appleVerify(memberId: Long, verifyRequest: AppleVerifyRequest): ChargeCompleteResponse {
|
||||||
val charge = chargeRepository.findByIdOrNull(verifyRequest.chargeId)
|
val charge = chargeRepository.findByIdOrNull(verifyRequest.chargeId)
|
||||||
?: throw SodaException("결제정보에 오류가 있습니다.")
|
?: throw SodaException("결제정보에 오류가 있습니다.")
|
||||||
val member = memberRepository.findByEmail(user.username)
|
val member = memberRepository.findByIdOrNull(memberId)
|
||||||
?: throw SodaException("로그인 정보를 확인해주세요.")
|
?: throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
|
||||||
if (charge.payment!!.paymentGateway == PaymentGateway.APPLE_IAP) {
|
if (charge.payment!!.paymentGateway == PaymentGateway.APPLE_IAP) {
|
||||||
|
@ -228,6 +241,12 @@ class ChargeService(
|
||||||
memberId = member.id!!
|
memberId = member.id!!
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return ChargeCompleteResponse(
|
||||||
|
price = BigDecimal(charge.payment!!.price).setScale(2, RoundingMode.HALF_UP).toDouble(),
|
||||||
|
currencyCode = charge.payment!!.locale?.takeLast(3) ?: "KRW",
|
||||||
|
isFirstCharged = chargeRepository.isFirstCharged(memberId)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
throw SodaException("결제정보에 오류가 있습니다.")
|
throw SodaException("결제정보에 오류가 있습니다.")
|
||||||
}
|
}
|
||||||
|
@ -271,7 +290,7 @@ class ChargeService(
|
||||||
productId: String,
|
productId: String,
|
||||||
purchaseToken: String,
|
purchaseToken: String,
|
||||||
paymentGateway: PaymentGateway
|
paymentGateway: PaymentGateway
|
||||||
) {
|
): ChargeCompleteResponse {
|
||||||
val charge = chargeRepository.findByIdOrNull(id = chargeId)
|
val charge = chargeRepository.findByIdOrNull(id = chargeId)
|
||||||
?: throw SodaException("결제정보에 오류가 있습니다.")
|
?: throw SodaException("결제정보에 오류가 있습니다.")
|
||||||
val member = memberRepository.findByIdOrNull(id = memberId)
|
val member = memberRepository.findByIdOrNull(id = memberId)
|
||||||
|
@ -290,6 +309,12 @@ class ChargeService(
|
||||||
memberId = member.id!!
|
memberId = member.id!!
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return ChargeCompleteResponse(
|
||||||
|
price = BigDecimal(charge.payment!!.price).setScale(2, RoundingMode.HALF_UP).toDouble(),
|
||||||
|
currencyCode = charge.payment!!.locale?.takeLast(3) ?: "KRW",
|
||||||
|
isFirstCharged = chargeRepository.isFirstCharged(memberId)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
throw SodaException("구매를 하지 못했습니다.\n고객센터로 문의해 주세요")
|
throw SodaException("구매를 하지 못했습니다.\n고객센터로 문의해 주세요")
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package kr.co.vividnext.sodalive.marketing
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||||
|
import javax.persistence.Entity
|
||||||
|
import javax.persistence.EnumType
|
||||||
|
import javax.persistence.Enumerated
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class AdMediaPartner(
|
||||||
|
var mediaGroup: String,
|
||||||
|
var pid: String,
|
||||||
|
var pidName: String,
|
||||||
|
@Enumerated(value = EnumType.STRING)
|
||||||
|
var type: AdMediaPartnerType,
|
||||||
|
var utmSource: String,
|
||||||
|
var utmMedium: String,
|
||||||
|
var isActive: Boolean = true
|
||||||
|
) : BaseEntity()
|
||||||
|
|
||||||
|
enum class AdMediaPartnerType {
|
||||||
|
SERIES, CONTENT, LIVE, CHANNEL, MAIN
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
package kr.co.vividnext.sodalive.marketing
|
||||||
|
|
||||||
|
import com.querydsl.core.types.dsl.DateTimePath
|
||||||
|
import com.querydsl.core.types.dsl.Expressions
|
||||||
|
import com.querydsl.core.types.dsl.StringTemplate
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
|
import kr.co.vividnext.sodalive.admin.marketing.GetAdminAdMediaPartnerResponseItem
|
||||||
|
import kr.co.vividnext.sodalive.admin.marketing.QGetAdminAdMediaPartnerResponseItem
|
||||||
|
import kr.co.vividnext.sodalive.marketing.QAdMediaPartner.adMediaPartner
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
interface AdMediaPartnerRepository : JpaRepository<AdMediaPartner, Long>, AdMediaPartnerQueryRepository
|
||||||
|
|
||||||
|
interface AdMediaPartnerQueryRepository {
|
||||||
|
fun findByPid(pid: String): AdMediaPartner?
|
||||||
|
fun getMediaPartnerList(offset: Long, limit: Long): List<GetAdminAdMediaPartnerResponseItem>
|
||||||
|
fun getMediaPartnerListTotalCount(): Int
|
||||||
|
}
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
class AdMediaPartnerQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : AdMediaPartnerQueryRepository {
|
||||||
|
override fun findByPid(pid: String): AdMediaPartner? {
|
||||||
|
return queryFactory
|
||||||
|
.selectFrom(adMediaPartner)
|
||||||
|
.where(adMediaPartner.pid.eq(pid), adMediaPartner.isActive.isTrue)
|
||||||
|
.fetchFirst()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMediaPartnerList(offset: Long, limit: Long): List<GetAdminAdMediaPartnerResponseItem> {
|
||||||
|
return queryFactory
|
||||||
|
.select(
|
||||||
|
QGetAdminAdMediaPartnerResponseItem(
|
||||||
|
adMediaPartner.id,
|
||||||
|
adMediaPartner.mediaGroup,
|
||||||
|
adMediaPartner.pid,
|
||||||
|
adMediaPartner.pidName,
|
||||||
|
adMediaPartner.type,
|
||||||
|
adMediaPartner.utmSource,
|
||||||
|
adMediaPartner.utmMedium,
|
||||||
|
adMediaPartner.isActive,
|
||||||
|
getFormattedDate(adMediaPartner.createdAt),
|
||||||
|
Expressions.constant("")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.from(adMediaPartner)
|
||||||
|
.orderBy(adMediaPartner.isActive.desc(), adMediaPartner.id.asc())
|
||||||
|
.offset(offset)
|
||||||
|
.limit(limit)
|
||||||
|
.fetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMediaPartnerListTotalCount(): Int {
|
||||||
|
return queryFactory
|
||||||
|
.select(adMediaPartner.id)
|
||||||
|
.from(adMediaPartner)
|
||||||
|
.fetch()
|
||||||
|
.size
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFormattedDate(dateTimePath: DateTimePath<LocalDateTime>): StringTemplate {
|
||||||
|
return Expressions.stringTemplate(
|
||||||
|
"DATE_FORMAT({0}, {1})",
|
||||||
|
Expressions.dateTimeTemplate(
|
||||||
|
LocalDateTime::class.java,
|
||||||
|
"CONVERT_TZ({0},{1},{2})",
|
||||||
|
dateTimePath,
|
||||||
|
"UTC",
|
||||||
|
"Asia/Seoul"
|
||||||
|
),
|
||||||
|
"%Y-%m-%d %H:%i:%s"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package kr.co.vividnext.sodalive.marketing
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import javax.persistence.Embeddable
|
||||||
|
import javax.persistence.EmbeddedId
|
||||||
|
import javax.persistence.Entity
|
||||||
|
import javax.persistence.EnumType
|
||||||
|
import javax.persistence.Enumerated
|
||||||
|
import javax.persistence.PreUpdate
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class AdTrackingHistory(
|
||||||
|
@EmbeddedId
|
||||||
|
val id: AdTrackingHistoryId,
|
||||||
|
val mediaGroup: String,
|
||||||
|
val pidName: String,
|
||||||
|
val price: Double = 0.toDouble(),
|
||||||
|
val locale: String? = null,
|
||||||
|
var updatedAt: LocalDateTime = LocalDateTime.now()
|
||||||
|
) {
|
||||||
|
@PreUpdate
|
||||||
|
fun preUpdate() {
|
||||||
|
updatedAt = LocalDateTime.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Embeddable
|
||||||
|
data class AdTrackingHistoryId(
|
||||||
|
val pid: String,
|
||||||
|
val memberId: Long,
|
||||||
|
@Enumerated(value = EnumType.STRING)
|
||||||
|
val type: AdTrackingHistoryType,
|
||||||
|
val createdAt: LocalDateTime = LocalDateTime.now()
|
||||||
|
) : Serializable
|
||||||
|
|
||||||
|
enum class AdTrackingHistoryType {
|
||||||
|
// 회원가입
|
||||||
|
SIGNUP,
|
||||||
|
|
||||||
|
// 첫결제
|
||||||
|
FIRST_PAYMENT,
|
||||||
|
|
||||||
|
// 재결제
|
||||||
|
REPEAT_PAYMENT
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package kr.co.vividnext.sodalive.marketing
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
|
||||||
|
interface AdTrackingRepository : JpaRepository<AdTrackingHistory, AdTrackingHistoryId>
|
|
@ -0,0 +1,43 @@
|
||||||
|
package kr.co.vividnext.sodalive.marketing
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class AdTrackingService(
|
||||||
|
private val repository: AdTrackingRepository,
|
||||||
|
private val mediaPartnerRepository: AdMediaPartnerRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val coroutineScope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
|
fun saveTrackingHistory(
|
||||||
|
pid: String,
|
||||||
|
type: AdTrackingHistoryType,
|
||||||
|
memberId: Long,
|
||||||
|
price: Double? = null,
|
||||||
|
locale: String? = null
|
||||||
|
) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
try {
|
||||||
|
val mediaPartner = mediaPartnerRepository.findByPid(pid)
|
||||||
|
|
||||||
|
if (mediaPartner != null) {
|
||||||
|
val id = AdTrackingHistoryId(pid = pid, memberId = memberId, type = type)
|
||||||
|
val trackingHistory = AdTrackingHistory(
|
||||||
|
id = id,
|
||||||
|
mediaGroup = mediaPartner.mediaGroup,
|
||||||
|
pidName = mediaPartner.pidName,
|
||||||
|
price = price ?: 0.toDouble(),
|
||||||
|
locale = locale
|
||||||
|
)
|
||||||
|
repository.save(trackingHistory)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package kr.co.vividnext.sodalive.member
|
||||||
|
|
||||||
|
data class MarketingInfoUpdateRequest(
|
||||||
|
val adid: String,
|
||||||
|
val pid: String
|
||||||
|
)
|
|
@ -7,6 +7,7 @@ import kr.co.vividnext.sodalive.member.following.CreatorFollowing
|
||||||
import kr.co.vividnext.sodalive.member.notification.MemberNotification
|
import kr.co.vividnext.sodalive.member.notification.MemberNotification
|
||||||
import kr.co.vividnext.sodalive.member.stipulation.StipulationAgree
|
import kr.co.vividnext.sodalive.member.stipulation.StipulationAgree
|
||||||
import kr.co.vividnext.sodalive.member.tag.MemberCreatorTag
|
import kr.co.vividnext.sodalive.member.tag.MemberCreatorTag
|
||||||
|
import java.time.LocalDateTime
|
||||||
import javax.persistence.CascadeType
|
import javax.persistence.CascadeType
|
||||||
import javax.persistence.Column
|
import javax.persistence.Column
|
||||||
import javax.persistence.Entity
|
import javax.persistence.Entity
|
||||||
|
@ -29,6 +30,12 @@ data class Member(
|
||||||
@Enumerated(value = EnumType.STRING)
|
@Enumerated(value = EnumType.STRING)
|
||||||
var role: MemberRole = MemberRole.USER,
|
var role: MemberRole = MemberRole.USER,
|
||||||
|
|
||||||
|
@Column(nullable = true)
|
||||||
|
var activePid: String? = null,
|
||||||
|
|
||||||
|
@Column(nullable = true)
|
||||||
|
var partnerExpirationDatetime: LocalDateTime? = null,
|
||||||
|
|
||||||
var isVisibleDonationRank: Boolean = true,
|
var isVisibleDonationRank: Boolean = true,
|
||||||
|
|
||||||
var isActive: Boolean = true,
|
var isActive: Boolean = true,
|
||||||
|
|
|
@ -2,9 +2,12 @@ package kr.co.vividnext.sodalive.member
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
|
import kr.co.vividnext.sodalive.marketing.AdTrackingHistoryType
|
||||||
|
import kr.co.vividnext.sodalive.marketing.AdTrackingService
|
||||||
import kr.co.vividnext.sodalive.member.block.MemberBlockRequest
|
import kr.co.vividnext.sodalive.member.block.MemberBlockRequest
|
||||||
import kr.co.vividnext.sodalive.member.following.CreatorFollowRequest
|
import kr.co.vividnext.sodalive.member.following.CreatorFollowRequest
|
||||||
import kr.co.vividnext.sodalive.member.login.LoginRequest
|
import kr.co.vividnext.sodalive.member.login.LoginRequest
|
||||||
|
import kr.co.vividnext.sodalive.member.login.LoginResponse
|
||||||
import kr.co.vividnext.sodalive.member.notification.UpdateNotificationSettingRequest
|
import kr.co.vividnext.sodalive.member.notification.UpdateNotificationSettingRequest
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||||
|
@ -23,7 +26,10 @@ import org.springframework.web.multipart.MultipartFile
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/member")
|
@RequestMapping("/member")
|
||||||
class MemberController(private val service: MemberService) {
|
class MemberController(
|
||||||
|
private val service: MemberService,
|
||||||
|
private val trackingService: AdTrackingService
|
||||||
|
) {
|
||||||
@GetMapping("/check/email")
|
@GetMapping("/check/email")
|
||||||
fun checkEmail(@RequestParam email: String) = service.duplicateCheckEmail(email)
|
fun checkEmail(@RequestParam email: String) = service.duplicateCheckEmail(email)
|
||||||
|
|
||||||
|
@ -40,7 +46,19 @@ class MemberController(private val service: MemberService) {
|
||||||
fun signUp(
|
fun signUp(
|
||||||
@RequestPart("profileImage", required = false) profileImage: MultipartFile? = null,
|
@RequestPart("profileImage", required = false) profileImage: MultipartFile? = null,
|
||||||
@RequestPart("request") requestString: String
|
@RequestPart("request") requestString: String
|
||||||
) = service.signUp(profileImage, requestString)
|
): ApiResponse<LoginResponse> {
|
||||||
|
val response = service.signUp(profileImage, requestString)
|
||||||
|
|
||||||
|
if (!response.marketingPid.isNullOrBlank()) {
|
||||||
|
trackingService.saveTrackingHistory(
|
||||||
|
pid = response.marketingPid,
|
||||||
|
type = AdTrackingHistoryType.SIGNUP,
|
||||||
|
memberId = response.memberId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiResponse.ok(message = "회원가입을 축하드립니다.", data = response.loginResponse)
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
fun login(@RequestBody loginRequest: LoginRequest) = service.login(loginRequest)
|
fun login(@RequestBody loginRequest: LoginRequest) = service.login(loginRequest)
|
||||||
|
@ -110,6 +128,22 @@ class MemberController(private val service: MemberService) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PutMapping("/marketing-info/update")
|
||||||
|
fun updateMarketingInfo(
|
||||||
|
@RequestBody request: MarketingInfoUpdateRequest,
|
||||||
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
|
) = run {
|
||||||
|
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
|
||||||
|
ApiResponse.ok(
|
||||||
|
service.updateMarketingInfo(
|
||||||
|
memberId = member.id!!,
|
||||||
|
adid = request.adid,
|
||||||
|
pid = request.pid
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@PutMapping("/adid/update")
|
@PutMapping("/adid/update")
|
||||||
fun updateAdid(
|
fun updateAdid(
|
||||||
@RequestBody request: AdidUpdateRequest,
|
@RequestBody request: AdidUpdateRequest,
|
||||||
|
|
|
@ -27,6 +27,7 @@ import kr.co.vividnext.sodalive.member.nickname.NicknameChangeLogRepository
|
||||||
import kr.co.vividnext.sodalive.member.notification.MemberNotificationService
|
import kr.co.vividnext.sodalive.member.notification.MemberNotificationService
|
||||||
import kr.co.vividnext.sodalive.member.notification.UpdateNotificationSettingRequest
|
import kr.co.vividnext.sodalive.member.notification.UpdateNotificationSettingRequest
|
||||||
import kr.co.vividnext.sodalive.member.signUp.SignUpRequest
|
import kr.co.vividnext.sodalive.member.signUp.SignUpRequest
|
||||||
|
import kr.co.vividnext.sodalive.member.signUp.SignUpResponse
|
||||||
import kr.co.vividnext.sodalive.member.signUp.SignUpValidator
|
import kr.co.vividnext.sodalive.member.signUp.SignUpValidator
|
||||||
import kr.co.vividnext.sodalive.member.stipulation.Stipulation
|
import kr.co.vividnext.sodalive.member.stipulation.Stipulation
|
||||||
import kr.co.vividnext.sodalive.member.stipulation.StipulationAgree
|
import kr.co.vividnext.sodalive.member.stipulation.StipulationAgree
|
||||||
|
@ -97,7 +98,7 @@ class MemberService(
|
||||||
fun signUp(
|
fun signUp(
|
||||||
profileImage: MultipartFile?,
|
profileImage: MultipartFile?,
|
||||||
requestString: String
|
requestString: String
|
||||||
): ApiResponse<LoginResponse> {
|
): SignUpResponse {
|
||||||
val stipulationTermsOfService = stipulationRepository.findByIdOrNull(StipulationIds.TERMS_OF_SERVICE_ID)
|
val stipulationTermsOfService = stipulationRepository.findByIdOrNull(StipulationIds.TERMS_OF_SERVICE_ID)
|
||||||
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.")
|
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.")
|
||||||
|
|
||||||
|
@ -117,7 +118,11 @@ class MemberService(
|
||||||
member.profileImage = uploadProfileImage(profileImage = profileImage, memberId = member.id!!)
|
member.profileImage = uploadProfileImage(profileImage = profileImage, memberId = member.id!!)
|
||||||
agreeTermsOfServiceAndPrivacyPolicy(member, stipulationTermsOfService, stipulationPrivacyPolicy)
|
agreeTermsOfServiceAndPrivacyPolicy(member, stipulationTermsOfService, stipulationPrivacyPolicy)
|
||||||
|
|
||||||
return ApiResponse.ok(message = "회원가입을 축하드립니다.", data = login(request.email, request.password))
|
return SignUpResponse(
|
||||||
|
memberId = member.id!!,
|
||||||
|
marketingPid = request.marketingPid,
|
||||||
|
loginResponse = login(request.email, request.password)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun login(request: LoginRequest): ApiResponse<LoginResponse> {
|
fun login(request: LoginRequest): ApiResponse<LoginResponse> {
|
||||||
|
@ -289,6 +294,11 @@ class MemberService(
|
||||||
container = request.container
|
container = request.container
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (!request.marketingPid.isNullOrBlank()) {
|
||||||
|
member.activePid = request.marketingPid
|
||||||
|
member.partnerExpirationDatetime = LocalDateTime.now().plusYears(1)
|
||||||
|
}
|
||||||
|
|
||||||
return repository.save(member)
|
return repository.save(member)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -636,4 +646,19 @@ class MemberService(
|
||||||
private fun getOrCreateLock(memberId: Long): ReentrantReadWriteLock {
|
private fun getOrCreateLock(memberId: Long): ReentrantReadWriteLock {
|
||||||
return tokenLocks.computeIfAbsent(memberId) { ReentrantReadWriteLock() }
|
return tokenLocks.computeIfAbsent(memberId) { ReentrantReadWriteLock() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun updateMarketingInfo(memberId: Long, adid: String, pid: String) {
|
||||||
|
val member = repository.findByIdOrNull(id = memberId)
|
||||||
|
?: throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
|
||||||
|
if (adid != member.adid) {
|
||||||
|
member.adid = adid
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pid != member.activePid && pid.isNotBlank()) {
|
||||||
|
member.activePid = pid
|
||||||
|
member.partnerExpirationDatetime = LocalDateTime.now().plusYears(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ data class SignUpRequest(
|
||||||
val password: String,
|
val password: String,
|
||||||
val nickname: String,
|
val nickname: String,
|
||||||
val gender: Gender,
|
val gender: Gender,
|
||||||
|
val marketingPid: String? = null,
|
||||||
val isAgreeTermsOfService: Boolean,
|
val isAgreeTermsOfService: Boolean,
|
||||||
val isAgreePrivacyPolicy: Boolean,
|
val isAgreePrivacyPolicy: Boolean,
|
||||||
val container: String = "api"
|
val container: String = "api"
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package kr.co.vividnext.sodalive.member.signUp
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.member.login.LoginResponse
|
||||||
|
|
||||||
|
data class SignUpResponse(
|
||||||
|
val memberId: Long,
|
||||||
|
val marketingPid: String?,
|
||||||
|
val loginResponse: LoginResponse
|
||||||
|
)
|
Loading…
Reference in New Issue