코인 충전, 코인 내역 API 추가
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
package kr.co.vividnext.sodalive.can.charge
|
||||
|
||||
import kr.co.vividnext.sodalive.can.Can
|
||||
import kr.co.vividnext.sodalive.can.payment.Payment
|
||||
import kr.co.vividnext.sodalive.can.use.UseCan
|
||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import javax.persistence.CascadeType
|
||||
import javax.persistence.Entity
|
||||
import javax.persistence.EnumType
|
||||
import javax.persistence.Enumerated
|
||||
import javax.persistence.FetchType
|
||||
import javax.persistence.JoinColumn
|
||||
import javax.persistence.ManyToOne
|
||||
import javax.persistence.OneToOne
|
||||
|
||||
@Entity
|
||||
data class Charge(
|
||||
var chargeCan: Int,
|
||||
var rewardCan: Int,
|
||||
@Enumerated(value = EnumType.STRING)
|
||||
var status: ChargeStatus = ChargeStatus.CHARGE
|
||||
) : BaseEntity() {
|
||||
@OneToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "can_id", nullable = true)
|
||||
var can: Can? = null
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "member_id", nullable = false)
|
||||
var member: Member? = null
|
||||
|
||||
@OneToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL])
|
||||
@JoinColumn(name = "payment_id", nullable = true)
|
||||
var payment: Payment? = null
|
||||
set(value) {
|
||||
value?.charge = this
|
||||
field = value
|
||||
}
|
||||
|
||||
@OneToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL])
|
||||
@JoinColumn(name = "use_can_id", nullable = true)
|
||||
var useCan: UseCan? = null
|
||||
|
||||
var title: String? = null
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
package kr.co.vividnext.sodalive.can.charge
|
||||
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
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.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/charge")
|
||||
class ChargeController(private val service: ChargeService) {
|
||||
|
||||
@PostMapping
|
||||
fun charge(
|
||||
@RequestBody chargeRequest: ChargeRequest,
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = run {
|
||||
if (member == null) {
|
||||
throw SodaException("로그인 정보를 확인해주세요.")
|
||||
}
|
||||
|
||||
ApiResponse.ok(service.charge(member, chargeRequest))
|
||||
}
|
||||
|
||||
@PostMapping("/verify")
|
||||
fun verify(
|
||||
@RequestBody verifyRequest: VerifyRequest,
|
||||
@AuthenticationPrincipal user: User
|
||||
) = ApiResponse.ok(service.verify(user, verifyRequest))
|
||||
|
||||
@PostMapping("/apple")
|
||||
fun appleCharge(
|
||||
@RequestBody chargeRequest: AppleChargeRequest,
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = run {
|
||||
if (member == null) {
|
||||
throw SodaException("로그인 정보를 확인해주세요.")
|
||||
}
|
||||
|
||||
ApiResponse.ok(service.appleCharge(member, chargeRequest))
|
||||
}
|
||||
|
||||
@PostMapping("/apple/verify")
|
||||
fun appleVerify(
|
||||
@RequestBody verifyRequest: AppleVerifyRequest,
|
||||
@AuthenticationPrincipal user: User
|
||||
) = ApiResponse.ok(service.appleVerify(user, verifyRequest))
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
package kr.co.vividnext.sodalive.can.charge
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
|
||||
|
||||
data class ChargeRequest(val canId: Long, val paymentGateway: PaymentGateway)
|
||||
|
||||
data class ChargeResponse(val chargeId: Long)
|
||||
|
||||
data class VerifyRequest(
|
||||
@JsonProperty("receipt_id")
|
||||
val receiptId: String,
|
||||
@JsonProperty("order_id")
|
||||
val orderId: String
|
||||
)
|
||||
|
||||
data class VerifyResult(
|
||||
@JsonProperty("receipt_id")
|
||||
val receiptId: String,
|
||||
val method: String,
|
||||
val status: Int,
|
||||
val price: Int
|
||||
)
|
||||
|
||||
data class AppleChargeRequest(
|
||||
val title: String,
|
||||
val chargeCan: Int,
|
||||
val paymentGateway: PaymentGateway,
|
||||
var price: Double? = null,
|
||||
var locale: String? = null
|
||||
)
|
||||
|
||||
data class AppleVerifyRequest(val receiptString: String, val chargeId: Long)
|
||||
|
||||
data class AppleVerifyResponse(val status: Int)
|
@@ -0,0 +1,7 @@
|
||||
package kr.co.vividnext.sodalive.can.charge
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
interface ChargeRepository : JpaRepository<Charge, Long>
|
@@ -0,0 +1,203 @@
|
||||
package kr.co.vividnext.sodalive.can.charge
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import kr.co.bootpay.Bootpay
|
||||
import kr.co.vividnext.sodalive.can.CanRepository
|
||||
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.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import org.json.JSONObject
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
|
||||
@Service
|
||||
@Transactional(readOnly = true)
|
||||
class ChargeService(
|
||||
private val chargeRepository: ChargeRepository,
|
||||
private val canRepository: CanRepository,
|
||||
private val memberRepository: MemberRepository,
|
||||
private val objectMapper: ObjectMapper,
|
||||
private val okHttpClient: OkHttpClient,
|
||||
@Value("\${bootpay.application-id}")
|
||||
private val bootpayApplicationId: String,
|
||||
@Value("\${bootpay.private-key}")
|
||||
private val bootpayPrivateKey: String,
|
||||
@Value("\${apple.iap-verify-sandbox-url}")
|
||||
private val appleInAppVerifySandBoxUrl: String,
|
||||
@Value("\${apple.iap-verify-url}")
|
||||
private val appleInAppVerifyUrl: String
|
||||
) {
|
||||
|
||||
@Transactional
|
||||
fun charge(member: Member, request: ChargeRequest): ChargeResponse {
|
||||
val can = canRepository.findByIdOrNull(request.canId)
|
||||
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.")
|
||||
|
||||
val charge = Charge(can.can, can.rewardCan)
|
||||
charge.title = can.title
|
||||
charge.member = member
|
||||
charge.can = can
|
||||
|
||||
val payment = Payment(paymentGateway = request.paymentGateway)
|
||||
payment.price = can.price.toDouble()
|
||||
charge.payment = payment
|
||||
|
||||
chargeRepository.save(charge)
|
||||
|
||||
return ChargeResponse(chargeId = charge.id!!)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun verify(user: User, verifyRequest: VerifyRequest) {
|
||||
val charge = chargeRepository.findByIdOrNull(verifyRequest.orderId.toLong())
|
||||
?: throw SodaException("결제정보에 오류가 있습니다.")
|
||||
val member = memberRepository.findByEmail(user.username)
|
||||
?: throw SodaException("로그인 정보를 확인해주세요.")
|
||||
|
||||
if (charge.payment!!.paymentGateway == PaymentGateway.PG) {
|
||||
val bootpay = Bootpay(bootpayApplicationId, bootpayPrivateKey)
|
||||
|
||||
try {
|
||||
bootpay.accessToken
|
||||
val verifyResult = objectMapper.convertValue(
|
||||
bootpay.getReceipt(verifyRequest.receiptId),
|
||||
VerifyResult::class.java
|
||||
)
|
||||
|
||||
if (verifyResult.status == 1 && verifyResult.price == charge.can?.price) {
|
||||
charge.payment?.receiptId = verifyResult.receiptId
|
||||
charge.payment?.method = verifyResult.method
|
||||
charge.payment?.status = PaymentStatus.COMPLETE
|
||||
member.charge(charge.chargeCan, charge.rewardCan, "pg")
|
||||
} else {
|
||||
throw SodaException("결제정보에 오류가 있습니다.")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
throw SodaException("결제정보에 오류가 있습니다.")
|
||||
}
|
||||
} else {
|
||||
throw SodaException("결제정보에 오류가 있습니다.")
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun appleCharge(member: Member, request: AppleChargeRequest): ChargeResponse {
|
||||
val charge = Charge(request.chargeCan, 0)
|
||||
charge.title = request.title
|
||||
charge.member = member
|
||||
|
||||
val payment = Payment(paymentGateway = request.paymentGateway)
|
||||
payment.price = if (request.price != null) {
|
||||
request.price!!
|
||||
} else {
|
||||
0.toDouble()
|
||||
}
|
||||
|
||||
payment.locale = request.locale
|
||||
|
||||
charge.payment = payment
|
||||
|
||||
chargeRepository.save(charge)
|
||||
|
||||
return ChargeResponse(chargeId = charge.id!!)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun appleVerify(user: User, verifyRequest: AppleVerifyRequest) {
|
||||
val charge = chargeRepository.findByIdOrNull(verifyRequest.chargeId)
|
||||
?: throw SodaException("결제정보에 오류가 있습니다.")
|
||||
val member = memberRepository.findByEmail(user.username)
|
||||
?: throw SodaException("로그인 정보를 확인해주세요.")
|
||||
|
||||
if (charge.payment!!.paymentGateway == PaymentGateway.APPLE_IAP) {
|
||||
// 검증로직
|
||||
if (requestRealServerVerify(verifyRequest)) {
|
||||
charge.payment?.receiptId = verifyRequest.receiptString
|
||||
charge.payment?.method = "애플(인 앱 결제)"
|
||||
charge.payment?.status = PaymentStatus.COMPLETE
|
||||
member.charge(charge.chargeCan, charge.rewardCan, "ios")
|
||||
} else {
|
||||
throw SodaException("결제정보에 오류가 있습니다.")
|
||||
}
|
||||
} else {
|
||||
throw SodaException("결제정보에 오류가 있습니다.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun requestRealServerVerify(verifyRequest: AppleVerifyRequest): Boolean {
|
||||
val body = JSONObject()
|
||||
body.put("receipt-data", verifyRequest.receiptString)
|
||||
val request = Request.Builder()
|
||||
.url(appleInAppVerifyUrl)
|
||||
.addHeader(HttpHeaders.CONTENT_TYPE, "application/json")
|
||||
.post(body.toString().toRequestBody("application/json".toMediaTypeOrNull()))
|
||||
.build()
|
||||
|
||||
val response = okHttpClient.newCall(request).execute()
|
||||
if (response.isSuccessful) {
|
||||
val responseString = response.body?.string()
|
||||
if (responseString != null) {
|
||||
val verifyResult = objectMapper.readValue(responseString, AppleVerifyResponse::class.java)
|
||||
return when (verifyResult.status) {
|
||||
0 -> {
|
||||
true
|
||||
}
|
||||
|
||||
21007 -> {
|
||||
requestSandboxServerVerify(verifyRequest)
|
||||
}
|
||||
|
||||
else -> {
|
||||
throw SodaException("결제정보에 오류가 있습니다.")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw SodaException("결제를 완료하지 못했습니다.")
|
||||
}
|
||||
} else {
|
||||
throw SodaException("결제를 완료하지 못했습니다.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun requestSandboxServerVerify(verifyRequest: AppleVerifyRequest): Boolean {
|
||||
val body = JSONObject()
|
||||
body.put("receipt-data", verifyRequest.receiptString)
|
||||
val request = Request.Builder()
|
||||
.url(appleInAppVerifySandBoxUrl)
|
||||
.addHeader(HttpHeaders.CONTENT_TYPE, "application/json")
|
||||
.post(body.toString().toRequestBody("application/json".toMediaTypeOrNull()))
|
||||
.build()
|
||||
|
||||
val response = okHttpClient.newCall(request).execute()
|
||||
if (response.isSuccessful) {
|
||||
val responseString = response.body?.string()
|
||||
if (responseString != null) {
|
||||
val verifyResult = objectMapper.readValue(responseString, AppleVerifyResponse::class.java)
|
||||
return when (verifyResult.status) {
|
||||
0 -> {
|
||||
true
|
||||
}
|
||||
|
||||
else -> {
|
||||
throw SodaException("결제정보에 오류가 있습니다.")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw SodaException("결제를 완료하지 못했습니다.")
|
||||
}
|
||||
} else {
|
||||
throw SodaException("결제를 완료하지 못했습니다.")
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
package kr.co.vividnext.sodalive.can.charge
|
||||
|
||||
enum class ChargeStatus {
|
||||
CHARGE, REFUND_CHARGE, EVENT, CANCEL,
|
||||
|
||||
// 관리자 지급
|
||||
ADMIN
|
||||
}
|
Reference in New Issue
Block a user