Quellcode durchsuchen

爱签增加个人意愿核身认证接口

zhou.xiaoning vor 1 Jahr
Ursprung
Commit
2ff354261a

+ 6 - 6
config/config.xml

@@ -57,10 +57,10 @@
     <SignKey value="EB49C115EEDE44049F11495F6EA7526F"/>
   </Tencent>
   <Asign>
-  <Url value="https://oapi.asign.cn"/>
-  <AppId value="332768562"/>
-  <PrivateKey value="MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDB1BczuEtnsE+4HmM1aj+n363w2FaXhJkm/3JStPWY93pNDEaWN9IevPsUAjKEXGw4kaYWJAs6oLcouPz5BUONOoRubk6cXk9juBPa1Kipyr5W5vlhaPOCMx9YSBGNGef6RoVBT+H9U3nVTJyJIFQM2teWNw8PrlbcHJAFJjwAwVySbOeJuhYMXK/kKTi7UNP1UGFL0e3n0CZyYH1/k8gcmzlz+nKWtQzTQi7ijFzg3ycoDf6YnGTzUOssdmGrxyjH+KRaFYXVGUg2StfjTaUEV1ww+zICRdIyQgHMRCn0hkvKuVbNCUPSx75Cz6AcW5X8/Oq6dv0zfZy50mZ7T1wtAgMBAAECggEAUYfUJqY72aa5MbpegsZpdkTJVsi5S8yz20qSHSYPNMPMhwJF4sQiN1nIxzbRZwm1X+osEZ4htwyJXrDJgDntm0dlE0qE+JNXE5wHhgHz3E/y1n0boxTRcQ/t/GV8NH0ULhthzAI9DiEB2235Xljh4fJfwsjwJelwke/VyMS3cp0nqp8/pyzhLKnqr7JL9uOQqYKo+RiAgX4Y2Lrll/vSIONw9fefCK5u9kTdXt5numUc+Vdt81sX62vNk38iNqqBW+suwr8KOOSM5mlffxKbRxHkPTCJTYA+Rdlfpdms3/Co7MotfWy0XamcW7TNyt8PkDQNLySF4kgIdKaKZjU4oQKBgQDpGeHMW1EwrQVjWPXhLmxDOse/3apRqLZ85urVn4wnDStMDzaDZoUf6PEmQlLPl8lyKxuKTw8xXPp2BKTGg6eseUVjA2fidufwnJ/a4CmkvFbTNLbEiNqRA6VUht7SGSviVwT8Fg0DH2139otlQdup/ypFQFUGKNUKPwR45PpD+QKBgQDU3pC2cBHEYcEwdw7xM9cKBLrF5CdHnBR8kA8L7bk2kzm23nWHWBr6frAhZTYeoTvADaZSMt1EURKS13J1q0wDkfUBolPif3pAjTZVJV6dMDonnbbvOXU/u4czsMp077RCfbbgr15gH2He6KPK0xmI1mME/JBVm4cy9kldAjK+1QKBgEdwJDBSCCbnz8/rsqdJZ+qOuCsIDyupjgvnZS8FD5nkQlG0E9vfbosiA83a47A1b4pqxaz0n85cFXJX5/vi219TZz16DLtt/5VRGvikXH7dLara6+x9mKZ6vI+UKfOvvTBHxQJQ67n6Rr1ONNhaf6yww86ncadKmkfimkdGvaBZAoGACUeelWVzUNVWH6/BBgDViglEvyXAFJ4YQonhLgrx/RAT/wXZp8nOe0cUlJAd+N3chfXSX1j6TmUeYypjQf/ys2Ekhiq2b3RRNwkw6itT4ZRdpKZ6eBlQJDQfo8A8QzbD6/YhB9B9U8FqedY4dboYLd/ytx/vhtvd2/nintxeq/UCgYEAqe1lh9lYbxPREsHjuhHiRzxqxdGS13hW2bvoLisAhQdNx/L9HFtEO5Nx2fRdzFRgXbH/QjfOxEadMia04uY0upqfg4fPT0Za2WIvn+uJBxpRiwupbAlKmP626jdmajJ03aQe1F7P8Ny+96Wh8x18uaMJx88Kg7e7+hi95WokLB4="/>
-  <NotifyUrl value="http://218.17.158.45:15105/api/Asign/HandleASignCompleted"/>
-  <OpenApiUrl value="http://192.168.31.134:5015/mtp2-onlineopen"/>
-</Asign>
+    <Url value="https://oapi.asign.cn"/>
+    <AppId value="332768562"/>
+    <PrivateKey value="MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDB1BczuEtnsE+4HmM1aj+n363w2FaXhJkm/3JStPWY93pNDEaWN9IevPsUAjKEXGw4kaYWJAs6oLcouPz5BUONOoRubk6cXk9juBPa1Kipyr5W5vlhaPOCMx9YSBGNGef6RoVBT+H9U3nVTJyJIFQM2teWNw8PrlbcHJAFJjwAwVySbOeJuhYMXK/kKTi7UNP1UGFL0e3n0CZyYH1/k8gcmzlz+nKWtQzTQi7ijFzg3ycoDf6YnGTzUOssdmGrxyjH+KRaFYXVGUg2StfjTaUEV1ww+zICRdIyQgHMRCn0hkvKuVbNCUPSx75Cz6AcW5X8/Oq6dv0zfZy50mZ7T1wtAgMBAAECggEAUYfUJqY72aa5MbpegsZpdkTJVsi5S8yz20qSHSYPNMPMhwJF4sQiN1nIxzbRZwm1X+osEZ4htwyJXrDJgDntm0dlE0qE+JNXE5wHhgHz3E/y1n0boxTRcQ/t/GV8NH0ULhthzAI9DiEB2235Xljh4fJfwsjwJelwke/VyMS3cp0nqp8/pyzhLKnqr7JL9uOQqYKo+RiAgX4Y2Lrll/vSIONw9fefCK5u9kTdXt5numUc+Vdt81sX62vNk38iNqqBW+suwr8KOOSM5mlffxKbRxHkPTCJTYA+Rdlfpdms3/Co7MotfWy0XamcW7TNyt8PkDQNLySF4kgIdKaKZjU4oQKBgQDpGeHMW1EwrQVjWPXhLmxDOse/3apRqLZ85urVn4wnDStMDzaDZoUf6PEmQlLPl8lyKxuKTw8xXPp2BKTGg6eseUVjA2fidufwnJ/a4CmkvFbTNLbEiNqRA6VUht7SGSviVwT8Fg0DH2139otlQdup/ypFQFUGKNUKPwR45PpD+QKBgQDU3pC2cBHEYcEwdw7xM9cKBLrF5CdHnBR8kA8L7bk2kzm23nWHWBr6frAhZTYeoTvADaZSMt1EURKS13J1q0wDkfUBolPif3pAjTZVJV6dMDonnbbvOXU/u4czsMp077RCfbbgr15gH2He6KPK0xmI1mME/JBVm4cy9kldAjK+1QKBgEdwJDBSCCbnz8/rsqdJZ+qOuCsIDyupjgvnZS8FD5nkQlG0E9vfbosiA83a47A1b4pqxaz0n85cFXJX5/vi219TZz16DLtt/5VRGvikXH7dLara6+x9mKZ6vI+UKfOvvTBHxQJQ67n6Rr1ONNhaf6yww86ncadKmkfimkdGvaBZAoGACUeelWVzUNVWH6/BBgDViglEvyXAFJ4YQonhLgrx/RAT/wXZp8nOe0cUlJAd+N3chfXSX1j6TmUeYypjQf/ys2Ekhiq2b3RRNwkw6itT4ZRdpKZ6eBlQJDQfo8A8QzbD6/YhB9B9U8FqedY4dboYLd/ytx/vhtvd2/nintxeq/UCgYEAqe1lh9lYbxPREsHjuhHiRzxqxdGS13hW2bvoLisAhQdNx/L9HFtEO5Nx2fRdzFRgXbH/QjfOxEadMia04uY0upqfg4fPT0Za2WIvn+uJBxpRiwupbAlKmP626jdmajJ03aQe1F7P8Ny+96Wh8x18uaMJx88Kg7e7+hi95WokLB4="/>
+    <NotifyUrl value="http://218.17.158.45:15105/api/Asign/HandleASignCompleted"/>
+    <OpenApiUrl value="http://192.168.31.134:5015/mtp2-onlineopen"/>
+  </Asign>
 </Configuration>

