快捷支付功能初始化
diff --git a/backend/build.gradle b/backend/build.gradle
index b1e80f4..655b7ee 100644
--- a/backend/build.gradle
+++ b/backend/build.gradle
@@ -74,7 +74,7 @@
     compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.9.1'
     compile group: 'log4j', name: 'log4j', version: '1.2.17'
 
-    compile group: 'com.supwisdom', name: 'payapi-sdk', version: '1.0.29'
+    compile group: 'com.supwisdom', name: 'payapi-sdk', version: '1.0.30-1-g0f6aba8'
     
     implementation 'org.hamcrest:hamcrest:2.1'
 }
diff --git a/backend/src/main/java/com/supwisdom/dlpay/framework/util/DataUtil.java b/backend/src/main/java/com/supwisdom/dlpay/framework/util/DataUtil.java
index ad2368d..8bbf1cb 100644
--- a/backend/src/main/java/com/supwisdom/dlpay/framework/util/DataUtil.java
+++ b/backend/src/main/java/com/supwisdom/dlpay/framework/util/DataUtil.java
@@ -14,6 +14,14 @@
     return prefix + repeat("*", length - start - end) + suffix;
   }
 
+  public static String nameDesensitization(String name) {
+    if (StringUtil.isEmpty(name)) {
+      return "";
+    }
+    String prefix = name.substring(1);
+    return prefix + repeat("*", name.length() - 1);
+  }
+
   public static String repeat(String str, int length) {
     StringBuilder sb = new StringBuilder(str);
     for (int i = 0; i < length - 1; i++) {
diff --git a/backend/src/main/java/com/supwisdom/dlpay/medical/domain/TBHospital.java b/backend/src/main/java/com/supwisdom/dlpay/medical/domain/TBHospital.java
index 00881d0..085f13a 100644
--- a/backend/src/main/java/com/supwisdom/dlpay/medical/domain/TBHospital.java
+++ b/backend/src/main/java/com/supwisdom/dlpay/medical/domain/TBHospital.java
@@ -38,6 +38,8 @@
   private String picid;
   @Column(name = "minpicid", length = 32)
   private String minpicid;
+  @Column(name = "shopaccno", length = 20)
+  private String shopaccno;
 
   public String getId() {
     return id;
@@ -118,4 +120,12 @@
   public void setMinpicid(String minpicid) {
     this.minpicid = minpicid;
   }
+
+  public String getShopaccno() {
+    return shopaccno;
+  }
+
+  public void setShopaccno(String shopaccno) {
+    this.shopaccno = shopaccno;
+  }
 }
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/MedicalApi.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/MedicalApi.kt
index e71bb2c..da3986c 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/MedicalApi.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/MedicalApi.kt
@@ -1,18 +1,19 @@
 package com.supwisdom.dlpay.medical
 
-import com.supwisdom.dlpay.api.bean.JsonResult
-import com.supwisdom.dlpay.api.bean.QueryCardParam
-import com.supwisdom.dlpay.api.bean.QueryUserParam
+import com.supwisdom.dlpay.api.bean.*
 import com.supwisdom.dlpay.framework.service.SystemUtilService
+import com.supwisdom.dlpay.framework.util.DataUtil
 import com.supwisdom.dlpay.framework.util.TradeDict
 import com.supwisdom.dlpay.medical.bean.AddPatientBean
 import com.supwisdom.dlpay.medical.bean.ConfirmRequestBean
 import com.supwisdom.dlpay.medical.bean.PaymentRequestBean
+import com.supwisdom.dlpay.medical.bean.YnrccPayRequestBean
 import com.supwisdom.dlpay.medical.exception.MedicineException
 import com.supwisdom.dlpay.medical.service.MedicalService
 import com.supwisdom.dlpay.medical.task.NotifyHISAsyncTask
 import com.supwisdom.dlpay.medical.util.MedicalConstant
 import com.supwisdom.dlpay.mobile.domain.TBMobileUser
+import com.supwisdom.dlpay.mobile.exception.UserLoginFailException
 import com.supwisdom.dlpay.mobile.service.MobileApiService
 import com.supwisdom.dlpay.paysdk.proxy.UserProxy
 import com.supwisdom.dlpay.util.ConstantUtil
@@ -48,6 +49,12 @@
      */
     @GetMapping("/hospital/list")
     fun getHospitalList(pageno: Int, pagesize: Int, name: String?): JsonResult? {
+        val p = SecurityContextHolder.getContext().authentication
+        val user = mobileApiService.findUserById(p.name)
+                ?: return JsonResult.error("用户不存在,请注册")
+        if (user.userid.isNullOrEmpty()) {
+            return JsonResult.error("用户未签约,请先签约")
+        }
         val data = medicalService.getHospitalList(pageno, pagesize, name)
         return JsonResult.ok().put("data", data)
     }
@@ -227,6 +234,9 @@
             val p = SecurityContextHolder.getContext().authentication
             val user = mobileApiService.findUserById(p.name)
                     ?: return JsonResult.error("用户不存在,请注册")
+            if (user.userid.isNullOrEmpty()) {
+                return JsonResult.error("用户未签约,请先签约")
+            }
             if (user.paypwd.isNullOrEmpty()) {
                 return JsonResult.error("支付密码未设置,请先设置")
             }
@@ -487,4 +497,186 @@
             return JsonResult.error("系统异常,请稍后重试")
         }
     }
