改进了入账算法
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 f49fea8..e465fbd 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 @@
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, String> {
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 dad6b2b..5596b24 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 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 ec7222f..9c49367 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 @@
* */
var outtradeno = "" //发起支付系统的流水号(对接系统根据此流水号查询本地流水对账)
+ var tryLockAccount = false
+
////////////////////////////////////////////////////////////////////////////////////
// 以下为内部参数,不需要调用者处理
val details = mutableListOf<TransDetail>()
@@ -153,6 +155,11 @@
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 @@
@Suppress("UNCHECKED_CAST", "IMPLICIT_CAST_TO_ANY")
fun <U> getAccountAndSubjNo(holder: AccountHolder<U>): Pair<String, String> {
- return when (holder.idType) {
- AccountHolder.IDTYPE_PERSON -> holder.get<TAccount>().let {
- it.accno to it.subjno
+ return if (this.tryLockAccount) {
+ when (holder.idType) {
+ AccountHolder.IDTYPE_PERSON -> holder.withLock<TAccount>(true).let {
+ it.accno to it.subjno
+ }
+ AccountHolder.IDTYPE_SHOP -> holder.withLock<TShopacc>(true).let {
+ it.shopaccno to it.subjno
+ }
+ AccountHolder.IDTYPE_SUBJECT -> holder.withLock<TSubject>(true).let {
+ it.subjno to it.subjno
+ }
+ else -> throw TransactionCheckException(TradeErrorCode.INPUT_DATA_ERROR, "不支持的账户类型")
}
- AccountHolder.IDTYPE_SHOP -> holder.get<TShopacc>().let {
- it.shopaccno to it.subjno
+ } else {
+ when (holder.idType) {
+ AccountHolder.IDTYPE_PERSON -> holder.get<TAccount>().let {
+ it.accno to it.subjno
+ }
+ AccountHolder.IDTYPE_SHOP -> holder.get<TShopacc>().let {
+ it.shopaccno to it.subjno
+ }
+ AccountHolder.IDTYPE_SUBJECT -> holder.get<TSubject>().let {
+ it.subjno to it.subjno
+ }
+ else -> throw TransactionCheckException(TradeErrorCode.INPUT_DATA_ERROR, "不支持的账户类型")
}
- AccountHolder.IDTYPE_SUBJECT -> holder.get<TSubject>().let {
- it.subjno to it.subjno
- }
- else -> throw TransactionCheckException(TradeErrorCode.INPUT_DATA_ERROR, "不支持的账户类型")
}
}
@@ -257,32 +279,38 @@
}
fun addDetail(accountHolder: AccountHolder<TTranstype>,
- amount: Double, summary: String): PersonTransBuilder {
+ amount: Double): PersonTransBuilder {
accountHolder.builder = this
val transtype = accountHolder.get<TTranstype>()
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 1d3b312..e16ab7b 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 @@
.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 c58e41c..176a67d 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.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 @@
}
private fun getLockUserdtlNowait(refno: String): TUserdtl {
- //TODO 加锁,非等待
- userdtlDao.findByRefno(refno).let {
+ userdtlDao.findByRefnoForUpdate(refno).let {
if (null != it) {
return it
}
@@ -119,8 +115,7 @@
}
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 @@
if (!overdraft && account.checkOverdraft())
throw TransactionProcessException(TradeErrorCode.SHORT_BALANCE_ERROR, "账户<$accno>余额不足")
+ account.tac = account.generateTac()
return accountDao.save(account) //入库更新
}
@@ -209,46 +205,57 @@
return finish(paydtl.refno, status, businessData)
}
+ private fun fail(userdtl: TUserdtl, businessData: Map<String, String>?) {
+ //失败
+ 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<String, String>?) {
+ 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<String, String>?): 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 @@
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 84da408..7bf801e 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 @@
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 @@
/**
* 两步交易,交易过程中判断交易状态,并更新交易状态为 wip
+ * 如果交易记录被锁,立刻抛出异常
+ * @throws TransactionProcessException
*/
fun wip(paydtl: TUserdtl): TUserdtl