test #14
@@ -9,6 +9,7 @@ import kr.co.vividnext.sodalive.can.payment.QPayment.payment
 | 
				
			|||||||
import kr.co.vividnext.sodalive.member.QMember.member
 | 
					import kr.co.vividnext.sodalive.member.QMember.member
 | 
				
			||||||
import org.springframework.data.jpa.repository.JpaRepository
 | 
					import org.springframework.data.jpa.repository.JpaRepository
 | 
				
			||||||
import org.springframework.stereotype.Repository
 | 
					import org.springframework.stereotype.Repository
 | 
				
			||||||
 | 
					import java.time.LocalDateTime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Repository
 | 
					@Repository
 | 
				
			||||||
interface ChargeRepository : JpaRepository<Charge, Long>, ChargeQueryRepository
 | 
					interface ChargeRepository : JpaRepository<Charge, Long>, ChargeQueryRepository
 | 
				
			||||||
@@ -16,6 +17,7 @@ interface ChargeRepository : JpaRepository<Charge, Long>, ChargeQueryRepository
 | 
				
			|||||||
interface ChargeQueryRepository {
 | 
					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(date: LocalDateTime): Int
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ChargeQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : ChargeQueryRepository {
 | 
					class ChargeQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : ChargeQueryRepository {
 | 
				
			||||||
@@ -59,6 +61,10 @@ class ChargeQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : Cha
 | 
				
			|||||||
            .fetchFirst()
 | 
					            .fetchFirst()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun getChargeCountAfterDate(date: LocalDateTime): Int {
 | 
				
			||||||
 | 
					        return 0
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun getPaymentGatewayCondition(container: String): BooleanExpression? {
 | 
					    private fun getPaymentGatewayCondition(container: String): BooleanExpression? {
 | 
				
			||||||
        val paymentGatewayCondition = when (container) {
 | 
					        val paymentGatewayCondition = when (container) {
 | 
				
			||||||
            "aos" -> {
 | 
					            "aos" -> {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.can.charge
 | 
				
			|||||||
import com.fasterxml.jackson.databind.ObjectMapper
 | 
					import com.fasterxml.jackson.databind.ObjectMapper
 | 
				
			||||||
import kr.co.bootpay.Bootpay
 | 
					import kr.co.bootpay.Bootpay
 | 
				
			||||||
import kr.co.vividnext.sodalive.can.CanRepository
 | 
					import kr.co.vividnext.sodalive.can.CanRepository
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.can.charge.event.ChargeSpringEvent
 | 
				
			||||||
import kr.co.vividnext.sodalive.can.payment.Payment
 | 
					import kr.co.vividnext.sodalive.can.payment.Payment
 | 
				
			||||||
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
 | 
					import kr.co.vividnext.sodalive.can.payment.PaymentGateway
 | 
				
			||||||
import kr.co.vividnext.sodalive.can.payment.PaymentStatus
 | 
					import kr.co.vividnext.sodalive.can.payment.PaymentStatus
 | 
				
			||||||
@@ -15,6 +16,7 @@ import okhttp3.Request
 | 
				
			|||||||
import okhttp3.RequestBody.Companion.toRequestBody
 | 
					import okhttp3.RequestBody.Companion.toRequestBody
 | 
				
			||||||
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.data.repository.findByIdOrNull
 | 
					import org.springframework.data.repository.findByIdOrNull
 | 
				
			||||||
import org.springframework.http.HttpHeaders
 | 
					import org.springframework.http.HttpHeaders
 | 
				
			||||||
import org.springframework.security.core.userdetails.User
 | 
					import org.springframework.security.core.userdetails.User
 | 
				
			||||||
@@ -29,6 +31,8 @@ class ChargeService(
 | 
				
			|||||||
    private val memberRepository: MemberRepository,
 | 
					    private val memberRepository: MemberRepository,
 | 
				
			||||||
    private val objectMapper: ObjectMapper,
 | 
					    private val objectMapper: ObjectMapper,
 | 
				
			||||||
    private val okHttpClient: OkHttpClient,
 | 
					    private val okHttpClient: OkHttpClient,
 | 
				
			||||||
 | 
					    private val applicationEventPublisher: ApplicationEventPublisher,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Value("\${bootpay.application-id}")
 | 
					    @Value("\${bootpay.application-id}")
 | 
				
			||||||
    private val bootpayApplicationId: String,
 | 
					    private val bootpayApplicationId: String,
 | 
				
			||||||
    @Value("\${bootpay.private-key}")
 | 
					    @Value("\${bootpay.private-key}")
 | 
				
			||||||
@@ -80,6 +84,8 @@ class ChargeService(
 | 
				
			|||||||
                    charge.payment?.method = verifyResult.method
 | 
					                    charge.payment?.method = verifyResult.method
 | 
				
			||||||
                    charge.payment?.status = PaymentStatus.COMPLETE
 | 
					                    charge.payment?.status = PaymentStatus.COMPLETE
 | 
				
			||||||
                    member.charge(charge.chargeCan, charge.rewardCan, "pg")
 | 
					                    member.charge(charge.chargeCan, charge.rewardCan, "pg")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    applicationEventPublisher.publishEvent(ChargeSpringEvent(chargeId = charge.id!!, member = member))
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    throw SodaException("결제정보에 오류가 있습니다.")
 | 
					                    throw SodaException("결제정보에 오류가 있습니다.")
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -127,6 +133,8 @@ class ChargeService(
 | 
				
			|||||||
                charge.payment?.method = "애플(인 앱 결제)"
 | 
					                charge.payment?.method = "애플(인 앱 결제)"
 | 
				
			||||||
                charge.payment?.status = PaymentStatus.COMPLETE
 | 
					                charge.payment?.status = PaymentStatus.COMPLETE
 | 
				
			||||||
                member.charge(charge.chargeCan, charge.rewardCan, "ios")
 | 
					                member.charge(charge.chargeCan, charge.rewardCan, "ios")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                applicationEventPublisher.publishEvent(ChargeSpringEvent(chargeId = charge.id!!, member = member))
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                throw SodaException("결제정보에 오류가 있습니다.")
 | 
					                throw SodaException("결제정보에 오류가 있습니다.")
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					package kr.co.vividnext.sodalive.can.charge.event
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.querydsl.jpa.impl.JPAQueryFactory
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.admin.event.ChargeEvent
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.admin.event.QChargeEvent.chargeEvent
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.can.charge.QCharge.charge
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.can.payment.PaymentStatus
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.can.payment.QPayment.payment
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.member.Member
 | 
				
			||||||
 | 
					import org.springframework.data.jpa.repository.JpaRepository
 | 
				
			||||||
 | 
					import java.time.LocalDateTime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ChargeEventRepository : JpaRepository<ChargeEvent, Long>, ChargeEventQueryRepository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ChargeEventQueryRepository {
 | 
				
			||||||
 | 
					    fun getChargeEvent(): ChargeEvent?
 | 
				
			||||||
 | 
					    fun getPaymentCount(member: Member, method: String, startDate: LocalDateTime, endDate: LocalDateTime): Int
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ChargeEventQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : ChargeEventQueryRepository {
 | 
				
			||||||
 | 
					    override fun getChargeEvent(): ChargeEvent? {
 | 
				
			||||||
 | 
					        val now = LocalDateTime.now()
 | 
				
			||||||
 | 
					        return queryFactory
 | 
				
			||||||
 | 
					            .selectFrom(chargeEvent)
 | 
				
			||||||
 | 
					            .where(
 | 
				
			||||||
 | 
					                chargeEvent.isActive.isTrue
 | 
				
			||||||
 | 
					                    .and(chargeEvent.startDate.loe(now))
 | 
				
			||||||
 | 
					                    .and(chargeEvent.endDate.goe(now))
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .orderBy(chargeEvent.id.asc())
 | 
				
			||||||
 | 
					            .fetchFirst()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun getPaymentCount(
 | 
				
			||||||
 | 
					        member: Member,
 | 
				
			||||||
 | 
					        method: String,
 | 
				
			||||||
 | 
					        startDate: LocalDateTime,
 | 
				
			||||||
 | 
					        endDate: LocalDateTime
 | 
				
			||||||
 | 
					    ): Int {
 | 
				
			||||||
 | 
					        val where = charge.member.eq(member)
 | 
				
			||||||
 | 
					            .and(charge.payment.method.eq(method))
 | 
				
			||||||
 | 
					            .and(
 | 
				
			||||||
 | 
					                charge.payment.status.eq(PaymentStatus.COMPLETE)
 | 
				
			||||||
 | 
					                    .or(charge.payment.status.eq(PaymentStatus.RETURN))
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return queryFactory
 | 
				
			||||||
 | 
					            .selectFrom(charge)
 | 
				
			||||||
 | 
					            .innerJoin(charge.payment, payment)
 | 
				
			||||||
 | 
					            .where(where)
 | 
				
			||||||
 | 
					            .fetch()
 | 
				
			||||||
 | 
					            .count()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,118 @@
 | 
				
			|||||||
 | 
					package kr.co.vividnext.sodalive.can.charge.event
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.can.charge.Charge
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.can.charge.ChargeRepository
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.can.charge.ChargeStatus
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.can.payment.Payment
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.can.payment.PaymentGateway
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.can.payment.PaymentStatus
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.common.SodaException
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.fcm.FcmEvent
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.fcm.FcmEventType
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.member.Member
 | 
				
			||||||
 | 
					import org.springframework.context.ApplicationEventPublisher
 | 
				
			||||||
 | 
					import org.springframework.data.repository.findByIdOrNull
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Service
 | 
				
			||||||
 | 
					import org.springframework.transaction.annotation.Transactional
 | 
				
			||||||
 | 
					import kotlin.math.ceil
 | 
				
			||||||
 | 
					import kotlin.math.round
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Service
 | 
				
			||||||
 | 
					class ChargeEventService(
 | 
				
			||||||
 | 
					    private val repository: ChargeEventRepository,
 | 
				
			||||||
 | 
					    private val chargeRepository: ChargeRepository,
 | 
				
			||||||
 | 
					    private val chargeEventRepository: ChargeEventRepository,
 | 
				
			||||||
 | 
					    private val applicationEventPublisher: ApplicationEventPublisher
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    @Transactional
 | 
				
			||||||
 | 
					    fun applyChargeEvent(chargeId: Long, member: Member) {
 | 
				
			||||||
 | 
					        val charge = chargeRepository.findByIdOrNull(chargeId)
 | 
				
			||||||
 | 
					            ?: throw SodaException("이벤트가 적용되지 않았습니다.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (member.auth != null) {
 | 
				
			||||||
 | 
					            val authDate = member.auth!!.createdAt!!
 | 
				
			||||||
 | 
					            val chargeCount = chargeRepository.getChargeCountAfterDate(authDate)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (chargeCount > 0) {
 | 
				
			||||||
 | 
					                applyOtherEvent(charge, member)
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                applyFirstChargeEvent(charge, member)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            applyOtherEvent(charge, member)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun applyOtherEvent(charge: Charge, member: Member) {
 | 
				
			||||||
 | 
					        val chargeEvent = repository.getChargeEvent()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (chargeEvent != null) {
 | 
				
			||||||
 | 
					            val eventChargeCount = chargeEventRepository.getPaymentCount(
 | 
				
			||||||
 | 
					                member = member,
 | 
				
			||||||
 | 
					                method = chargeEvent.title,
 | 
				
			||||||
 | 
					                startDate = chargeEvent.startDate,
 | 
				
			||||||
 | 
					                endDate = chargeEvent.endDate
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (eventChargeCount < chargeEvent.availableCount) {
 | 
				
			||||||
 | 
					                val additionalCan = round(charge.chargeCan * chargeEvent.addPercent).toInt()
 | 
				
			||||||
 | 
					                applyEvent(
 | 
				
			||||||
 | 
					                    additionalCan = additionalCan,
 | 
				
			||||||
 | 
					                    member = member,
 | 
				
			||||||
 | 
					                    paymentGateway = charge.payment?.paymentGateway!!,
 | 
				
			||||||
 | 
					                    method = chargeEvent.title
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                applicationEventPublisher.publishEvent(
 | 
				
			||||||
 | 
					                    FcmEvent(
 | 
				
			||||||
 | 
					                        type = FcmEventType.INDIVIDUAL,
 | 
				
			||||||
 | 
					                        title = chargeEvent.title,
 | 
				
			||||||
 | 
					                        message = "$additionalCan 캔이 추가 지급되었습니다.",
 | 
				
			||||||
 | 
					                        recipients = listOf(member.id!!),
 | 
				
			||||||
 | 
					                        isAuth = false
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun applyFirstChargeEvent(charge: Charge, member: Member) {
 | 
				
			||||||
 | 
					        val additionalCan = ceil(charge.chargeCan * 0.2).toInt()
 | 
				
			||||||
 | 
					        applyEvent(
 | 
				
			||||||
 | 
					            additionalCan = additionalCan,
 | 
				
			||||||
 | 
					            member = member,
 | 
				
			||||||
 | 
					            paymentGateway = charge.payment?.paymentGateway!!,
 | 
				
			||||||
 | 
					            method = "첫 충전 이벤트"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        applicationEventPublisher.publishEvent(
 | 
				
			||||||
 | 
					            FcmEvent(
 | 
				
			||||||
 | 
					                type = FcmEventType.INDIVIDUAL,
 | 
				
			||||||
 | 
					                title = "첫 충전 이벤트",
 | 
				
			||||||
 | 
					                message = "$additionalCan 캔이 추가 지급되었습니다.",
 | 
				
			||||||
 | 
					                recipients = listOf(member.id!!),
 | 
				
			||||||
 | 
					                isAuth = false
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun applyEvent(additionalCan: Int, member: Member, paymentGateway: PaymentGateway, method: String) {
 | 
				
			||||||
 | 
					        val eventCharge = Charge(0, additionalCan, status = ChargeStatus.EVENT)
 | 
				
			||||||
 | 
					        eventCharge.title = "$additionalCan 캔"
 | 
				
			||||||
 | 
					        eventCharge.member = member
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val payment = Payment(
 | 
				
			||||||
 | 
					            status = PaymentStatus.COMPLETE,
 | 
				
			||||||
 | 
					            paymentGateway = paymentGateway
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        payment.method = method
 | 
				
			||||||
 | 
					        eventCharge.payment = payment
 | 
				
			||||||
 | 
					        chargeRepository.save(eventCharge)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        when (paymentGateway) {
 | 
				
			||||||
 | 
					            PaymentGateway.PG -> member.charge(0, additionalCan, "pg")
 | 
				
			||||||
 | 
					            PaymentGateway.GOOGLE_IAP -> member.charge(0, additionalCan, "aos")
 | 
				
			||||||
 | 
					            PaymentGateway.APPLE_IAP -> member.charge(0, additionalCan, "ios")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					package kr.co.vividnext.sodalive.can.charge.event
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.member.Member
 | 
				
			||||||
 | 
					import org.springframework.context.event.EventListener
 | 
				
			||||||
 | 
					import org.springframework.scheduling.annotation.Async
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Component
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ChargeSpringEvent(
 | 
				
			||||||
 | 
					    val chargeId: Long,
 | 
				
			||||||
 | 
					    val member: Member
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Component
 | 
				
			||||||
 | 
					class ChargeSpringEventListener(
 | 
				
			||||||
 | 
					    private val chargeEventService: ChargeEventService
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    @Async
 | 
				
			||||||
 | 
					    @EventListener
 | 
				
			||||||
 | 
					    fun applyChargeEvent(event: ChargeSpringEvent) {
 | 
				
			||||||
 | 
					        chargeEventService.applyChargeEvent(event.chargeId, event.member)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user