+
+    /**
+     * 快捷支付签约查询用户信息
+     */
+    @PostMapping("/quickpay/signinfo")
+    fun quickPaySignInfo(): JsonResult? {
+        try {
+            val p = SecurityContextHolder.getContext().authentication
+            val user = mobileApiService.findUserById(p.name)
+                    ?: return JsonResult.error("用户不存在,请注册")
+            if (user.userid.isNullOrEmpty()) {
+                return JsonResult.error("请先签约大理市民卡")
+            }
+            val personResponse = userProxy.queryPerson(user.userid)
+            if (personResponse.retcode != MedicalConstant.PAYAPI_SUCCESS_RETCODE) {
+                logger.error { "查询用户[${user.userid}]信息失败:${personResponse.retmsg}" }
+                return JsonResult.error("签约失败:查询用户信息失败")
+            }
+            val person = personResponse.person
+            val bankCardResponse = userProxy.queryCard(QueryCardParam().apply {
+                this.userid = user.userid
+                this.cardtype = ConstantUtil.CARDTYPE_BANKCARD
+            })
+            if (bankCardResponse.retcode != MedicalConstant.PAYAPI_SUCCESS_RETCODE) {
+                logger.error { "查询用户[${user.userid}]银行卡失败:${bankCardResponse.retmsg}" }
+                return JsonResult.error("签约失败:查询用户银行卡失败")
+            }
+            return JsonResult.ok().put("name", DataUtil.nameDesensitization(person.name))
+                    ?.put("idno", DataUtil.desensitization(person.idno, 3, 4))
+                    ?.put("bankcardno", DataUtil.desensitization(bankCardResponse.card.cardno, 4, 4))
+        } catch (e: Exception) {
+            logger.error("系统异常", e)
+            if (e is MedicineException) {
+                return JsonResult.error(e.message)
+            }
+            return JsonResult.error("系统异常,查询签约信息失败")
+        }
+    }
+
+    /**
+     * 快捷支付签约初始化
+     */
+    @PostMapping("/quickpay/signinit")
+    fun quickPaySignInit(hospitalcode: String, bankcardno: String): JsonResult? {
+        try {
+            val p = SecurityContextHolder.getContext().authentication
+            val user = mobileApiService.findUserById(p.name)
+                    ?: return JsonResult.error("用户不存在,请注册")
+            if (user.userid.isNullOrEmpty()) {
+                return JsonResult.error("请先签约大理市民卡")
+            }
+            val hospital = medicalService.getHospitalByCode(hospitalcode)
+                    ?: return JsonResult.error("未找到该医院")
+
+            val signResponse = userProxy.applyYnrccPaySign(YnrccPaySignApplyParam().apply {
+                this.bankcardno = bankcardno
+                this.shopaccno = hospital.shopaccno
+                this.userid = user.userid
+            })
+            if (signResponse.retcode != MedicalConstant.PAYAPI_SUCCESS_RETCODE) {
+                logger.error("快捷支付签约初始化失败:${signResponse.retcode},${signResponse.retmsg}")
+                return JsonResult.error("签约失败:${signResponse.retmsg}")
+            }
+            return JsonResult.ok().put("data", signResponse)
+        } catch (e: Exception) {
+            logger.error("系统异常", e)
+            if (e is MedicineException) {
+                return JsonResult.error(e.message)
+            }
+            return JsonResult.error("系统异常,签约失败")
+        }
+    }
+
+    /**
+     * 快捷支付签约确认
+     */
+    @PostMapping("/quickpay/signconfirm")
+    fun quickPaySignConfirm(signno: String, plain: String, signature: String): JsonResult? {
+        try {
+            val p = SecurityContextHolder.getContext().authentication
+            val user = mobileApiService.findUserById(p.name)
+                    ?: return JsonResult.error("用户不存在,请注册")
+            if (user.userid.isNullOrEmpty()) {
+                return JsonResult.error("请先签约大理市民卡")
+            }
+            val confirmResponse = userProxy.confirmYnrccPaySign(YnrccPaySignConfirmParam().apply {
+                this.signNo = signno
+                this.plain = plain
+                this.signature = signature
+            })
+            if (confirmResponse.retcode != MedicalConstant.PAYAPI_SUCCESS_RETCODE) {
+                logger.error("快捷支付签约确认失败:${confirmResponse.retcode},${confirmResponse.retmsg}")
+                return JsonResult.error("签约失败:${confirmResponse.retmsg}")
+            }
+            return JsonResult.ok()
+        } catch (e: Exception) {
+            logger.error("系统异常", e)
+            if (e is MedicineException) {
+                return JsonResult.error(e.message)
+            }
+            return JsonResult.error("系统异常,签约失败")
+        }
+    }
+
+    /**
+     * 快捷支付解约
+     */
+    @PostMapping("/quickpay/signcancel")
+    fun quickPaySignCancel(hospitalcode: String, bankcardno: String): JsonResult? {
+        try {
+            val p = SecurityContextHolder.getContext().authentication
+            val user = mobileApiService.findUserById(p.name)
+                    ?: return JsonResult.error("用户不存在,请注册")
+            if (user.userid.isNullOrEmpty()) {
+                return JsonResult.error("请先签约大理市民卡")
+            }
+            val hospital = medicalService.getHospitalByCode(hospitalcode)
+                    ?: return JsonResult.error("未找到该医院")
+            val cancelResponse = userProxy.cancelYnrccPaySign(YnrccPaySignCancelParam().apply {
+                this.bankcardno = bankcardno
+                this.shopaccno = hospital.shopaccno
+                this.userid = user.userid
+            })
+            if (cancelResponse.retcode != MedicalConstant.PAYAPI_SUCCESS_RETCODE) {
+                logger.error("快捷支付解约失败:${cancelResponse.retcode},${cancelResponse.retmsg}")
+                return JsonResult.error("解约失败:${cancelResponse.retmsg}")
+            }
+            return JsonResult.ok()
+        } catch (e: Exception) {
+            logger.error("系统异常", e)
+            if (e is MedicineException) {
+                return JsonResult.error(e.message)
+            }
+            return JsonResult.error("系统异常,解约失败")
+        }
+    }
+
+    /**
+     * 快捷支付支付功能
+     */
+    @PostMapping("/quickpay/payinit")
+    fun quickPayInit(@RequestBody bean: YnrccPayRequestBean): JsonResult? {
+        try {
+            val p = SecurityContextHolder.getContext().authentication
+            val user = mobileApiService.findUserById(p.name)
+                    ?: return JsonResult.error("用户不存在,请注册")
+            if (user.userid.isNullOrEmpty()) {
+                return JsonResult.error("用户未签约,请先签约")
+            }
+            if (user.paypwd.isNullOrEmpty()) {
+                return JsonResult.error("支付密码未设置,请先设置")
+            }
+            val paypwdtimes = user.checkPaypwdtime()
+            if (paypwdtimes == -1) {
+                return JsonResult.error("支付密码错误次数过多,请30分钟后再试")
+            } else if (paypwdtimes == 1) {
+                mobileApiService.saveUser(user)
+            }
+            val encoder = BCryptPasswordEncoder()
+            if (!encoder.matches(bean.paypwd, user.paypwd)) {
+                user.updatePaypwderror(false).also {
+                    if (it) mobileApiService.saveUser(user)
+                }
+                return JsonResult.error("支付密码错误")
+            } else {
+                user.updatePaypwderror(true).also {
+                    if (it) mobileApiService.saveUser(user)
+                }
+            }
+            if (TradeDict.STATUS_NORMAL != user.status) {
+                return JsonResult.error("用户状态异常")
+            }
+            medicalService.medicalYnrccPayInit(bean)
+            return JsonResult.ok()
+        } catch (e: Exception) {
+            logger.error("系统异常", e)
+            if (e is MedicineException) {
+                return JsonResult.error(e.message)
+            }
+            return JsonResult.error("系统异常,解约失败")
+        }
+    }
 }
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/bean/YnrccPayRequestBean.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/bean/YnrccPayRequestBean.kt
new file mode 100644
index 0000000..970625d
--- /dev/null
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/bean/YnrccPayRequestBean.kt
@@ -0,0 +1,8 @@
+package com.supwisdom.dlpay.medical.bean
+
+class YnrccPayRequestBean {
+    var billno: String = ""
+    var paypwd: String = ""
+    var uid: String = ""
+    var userid: String = ""
+}
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/service/MedicalService.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/service/MedicalService.kt
index d8197ab..d0fad00 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/service/MedicalService.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/service/MedicalService.kt
@@ -2,6 +2,7 @@
 
 import com.supwisdom.dlpay.framework.jpa.page.Pagination
 import com.supwisdom.dlpay.medical.bean.*
