定义InApp 支付接口,实现记账业务
diff --git a/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/InAppPayParam.java b/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/InAppPayParam.java
index 9b7ecf9..6564025 100644
--- a/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/InAppPayParam.java
+++ b/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/InAppPayParam.java
@@ -24,8 +24,13 @@
private String recipientType;
@NotNull
+ private String summary; // 交易类型,目前支持 pay(消费),deposit(充值)
+
+ @NotNull
private String sourceType;
+ private boolean anonymous = false;
+
private String userid;
private String merchno;
@@ -33,7 +38,7 @@
@Positive(message = "金额不合法")
private Integer totalAmount;
- private String describe;
+ private String description;
@TransDate
private String transDate;
diff --git a/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/InAppPayResponse.java b/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/InAppPayResponse.java
index 8858860..053fe03 100644
--- a/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/InAppPayResponse.java
+++ b/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/InAppPayResponse.java
@@ -15,4 +15,7 @@
private String message;
private String status;
+
+ private String callbackUrl;
+
}
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/PayApiApplication.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/PayApiApplication.kt
index 27b9c1c..2eda4b3 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/PayApiApplication.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/PayApiApplication.kt
@@ -237,12 +237,10 @@
@SpringBootApplication
@EnableDiscoveryClient
@EnableScheduling
-@EnableSchedulerLock(defaultLockAtMostFor = "PT15m")
+@EnableSchedulerLock(defaultLockAtMostFor = "PT15M")
@EnableCaching
-@EnableHttpHeaderTenantInterceptor
-@EnableSessionTenantInterceptor
@EnableJwtTenantInterceptor
-@EnableTenantDetailsStorage
+@EnableTenantDetailsStorage(enableHttpHeader = true, enableHttpSession = true)
@EnableMultiTenantTaskWorker(propertyName = "payapi.multitenant")
@ServletComponentScan
@EnableAsync
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/async_tasks.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/async_tasks.kt
index d9c159e..db1aaf3 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/async_tasks.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/async_tasks.kt
@@ -4,6 +4,7 @@
import com.supwisdom.dlpay.agent.AgentPayServiceContext
import com.supwisdom.dlpay.agent.DtlStatus
import com.supwisdom.dlpay.agent.citizencard.YnrccUtil
+import com.supwisdom.dlpay.api.controller.TransactionContainer
import com.supwisdom.dlpay.api.domain.TDtlQuery
import com.supwisdom.dlpay.api.domain.TShopdtl
import com.supwisdom.dlpay.api.domain.TTransactionMain
@@ -23,6 +24,7 @@
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
+import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.retry.annotation.Backoff
import org.springframework.retry.annotation.Recover
import org.springframework.retry.annotation.Retryable
@@ -100,20 +102,15 @@
@Component
-class AgentQueryResultTask {
+class AgentQueryResultTask(private val transactionService: TransactionServiceProxy,
+ private val agentPayServiceContext: AgentPayServiceContext,
+ private val dtlQueryResultService: DtlQueryResultService,
+ private val systemUtilService: SystemUtilService,
+ redisConnectionFactory: RedisConnectionFactory) {
+
private val logger = KotlinLogging.logger { }
- @Autowired
- lateinit var transactionService: TransactionServiceProxy
-
- @Autowired
- private lateinit var agentPayServiceContext: AgentPayServiceContext
-
- @Autowired
- private lateinit var dtlQueryResultService: DtlQueryResultService
-
- @Autowired
- private lateinit var systemUtilService: SystemUtilService
+ private val transactionContainer = TransactionContainer("qrcode.pay", redisConnectionFactory)
@Recover
fun quertRecover(ex: TaskRetryException, details: TenantDetails,
@@ -146,6 +143,7 @@
when (resp.code) {
AgentCode.SUCCESS -> {
+ transactionContainer.remove(transaction.refno)
//查询成功
when (resp.dtlStatus) {
DtlStatus.SUCCESS ->
@@ -166,12 +164,15 @@
}
}
}
- AgentCode.REFNO_NOT_EXISTS ->
+ AgentCode.REFNO_NOT_EXISTS -> {
+ transactionContainer.remove(transaction.refno)
transactionService.fail(transaction.refno, "银行流水不存在") //银行返回流水不存在
+ }
AgentCode.REQUIRE_QUERY -> {
throw TaskRetryException()
}
else -> {
+ transactionContainer.remove(transaction.refno)
//其他明确错误,查询失败
logger.error("查询refno=[${transaction.refno}]流水结果返回失败:code=[${resp.agentCode}],message=[${resp.agentMsg}]")
}
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/consume_api_controller.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/consume_api_controller.kt
index 52f0529..93e305d 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/consume_api_controller.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/consume_api_controller.kt
@@ -17,11 +17,10 @@
import com.supwisdom.dlpay.exception.TransactionCheckException
import com.supwisdom.dlpay.framework.ResponseBodyBuilder
import com.supwisdom.dlpay.framework.service.SystemUtilService
-import com.supwisdom.dlpay.framework.task.TaskExecuteTimes
import com.supwisdom.dlpay.framework.util.*
import com.supwisdom.multitenant.TenantContextHolder
import org.apache.commons.lang3.StringUtils
-import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.http.ResponseEntity
import org.springframework.validation.annotation.Validated
@@ -31,35 +30,25 @@
@RestController
@RequestMapping("/api/consume")
-class ConsumeAPIController {
- @Autowired
- lateinit var accountUtilServcie: AccountUtilServcie
- @Autowired
- lateinit var userService: UserService
- @Autowired
- lateinit var systemUtilService: SystemUtilService
- @Autowired
- lateinit var consumePayService: ConsumePayService
- @Autowired
- lateinit var transactionService: TransactionServiceProxy
- @Autowired
- lateinit var cardService: CardService
- @Autowired
- private lateinit var agentPayServiceContext: AgentPayServiceContext
+class ConsumeAPIController(private val qrCodeService: QRCodeService,
+ private val accountUtilServcie: AccountUtilServcie,
+ private val userService: UserService,
+ private val systemUtilService: SystemUtilService,
+ private val consumePayService: ConsumePayService,
+ private val transactionService: TransactionServiceProxy,
+ private val cardService: CardService,
+ private val agentPayServiceContext: AgentPayServiceContext,
+ private val sourceTypeService: SourceTypeService,
+ private val agentQueryResultTask: AgentQueryResultTask,
+ private val agentServiceProxy: AgentServiceProxy,
+ redisConnectionFactory: RedisConnectionFactory) {
- @Autowired
- private lateinit var sourceTypeService: SourceTypeService
- @Autowired
- lateinit var agentQueryResultTask: AgentQueryResultTask
+ private val redisTemplate = RedisTemplate<String, String>()
- @Autowired
- private lateinit var agentServiceProxy: AgentServiceProxy
-
- @Autowired
- lateinit var redisTemplate: RedisTemplate<String, String>
-
- @Autowired
- private lateinit var qrCodeService: QRCodeService
+ init {
+ redisTemplate.setConnectionFactory(redisConnectionFactory)
+ redisTemplate.afterPropertiesSet()
+ }
/**
* ============================================================================
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/transaction_controller.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/transaction_controller.kt
index f0846af..63312ad 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/transaction_controller.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/transaction_controller.kt
@@ -1,33 +1,55 @@
package com.supwisdom.dlpay.api.controller
+import com.supwisdom.dlpay.api.TransactionBuilder
import com.supwisdom.dlpay.api.bean.InAppPayParam
import com.supwisdom.dlpay.api.bean.InAppPayResponse
import com.supwisdom.dlpay.api.bean.TransactionQueryResponse
+import com.supwisdom.dlpay.api.domain.TSourceType
+import com.supwisdom.dlpay.api.service.AccountUtilServcie
+import com.supwisdom.dlpay.api.service.SourceTypeService
import com.supwisdom.dlpay.api.service.TransactionServiceProxy
+import com.supwisdom.dlpay.exception.TransactionCheckException
import com.supwisdom.dlpay.framework.ResponseBodyBuilder
import com.supwisdom.dlpay.framework.service.SystemUtilService
+import com.supwisdom.dlpay.framework.util.TradeCode
+import com.supwisdom.dlpay.framework.util.TradeDict
+import com.supwisdom.dlpay.framework.util.TradeErrorCode
import com.supwisdom.multitenant.TenantContextHolder
-import org.springframework.http.HttpStatus
+import com.supwisdom.multitenant.storage.TenantDetailsRegistrar
+import org.springframework.context.annotation.Lazy
+import org.springframework.data.redis.connection.RedisConnectionFactory
+import org.springframework.data.redis.core.RedisTemplate
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RestController
import javax.validation.Valid
+import kotlin.math.roundToInt
@RestController("/api/transaction")
class TransactionController(private val transactionServiceProxy: TransactionServiceProxy,
- private val systemUtilService: SystemUtilService) {
+ private val systemUtilService: SystemUtilService,
+ private val accountUtilServcie: AccountUtilServcie,
+ private val sourceTypeService: SourceTypeService,
+ redisConnectionFactory: RedisConnectionFactory) {
- @GetMapping("/query")
+ private val transactionContainer = TransactionContainer("inapp.pay", redisConnectionFactory)
+
+ @GetMapping("/inapp/query")
fun queryTransaction(refno: String): ResponseEntity<*> {
val tranaction = transactionServiceProxy.findTransactionByRefno(refno)
- ?: return ResponseBodyBuilder.fail(HttpStatus.NOT_FOUND, "未找到流水")
+ ?: return ResponseBodyBuilder.notFound("未找到流水")
if (tranaction.tenantid != TenantContextHolder.getContext().tenant.id) {
- return ResponseBodyBuilder.fail(HttpStatus.CONFLICT, "未找到流水")
+ return ResponseBodyBuilder.conflict("未找到流水")
}
+ if (tranaction.status != TradeDict.DTL_STATUS_SUCCESS) {
+ transactionContainer.add(refno)
+ // 查询第三方
+ transactionContainer.remove(refno)
+ }
return ResponseBodyBuilder.successEntity(TransactionQueryResponse().apply {
this.refno = tranaction.refno
accdate = tranaction.accdate
@@ -37,18 +59,138 @@
}, "成功")
}
+ private fun transSummary(sourceType: TSourceType, param: InAppPayParam): String {
+ return when (param.summary) {
+ "pay" -> "${sourceType.paydesc}消费"
+ "deposit" -> "${sourceType.paydesc}充值"
+ else -> throw TransactionCheckException(TradeErrorCode.INPUT_DATA_ERROR, "交易摘要类型错误")
+ }
+ }
+
@PostMapping("/inapp/payinit")
- fun inAppPayInit(@Valid param: InAppPayParam): ResponseEntity<InAppPayResponse> {
+ fun inAppPayInit(@Valid param: InAppPayParam): ResponseEntity<*> {
val response = InAppPayResponse()
+ if (transactionContainer.count() > 100) {
+ return ResponseBodyBuilder.serviceUnavailable("第三方请求异常")
+ }
+ val sourceType = sourceTypeService.getBySourceType(param.sourceType)
+ ?: return ResponseBodyBuilder.badRequest("source type <${param.sourceType}> 不存在")
+
+ val subject = accountUtilServcie.readSubject(sourceType.depositeSubjno)
+
+ val tradeCode = when (param.inAppType) {
+ "h5" -> TradeCode.TRANSCODE_H5PAY
+ "native" -> TradeCode.TRANSCODE_APPPAY
+ else -> return ResponseBodyBuilder.badRequest("app typ <${param.inAppType}> 不支持")
+ }
+
+ val builder = TransactionBuilder()
+ .setTransInfo(param.transDate, param.transTime, tradeCode, sourceType.sourceType)
+ .setOutTransInfo(TenantContextHolder.getContext().tenant.id, param.billno)
+ .apply {
+ description = param.description
+ }
+ val amount = param.totalAmount / 100.0
+ when (param.recipientType) {
+ "shop" -> {
+ // check shop acc
+ val shopacc = accountUtilServcie.readShopbyShopaccno(param.merchno)
+ builder.shop(shopacc)
+ .setAmount(amount, TradeDict.TRADE_FLAG_IN)
+ .setOpposite(subject.subjno, subject.subjname)
+ if (!param.isAnonymous) {
+ // 支付给商户,并且不是匿名支付
+ val account = accountUtilServcie.readAccount(param.userid)
+ builder.person(account)
+ .setAmount(amount, TradeDict.TRADE_FLAG_OUT)
+ .setOpposite(subject.subjno, subject.subjname)
+ .and()
+ .addDebitCreditRecord(subject.subjno, subject.subjno,
+ account.accno, account.subjno, amount,
+ transSummary(sourceType, param))
+ .addDebitCreditRecord(account.accno, account.subjno,
+ shopacc.shopaccno, shopacc.subjno, amount,
+ transSummary(sourceType, param))
+ } else {
+ // 匿名消费,直接到商户账户
+ builder.addDebitCreditRecord(subject.subjno, subject.subjno,
+ shopacc.shopaccno, shopacc.subjno, amount, transSummary(sourceType, param))
+ }
+ }
+ "person" -> {
+ // check person
+ val account = accountUtilServcie.readAccount(param.userid)
+ builder.person(account)
+ .setAmount(amount, TradeDict.TRADE_FLAG_IN)
+ .setOpposite(subject.subjno, subject.subjname)
+ .and()
+ .addDebitCreditRecord(subject.subjno, subject.subjno,
+ account.accno, account.subjno, amount, transSummary(sourceType, param))
+ }
+ else -> {
+ return ResponseBodyBuilder.badRequest("recipient type 错误")
+ }
+ }
+ val transaction = transactionServiceProxy.init(builder)
+ // 调用第三方完成交易初始化
+
+ transactionServiceProxy.wip(transaction.refno)
+ transactionContainer.add(transaction.refno)
+ response.apply {
+ when (param.recipientType) {
+ "shop" -> actuallyAmount = (transaction.shopDtl.amount * 100).roundToInt()
+ "person" -> actuallyAmount = (transaction.personDtl.amount * 100).roundToInt()
+ }
+ this.refno = transaction.refno
+ this.status = transaction.status
+ this.message = "初始化成功"
+ }
return ResponseBodyBuilder.successEntity(response)
}
}
+class TransactionContainer(private val name: String,
+ redisConnectionFactory: RedisConnectionFactory) {
+ private val redisTemplate: RedisTemplate<Any, Any> = RedisTemplate<Any, Any>()
+
+ init {
+ redisTemplate.setConnectionFactory(redisConnectionFactory)
+ redisTemplate.afterPropertiesSet()
+ }
+
+ private fun keyName(): String = "TRANS:REFNO:$name"
+
+ fun add(refno: String): Long {
+ redisTemplate.opsForSet().add(keyName(), refno)
+ return redisTemplate.opsForSet().size(keyName()) ?: 0L
+ }
+
+ fun has(refno: String): Boolean {
+ return redisTemplate.opsForSet().isMember(keyName(), refno) ?: false
+ }
+
+ fun remove(refno: String): Long {
+ redisTemplate.opsForSet().remove(keyName(), refno)
+ return redisTemplate.opsForSet().size(keyName()) ?: 0L
+ }
+
+ fun count(): Long {
+ return redisTemplate.opsForSet().size(keyName()) ?: 0L
+ }
+}
+
@RestController("/api/notify")
-class TransactionNotifyController {
+class TransactionNotifyController(@Lazy private val tenantDetailsRegistrar: TenantDetailsRegistrar,
+ private val transactionServiceProxy: TransactionServiceProxy) {
@PostMapping("/inapp/alipay/{tenant}")
- fun inAppPayCallback(@PathVariable("tenant") tenant: String): ResponseEntity<Any> {
- TODO("")
+ fun inAppPayCallback(@PathVariable("tenant") tenant: String,
+ refno: String): ResponseEntity<*> {
+ val tenantDetails = tenantDetailsRegistrar.getTenantDetailsById(tenant)
+ ?: return ResponseBodyBuilder.notFound("请求租户 <$tenant> 不存在")
+ TenantContextHolder.getContext().tenant = tenantDetails.get()
+ val transaction = transactionServiceProxy.success(refno)
+ return ResponseEntity.ok(ResponseBodyBuilder.create()
+ .data("refno", transaction.refno))
}
}
\ No newline at end of file
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/transaction_builder.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/transaction_builder.kt
index 1b81661..85ed71a 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/transaction_builder.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/transaction_builder.kt
@@ -42,14 +42,16 @@
return if (hasOpposite()) this.oppAccName else ""
}
- fun setOpposite(accno: String, accName: String) {
+ fun setOpposite(accno: String, accName: String): SubTransactionBuilder<T> {
this.oppAccno = accno
this.oppAccName = accName
+ return this
}
- fun setOpposite(accout: AccountProxy<*>) {
+ fun setOpposite(accout: AccountProxy<*>): SubTransactionBuilder<T> {
this.oppAccno = accout.getAccountNo()
this.oppAccName = accout.getAccountName()
+ return this
}
fun and(): TransactionBuilder {
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/framework/framework_util.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/framework/framework_util.kt
index 8614d23..597dedf 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/framework/framework_util.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/framework/framework_util.kt
@@ -34,6 +34,30 @@
fun fail(status: HttpStatus, msg: String): ResponseEntity<*> {
return ResponseEntity.status(status).body(msg)
}
+
+ fun notFound(msg: String): ResponseEntity<*> {
+ return fail(HttpStatus.NOT_FOUND, msg)
+ }
+
+ fun badRequest(msg: String): ResponseEntity<*> {
+ return fail(HttpStatus.BAD_REQUEST, msg)
+ }
+
+ fun conflict(msg: String): ResponseEntity<*> {
+ return fail(HttpStatus.CONFLICT, msg)
+ }
+
+ fun forbbiden(msg: String): ResponseEntity<*> {
+ return fail(HttpStatus.FORBIDDEN, msg)
+ }
+
+ fun unauthorized(msg: String): ResponseEntity<*> {
+ return fail(HttpStatus.UNAUTHORIZED, msg)
+ }
+
+ fun serviceUnavailable(msg: String): ResponseEntity<*> {
+ return fail(HttpStatus.SERVICE_UNAVAILABLE, msg)
+ }
}
private var retCode = INVALIDE_RETCODE