캔 쿠폰 시스템 #107
|
@ -44,6 +44,7 @@ dependencies {
|
|||
kapt("org.springframework.boot:spring-boot-configuration-processor")
|
||||
|
||||
// aws
|
||||
implementation("com.amazonaws:aws-java-sdk-sqs:1.12.380")
|
||||
implementation("com.amazonaws:aws-java-sdk-ses:1.12.380")
|
||||
implementation("com.amazonaws:aws-java-sdk-s3:1.12.380")
|
||||
implementation("com.amazonaws:aws-java-sdk-cloudfront:1.12.380")
|
||||
|
@ -58,6 +59,8 @@ dependencies {
|
|||
// firebase admin sdk
|
||||
implementation("com.google.firebase:firebase-admin:9.2.0")
|
||||
|
||||
implementation("org.apache.poi:poi-ooxml:5.2.3")
|
||||
|
||||
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
||||
runtimeOnly("com.h2database:h2")
|
||||
runtimeOnly("com.mysql:mysql-connector-j")
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package kr.co.vividnext.sodalive.aws.sqs
|
||||
|
||||
import org.springframework.context.event.EventListener
|
||||
import org.springframework.scheduling.annotation.Async
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
enum class SqsEventType {
|
||||
GENERATE_COUPON
|
||||
}
|
||||
|
||||
class SqsEvent(
|
||||
val type: SqsEventType,
|
||||
val message: String
|
||||
)
|
||||
|
||||
@Component
|
||||
class SqsEventListener(private val sqsService: SqsService) {
|
||||
@Async
|
||||
@EventListener
|
||||
fun sendMessage(sqsEvent: SqsEvent) {
|
||||
when (sqsEvent.type) {
|
||||
SqsEventType.GENERATE_COUPON -> sqsService.sendGenerateCouponMessage(sqsEvent.message)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package kr.co.vividnext.sodalive.aws.sqs
|
||||
|
||||
import com.amazonaws.services.sqs.AmazonSQS
|
||||
import com.amazonaws.services.sqs.model.SendMessageRequest
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class SqsService(
|
||||
private val amazonSQS: AmazonSQS,
|
||||
|
||||
@Value("\${cloud.aws.sqs.generate-coupon-url}")
|
||||
private val generateCouponQueueUrl: String
|
||||
) {
|
||||
fun sendGenerateCouponMessage(message: String) {
|
||||
val request = SendMessageRequest()
|
||||
.withQueueUrl(generateCouponQueueUrl)
|
||||
.withMessageBody(message)
|
||||
|
||||
amazonSQS.sendMessage(request)
|
||||
}
|
||||
}
|
|
@ -111,6 +111,10 @@ class CanService(private val repository: CanRepository) {
|
|||
"제휴보상"
|
||||
}
|
||||
|
||||
ChargeStatus.COUPON -> {
|
||||
it.payment!!.method ?: "쿠폰충전"
|
||||
}
|
||||
|
||||
else -> {
|
||||
"환불"
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
|
|||
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.payment.Payment
|
||||
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
|
||||
import kr.co.vividnext.sodalive.can.payment.PaymentStatus
|
||||
|
@ -29,6 +30,8 @@ class ChargeService(
|
|||
private val chargeRepository: ChargeRepository,
|
||||
private val canRepository: CanRepository,
|
||||
private val memberRepository: MemberRepository,
|
||||
private val couponNumberRepository: CanCouponNumberRepository,
|
||||
|
||||
private val objectMapper: ObjectMapper,
|
||||
private val okHttpClient: OkHttpClient,
|
||||
private val applicationEventPublisher: ApplicationEventPublisher,
|
||||
|
@ -43,6 +46,33 @@ class ChargeService(
|
|||
private val appleInAppVerifyUrl: String
|
||||
) {
|
||||
|
||||
@Transactional
|
||||
fun chargeByCoupon(couponNumber: String, member: Member) {
|
||||
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)
|
||||
|
||||
member.charge(0, coupon.can, "pg")
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun charge(member: Member, request: ChargeRequest): ChargeResponse {
|
||||
val can = canRepository.findByIdOrNull(request.canId)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package kr.co.vividnext.sodalive.can.charge
|
||||
|
||||
enum class ChargeStatus {
|
||||
CHARGE, REFUND_CHARGE, EVENT, CANCEL,
|
||||
CHARGE, REFUND_CHARGE, EVENT, COUPON, CANCEL,
|
||||
|
||||
// 관리자 지급
|
||||
ADMIN,
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package kr.co.vividnext.sodalive.can.coupon
|
||||
|
||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||
import java.time.LocalDateTime
|
||||
import javax.persistence.Entity
|
||||
|
||||
@Entity
|
||||
data class CanCoupon(
|
||||
val couponName: String,
|
||||
val can: Int,
|
||||
val couponCount: Int,
|
||||
val validity: LocalDateTime,
|
||||
val isActive: Boolean,
|
||||
val isMultipleUse: Boolean
|
||||
) : BaseEntity()
|
|
@ -0,0 +1,107 @@
|
|||
package kr.co.vividnext.sodalive.can.coupon
|
||||
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import org.springframework.core.io.InputStreamResource
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import java.net.URLEncoder
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/can/coupon")
|
||||
class CanCouponController(private val service: CanCouponService) {
|
||||
@PostMapping
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun generateCoupon(
|
||||
@RequestBody request: GenerateCanCouponRequest,
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
|
||||
ApiResponse.ok(service.generateCoupon(request))
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun getCouponList(
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
||||
pageable: Pageable
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
|
||||
ApiResponse.ok(service.getCouponList(offset = pageable.offset, limit = pageable.pageSize.toLong()))
|
||||
}
|
||||
|
||||
@GetMapping("/number-list")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun getCouponNumberList(
|
||||
@RequestParam couponId: Long,
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
||||
pageable: Pageable
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
|
||||
ApiResponse.ok(
|
||||
service.getCouponNumberList(
|
||||
couponId = couponId,
|
||||
offset = pageable.offset,
|
||||
limit = pageable.pageSize.toLong()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@GetMapping("/number-list/download")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun downloadCouponNumberList(
|
||||
@RequestParam couponId: Long,
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
|
||||
val fileName = "쿠폰번호리스트.xlsx"
|
||||
val encodedFileName = URLEncoder.encode(
|
||||
fileName,
|
||||
StandardCharsets.UTF_8.toString()
|
||||
).replace("+", "%20")
|
||||
val contentDisposition = "attachment; filename*=UTF-8''$encodedFileName"
|
||||
val headers = HttpHeaders().apply {
|
||||
add(HttpHeaders.CONTENT_DISPOSITION, contentDisposition)
|
||||
}
|
||||
|
||||
val response = service.downloadCouponNumberList(couponId)
|
||||
ResponseEntity.ok()
|
||||
.headers(headers)
|
||||
.contentType(
|
||||
MediaType
|
||||
.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
)
|
||||
.body(InputStreamResource(response))
|
||||
}
|
||||
|
||||
@PostMapping("/use")
|
||||
fun useCanCoupon(
|
||||
@RequestBody request: UseCanCouponRequest,
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
|
||||
ApiResponse.ok(
|
||||
service.useCanCoupon(
|
||||
couponNumber = request.couponNumber,
|
||||
memberId = member.id!!
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package kr.co.vividnext.sodalive.can.coupon
|
||||
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class CanCouponIssueService(private val couponNumberRepository: CanCouponNumberRepository) {
|
||||
fun validateAvailableUseCoupon(couponNumber: String, memberId: Long) {
|
||||
val canCouponNumber = checkCanCouponNumber(couponNumber)
|
||||
|
||||
if (!isMultipleUse(canCouponNumber)) {
|
||||
val canCouponNumberList = couponNumberRepository.findByMemberId(memberId = memberId)
|
||||
if (canCouponNumberList.isNotEmpty()) {
|
||||
throw SodaException("해당 쿠폰은 1회만 충전이 가능합니다.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkCanCouponNumber(couponNumber: String): CanCouponNumber {
|
||||
val canCouponNumber = couponNumberRepository.findByCouponNumber(couponNumber = couponNumber)
|
||||
?: throw SodaException("잘못된 쿠폰번호입니다.\n고객센터로 문의해 주시기 바랍니다.")
|
||||
|
||||
if (canCouponNumber.member != null) {
|
||||
throw SodaException("이미 사용한 쿠폰번호 입니다.")
|
||||
}
|
||||
|
||||
return canCouponNumber
|
||||
}
|
||||
|
||||
private fun isMultipleUse(canCouponNumber: CanCouponNumber) = canCouponNumber.canCoupon!!.isMultipleUse
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package kr.co.vividnext.sodalive.can.coupon
|
||||
|
||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import javax.persistence.Entity
|
||||
import javax.persistence.FetchType
|
||||
import javax.persistence.JoinColumn
|
||||
import javax.persistence.ManyToOne
|
||||
import javax.persistence.OneToOne
|
||||
|
||||
@Entity
|
||||
data class CanCouponNumber(
|
||||
val couponNumber: String
|
||||
) : BaseEntity() {
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "can_coupon_id", nullable = false)
|
||||
var canCoupon: CanCoupon? = null
|
||||
|
||||
@OneToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "member_id", nullable = true)
|
||||
var member: Member? = null
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
package kr.co.vividnext.sodalive.can.coupon
|
||||
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
import kr.co.vividnext.sodalive.can.coupon.QCanCouponNumber.canCouponNumber
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
|
||||
interface CanCouponNumberRepository : JpaRepository<CanCouponNumber, Long>, CanCouponNumberQueryRepository
|
||||
|
||||
interface CanCouponNumberQueryRepository {
|
||||
fun getUseCouponCount(id: Long): Int
|
||||
|
||||
fun getCouponNumberTotalCount(couponId: Long): Int
|
||||
|
||||
fun getCouponNumberList(couponId: Long, offset: Long, limit: Long): List<GetCouponNumberListItemResponse>
|
||||
|
||||
fun getAllCouponNumberList(couponId: Long): List<GetCouponNumberListItemResponse>
|
||||
|
||||
fun findByCouponNumber(couponNumber: String): CanCouponNumber?
|
||||
|
||||
fun findByMemberId(memberId: Long): List<Long>
|
||||
}
|
||||
|
||||
class CanCouponNumberQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : CanCouponNumberQueryRepository {
|
||||
override fun getUseCouponCount(id: Long): Int {
|
||||
return queryFactory
|
||||
.select(canCouponNumber.id)
|
||||
.from(canCouponNumber)
|
||||
.where(canCouponNumber.member.isNotNull)
|
||||
.fetch()
|
||||
.size
|
||||
}
|
||||
|
||||
override fun getCouponNumberTotalCount(couponId: Long): Int {
|
||||
return queryFactory
|
||||
.select(canCouponNumber.id)
|
||||
.from(canCouponNumber)
|
||||
.where(canCouponNumber.canCoupon.id.eq(couponId))
|
||||
.fetch()
|
||||
.size
|
||||
}
|
||||
|
||||
override fun getCouponNumberList(couponId: Long, offset: Long, limit: Long): List<GetCouponNumberListItemResponse> {
|
||||
return queryFactory
|
||||
.select(
|
||||
QGetCouponNumberListItemResponse(
|
||||
canCouponNumber.id,
|
||||
canCouponNumber.couponNumber,
|
||||
canCouponNumber.member.isNotNull
|
||||
)
|
||||
)
|
||||
.from(canCouponNumber)
|
||||
.where(canCouponNumber.canCoupon.id.eq(couponId))
|
||||
.orderBy(canCouponNumber.id.asc())
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
.fetch()
|
||||
}
|
||||
|
||||
override fun getAllCouponNumberList(couponId: Long): List<GetCouponNumberListItemResponse> {
|
||||
return queryFactory
|
||||
.select(
|
||||
QGetCouponNumberListItemResponse(
|
||||
canCouponNumber.id,
|
||||
canCouponNumber.couponNumber,
|
||||
canCouponNumber.member.isNotNull
|
||||
)
|
||||
)
|
||||
.from(canCouponNumber)
|
||||
.where(canCouponNumber.canCoupon.id.eq(couponId))
|
||||
.orderBy(canCouponNumber.id.asc())
|
||||
.fetch()
|
||||
}
|
||||
|
||||
override fun findByCouponNumber(couponNumber: String): CanCouponNumber? {
|
||||
return queryFactory
|
||||
.selectFrom(canCouponNumber)
|
||||
.where(canCouponNumber.couponNumber.eq(couponNumber))
|
||||
.fetchFirst()
|
||||
}
|
||||
|
||||
override fun findByMemberId(memberId: Long): List<Long> {
|
||||
return queryFactory
|
||||
.select(canCouponNumber.id)
|
||||
.from(canCouponNumber)
|
||||
.where(canCouponNumber.member.id.eq(memberId))
|
||||
.fetch()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package kr.co.vividnext.sodalive.can.coupon
|
||||
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
import kr.co.vividnext.sodalive.can.coupon.QCanCoupon.canCoupon
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
|
||||
interface CanCouponRepository : JpaRepository<CanCoupon, Long>, CanCouponQueryRepository
|
||||
|
||||
interface CanCouponQueryRepository {
|
||||
fun getCouponTotalCount(): Int
|
||||
fun getCouponList(offset: Long, limit: Long): List<CanCoupon>
|
||||
}
|
||||
|
||||
class CanCouponQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : CanCouponQueryRepository {
|
||||
override fun getCouponTotalCount(): Int {
|
||||
return queryFactory
|
||||
.select(canCoupon.id)
|
||||
.from(canCoupon)
|
||||
.fetch()
|
||||
.size
|
||||
}
|
||||
|
||||
override fun getCouponList(offset: Long, limit: Long): List<CanCoupon> {
|
||||
return queryFactory
|
||||
.selectFrom(canCoupon)
|
||||
.orderBy(canCoupon.id.desc())
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
.fetch()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
package kr.co.vividnext.sodalive.can.coupon
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import kr.co.vividnext.sodalive.aws.sqs.SqsEvent
|
||||
import kr.co.vividnext.sodalive.aws.sqs.SqsEventType
|
||||
import kr.co.vividnext.sodalive.can.charge.ChargeService
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
||||
import org.springframework.context.ApplicationEventPublisher
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Service
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@Service
|
||||
class CanCouponService(
|
||||
private val issueService: CanCouponIssueService,
|
||||
private val chargeService: ChargeService,
|
||||
|
||||
private val repository: CanCouponRepository,
|
||||
private val couponNumberRepository: CanCouponNumberRepository,
|
||||
|
||||
private val memberRepository: MemberRepository,
|
||||
|
||||
private val objectMapper: ObjectMapper,
|
||||
private val applicationEventPublisher: ApplicationEventPublisher
|
||||
) {
|
||||
fun generateCoupon(request: GenerateCanCouponRequest) {
|
||||
val message = objectMapper.writeValueAsString(request)
|
||||
applicationEventPublisher.publishEvent(SqsEvent(type = SqsEventType.GENERATE_COUPON, message = message))
|
||||
}
|
||||
|
||||
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!!)
|
||||
GetCouponListItemResponse(
|
||||
id = it.id!!,
|
||||
couponName = it.couponName,
|
||||
can = it.can,
|
||||
couponCount = it.couponCount,
|
||||
useCouponCount = useCouponCount,
|
||||
validity = it.validity.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")),
|
||||
isMultipleUse = it.isMultipleUse,
|
||||
isActive = it.isActive
|
||||
)
|
||||
}
|
||||
.toList()
|
||||
|
||||
return GetCouponListResponse(totalCount, items)
|
||||
}
|
||||
|
||||
fun getCouponNumberList(couponId: Long, offset: Long, limit: Long): GetCouponNumberListResponse {
|
||||
val totalCount = couponNumberRepository.getCouponNumberTotalCount(couponId = couponId)
|
||||
val items = couponNumberRepository.getCouponNumberList(couponId = couponId, offset = offset, limit = limit)
|
||||
return GetCouponNumberListResponse(totalCount, items)
|
||||
}
|
||||
|
||||
fun downloadCouponNumberList(couponId: Long): ByteArrayInputStream {
|
||||
val header = listOf("순번", "쿠폰번호", "사용여부")
|
||||
val byteArrayOutputStream = ByteArrayOutputStream()
|
||||
val couponNumberList = couponNumberRepository.getAllCouponNumberList(couponId)
|
||||
|
||||
val workbook = XSSFWorkbook()
|
||||
try {
|
||||
val sheet = workbook.createSheet()
|
||||
val row = sheet.createRow(0)
|
||||
header.forEachIndexed { index, string ->
|
||||
val cell = row.createCell(index)
|
||||
cell.setCellValue(string)
|
||||
}
|
||||
|
||||
couponNumberList.forEachIndexed { index, item ->
|
||||
val couponNumberRow = sheet.createRow(index + 1)
|
||||
couponNumberRow.createCell(0).setCellValue(item.couponNumberId.toString())
|
||||
couponNumberRow.createCell(1).setCellValue(insertHyphens(item.couponNumber))
|
||||
couponNumberRow.createCell(2).setCellValue(
|
||||
if (item.isUsed) {
|
||||
"O"
|
||||
} else {
|
||||
"X"
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
workbook.write(byteArrayOutputStream)
|
||||
return ByteArrayInputStream(byteArrayOutputStream.toByteArray())
|
||||
} catch (e: IOException) {
|
||||
throw SodaException("다운로드를 하지 못했습니다.\n다시 시도해 주세요.")
|
||||
} finally {
|
||||
workbook.close()
|
||||
byteArrayOutputStream.close()
|
||||
}
|
||||
}
|
||||
|
||||
fun useCanCoupon(couponNumber: String, memberId: Long) {
|
||||
val member = memberRepository.findByIdOrNull(id = memberId)
|
||||
?: throw SodaException("로그인 정보를 확인해주세요.")
|
||||
|
||||
if (member.auth == null) throw SodaException("쿠폰은 본인인증을 하셔야 사용이 가능합니다.")
|
||||
|
||||
issueService.validateAvailableUseCoupon(couponNumber, memberId)
|
||||
|
||||
chargeService.chargeByCoupon(couponNumber, member)
|
||||
}
|
||||
|
||||
private fun insertHyphens(input: String): String {
|
||||
return input.chunked(4).joinToString("-")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package kr.co.vividnext.sodalive.can.coupon
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
|
||||
data class GenerateCanCouponRequest(
|
||||
@JsonProperty("couponName") val couponName: String,
|
||||
@JsonProperty("can") val can: Int,
|
||||
@JsonProperty("validity") val validity: String,
|
||||
@JsonProperty("isMultipleUse") val isMultipleUse: Boolean,
|
||||
@JsonProperty("couponNumberCount") val couponNumberCount: Int
|
||||
)
|
|
@ -0,0 +1,17 @@
|
|||
package kr.co.vividnext.sodalive.can.coupon
|
||||
|
||||
data class GetCouponListResponse(
|
||||
val totalCount: Int,
|
||||
val items: List<GetCouponListItemResponse>
|
||||
)
|
||||
|
||||
data class GetCouponListItemResponse(
|
||||
val id: Long,
|
||||
val couponName: String,
|
||||
val can: Int,
|
||||
val couponCount: Int,
|
||||
val useCouponCount: Int,
|
||||
val validity: String,
|
||||
val isMultipleUse: Boolean,
|
||||
val isActive: Boolean
|
||||
)
|
|
@ -0,0 +1,14 @@
|
|||
package kr.co.vividnext.sodalive.can.coupon
|
||||
|
||||
import com.querydsl.core.annotations.QueryProjection
|
||||
|
||||
data class GetCouponNumberListResponse(
|
||||
val totalCount: Int,
|
||||
val items: List<GetCouponNumberListItemResponse>
|
||||
)
|
||||
|
||||
data class GetCouponNumberListItemResponse @QueryProjection constructor(
|
||||
val couponNumberId: Long,
|
||||
val couponNumber: String,
|
||||
val isUsed: Boolean
|
||||
)
|
|
@ -0,0 +1,3 @@
|
|||
package kr.co.vividnext.sodalive.can.coupon
|
||||
|
||||
data class UseCanCouponRequest(val couponNumber: String)
|
|
@ -0,0 +1,30 @@
|
|||
package kr.co.vividnext.sodalive.configs
|
||||
|
||||
import com.amazonaws.auth.AWSStaticCredentialsProvider
|
||||
import com.amazonaws.auth.BasicAWSCredentials
|
||||
import com.amazonaws.services.sqs.AmazonSQS
|
||||
import com.amazonaws.services.sqs.AmazonSQSClientBuilder
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
|
||||
@Configuration
|
||||
class AmazonSQSConfig(
|
||||
@Value("\${cloud.aws.credentials.access-key}")
|
||||
private val accessKey: String,
|
||||
@Value("\${cloud.aws.credentials.secret-key}")
|
||||
private val secretKey: String,
|
||||
@Value("\${cloud.aws.region.static}")
|
||||
private val region: String
|
||||
) {
|
||||
@Bean
|
||||
fun amazonSQS(): AmazonSQS {
|
||||
val basicAWSCredentials = BasicAWSCredentials(accessKey, secretKey)
|
||||
val awsStaticCredentialsProvider = AWSStaticCredentialsProvider(basicAWSCredentials)
|
||||
|
||||
return AmazonSQSClientBuilder.standard()
|
||||
.withCredentials(awsStaticCredentialsProvider)
|
||||
.withRegion(region)
|
||||
.build()
|
||||
}
|
||||
}
|
|
@ -37,6 +37,8 @@ cloud:
|
|||
keyPairId: ${CONTENT_CLOUD_FRONT_KEY_PAIR_ID}
|
||||
cloudFront:
|
||||
host: ${CLOUD_FRONT_HOST}
|
||||
sqs:
|
||||
generateCouponUrl: ${AMAZON_SQS_GENERATE_CAN_COUPON_QUEUE_URL}
|
||||
region:
|
||||
static: ap-northeast-2
|
||||
stack:
|
||||
|
|
Loading…
Reference in New Issue