+import com.supwisdom.dlpay.medical.domain.TBHospital
 import com.supwisdom.dlpay.medical.domain.TBMedicalCard
 import com.supwisdom.dlpay.medical.domain.TBMedicalDtl
 import org.springframework.transaction.annotation.Transactional
@@ -81,4 +82,10 @@
 
     @Transactional
     fun queryRefundResult(bean: PayRefundRequestBean): TBMedicalDtl
+
+    @Transactional
+    fun getHospitalByCode(code: String): TBHospital?
+
+    @Transactional
+    fun medicalYnrccPayInit(bean: YnrccPayRequestBean)
 }
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/service/impl/MedicalServiceImpl.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/service/impl/MedicalServiceImpl.kt
index 499ca0d..4c609a1 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/service/impl/MedicalServiceImpl.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/service/impl/MedicalServiceImpl.kt
@@ -1,9 +1,6 @@
 package com.supwisdom.dlpay.medical.service.impl
 
-import com.supwisdom.dlpay.api.bean.CitizenCardPayfinishParam
-import com.supwisdom.dlpay.api.bean.CitizenCardPayinitParam
-import com.supwisdom.dlpay.api.bean.ConsumePayCancelParam
-import com.supwisdom.dlpay.api.bean.QueryDtlResultParam
+import com.supwisdom.dlpay.api.bean.*
 import com.supwisdom.dlpay.framework.jpa.page.Pagination
 import com.supwisdom.dlpay.framework.service.SystemUtilService
 import com.supwisdom.dlpay.framework.util.MoneyUtil
