diff --git a/build.gradle.kts b/build.gradle.kts index 84666cf..e34510a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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") diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/aws/sqs/SqsEvent.kt b/src/main/kotlin/kr/co/vividnext/sodalive/aws/sqs/SqsEvent.kt new file mode 100644 index 0000000..f239665 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/aws/sqs/SqsEvent.kt @@ -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) + } + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/aws/sqs/SqsService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/aws/sqs/SqsService.kt new file mode 100644 index 0000000..dd21e5d --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/aws/sqs/SqsService.kt @@ -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) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/CanService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/CanService.kt index 1c0ca4b..182ca9d 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/can/CanService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/CanService.kt @@ -111,6 +111,10 @@ class CanService(private val repository: CanRepository) { "제휴보상" } + ChargeStatus.COUPON -> { + it.payment!!.method ?: "쿠폰충전" + } + else -> { "환불" } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeService.kt index a8756b5..d711cb5 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeService.kt @@ -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) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeStatus.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeStatus.kt index 1585dcd..c31a16d 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeStatus.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeStatus.kt @@ -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, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCoupon.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCoupon.kt new file mode 100644 index 0000000..d122cbb --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCoupon.kt @@ -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() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponController.kt new file mode 100644 index 0000000..5962f3a --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponController.kt @@ -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!! + ) + ) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponIssueService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponIssueService.kt new file mode 100644 index 0000000..547e7fa --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponIssueService.kt @@ -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 +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponNumber.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponNumber.kt new file mode 100644 index 0000000..3238e54 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponNumber.kt @@ -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 +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponNumberRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponNumberRepository.kt new file mode 100644 index 0000000..0e4ccd1 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponNumberRepository.kt @@ -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, CanCouponNumberQueryRepository + +interface CanCouponNumberQueryRepository { + fun getUseCouponCount(id: Long): Int + + fun getCouponNumberTotalCount(couponId: Long): Int + + fun getCouponNumberList(couponId: Long, offset: Long, limit: Long): List + + fun getAllCouponNumberList(couponId: Long): List + + fun findByCouponNumber(couponNumber: String): CanCouponNumber? + + fun findByMemberId(memberId: Long): List +} + +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 { + 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 { + 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 { + return queryFactory + .select(canCouponNumber.id) + .from(canCouponNumber) + .where(canCouponNumber.member.id.eq(memberId)) + .fetch() + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponRepository.kt new file mode 100644 index 0000000..012ab52 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponRepository.kt @@ -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, CanCouponQueryRepository + +interface CanCouponQueryRepository { + fun getCouponTotalCount(): Int + fun getCouponList(offset: Long, limit: Long): List +} + +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 { + return queryFactory + .selectFrom(canCoupon) + .orderBy(canCoupon.id.desc()) + .offset(offset) + .limit(limit) + .fetch() + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponService.kt new file mode 100644 index 0000000..f0474b8 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/CanCouponService.kt @@ -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("-") + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/GenerateCanCouponRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/GenerateCanCouponRequest.kt new file mode 100644 index 0000000..98028b2 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/GenerateCanCouponRequest.kt @@ -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 +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/GetCouponListResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/GetCouponListResponse.kt new file mode 100644 index 0000000..f26bab4 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/GetCouponListResponse.kt @@ -0,0 +1,17 @@ +package kr.co.vividnext.sodalive.can.coupon + +data class GetCouponListResponse( + val totalCount: Int, + val items: List +) + +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 +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/GetCouponNumberListResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/GetCouponNumberListResponse.kt new file mode 100644 index 0000000..a79c083 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/GetCouponNumberListResponse.kt @@ -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 +) + +data class GetCouponNumberListItemResponse @QueryProjection constructor( + val couponNumberId: Long, + val couponNumber: String, + val isUsed: Boolean +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/UseCanCouponRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/UseCanCouponRequest.kt new file mode 100644 index 0000000..b0083ea --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/coupon/UseCanCouponRequest.kt @@ -0,0 +1,3 @@ +package kr.co.vividnext.sodalive.can.coupon + +data class UseCanCouponRequest(val couponNumber: String) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/configs/AmazonSQSConfig.kt b/src/main/kotlin/kr/co/vividnext/sodalive/configs/AmazonSQSConfig.kt new file mode 100644 index 0000000..ff7e7e1 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/configs/AmazonSQSConfig.kt @@ -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() + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 1f71979..62f24c6 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -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: