改进了入账算法
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