+ 56 - 24
controllers/asign/contract.go

@@ -7,6 +7,7 @@ import (
 	"mtp2_if/logger"
 	asignService "mtp2_if/services/asign"
 	"net/http"
+	"net/url"
 
 	"github.com/gin-gonic/gin"
 )
@@ -39,36 +40,67 @@ func CreateContract(c *gin.Context) {
 	}
 }
 
-// 爱签合同签署完成后异步通知 POST
+// 爱签异步通知
 func HandleASignCompleted(c *gin.Context) {
 	g := app.Gin{C: c}
 
-	action, ok := g.C.GetPostForm("action")
-	if !ok {
-		err := errors.New("获取action失败")
-		g.C.String(http.StatusBadRequest, "%s", err)
-		return
-	}
-	contractNo, ok := g.C.GetPostForm("contractNo")
-	if !ok {
-		err := errors.New("获取contractNo失败")
-		g.C.String(http.StatusBadRequest, "%s", err)
-		return
-	}
-	status, ok := g.C.GetPostForm("status")
-	if !ok {
-		err := errors.New("获取status失败")
-		g.C.String(http.StatusBadRequest, "%s", err)
-		return
-	}
+	if g.C.Request.Method == "POST" {
+		// 爱签合同签署完成后异步通知 POST
+		action, ok := g.C.GetPostForm("action")
+		if !ok {
+			err := errors.New("获取action失败")
+			g.C.String(http.StatusBadRequest, "%s", err)
+			return
+		}
+		contractNo, ok := g.C.GetPostForm("contractNo")
+		if !ok {
+			err := errors.New("获取contractNo失败")
+			g.C.String(http.StatusBadRequest, "%s", err)
+			return
+		}
+		status, ok := g.C.GetPostForm("status")
+		if !ok {
+			err := errors.New("获取status失败")
+			g.C.String(http.StatusBadRequest, "%s", err)
+			return
+		}
 
-	if action == "signCompleted" {
-		if err := asignService.ASignCompleted(contractNo, status); err == nil {
+		if action == "signCompleted" {
+			if err := asignService.ASignCompleted(contractNo, status); err == nil {
+				g.C.String(http.StatusOK, "%s", "ok")
+			} else {
+				g.C.String(http.StatusBadRequest, "%s", err)
+			}
+		} else {
 			g.C.String(http.StatusOK, "%s", "ok")
+		}
+	} else if g.C.Request.Method == "GET" {
+		// 爱签个人意愿核身认证接口异步通知 GET
+		logger.GetLogger().Info("接收爱签个人意愿核身认证接口异步通知, url:" + g.C.Request.URL.String())
+
+		sign := g.C.Query("sign")
+		result := g.C.Query("result")
+		msg := g.C.Query("msg")
+		recordId := g.C.Query("recordId")
+		if sign == "" || result == "" || msg == "" || recordId == "" {
+			g.C.String(http.StatusOK, "提交信息失败,请关闭此页面稍后重试")
+			return
+		}
+
+		if result != "1" {
+			// 认证失败
+			t, _ := url.QueryUnescape(msg)
+			if t == "" {
+				t = "提交信息失败,请关闭此页面稍后重试."
+			}
+			g.C.String(http.StatusOK, t)
+			return
+		}
+
+		if err := asignService.HandleWillFace(sign, result, msg, recordId); err == nil {
+			g.C.String(http.StatusOK, "提交信息成功,请关闭此页面")
 		} else {
-			g.C.String(http.StatusBadRequest, "%s", err)
+			g.C.String(http.StatusOK, "提交信息失败,请关闭此页面稍后重试")
 		}
-	} else {
-		g.C.String(http.StatusOK, "%s", "ok")
 	}
 }

+ 1 - 0
routers/router.go

@@ -856,6 +856,7 @@ func InitRouter() *gin.Engine {
 	{
 		asignR.Use().POST("SyncContractStatus", asign.SyncContractStatus)
 		asignR.Use().POST("HandleASignCompleted", asign.HandleASignCompleted)
+		asignR.Use().GET("HandleASignCompleted", asign.HandleASignCompleted)
 
 		if config.SerCfg.GetDebugMode() {
 			asignR.Use().POST("CreateSeal", asign.CreateSeal)

+ 21 - 2
services/asign/apiModels.go

@@ -15,7 +15,8 @@ type APIReqData interface {
 		APIDownloadContractReq |
 		APICreateSealReq |
 		APIModifySealReq |
-		APIGetUserSealsReq
+		APIGetUserSealsReq |
+		APIWillFaceReq
 }
 
 type APIReq[T APIReqData] struct {
@@ -29,7 +30,8 @@ type APIRspData interface {
 		APIAddUserRsp |
 		APICaptchaVerifyRsp |
 		APIGetUserRsp |
-		APIGetUserSealsRsp
+		APIGetUserSealsRsp |
+		APIWillFaceRsp
 }
 
 type APIRsp[T APIRspData] struct {
@@ -389,3 +391,20 @@ type APIGetUserSealsRsp struct {
 	CompanyName string        `json:"companyName"` // 企业名称(当用户类型为企业时返回)
 	List        []APISealInfo `json:"list"`        // 印章列表
 }
+
+// APIWillFaceReq 个人意愿核身认证(双录)入参
+type APIWillFaceReq struct {
+	RealName    string `json:"realName" binding:"required"` // 真实姓名
+	IdCardNo    string `json:"idCardNo" binding:"required"` // 身份证号
+	Question    string `json:"question"`                    // 意愿核身过程中的播报文本,长度120以内。默认为:“您好,为确保您本人操作,此次签约全程录音录像。请问您本次业务是本人自愿办理吗?请回答:我确认或是的”
+	Answer      string `json:"answer"`                      // 用户回答文本,单个回答长度10以内,支持多个文本作为识别内容,文本之间用“|”分割,总长度32以内默认为:“我确认|是的”。
+	RedirectUrl string `json:"redirectUrl"`                 // 人脸认证结果回调通知URL,可拼接参数
+}
+
+// APIWillFaceRsp 个人意愿核身认证(双录)出参
+type APIWillFaceRsp struct {
+	Result   int    `json:"result" binding:"required"`   // 认证结果:0.暂无结果/认证中
+	SerialNo string `json:"serialNo" binding:"required"` // 认证流水号
+	SType    string `json:"type" binding:"required"`     // 认证类型
+	FaceUrl  string `json:"faceUrl" binding:"required"`  // 意愿核身链接
+}

+ 1 - 0
services/asign/const.go

@@ -21,4 +21,5 @@ const (
 	APIURL_AddSigner                 = "/contract/addSigner"        // 添加签署方
 	APIURL_Contract_Status           = "/contract/status"           // 查询合同状态
 	APIURL_GetUserSeals              = "/user/getUserSeals"         // 获取指定用户的印章
+	APIURL_Person_WillFace           = "/auth/person/willFace"      // 个人意愿核身认证(双录)
 )

+ 10 - 0
services/asign/models.go

@@ -97,3 +97,13 @@ type SyncContractStatusRsp struct {
 	ContractName string `json:"contractName"` // 合同名称
 	Status       int    `json:"status"`       // 爱签合同状态:0:等待签约 1:签约中 2:已签约 3:过期 4:拒签 6:作废 -2:状态异常
 }
+
+type WillFaceReq struct {
+	UserId   int    `form:"userId" binding:"required"`   // 用户ID
+	RealName string `json:"realName" binding:"required"` // 真实姓名
+	IdCardNo string `json:"idCardNo" binding:"required"` // 身份证号
+}
+
+type WillFaceRsp struct {
+	FaceUrl string `json:"faceUrl"` // 意愿核身链接
+}

+ 81 - 0
services/asign/servcies.go

@@ -728,6 +728,52 @@ func CreateContract(req CreateContractReq) (rsp CreateContractRsp, err error) {
 	return
 }
 
+// WillFace 个人意愿核身认证
+func WillFace(req WillFaceReq) (rsp WillFaceRsp, err error) {
+	// 从交易库中获取类型为实名认证的电子签信息
+	var record models.Useresignrecord
+	has, err := db.GetEngine().Where("TEMPLATETYPE = 3 AND USERID = ?", req.UserId).Get(&record)
+	if !has {
+		if err == nil {
+			err = errors.New("获取用户电子签记录失败")
+		}
+		logger.GetLogger().Error("个人意愿核身认证异常,错误:", err.Error())
+		return
+	}
+
+	// 调用爱签接口
+	apiWillFaceReq := APIWillFaceReq{
+		RealName:    req.RealName,
+		IdCardNo:    req.IdCardNo,
+		Question:    record.TEMPLATENO,
+		Answer:      record.TEMPLATENAME,
+		RedirectUrl: config.SerCfg.AsignCfg.NotifyUrl + "?recordId=" + strconv.Itoa(int(record.RECORDID)),
+	}
+	apiReq := APIReq[APIWillFaceReq]{Data: apiWillFaceReq}
+	apiRsp, err := APIPost[APIWillFaceReq, APIWillFaceRsp](apiReq, APIURL_Person_WillFace)
+	if err != nil {
+		return
+	}
+	if apiRsp.Code != CODE_SUCCESS {
+		err = errors.New(apiRsp.Msg)
+		return
+	}
+
+	rsp.FaceUrl = apiRsp.Data.FaceUrl
+
+	// 更新用户电子签记录
+	sql := fmt.Sprintf(`
+		UPDATE useresignrecord
+		SET RECORDSTATUS = 2,
+			UPDATETIME = SYSDATE, 
+			SIGNURL = '%v'
+		WHERE RECORDID = %v 
+	`, rsp.FaceUrl, record.RECORDID)
+	_, err = db.GetEngine().Exec(sql)
+
+	return
+}
+
 // 构建甲方签章策略
 func buildStrategyByPartyA(templateConfigId int) (signStrategyList []APISignStrategy, err error) {
 	signStrategyList = make([]APISignStrategy, 0)
@@ -841,3 +887,38 @@ func ASignCompleted(contractNo, status string) (err error) {
 
 	return
 }
+
+// 个人意愿核身认证爱签回调通知处理
+func HandleWillFace(sign, result, msg, recordId string) (err error) {
+	// 获取用户电子签记录
+	useresignrecord := new(models.Useresignrecord)
+	has, err := db.GetEngine().Where("RECORDID = ?", recordId).Get(useresignrecord)
+	if err != nil || !has {
+		if err == nil {
+			err = errors.New("无对应电子签记录")
+		}
+		logger.GetLogger().Error("获取用户电子签记录失败, 错误:", err)
+		return
+	}
+
+	// 人脸认证结果
+	// 1:认证成功
+	// 2:认证失败
+	if result == "1" {
+		// FIXME: - 这里应该校验签名(姓名和身份证号码)
+
+		// 更新数据库记录
+		sql := fmt.Sprintf(`
+			UPDATE useresignrecord
+			SET RECORDSTATUS = 3,
+				UPDATETIME = SYSDATE
+			WHERE RECORDID = %v  
+		`, recordId)
+		if _, err = db.GetEngine().Exec(sql); err != nil {
+			logger.GetLogger().Error("更新用户电子签记录失败, 错误:", err)
+			return
+		}
+	}
+
+	return
+}