| @@ -5,6 +5,7 @@ import kr.co.bootpay.Bootpay | ||||
| import kr.co.vividnext.sodalive.can.CanRepository | ||||
| import kr.co.vividnext.sodalive.can.charge.event.ChargeSpringEvent | ||||
| import kr.co.vividnext.sodalive.can.coupon.CanCouponNumberRepository | ||||
| import kr.co.vividnext.sodalive.can.coupon.CouponType | ||||
| import kr.co.vividnext.sodalive.can.payment.Payment | ||||
| import kr.co.vividnext.sodalive.can.payment.PaymentGateway | ||||
| import kr.co.vividnext.sodalive.can.payment.PaymentStatus | ||||
| @@ -12,6 +13,11 @@ import kr.co.vividnext.sodalive.common.SodaException | ||||
| import kr.co.vividnext.sodalive.google.GooglePlayService | ||||
| import kr.co.vividnext.sodalive.member.Member | ||||
| import kr.co.vividnext.sodalive.member.MemberRepository | ||||
| import kr.co.vividnext.sodalive.point.MemberPoint | ||||
| import kr.co.vividnext.sodalive.point.MemberPointRepository | ||||
| import kr.co.vividnext.sodalive.point.PointGrantLog | ||||
| import kr.co.vividnext.sodalive.point.PointGrantLogRepository | ||||
| import kr.co.vividnext.sodalive.useraction.ActionType | ||||
| import okhttp3.MediaType.Companion.toMediaTypeOrNull | ||||
| import okhttp3.OkHttpClient | ||||
| import okhttp3.Request | ||||
| @@ -27,6 +33,7 @@ import org.springframework.stereotype.Service | ||||
| import org.springframework.transaction.annotation.Transactional | ||||
| import java.math.BigDecimal | ||||
| import java.math.RoundingMode | ||||
| import java.time.LocalDateTime | ||||
|  | ||||
| @Service | ||||
| @Transactional(readOnly = true) | ||||
| @@ -36,6 +43,9 @@ class ChargeService( | ||||
|     private val memberRepository: MemberRepository, | ||||
|     private val couponNumberRepository: CanCouponNumberRepository, | ||||
|  | ||||
|     private val grantLogRepository: PointGrantLogRepository, | ||||
|     private val memberPointRepository: MemberPointRepository, | ||||
|  | ||||
|     private val objectMapper: ObjectMapper, | ||||
|     private val okHttpClient: OkHttpClient, | ||||
|     private val applicationEventPublisher: ApplicationEventPublisher, | ||||
| @@ -57,30 +67,63 @@ class ChargeService( | ||||
| ) { | ||||
|  | ||||
|     @Transactional | ||||
|     fun chargeByCoupon(couponNumber: String, member: Member) { | ||||
|     fun chargeByCoupon(couponNumber: String, member: Member): String { | ||||
|         val canCouponNumber = couponNumberRepository.findByCouponNumber(couponNumber = couponNumber) | ||||
|             ?: throw SodaException("잘못된 쿠폰번호입니다.\n고객센터로 문의해 주시기 바랍니다.") | ||||
|  | ||||
|         if (canCouponNumber.member != null) { | ||||
|             throw SodaException("이미 사용한 쿠폰번호 입니다.") | ||||
|         } | ||||
|  | ||||
|         canCouponNumber.member = member | ||||
|  | ||||
|         val coupon = canCouponNumber.canCoupon!! | ||||
|         val couponCharge = Charge(0, coupon.can, status = ChargeStatus.COUPON) | ||||
|         couponCharge.title = "${coupon.can} 캔" | ||||
|         couponCharge.member = member | ||||
|  | ||||
|         val payment = Payment( | ||||
|             status = PaymentStatus.COMPLETE, | ||||
|             paymentGateway = PaymentGateway.PG | ||||
|         ) | ||||
|         payment.method = coupon.couponName | ||||
|         couponCharge.payment = payment | ||||
|         chargeRepository.save(couponCharge) | ||||
|         when (coupon.couponType) { | ||||
|             CouponType.CAN -> { | ||||
|                 val couponCharge = Charge(0, coupon.can, status = ChargeStatus.COUPON) | ||||
|                 couponCharge.title = "${coupon.can} 캔" | ||||
|                 couponCharge.member = member | ||||
|  | ||||
|         member.charge(0, coupon.can, "pg") | ||||
|                 val payment = Payment( | ||||
|                     status = PaymentStatus.COMPLETE, | ||||
|                     paymentGateway = PaymentGateway.PG | ||||
|                 ) | ||||
|                 payment.method = coupon.couponName | ||||
|                 couponCharge.payment = payment | ||||
|                 chargeRepository.save(couponCharge) | ||||
|  | ||||
|                 member.charge(0, coupon.can, "pg") | ||||
|                 return "쿠폰 사용이 완료되었습니다.\n${coupon.can}캔이 지급되었습니다." | ||||
|             } | ||||
|  | ||||
|             CouponType.POINT -> { | ||||
|                 val memberId = member.id!! | ||||
|                 val point = coupon.can | ||||
|                 val actionType = ActionType.COUPON | ||||
|  | ||||
|                 grantLogRepository.save( | ||||
|                     PointGrantLog( | ||||
|                         memberId = memberId, | ||||
|                         point = point, | ||||
|                         actionType = actionType, | ||||
|                         policyId = null, | ||||
|                         orderId = null, | ||||
|                         couponName = coupon.couponName | ||||
|                     ) | ||||
|                 ) | ||||
|  | ||||
|                 memberPointRepository.save( | ||||
|                     MemberPoint( | ||||
|                         memberId = memberId, | ||||
|                         point = point, | ||||
|                         actionType = actionType, | ||||
|                         expiresAt = LocalDateTime.now().plusDays(3) | ||||
|                     ) | ||||
|                 ) | ||||
|  | ||||
|                 return "쿠폰 사용이 완료되었습니다.\n${coupon.can}포인트가 지급되었습니다." | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Transactional | ||||
|   | ||||
| @@ -3,13 +3,21 @@ package kr.co.vividnext.sodalive.can.coupon | ||||
| import kr.co.vividnext.sodalive.common.BaseEntity | ||||
| import java.time.LocalDateTime | ||||
| import javax.persistence.Entity | ||||
| import javax.persistence.EnumType | ||||
| import javax.persistence.Enumerated | ||||
|  | ||||
| @Entity | ||||
| data class CanCoupon( | ||||
|     val couponName: String, | ||||
|     @Enumerated(EnumType.STRING) | ||||
|     val couponType: CouponType, | ||||
|     val can: Int, | ||||
|     val couponCount: Int, | ||||
|     var validity: LocalDateTime, | ||||
|     var isActive: Boolean, | ||||
|     var isMultipleUse: Boolean | ||||
| ) : BaseEntity() | ||||
|  | ||||
| enum class CouponType { | ||||
|     CAN, POINT | ||||
| } | ||||
|   | ||||
| @@ -109,11 +109,11 @@ class CanCouponController(private val service: CanCouponService) { | ||||
|     ) = run { | ||||
|         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||
|  | ||||
|         ApiResponse.ok( | ||||
|             service.useCanCoupon( | ||||
|                 couponNumber = request.couponNumber, | ||||
|                 memberId = member.id!! | ||||
|             ) | ||||
|         val completeMessage = service.useCanCoupon( | ||||
|             couponNumber = request.couponNumber, | ||||
|             memberId = member.id!! | ||||
|         ) | ||||
|  | ||||
|         ApiResponse.ok(Unit, completeMessage) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -79,6 +79,7 @@ class CanCouponNumberQueryRepositoryImpl(private val queryFactory: JPAQueryFacto | ||||
|     override fun findByCouponNumber(couponNumber: String): CanCouponNumber? { | ||||
|         return queryFactory | ||||
|             .selectFrom(canCouponNumber) | ||||
|             .innerJoin(canCouponNumber.canCoupon, canCoupon) | ||||
|             .where(canCouponNumber.couponNumber.eq(couponNumber)) | ||||
|             .fetchFirst() | ||||
|     } | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| package kr.co.vividnext.sodalive.can.coupon | ||||
|  | ||||
| import com.querydsl.core.types.dsl.CaseBuilder | ||||
| import com.querydsl.core.types.dsl.DateTimePath | ||||
| import com.querydsl.core.types.dsl.Expressions | ||||
| import com.querydsl.core.types.dsl.StringTemplate | ||||
| @@ -30,6 +31,9 @@ class CanCouponQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : | ||||
|                 QGetCouponListItemResponse( | ||||
|                     canCoupon.id, | ||||
|                     canCoupon.couponName, | ||||
|                     CaseBuilder() | ||||
|                         .`when`(canCoupon.couponType.eq(CouponType.POINT)).then("포인트 쿠폰") | ||||
|                         .otherwise("캔 쿠폰"), | ||||
|                     canCoupon.can, | ||||
|                     canCoupon.couponCount, | ||||
|                     Expressions.ZERO, | ||||
|   | ||||
| @@ -68,15 +68,12 @@ class CanCouponService( | ||||
|  | ||||
|     fun getCouponList(offset: Long, limit: Long): GetCouponListResponse { | ||||
|         val totalCount = repository.getCouponTotalCount() | ||||
|  | ||||
|         val items = repository.getCouponList(offset = offset, limit = limit) | ||||
|             .asSequence() | ||||
|             .map { | ||||
|                 val useCouponCount = couponNumberRepository.getUseCouponCount(id = it.id) | ||||
|                 it.useCouponCount = useCouponCount | ||||
|                 it | ||||
|             } | ||||
|             .toList() | ||||
|  | ||||
|         return GetCouponListResponse(totalCount, items) | ||||
|     } | ||||
| @@ -124,7 +121,7 @@ class CanCouponService( | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun useCanCoupon(couponNumber: String, memberId: Long) { | ||||
|     fun useCanCoupon(couponNumber: String, memberId: Long): String { | ||||
|         val member = memberRepository.findByIdOrNull(id = memberId) | ||||
|             ?: throw SodaException("로그인 정보를 확인해주세요.") | ||||
|  | ||||
| @@ -132,7 +129,7 @@ class CanCouponService( | ||||
|  | ||||
|         issueService.validateAvailableUseCoupon(couponNumber, memberId) | ||||
|  | ||||
|         chargeService.chargeByCoupon(couponNumber, member) | ||||
|         return chargeService.chargeByCoupon(couponNumber, member) | ||||
|     } | ||||
|  | ||||
|     private fun insertHyphens(input: String): String { | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonProperty | ||||
|  | ||||
| data class GenerateCanCouponRequest( | ||||
|     @JsonProperty("couponName") val couponName: String, | ||||
|     @JsonProperty("couponType") val couponType: CouponType, | ||||
|     @JsonProperty("can") val can: Int, | ||||
|     @JsonProperty("validity") val validity: String, | ||||
|     @JsonProperty("isMultipleUse") val isMultipleUse: Boolean, | ||||
|   | ||||
| @@ -10,6 +10,7 @@ data class GetCouponListResponse( | ||||
| data class GetCouponListItemResponse @QueryProjection constructor( | ||||
|     val id: Long, | ||||
|     val couponName: String, | ||||
|     val couponType: String, | ||||
|     val can: Int, | ||||
|     val couponCount: Int, | ||||
|     var useCouponCount: Int, | ||||
|   | ||||
| @@ -262,7 +262,8 @@ class AudioContentQueryRepositoryImpl( | ||||
|                     audioContent.releaseDate.gt(LocalDateTime.now()), | ||||
|                     Expressions.asBoolean(false), | ||||
|                     Expressions.asBoolean(false), | ||||
|                     audioContent.remaining.loe(0) | ||||
|                     audioContent.remaining.loe(0), | ||||
|                     audioContent.isPointAvailable | ||||
|                 ) | ||||
|             ) | ||||
|             .from(audioContent) | ||||
| @@ -488,7 +489,8 @@ class AudioContentQueryRepositoryImpl( | ||||
|                     member.profileImage.prepend("/").prepend(imageHost), | ||||
|                     member.nickname, | ||||
|                     audioContent.price, | ||||
|                     audioContent.duration | ||||
|                     audioContent.duration, | ||||
|                     audioContent.isPointAvailable | ||||
|                 ) | ||||
|             ) | ||||
|             .from(audioContent) | ||||
| @@ -557,7 +559,8 @@ class AudioContentQueryRepositoryImpl( | ||||
|                     member.profileImage.prepend("/").prepend(imageHost), | ||||
|                     member.nickname, | ||||
|                     audioContent.price, | ||||
|                     audioContent.duration | ||||
|                     audioContent.duration, | ||||
|                     audioContent.isPointAvailable | ||||
|                 ) | ||||
|             ) | ||||
|             .from(audioContent) | ||||
| @@ -776,7 +779,8 @@ class AudioContentQueryRepositoryImpl( | ||||
|                     member.profileImage.prepend("/").prepend(cloudfrontHost), | ||||
|                     member.nickname, | ||||
|                     audioContent.price, | ||||
|                     audioContent.duration | ||||
|                     audioContent.duration, | ||||
|                     audioContent.isPointAvailable | ||||
|                 ) | ||||
|             ) | ||||
|             .from(audioContent) | ||||
| @@ -896,7 +900,8 @@ class AudioContentQueryRepositoryImpl( | ||||
|                         .prepend(imageHost), | ||||
|                     member.nickname, | ||||
|                     audioContent.price, | ||||
|                     audioContent.duration | ||||
|                     audioContent.duration, | ||||
|                     audioContent.isPointAvailable | ||||
|                 ) | ||||
|             ) | ||||
|             .from(audioContent) | ||||
| @@ -956,7 +961,8 @@ class AudioContentQueryRepositoryImpl( | ||||
|                     member.profileImage.prepend("/").prepend(imageHost), | ||||
|                     member.nickname, | ||||
|                     audioContent.price, | ||||
|                     audioContent.duration | ||||
|                     audioContent.duration, | ||||
|                     audioContent.isPointAvailable | ||||
|                 ) | ||||
|             ) | ||||
|             .from(audioContentCurationItem) | ||||
| @@ -1018,6 +1024,7 @@ class AudioContentQueryRepositoryImpl( | ||||
|                     audioContent.duration, | ||||
|                     member.id, | ||||
|                     member.nickname, | ||||
|                     audioContent.isPointAvailable, | ||||
|                     member.profileImage.prepend("/").prepend(imageHost) | ||||
|                 ) | ||||
|             ) | ||||
|   | ||||
| @@ -21,5 +21,6 @@ data class GetAudioContentListItem @QueryProjection constructor( | ||||
|     val isScheduledToOpen: Boolean, | ||||
|     var isRented: Boolean, | ||||
|     var isOwned: Boolean, | ||||
|     var isSoldOut: Boolean | ||||
|     var isSoldOut: Boolean, | ||||
|     val isPointAvailable: Boolean | ||||
| ) | ||||
|   | ||||
| @@ -11,5 +11,6 @@ data class GetAudioContentMainItem @QueryProjection constructor( | ||||
|     @JsonProperty("creatorProfileImageUrl") val creatorProfileImageUrl: String, | ||||
|     @JsonProperty("creatorNickname") val creatorNickname: String, | ||||
|     @JsonProperty("price") val price: Int, | ||||
|     @JsonProperty("duration") val duration: String | ||||
|     @JsonProperty("duration") val duration: String, | ||||
|     @JsonProperty("isPointAvailable") val isPointAvailable: Boolean | ||||
| ) | ||||
|   | ||||
| @@ -18,5 +18,6 @@ data class GetAudioContentRankingItem @QueryProjection constructor( | ||||
|     @JsonProperty("duration") val duration: String, | ||||
|     @JsonProperty("creatorId") val creatorId: Long, | ||||
|     @JsonProperty("creatorNickname") val creatorNickname: String, | ||||
|     @JsonProperty("isPointAvailable") val isPointAvailable: Boolean, | ||||
|     @JsonProperty("creatorProfileImageUrl") val creatorProfileImageUrl: String | ||||
| ) | ||||
|   | ||||
| @@ -94,7 +94,8 @@ class AudioContentCurationQueryRepository(private val queryFactory: JPAQueryFact | ||||
|                     member.profileImage.prepend("/").prepend(cloudfrontHost), | ||||
|                     member.nickname, | ||||
|                     audioContent.price, | ||||
|                     audioContent.duration | ||||
|                     audioContent.duration, | ||||
|                     audioContent.isPointAvailable | ||||
|                 ) | ||||
|             ) | ||||
|             .from(audioContent) | ||||
|   | ||||
| @@ -97,7 +97,8 @@ class ContentMainTabTagCurationRepository( | ||||
|                     member.profileImage.prepend("/").prepend(imageHost), | ||||
|                     member.nickname, | ||||
|                     audioContent.price, | ||||
|                     audioContent.duration | ||||
|                     audioContent.duration, | ||||
|                     audioContent.isPointAvailable | ||||
|                 ) | ||||
|             ) | ||||
|             .from(contentHashTagCurationItem) | ||||
|   | ||||
| @@ -210,7 +210,8 @@ class OrderQueryRepositoryImpl( | ||||
|                         .prepend(coverImageHost), | ||||
|                     member.nickname, | ||||
|                     audioContent.price, | ||||
|                     audioContent.duration | ||||
|                     audioContent.duration, | ||||
|                     audioContent.isPointAvailable | ||||
|                 ) | ||||
|             ) | ||||
|             .from(order) | ||||
|   | ||||
| @@ -124,7 +124,8 @@ class ContentSeriesContentQueryRepositoryImpl( | ||||
|                     audioContent.duration, | ||||
|                     audioContent.price, | ||||
|                     Expressions.asBoolean(false), | ||||
|                     Expressions.asBoolean(false) | ||||
|                     Expressions.asBoolean(false), | ||||
|                     audioContent.isPointAvailable | ||||
|                 ) | ||||
|             ) | ||||
|             .from(seriesContent) | ||||
|   | ||||
| @@ -15,5 +15,6 @@ data class GetSeriesContentListItem @QueryProjection constructor( | ||||
|     val duration: String, | ||||
|     val price: Int, | ||||
|     var isRented: Boolean, | ||||
|     var isOwned: Boolean | ||||
|     var isOwned: Boolean, | ||||
|     val isPointAvailable: Boolean | ||||
| ) | ||||
|   | ||||
| @@ -13,5 +13,6 @@ data class PointGrantLog( | ||||
|     @Enumerated(EnumType.STRING) | ||||
|     val actionType: ActionType, | ||||
|     val policyId: Long?, | ||||
|     val orderId: Long? | ||||
|     val orderId: Long?, | ||||
|     val couponName: String? = null | ||||
| ) : BaseEntity() | ||||
|   | ||||
| @@ -63,11 +63,11 @@ class PointGrantLogQueryRepositoryImpl( | ||||
|                 QGetPointRewardStatusResponse( | ||||
|                     pointGrantLog.point.stringValue().concat(" 포인트"), | ||||
|                     formattedDate, | ||||
|                     pointRewardPolicy.title | ||||
|                     pointRewardPolicy.title.coalesce(pointGrantLog.couponName) | ||||
|                 ) | ||||
|             ) | ||||
|             .from(pointGrantLog) | ||||
|             .innerJoin(pointRewardPolicy).on(pointGrantLog.policyId.eq(pointRewardPolicy.id)) | ||||
|             .leftJoin(pointRewardPolicy).on(pointGrantLog.policyId.eq(pointRewardPolicy.id)) | ||||
|             .where(pointGrantLog.memberId.eq(memberId)) | ||||
|             .orderBy(pointGrantLog.id.desc()) | ||||
|             .fetch() | ||||
|   | ||||
| @@ -102,6 +102,7 @@ class RankingRepository( | ||||
|                     audioContent.duration, | ||||
|                     member.id, | ||||
|                     member.nickname, | ||||
|                     audioContent.isPointAvailable, | ||||
|                     member.profileImage.prepend("/").prepend(imageHost) | ||||
|                 ) | ||||
|             ) | ||||
| @@ -560,6 +561,7 @@ class RankingRepository( | ||||
|                     audioContent.duration, | ||||
|                     member.id, | ||||
|                     member.nickname, | ||||
|                     audioContent.isPointAvailable, | ||||
|                     member.profileImage.prepend("/").prepend(imageHost) | ||||
|                 ) | ||||
|             ) | ||||
| @@ -726,6 +728,7 @@ class RankingRepository( | ||||
|                     audioContent.duration, | ||||
|                     member.id, | ||||
|                     member.nickname, | ||||
|                     audioContent.isPointAvailable, | ||||
|                     member.profileImage.prepend("/").prepend(imageHost) | ||||
|                 ) | ||||
|             ) | ||||
| @@ -782,6 +785,7 @@ class RankingRepository( | ||||
|                     audioContent.duration, | ||||
|                     member.id, | ||||
|                     member.nickname, | ||||
|                     audioContent.isPointAvailable, | ||||
|                     member.profileImage.prepend("/").prepend(imageHost) | ||||
|                 ) | ||||
|             ) | ||||
|   | ||||
| @@ -5,5 +5,6 @@ enum class ActionType(val displayName: String) { | ||||
|     USER_AUTHENTICATION("본인인증"), | ||||
|     CONTENT_COMMENT("콘텐츠 댓글"), | ||||
|     ORDER_CONTENT_COMMENT("구매한 콘텐츠 댓글"), | ||||
|     LIVE_CONTINUOUS_LISTEN_30("라이브 연속 청취 30분") | ||||
|     LIVE_CONTINUOUS_LISTEN_30("라이브 연속 청취 30분"), | ||||
|     COUPON("쿠폰") | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user