From: Tang Cheng Date: Thu, 20 Feb 2020 03:32:21 +0000 (+0800) Subject: 定义InApp 支付接口,实现记账业务 X-Git-Url: https://source.supwisdom.com/gerrit/gitweb?a=commitdiff_plain;h=45bc7d5b3c22d5aef1c8d7c007ae2222e60a86fb;p=epayment%2Ffood_payapi.git 定义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 9b7ecf9b..65640251 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 @@ -23,9 +23,14 @@ public class InAppPayParam extends APIRequestParam { @StringList(value = {"shop", "person"}) 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 @@ public class InAppPayParam extends APIRequestParam { @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 88588605..053fe03d 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 @@ public class InAppPayResponse extends ApiResponse { 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 27b9c1c6..2eda4b3f 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 @@ class MyTenantJwtConfigAdapter : JwtTenantConfigAdapter { @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 d9c159e4..db1aaf34 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.AgentCode 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.aop.interceptor.AsyncUncaughtExceptionHandler 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 @@ class ShopAccBalanceAsyncTask { @Component -class AgentQueryResultTask { - private val logger = KotlinLogging.logger { } - - @Autowired - lateinit var transactionService: TransactionServiceProxy - - @Autowired - private lateinit var agentPayServiceContext: AgentPayServiceContext +class AgentQueryResultTask(private val transactionService: TransactionServiceProxy, + private val agentPayServiceContext: AgentPayServiceContext, + private val dtlQueryResultService: DtlQueryResultService, + private val systemUtilService: SystemUtilService, + redisConnectionFactory: RedisConnectionFactory) { - @Autowired - private lateinit var dtlQueryResultService: DtlQueryResultService + private val logger = KotlinLogging.logger { } - @Autowired - private lateinit var systemUtilService: SystemUtilService + private val transactionContainer = TransactionContainer("qrcode.pay", redisConnectionFactory) @Recover fun quertRecover(ex: TaskRetryException, details: TenantDetails, @@ -146,6 +143,7 @@ class AgentQueryResultTask { when (resp.code) { AgentCode.SUCCESS -> { + transactionContainer.remove(transaction.refno) //查询成功 when (resp.dtlStatus) { DtlStatus.SUCCESS -> @@ -166,12 +164,15 @@ class AgentQueryResultTask { } } } - 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 52f0529e..93e305d6 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.api.service.* 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 @@ import javax.validation.Valid @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 - - @Autowired - private lateinit var sourceTypeService: SourceTypeService - @Autowired - lateinit var agentQueryResultTask: AgentQueryResultTask - - @Autowired - private lateinit var agentServiceProxy: AgentServiceProxy - - @Autowired - lateinit var redisTemplate: RedisTemplate - - @Autowired - private lateinit var qrCodeService: QRCodeService +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) { + + private val redisTemplate = RedisTemplate() + + 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 f0846afe..63312adb 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 @@ class TransactionController(private val transactionServiceProxy: TransactionServ }, "成功") } + 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 { + 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 = RedisTemplate() + + 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 { - 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 1b816618..85ed71a0 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 @@ open class SubTransactionBuilder>(val parent: Trans return if (hasOpposite()) this.oppAccName else "" } - fun setOpposite(accno: String, accName: String) { + fun setOpposite(accno: String, accName: String): SubTransactionBuilder { this.oppAccno = accno this.oppAccName = accName + return this } - fun setOpposite(accout: AccountProxy<*>) { + fun setOpposite(accout: AccountProxy<*>): SubTransactionBuilder { 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 8614d232..597dedf2 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 @@ class ResponseBodyBuilder private constructor() { 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