@@ -17,8 +14,11 @@
 import com.supwisdom.dlpay.medical.util.MedicalClient
 import com.supwisdom.dlpay.medical.util.MedicalConstant
 import com.supwisdom.dlpay.paysdk.proxy.CitizenCardPayProxy
+import com.supwisdom.dlpay.paysdk.proxy.ConsumePropxy
 import com.supwisdom.dlpay.paysdk.proxy.TransactionProxy
+import com.supwisdom.dlpay.paysdk.proxy.UserProxy
 import com.supwisdom.dlpay.portal.util.PortalConstant
+import com.supwisdom.dlpay.util.ConstantUtil
 import mu.KotlinLogging
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.data.domain.PageRequest
@@ -48,6 +48,10 @@
     @Autowired
     lateinit var transactionProxy: TransactionProxy
     @Autowired
+    lateinit var consumePropxy: ConsumePropxy
+    @Autowired
+    lateinit var userProxy: UserProxy
+    @Autowired
     lateinit var labReportDao: LabReportDao
     @Autowired
     lateinit var labDetailDao: LabDetailDao
@@ -484,7 +488,7 @@
             }
             val payInit = citizenCardPayProxy.citizencardPayinit(initParam)
             if (payInit.retcode != MedicalConstant.PAYAPI_SUCCESS_RETCODE) {
-                logger.error("支付初始化失败:[$payInit.retmsg]")
+                logger.error("支付初始化失败:[${payInit.retmsg}]")
                 throw MedicineException("系统异常,支付失败")
             }
             medicalDtl.refno = payInit.refno
