From 2be091e00ed7a63fdbf942c943801b47201abe15 Mon Sep 17 00:00:00 2001 From: Tang Cheng Date: Mon, 15 Apr 2019 15:13:57 +0800 Subject: [PATCH] =?utf8?q?=E6=94=B9=E8=BF=9B=E4=BA=86=E5=85=A5=E8=B4=A6?= =?utf8?q?=E7=AE=97=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../dlpay/consume/dao/UserdtlDao.java | 11 +++ .../dlpay/framework/util/TradeDict.java | 8 -- .../dlpay/consume/comsume_builder.kt | 76 ++++++++++----- .../consume/controller/consume_service.kt | 9 +- .../consume/service/impl/pay_service_impl.kt | 96 ++++++++++--------- .../dlpay/consume/service/pay_service.kt | 4 +- 6 files changed, 123 insertions(+), 81 deletions(-) diff --git a/src/main/java/com/supwisdom/dlpay/consume/dao/UserdtlDao.java b/src/main/java/com/supwisdom/dlpay/consume/dao/UserdtlDao.java index f49fea85..e465fbd8 100644 --- a/src/main/java/com/supwisdom/dlpay/consume/dao/UserdtlDao.java +++ b/src/main/java/com/supwisdom/dlpay/consume/dao/UserdtlDao.java @@ -2,9 +2,20 @@ package com.supwisdom.dlpay.consume.dao; import com.supwisdom.dlpay.consume.domain.TUserdtl; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Lock; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.jpa.repository.QueryHints; import org.springframework.stereotype.Repository; +import javax.persistence.LockModeType; +import javax.persistence.QueryHint; + @Repository public interface UserdtlDao extends JpaRepository { TUserdtl findByRefno(String refno); + + @Lock(LockModeType.PESSIMISTIC_WRITE) + @QueryHints({@QueryHint(name = "javax.persistence.lock.timeout", value ="0")}) + @Query("select dtl from TUserdtl dtl where dtl.refno = ?1") + TUserdtl findByRefnoForUpdate(String refno); } diff --git a/src/main/java/com/supwisdom/dlpay/framework/util/TradeDict.java b/src/main/java/com/supwisdom/dlpay/framework/util/TradeDict.java index dad6b2bb..5596b244 100644 --- a/src/main/java/com/supwisdom/dlpay/framework/util/TradeDict.java +++ b/src/main/java/com/supwisdom/dlpay/framework/util/TradeDict.java @@ -32,14 +32,6 @@ public class TradeDict { public static final String TRANS_DIRECTION_CREDIT = "credit"; public static final String TRANS_DIRECTION_DEBIT = "debit"; - /** - * 科目定义 subjno - * 2004 - 商户科目 - * 220201 - 个人存款 - * */ - public static final String SUBJNO_SHOP = "2004"; - public static final String SUBJNO_ACCOUNT = "220201"; - /** * 支付方式 diff --git a/src/main/kotlin/com/supwisdom/dlpay/consume/comsume_builder.kt b/src/main/kotlin/com/supwisdom/dlpay/consume/comsume_builder.kt index ec7222f1..9c493677 100644 --- a/src/main/kotlin/com/supwisdom/dlpay/consume/comsume_builder.kt +++ b/src/main/kotlin/com/supwisdom/dlpay/consume/comsume_builder.kt @@ -107,6 +107,8 @@ class PersonTransBuilder private constructor(accUitl: AccountUtilServcie) { * */ var outtradeno = "" //发起支付系统的流水号(对接系统根据此流水号查询本地流水对账) + var tryLockAccount = false + //////////////////////////////////////////////////////////////////////////////////// // 以下为内部参数,不需要调用者处理 val details = mutableListOf() @@ -153,6 +155,11 @@ class PersonTransBuilder private constructor(accUitl: AccountUtilServcie) { return this } + fun tryLock(lock: Boolean): PersonTransBuilder { + this.tryLockAccount = lock + return this + } + fun setTradeno(outtradeno: String): PersonTransBuilder { this.outtradeno = outtradeno return this @@ -220,17 +227,32 @@ class PersonTransBuilder private constructor(accUitl: AccountUtilServcie) { @Suppress("UNCHECKED_CAST", "IMPLICIT_CAST_TO_ANY") fun getAccountAndSubjNo(holder: AccountHolder): Pair { - return when (holder.idType) { - AccountHolder.IDTYPE_PERSON -> holder.get().let { - it.accno to it.subjno - } - AccountHolder.IDTYPE_SHOP -> holder.get().let { - it.shopaccno to it.subjno + return if (this.tryLockAccount) { + when (holder.idType) { + AccountHolder.IDTYPE_PERSON -> holder.withLock(true).let { + it.accno to it.subjno + } + AccountHolder.IDTYPE_SHOP -> holder.withLock(true).let { + it.shopaccno to it.subjno + } + AccountHolder.IDTYPE_SUBJECT -> holder.withLock(true).let { + it.subjno to it.subjno + } + else -> throw TransactionCheckException(TradeErrorCode.INPUT_DATA_ERROR, "不支持的账户类型") } - AccountHolder.IDTYPE_SUBJECT -> holder.get().let { - it.subjno to it.subjno + } else { + when (holder.idType) { + AccountHolder.IDTYPE_PERSON -> holder.get().let { + it.accno to it.subjno + } + AccountHolder.IDTYPE_SHOP -> holder.get().let { + it.shopaccno to it.subjno + } + AccountHolder.IDTYPE_SUBJECT -> holder.get().let { + it.subjno to it.subjno + } + else -> throw TransactionCheckException(TradeErrorCode.INPUT_DATA_ERROR, "不支持的账户类型") } - else -> throw TransactionCheckException(TradeErrorCode.INPUT_DATA_ERROR, "不支持的账户类型") } } @@ -257,32 +279,38 @@ class PersonTransBuilder private constructor(accUitl: AccountUtilServcie) { } fun addDetail(accountHolder: AccountHolder, - amount: Double, summary: String): PersonTransBuilder { + amount: Double): PersonTransBuilder { accountHolder.builder = this val transtype = accountHolder.get() var debitAccNo = "" var debitSubjNo = "" - if (transtype.drsubjno == "200") { - - } - var creditAccNo = "" var creditSubjNo = "" - if (transtype.crsubjno == "220201") { - accountHolder.childrenHolder.firstOrNull { - (it.get() as TAccount).subjno == transtype.crsubjno - }?.let { - creditAccNo = (it.get() as TAccount).accno - creditSubjNo = (it.get() as TAccount).subjno + + accountHolder.childrenHolder.map { + getAccountAndSubjNo(it) + }.forEach { + if (it.second == transtype.drsubjno) { + debitAccNo = it.first + debitSubjNo = it.second + } else if (it.second == transtype.crsubjno) { + creditAccNo = it.first + creditSubjNo = it.second } - } else if (transtype.crsubjno == "2002") { - } else { - throw TransactionCheckException(TradeErrorCode.INPUT_DATA_ERROR, - "transtype<${accountHolder.accountId}> 不支持") } + if (debitAccNo.isEmpty()) { + throw TransactionCheckException(TradeErrorCode.INPUT_DATA_ERROR, + "交易配置<${transtype.transtype}> 借方账号配置错误") + } + if (creditAccNo.isEmpty()) { + throw TransactionCheckException(TradeErrorCode.INPUT_DATA_ERROR, + "交易配置<${transtype.transtype}> 贷方账号配置错误") + } + this.details.add(TransDetail(debitAccNo, debitSubjNo, creditAccNo, creditSubjNo, + amount, transtype.summary, this.details.size + 1)) return this } diff --git a/src/main/kotlin/com/supwisdom/dlpay/consume/controller/consume_service.kt b/src/main/kotlin/com/supwisdom/dlpay/consume/controller/consume_service.kt index 1d3b3122..e16ab7bf 100644 --- a/src/main/kotlin/com/supwisdom/dlpay/consume/controller/consume_service.kt +++ b/src/main/kotlin/com/supwisdom/dlpay/consume/controller/consume_service.kt @@ -78,23 +78,24 @@ class ConsumeController { .selectPaytype(PaytypeUtil.YKTPAY, payinfo) .setOuttradeno(outtradeno) .setOwner(person) + .tryLock(true) .setTransinfo(TradeCode.TRANSCODE_YKTPAY, "一卡通支付") .chooseTradetype(Tradetype.CONSUME) .also { when (feetype) { TradeDict.FEETYPE_CONSUME_MEALER -> { - it.addDetail(AccountHolder.subject(Subject.SUBJ_PAY_YKT), + it.addDetail(AccountHolder.subject(Subject.SUBJNO_PAY_YKT), AccountHolder.shop(shopid), amount / 100.0, "一卡通支付") .addDetail(AccountHolder.transType(TranstypeCode.TT_CONSUUME_MANAGE_FEE) .with(AccountHolder.shop(shopid)), - manageFee / 100.0, "收搭伙费") + manageFee / 100.0) } TradeDict.FEETYPE_CONSUME_DISCOUNT -> { - it.addDetail(AccountHolder.subject(Subject.SUBJ_PAY_YKT), + it.addDetail(AccountHolder.subject(Subject.SUBJNO_PAY_YKT), AccountHolder.shop(shopid), (amount - manageFee) / 100.0, "一卡通支付") - .addDetail(AccountHolder.subject(Subject.SUBJ_DISCOUNT_PRE_DEPOSIT), + .addDetail(AccountHolder.subject(Subject.SUBJNO_CONSUME_DISCOUNT), AccountHolder.shop(shopid), manageFee / 100.0, "优惠折扣") } diff --git a/src/main/kotlin/com/supwisdom/dlpay/consume/service/impl/pay_service_impl.kt b/src/main/kotlin/com/supwisdom/dlpay/consume/service/impl/pay_service_impl.kt index c58e41c7..176a67da 100644 --- a/src/main/kotlin/com/supwisdom/dlpay/consume/service/impl/pay_service_impl.kt +++ b/src/main/kotlin/com/supwisdom/dlpay/consume/service/impl/pay_service_impl.kt @@ -17,10 +17,7 @@ import com.supwisdom.dlpay.framework.domain.TShopacc import com.supwisdom.dlpay.framework.domain.TSubject import com.supwisdom.dlpay.framework.domain.TTranstype import com.supwisdom.dlpay.framework.service.SystemUtilService -import com.supwisdom.dlpay.framework.util.StringUtil -import com.supwisdom.dlpay.framework.util.TradeDict -import com.supwisdom.dlpay.framework.util.TradeErrorCode -import com.supwisdom.dlpay.framework.util.Tradetype +import com.supwisdom.dlpay.framework.util.* import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service import javax.persistence.EntityManager @@ -109,8 +106,7 @@ class PersonBalancePayServiceImpl : PersonBalancePayService { } private fun getLockUserdtlNowait(refno: String): TUserdtl { - //TODO 加锁,非等待 - userdtlDao.findByRefno(refno).let { + userdtlDao.findByRefnoForUpdate(refno).let { if (null != it) { return it } @@ -119,8 +115,7 @@ class PersonBalancePayServiceImpl : PersonBalancePayService { } private fun getLockUserdtl(refno: String): TUserdtl { - //TODO 加锁,等待 - return userdtlDao.findByRefno(refno) + return userdtlDao.findByRefnoForUpdate(refno) } private fun checkOuttradenoExist(outtradeno: String): Boolean { @@ -140,6 +135,7 @@ class PersonBalancePayServiceImpl : PersonBalancePayService { if (!overdraft && account.checkOverdraft()) throw TransactionProcessException(TradeErrorCode.SHORT_BALANCE_ERROR, "账户<$accno>余额不足") + account.tac = account.generateTac() return accountDao.save(account) //入库更新 } @@ -209,46 +205,57 @@ class PersonBalancePayServiceImpl : PersonBalancePayService { return finish(paydtl.refno, status, businessData) } + private fun fail(userdtl: TUserdtl, businessData: Map?) { + //失败 + if (TradeDict.DTL_STATUS_SUCCESS == userdtl.status) + throw TransactionProcessException(TradeErrorCode.TRANSACTION_IS_FINISHED, "流水已经交易成功") + userdtl.status = TradeDict.DTL_STATUS_FAIL + userdtl.endtime = systemUtilService.sysdatetime.hostdatetime + userdtl.remark = businessData?.get("errmsg") + userdtlDao.save(userdtl) + } + + private fun success(userdtl: TUserdtl, businessData: Map?) { + if (TradeDict.DTL_STATUS_SUCCESS == userdtl.status) { + return + } + + debitCreditDtlDao.findByRefno(userdtl.refno).forEach { detail -> + //个人账户入账 + if (Subject.SUBJNO_PERSONAL_DEPOSIT == detail.drsubjno) { + doDealAccount(detail.draccno, -1 * detail.amount, false) //借方消费 + } + if (Subject.SUBJNO_PERSONAL_DEPOSIT == detail.crsubjno) { + doDealAccount(detail.craccno, detail.amount, false) //贷方充值 + } + + //商户入账 + if (Subject.SUBJNO_MACHANT_INCOME == detail.drsubjno) { + doDealShopacc(detail.draccno, -1 * detail.amount) + } + if (Subject.SUBJNO_MACHANT_INCOME == detail.crsubjno) { + doDealShopacc(detail.craccno, detail.amount) + } + } + + userdtl.status = TradeDict.DTL_STATUS_SUCCESS + userdtl.accdate = systemUtilService.accdate //入账成功时更新 + userdtl.endtime = systemUtilService.sysdatetime.hostdatetime + //TODO 存储一些业务参数 + userdtlDao.save(userdtl) + } + override fun finish(refno: String, status: String, businessData: Map?): TUserdtl { val userdtl = getLockUserdtl(refno) return when (status) { TradeDict.DTL_STATUS_FAIL -> { - //失败 - if (TradeDict.DTL_STATUS_SUCCESS == userdtl.status) - throw TransactionProcessException(TradeErrorCode.TRANSACTION_IS_FINISHED, "流水已经交易成功") - userdtl.status = TradeDict.DTL_STATUS_FAIL - userdtl.endtime = systemUtilService.sysdatetime.hostdatetime - userdtl.remark = businessData?.get("errmsg") - userdtlDao.save(userdtl) + fail(userdtl, businessData) + userdtl } TradeDict.DTL_STATUS_SUCCESS -> { //成功 - if (TradeDict.DTL_STATUS_SUCCESS == userdtl.status) - return userdtl //已成功直接返回 - - debitCreditDtlDao.findByRefno(userdtl.refno).forEach { detail -> - //个人账户入账 - if (TradeDict.SUBJNO_ACCOUNT == detail.drsubjno) { - doDealAccount(detail.draccno, -1 * detail.amount, false) //借方消费 - } - if (TradeDict.SUBJNO_ACCOUNT == detail.crsubjno) { - doDealAccount(detail.craccno, detail.amount, false) //贷方充值 - } - - //商户入账 - if (TradeDict.SUBJNO_SHOP == detail.drsubjno) { - doDealShopacc(detail.draccno, -1 * detail.amount) - } - if (TradeDict.SUBJNO_SHOP == detail.crsubjno) { - doDealShopacc(detail.craccno, detail.amount) - } - } - - userdtl.status = TradeDict.DTL_STATUS_SUCCESS - userdtl.accdate = systemUtilService.accdate //入账成功时更新 - userdtl.endtime = systemUtilService.sysdatetime.hostdatetime - //TODO 存储一些业务参数 - userdtlDao.save(userdtl) + success(userdtl, businessData) + userdtl } else -> throw TransactionProcessException(TradeErrorCode.TRANSDTL_STATUS_ERROR, "未指定明确的交易结束状态") } @@ -264,9 +271,10 @@ class PersonBalancePayServiceImpl : PersonBalancePayService { return userdtl } - if (TradeDict.DTL_STATUS_INIT != userdtl.status) - throw TransactionProcessException(TradeErrorCode.TRANSDTL_STATUS_NOT_INIT, "交易参考号<$refno>非初始化流水") - + if (TradeDict.DTL_STATUS_INIT != userdtl.status) { + throw TransactionProcessException(TradeErrorCode.TRANSDTL_STATUS_NOT_INIT, + "交易参考号<$refno>非初始化流水") + } userdtl.status = TradeDict.DTL_STATUS_WIP //待支付 return userdtlDao.save(userdtl) } diff --git a/src/main/kotlin/com/supwisdom/dlpay/consume/service/pay_service.kt b/src/main/kotlin/com/supwisdom/dlpay/consume/service/pay_service.kt index 84da4085..7bf801ec 100644 --- a/src/main/kotlin/com/supwisdom/dlpay/consume/service/pay_service.kt +++ b/src/main/kotlin/com/supwisdom/dlpay/consume/service/pay_service.kt @@ -18,7 +18,7 @@ interface AccountUtilServcie { fun readSubjectForUpdate(subjno: String, nowait: Boolean): TSubject fun readSubject(subjno: String): TSubject - fun readTranstype(transtype: Int) : TTranstype + fun readTranstype(transtype: Int): TTranstype } interface PersonBalancePayService { @@ -42,6 +42,8 @@ interface PersonBalancePayService { /** * 两步交易,交易过程中判断交易状态,并更新交易状态为 wip + * 如果交易记录被锁,立刻抛出异常 + * @throws TransactionProcessException */ fun wip(paydtl: TUserdtl): TUserdtl -- 2.17.1