@@ -961,4 +965,24 @@
             throw MedicineException("该流水没有退款流水")
         }
     }
+
+    override fun getHospitalByCode(code: String): TBHospital? {
+        return hospitalDao.findByHospitalcodeAndStatus(code, TradeDict.STATUS_NORMAL)
+    }
+
+    override fun medicalYnrccPayInit(bean: YnrccPayRequestBean) {
+        val bankCardResponse = userProxy.queryCard(QueryCardParam().apply {
+            this.userid = bean.userid
+            this.cardtype = ConstantUtil.CARDTYPE_BANKCARD
+        })
+        if (bankCardResponse.retcode != MedicalConstant.PAYAPI_SUCCESS_RETCODE) {
+            logger.error { "查询用户[${bean.userid}]银行卡失败:${bankCardResponse.retmsg}" }
+            throw MedicineException("支付失败:查询用户银行卡失败")
+        }
+        consumePropxy.ynrccPayOrder(YnrccPayOrderParam().apply {
+            this.userid = bean.userid
+            this.bankcardno = bankCardResponse.card.cardno
+            this.billno = bean.billno
+        })
+    }
 }
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/service/YnrccPayService.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/service/YnrccPayService.kt
new file mode 100644
index 0000000..5e30724
--- /dev/null
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/service/YnrccPayService.kt
@@ -0,0 +1,5 @@
+package com.supwisdom.dlpay.mobile.service
+
+interface YnrccPayService {
+
+}
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/service/impl/YnrccPayServiceImpl.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/service/impl/YnrccPayServiceImpl.kt
new file mode 100644
index 0000000..06ef58a
--- /dev/null
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/service/impl/YnrccPayServiceImpl.kt
@@ -0,0 +1,9 @@
+package com.supwisdom.dlpay.mobile.service.impl
+
+import com.supwisdom.dlpay.mobile.service.YnrccPayService
+import org.springframework.stereotype.Service
+
+@Service
+class YnrccPayServiceImpl : YnrccPayService{
+
+}
\ No newline at end of file