f
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -20,3 +20,4 @@ data/*
|
|||||||
/tmp/
|
/tmp/
|
||||||
|
|
||||||
/app/api
|
/app/api
|
||||||
|
**/__debug_bin*.exe
|
||||||
19
app/main/api/desc/front/tianyuan.api
Normal file
19
app/main/api/desc/front/tianyuan.api
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
syntax = "v1"
|
||||||
|
|
||||||
|
info (
|
||||||
|
title: "天远异步回调"
|
||||||
|
desc: "天远车辆类接口异步回调入口"
|
||||||
|
version: "v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 该服务只提供第三方回调入口,不依赖登录态
|
||||||
|
@server (
|
||||||
|
prefix: api/v1
|
||||||
|
group: tianyuan
|
||||||
|
)
|
||||||
|
service main {
|
||||||
|
@doc "天远车辆类接口异步回调"
|
||||||
|
@handler vehicleCallback
|
||||||
|
post /tianyuan/vehicle/callback
|
||||||
|
}
|
||||||
|
|
||||||
39
app/main/api/desc/front/upload.api
Normal file
39
app/main/api/desc/front/upload.api
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
syntax = "v1"
|
||||||
|
|
||||||
|
info (
|
||||||
|
title: "上传"
|
||||||
|
desc: "图片上传,用于行驶证等需 URL 的接口"
|
||||||
|
version: "v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
UploadImageReq {
|
||||||
|
ImageBase64 string `json:"image_base64" validate:"required"` // 图片 base64(不含 data URL 前缀)
|
||||||
|
}
|
||||||
|
UploadImageResp {
|
||||||
|
Url string `json:"url"` // 可公网访问的图片 URL
|
||||||
|
}
|
||||||
|
ServeUploadedFileReq {
|
||||||
|
FileName string `path:"fileName"` // 文件名,如 uuid.jpg
|
||||||
|
}
|
||||||
|
// 实际由 Handler 根据 FilePath/ContentType 写文件流,不返回 JSON
|
||||||
|
ServeUploadedFileResp {
|
||||||
|
FilePath string `json:"-"` // 内部:本地文件路径
|
||||||
|
ContentType string `json:"-"` // 内部:Content-Type
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@server (
|
||||||
|
prefix: api/v1
|
||||||
|
group: upload
|
||||||
|
)
|
||||||
|
service main {
|
||||||
|
@doc "上传图片,返回可访问 URL(如行驶证);限制 3MB"
|
||||||
|
@handler UploadImage
|
||||||
|
post /upload/image (UploadImageReq) returns (UploadImageResp)
|
||||||
|
|
||||||
|
@doc "访问已上传文件(供第三方或前端通过返回的 URL 拉取)"
|
||||||
|
@handler ServeUploadedFile
|
||||||
|
get /upload/file/:fileName (ServeUploadedFileReq) returns (ServeUploadedFileResp)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -14,6 +14,8 @@ import "./front/product.api"
|
|||||||
import "./front/agent.api"
|
import "./front/agent.api"
|
||||||
import "./front/app.api"
|
import "./front/app.api"
|
||||||
import "./front/authorization.api"
|
import "./front/authorization.api"
|
||||||
|
import "./front/upload.api"
|
||||||
|
import "./front/tianyuan.api"
|
||||||
// 后台
|
// 后台
|
||||||
import "./admin/auth.api"
|
import "./admin/auth.api"
|
||||||
import "./admin/menu.api"
|
import "./admin/menu.api"
|
||||||
|
|||||||
@@ -86,4 +86,7 @@ Tianyuanapi:
|
|||||||
Timeout: 60
|
Timeout: 60
|
||||||
Authorization:
|
Authorization:
|
||||||
FileBaseURL: "https://www.tianyuancha.cn/api/v1/auth-docs" # 授权书文件访问基础URL
|
FileBaseURL: "https://www.tianyuancha.cn/api/v1/auth-docs" # 授权书文件访问基础URL
|
||||||
|
Upload:
|
||||||
|
FileBaseURL: "https://www.tianyuancha.cn/api/v1/upload/file" # 上传图片访问基础 URL
|
||||||
|
TempFileMaxAgeH: 24 # 临时文件保留时长(小时),超时自动删除,0 表示默认 24
|
||||||
ExtensionTime: 24 # 佣金解冻延迟时间,单位:24小时
|
ExtensionTime: 24 # 佣金解冻延迟时间,单位:24小时
|
||||||
|
|||||||
@@ -95,4 +95,6 @@ Tianyuanapi:
|
|||||||
Timeout: 60
|
Timeout: 60
|
||||||
Authorization:
|
Authorization:
|
||||||
FileBaseURL: "https://www.tianyuancha.cn/api/v1/auth-docs" # 授权书文件访问基础URL
|
FileBaseURL: "https://www.tianyuancha.cn/api/v1/auth-docs" # 授权书文件访问基础URL
|
||||||
|
Upload:
|
||||||
|
FileBaseURL: "https://www.tianyuancha.cn/api/v1/upload/file" # 上传图片访问基础 URL(行驶证等)
|
||||||
ExtensionTime: 24 # 佣金解冻延迟时间,单位:24小时
|
ExtensionTime: 24 # 佣金解冻延迟时间,单位:24小时
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ type Config struct {
|
|||||||
SystemConfig SystemConfig
|
SystemConfig SystemConfig
|
||||||
WechatH5 WechatH5Config
|
WechatH5 WechatH5Config
|
||||||
Authorization AuthorizationConfig // 授权书配置
|
Authorization AuthorizationConfig // 授权书配置
|
||||||
|
Upload UploadConfig // 图片上传(行驶证等)配置
|
||||||
WechatMini WechatMiniConfig
|
WechatMini WechatMiniConfig
|
||||||
Query QueryConfig
|
Query QueryConfig
|
||||||
AdminConfig AdminConfig
|
AdminConfig AdminConfig
|
||||||
@@ -136,3 +137,9 @@ type TianyuanapiConfig struct {
|
|||||||
type AuthorizationConfig struct {
|
type AuthorizationConfig struct {
|
||||||
FileBaseURL string // 授权书文件访问基础URL
|
FileBaseURL string // 授权书文件访问基础URL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UploadConfig 图片上传(行驶证等)配置,临时存储,按 hash 去重
|
||||||
|
type UploadConfig struct {
|
||||||
|
FileBaseURL string `json:",optional"` // 上传文件访问基础 URL,如 https://xxx/api/v1/upload/file
|
||||||
|
TempFileMaxAgeH int `json:",optional"` // 临时文件保留时长(小时),超时删除,0 表示默认 24 小时
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import (
|
|||||||
pay "tyc-server/app/main/api/internal/handler/pay"
|
pay "tyc-server/app/main/api/internal/handler/pay"
|
||||||
product "tyc-server/app/main/api/internal/handler/product"
|
product "tyc-server/app/main/api/internal/handler/product"
|
||||||
query "tyc-server/app/main/api/internal/handler/query"
|
query "tyc-server/app/main/api/internal/handler/query"
|
||||||
|
tianyuan "tyc-server/app/main/api/internal/handler/tianyuan"
|
||||||
|
upload "tyc-server/app/main/api/internal/handler/upload"
|
||||||
user "tyc-server/app/main/api/internal/handler/user"
|
user "tyc-server/app/main/api/internal/handler/user"
|
||||||
"tyc-server/app/main/api/internal/svc"
|
"tyc-server/app/main/api/internal/svc"
|
||||||
|
|
||||||
@@ -770,7 +772,7 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
|
|
||||||
server.AddRoutes(
|
server.AddRoutes(
|
||||||
rest.WithMiddlewares(
|
rest.WithMiddlewares(
|
||||||
[]rest.Middleware{serverCtx.UserDisableInterceptor, serverCtx.UserAuthInterceptor},
|
[]rest.Middleware{serverCtx.UserAuthInterceptor},
|
||||||
[]rest.Route{
|
[]rest.Route{
|
||||||
{
|
{
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
@@ -810,7 +812,7 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
|
|
||||||
server.AddRoutes(
|
server.AddRoutes(
|
||||||
rest.WithMiddlewares(
|
rest.WithMiddlewares(
|
||||||
[]rest.Middleware{serverCtx.UserDisableInterceptor, serverCtx.UserAuthInterceptor},
|
[]rest.Middleware{serverCtx.UserAuthInterceptor},
|
||||||
[]rest.Route{
|
[]rest.Route{
|
||||||
{
|
{
|
||||||
Method: http.MethodPost,
|
Method: http.MethodPost,
|
||||||
@@ -830,7 +832,7 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
|
|
||||||
server.AddRoutes(
|
server.AddRoutes(
|
||||||
rest.WithMiddlewares(
|
rest.WithMiddlewares(
|
||||||
[]rest.Middleware{serverCtx.UserDisableInterceptor, serverCtx.UserAuthInterceptor},
|
[]rest.Middleware{serverCtx.UserAuthInterceptor},
|
||||||
[]rest.Route{
|
[]rest.Route{
|
||||||
{
|
{
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
@@ -880,7 +882,7 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
|
|
||||||
server.AddRoutes(
|
server.AddRoutes(
|
||||||
rest.WithMiddlewares(
|
rest.WithMiddlewares(
|
||||||
[]rest.Middleware{serverCtx.AuthInterceptor, serverCtx.UserDisableInterceptor},
|
[]rest.Middleware{serverCtx.AuthInterceptor},
|
||||||
[]rest.Route{
|
[]rest.Route{
|
||||||
{
|
{
|
||||||
Method: http.MethodPost,
|
Method: http.MethodPost,
|
||||||
@@ -987,7 +989,7 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
|
|
||||||
server.AddRoutes(
|
server.AddRoutes(
|
||||||
rest.WithMiddlewares(
|
rest.WithMiddlewares(
|
||||||
[]rest.Middleware{serverCtx.UserDisableInterceptor, serverCtx.UserAuthInterceptor},
|
[]rest.Middleware{serverCtx.UserAuthInterceptor},
|
||||||
[]rest.Route{
|
[]rest.Route{
|
||||||
{
|
{
|
||||||
Method: http.MethodPost,
|
Method: http.MethodPost,
|
||||||
@@ -1039,7 +1041,7 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
|
|
||||||
server.AddRoutes(
|
server.AddRoutes(
|
||||||
rest.WithMiddlewares(
|
rest.WithMiddlewares(
|
||||||
[]rest.Middleware{serverCtx.AuthInterceptor, serverCtx.UserDisableInterceptor},
|
[]rest.Middleware{serverCtx.AuthInterceptor},
|
||||||
[]rest.Route{
|
[]rest.Route{
|
||||||
{
|
{
|
||||||
// query service agent
|
// query service agent
|
||||||
@@ -1059,7 +1061,7 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
|
|
||||||
server.AddRoutes(
|
server.AddRoutes(
|
||||||
rest.WithMiddlewares(
|
rest.WithMiddlewares(
|
||||||
[]rest.Middleware{serverCtx.UserDisableInterceptor, serverCtx.UserAuthInterceptor},
|
[]rest.Middleware{serverCtx.UserAuthInterceptor},
|
||||||
[]rest.Route{
|
[]rest.Route{
|
||||||
{
|
{
|
||||||
// query service
|
// query service
|
||||||
@@ -1075,7 +1077,7 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
|
|
||||||
server.AddRoutes(
|
server.AddRoutes(
|
||||||
rest.WithMiddlewares(
|
rest.WithMiddlewares(
|
||||||
[]rest.Middleware{serverCtx.UserDisableInterceptor, serverCtx.UserAuthInterceptor},
|
[]rest.Middleware{serverCtx.UserAuthInterceptor},
|
||||||
[]rest.Route{
|
[]rest.Route{
|
||||||
{
|
{
|
||||||
// 生成分享链接
|
// 生成分享链接
|
||||||
@@ -1148,6 +1150,36 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
rest.WithPrefix("/api/v1"),
|
rest.WithPrefix("/api/v1"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
server.AddRoutes(
|
||||||
|
[]rest.Route{
|
||||||
|
{
|
||||||
|
// 天远车辆类接口异步回调
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/tianyuan/vehicle/callback",
|
||||||
|
Handler: tianyuan.VehicleCallbackHandler(serverCtx),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rest.WithPrefix("/api/v1"),
|
||||||
|
)
|
||||||
|
|
||||||
|
server.AddRoutes(
|
||||||
|
[]rest.Route{
|
||||||
|
{
|
||||||
|
// 访问已上传文件(供第三方或前端通过返回的 URL 拉取)
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/upload/file/:fileName",
|
||||||
|
Handler: upload.ServeUploadedFileHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 上传图片,返回可访问 URL(如行驶证);限制 3MB
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/upload/image",
|
||||||
|
Handler: upload.UploadImageHandler(serverCtx),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rest.WithPrefix("/api/v1"),
|
||||||
|
)
|
||||||
|
|
||||||
server.AddRoutes(
|
server.AddRoutes(
|
||||||
[]rest.Route{
|
[]rest.Route{
|
||||||
{
|
{
|
||||||
@@ -1179,7 +1211,7 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
|
|
||||||
server.AddRoutes(
|
server.AddRoutes(
|
||||||
rest.WithMiddlewares(
|
rest.WithMiddlewares(
|
||||||
[]rest.Middleware{serverCtx.AuthInterceptor, serverCtx.UserDisableInterceptor},
|
[]rest.Middleware{serverCtx.AuthInterceptor},
|
||||||
[]rest.Route{
|
[]rest.Route{
|
||||||
{
|
{
|
||||||
// 绑定手机号
|
// 绑定手机号
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package tianyuan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
tianyuanlogic "tyc-server/app/main/api/internal/logic/tianyuan"
|
||||||
|
"tyc-server/app/main/api/internal/svc"
|
||||||
|
"tyc-server/common/result"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VehicleCallbackHandler 天远车辆类接口异步回调入口
|
||||||
|
// 约定:第三方在回调 URL 上携带 order_no / api_id 等标识,例如:/api/v1/tianyuan/vehicle/callback?order_no=Q_xxx&api_id=QCXG1U4U
|
||||||
|
// 回调 Body 为该接口最终的 JSON 结果。
|
||||||
|
func VehicleCallbackHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
l := tianyuanlogic.NewVehicleCallbackLogic(r.Context(), svcCtx)
|
||||||
|
if err := l.Handle(r); err != nil {
|
||||||
|
// 对第三方尽量返回 200,避免无限重试,这里使用统一 result 封装错误
|
||||||
|
result.HttpResult(r, w, map[string]interface{}{
|
||||||
|
"code": 500,
|
||||||
|
"msg": "fail",
|
||||||
|
}, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
httpx.OkJson(w, map[string]interface{}{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package upload
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"tyc-server/app/main/api/internal/logic/upload"
|
||||||
|
"tyc-server/app/main/api/internal/svc"
|
||||||
|
"tyc-server/app/main/api/internal/types"
|
||||||
|
"tyc-server/common/result"
|
||||||
|
"tyc-server/pkg/lzkit/validator"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ServeUploadedFileHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.ServeUploadedFileReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
result.ParamErrorResult(r, w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := validator.Validate(req); err != nil {
|
||||||
|
result.ParamValidateErrorResult(r, w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l := upload.NewServeUploadedFileLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.ServeUploadedFile(&req)
|
||||||
|
if err != nil {
|
||||||
|
result.HttpResult(r, w, nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if resp != nil && resp.FilePath != "" {
|
||||||
|
f, openErr := os.Open(resp.FilePath)
|
||||||
|
if openErr != nil {
|
||||||
|
result.HttpResult(r, w, nil, openErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
w.Header().Set("Content-Type", resp.ContentType)
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = f.WriteTo(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
httpx.OkJson(w, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
29
app/main/api/internal/handler/upload/uploadimagehandler.go
Normal file
29
app/main/api/internal/handler/upload/uploadimagehandler.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package upload
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
"tyc-server/app/main/api/internal/logic/upload"
|
||||||
|
"tyc-server/app/main/api/internal/svc"
|
||||||
|
"tyc-server/app/main/api/internal/types"
|
||||||
|
"tyc-server/common/result"
|
||||||
|
"tyc-server/pkg/lzkit/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UploadImageHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.UploadImageReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
result.ParamErrorResult(r, w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := validator.Validate(req); err != nil {
|
||||||
|
result.ParamValidateErrorResult(r, w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l := upload.NewUploadImageLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.UploadImage(&req)
|
||||||
|
result.HttpResult(r, w, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -86,6 +86,23 @@ var productHandlers = map[string]queryHandlerFunc{
|
|||||||
"toc_VehicleMaintenanceDetail": runVehicleMaintenanceDetailReq,
|
"toc_VehicleMaintenanceDetail": runVehicleMaintenanceDetailReq,
|
||||||
"toc_VehicleClaimDetail": runVehicleClaimDetailReq,
|
"toc_VehicleClaimDetail": runVehicleClaimDetailReq,
|
||||||
"toc_VehicleClaimVerify": runVehicleClaimVerifyReq,
|
"toc_VehicleClaimVerify": runVehicleClaimVerifyReq,
|
||||||
|
// 核验工具(verify feature.md)
|
||||||
|
"toc_PoliceTwoFactors": runVerifyAuthTwoReq,
|
||||||
|
"toc_PoliceThreeFactors": runVerifyAuthThreeReq,
|
||||||
|
"toc_ProfessionalCertificate": runVerifyCertReq,
|
||||||
|
"toc_PersonalConsumptionCapacityLevel": runVerifyConsumptionReq, // 个人消费能力(沿用现有 product_en)
|
||||||
|
"toc_OperatorTwoFactors": runVerifyYysTwoReq,
|
||||||
|
"toc_MobileThreeFactors": runVerifyYysThreeReq,
|
||||||
|
"toc_NumberRecycle": runVerifyMobileOnlyReq,
|
||||||
|
"toc_MobileEmptyCheck": runVerifyMobileOnlyReq,
|
||||||
|
"toc_MobilePortability": runVerifyMobileOnlyReq,
|
||||||
|
"toc_MobileOnlineStatus": runVerifyMobileOnlyReq,
|
||||||
|
"toc_MobileOnlineDuration": runVerifyMobileOnlyReq,
|
||||||
|
"toc_MobileAttribution": runVerifyMobileOnlyReq,
|
||||||
|
"toc_MobileConsumptionRange": runVerifyYysConsumptionReq,
|
||||||
|
"toc_EnterpriseRelation": runVerifyEntRelationReq,
|
||||||
|
"toc_BankcardFourFactors": runVerifyBankFourReq,
|
||||||
|
"toc_BankcardBlacklist": runVerifyBankBlackReq,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *QueryServiceLogic) PreprocessLogic(req *types.QueryServiceReq, product string) (*types.QueryServiceResp, error) {
|
func (l *QueryServiceLogic) PreprocessLogic(req *types.QueryServiceReq, product string) (*types.QueryServiceResp, error) {
|
||||||
@@ -242,9 +259,11 @@ func runVehicleVinCodeReq(l *QueryServiceLogic, decryptData []byte, product stri
|
|||||||
if validatorErr := validator.Validate(data); validatorErr != nil {
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
}
|
}
|
||||||
|
if data.Mobile != "" && data.Code != "" {
|
||||||
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
return nil, verifyCodeErr
|
return nil, verifyCodeErr
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"vin_code": data.VinCode,
|
"vin_code": data.VinCode,
|
||||||
}, nil
|
}, nil
|
||||||
@@ -259,12 +278,14 @@ func runVehicleMileageMixedReq(l *QueryServiceLogic, decryptData []byte, product
|
|||||||
if validatorErr := validator.Validate(data); validatorErr != nil {
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
}
|
}
|
||||||
|
if data.Mobile != "" && data.Code != "" {
|
||||||
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
return nil, verifyCodeErr
|
return nil, verifyCodeErr
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// 回调地址由后端在 ApiRequestService 中统一生成,此处不再下发 return_url
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"vin_code": data.VinCode,
|
"vin_code": data.VinCode,
|
||||||
"return_url": data.ReturnURL,
|
|
||||||
"image_url": data.ImageURL,
|
"image_url": data.ImageURL,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -278,9 +299,11 @@ func runVehicleVinValuationReq(l *QueryServiceLogic, decryptData []byte, product
|
|||||||
if validatorErr := validator.Validate(data); validatorErr != nil {
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
}
|
}
|
||||||
|
if data.Mobile != "" && data.Code != "" {
|
||||||
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
return nil, verifyCodeErr
|
return nil, verifyCodeErr
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"vin_code": data.VinCode,
|
"vin_code": data.VinCode,
|
||||||
"vehicle_location": data.VehicleLocation,
|
"vehicle_location": data.VehicleLocation,
|
||||||
@@ -297,13 +320,15 @@ func runVehicleTransferSimpleReq(l *QueryServiceLogic, decryptData []byte, produ
|
|||||||
if validatorErr := validator.Validate(data); validatorErr != nil {
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
}
|
}
|
||||||
|
if data.Mobile != "" && data.Code != "" {
|
||||||
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
return nil, verifyCodeErr
|
return nil, verifyCodeErr
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return map[string]interface{}{"vin_code": data.VinCode}, nil
|
return map[string]interface{}{"vin_code": data.VinCode}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// runVehicleMaintenanceSimpleReq 车辆维保简版 QCXG3Y6B(仅必填 vin_code, return_url)
|
// runVehicleMaintenanceSimpleReq 车辆维保简版 QCXG3Y6B(仅必填 vin_code;回调地址后端自动生成)
|
||||||
func runVehicleMaintenanceSimpleReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
func runVehicleMaintenanceSimpleReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
var data types.TocVehicleMaintenanceSimpleReq
|
var data types.TocVehicleMaintenanceSimpleReq
|
||||||
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
@@ -312,16 +337,18 @@ func runVehicleMaintenanceSimpleReq(l *QueryServiceLogic, decryptData []byte, pr
|
|||||||
if validatorErr := validator.Validate(data); validatorErr != nil {
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
}
|
}
|
||||||
|
if data.Mobile != "" && data.Code != "" {
|
||||||
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
return nil, verifyCodeErr
|
return nil, verifyCodeErr
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// 回调地址由后端在 ApiRequestService 中统一生成,此处不再下发 return_url
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"vin_code": data.VinCode,
|
"vin_code": data.VinCode,
|
||||||
"return_url": data.ReturnURL,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// runVehicleMaintenanceDetailReq 车辆维保详细版 QCXG3Z3L(仅必填 vin_code, return_url)
|
// runVehicleMaintenanceDetailReq 车辆维保详细版 QCXG3Z3L(仅必填 vin_code;回调地址后端自动生成)
|
||||||
func runVehicleMaintenanceDetailReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
func runVehicleMaintenanceDetailReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
var data types.TocVehicleMaintenanceDetailReq
|
var data types.TocVehicleMaintenanceDetailReq
|
||||||
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
@@ -330,16 +357,18 @@ func runVehicleMaintenanceDetailReq(l *QueryServiceLogic, decryptData []byte, pr
|
|||||||
if validatorErr := validator.Validate(data); validatorErr != nil {
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
}
|
}
|
||||||
|
if data.Mobile != "" && data.Code != "" {
|
||||||
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
return nil, verifyCodeErr
|
return nil, verifyCodeErr
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// 回调地址由后端在 ApiRequestService 中统一生成,此处不再下发 return_url
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"vin_code": data.VinCode,
|
"vin_code": data.VinCode,
|
||||||
"return_url": data.ReturnURL,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// runVehicleClaimDetailReq 车辆出险详版 QCXGP00W(仅必填 vin_code, return_url, vlphoto_data),vlphoto_data 由 API 层加密为 data
|
// runVehicleClaimDetailReq 车辆出险详版 QCXGP00W(仅必填 vin_code, vlphoto_data;回调地址后端自动生成),vlphoto_data 由 API 层加密为 data
|
||||||
func runVehicleClaimDetailReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
func runVehicleClaimDetailReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
var data types.TocVehicleClaimDetailReq
|
var data types.TocVehicleClaimDetailReq
|
||||||
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
|
||||||
@@ -348,12 +377,14 @@ func runVehicleClaimDetailReq(l *QueryServiceLogic, decryptData []byte, product
|
|||||||
if validatorErr := validator.Validate(data); validatorErr != nil {
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
}
|
}
|
||||||
|
if data.Mobile != "" && data.Code != "" {
|
||||||
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
return nil, verifyCodeErr
|
return nil, verifyCodeErr
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// 回调地址由后端在 ApiRequestService 中统一生成,此处不再下发 return_url
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"vin_code": data.VinCode,
|
"vin_code": data.VinCode,
|
||||||
"return_url": data.ReturnURL,
|
|
||||||
"vlphoto_data": data.VlphotoData,
|
"vlphoto_data": data.VlphotoData,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -367,12 +398,151 @@ func runVehicleClaimVerifyReq(l *QueryServiceLogic, decryptData []byte, product
|
|||||||
if validatorErr := validator.Validate(data); validatorErr != nil {
|
if validatorErr := validator.Validate(data); validatorErr != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
|
||||||
}
|
}
|
||||||
|
if data.Mobile != "" && data.Code != "" {
|
||||||
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
if verifyCodeErr := l.VerifyCode(data.Mobile, data.Code); verifyCodeErr != nil {
|
||||||
return nil, verifyCodeErr
|
return nil, verifyCodeErr
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
auth := data.Authorized
|
||||||
|
if auth == "" {
|
||||||
|
auth = "1"
|
||||||
|
}
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"vin_code": data.VINCode,
|
"vin_code": data.VINCode,
|
||||||
"authorized": data.Authorized,
|
"authorized": auth,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------- 核验工具 handlers ---------------
|
||||||
|
func runVerifyAuthTwoReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVerifyAuthTwoReq
|
||||||
|
if err := json.Unmarshal(decryptData, &data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
|
||||||
|
}
|
||||||
|
if err := validator.Validate(data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
|
||||||
|
}
|
||||||
|
return map[string]interface{}{"mobile_no": data.MobileNo, "id_card": data.IDCard, "name": data.Name}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runVerifyAuthThreeReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVerifyAuthThreeReq
|
||||||
|
if err := json.Unmarshal(decryptData, &data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
|
||||||
|
}
|
||||||
|
if err := validator.Validate(data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
|
||||||
|
}
|
||||||
|
return map[string]interface{}{"photo_data": data.PhotoData, "id_card": data.IDCard, "name": data.Name}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runVerifyCertReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVerifyCertReq
|
||||||
|
if err := json.Unmarshal(decryptData, &data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
|
||||||
|
}
|
||||||
|
if err := validator.Validate(data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
|
||||||
|
}
|
||||||
|
return map[string]interface{}{"id_card": data.IDCard, "name": data.Name}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runVerifyConsumptionReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVerifyConsumptionReq
|
||||||
|
if err := json.Unmarshal(decryptData, &data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
|
||||||
|
}
|
||||||
|
if err := validator.Validate(data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
|
||||||
|
}
|
||||||
|
return map[string]interface{}{"mobile_no": data.MobileNo, "id_card": data.IDCard, "name": data.Name}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runVerifyYysTwoReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVerifyYysTwoReq
|
||||||
|
if err := json.Unmarshal(decryptData, &data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
|
||||||
|
}
|
||||||
|
if err := validator.Validate(data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
|
||||||
|
}
|
||||||
|
return map[string]interface{}{"mobile_no": data.MobileNo, "name": data.Name}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runVerifyYysThreeReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVerifyYysThreeReq
|
||||||
|
if err := json.Unmarshal(decryptData, &data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
|
||||||
|
}
|
||||||
|
if err := validator.Validate(data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
|
||||||
|
}
|
||||||
|
return map[string]interface{}{"mobile_no": data.MobileNo, "id_card": data.IDCard, "name": data.Name}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runVerifyMobileOnlyReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVerifyMobileOnlyReq
|
||||||
|
if err := json.Unmarshal(decryptData, &data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
|
||||||
|
}
|
||||||
|
if err := validator.Validate(data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
|
||||||
|
}
|
||||||
|
return map[string]interface{}{"mobile_no": data.MobileNo}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runVerifyYysConsumptionReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVerifyYysConsumptionReq
|
||||||
|
if err := json.Unmarshal(decryptData, &data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
|
||||||
|
}
|
||||||
|
if err := validator.Validate(data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
|
||||||
|
}
|
||||||
|
return map[string]interface{}{"mobile_no": data.MobileNo, "authorized": data.Authorized}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runVerifyEntRelationReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVerifyEntRelationReq
|
||||||
|
if err := json.Unmarshal(decryptData, &data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
|
||||||
|
}
|
||||||
|
out := map[string]interface{}{}
|
||||||
|
if data.IDCard != "" {
|
||||||
|
out["id_card"] = data.IDCard
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runVerifyBankFourReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVerifyBankFourReq
|
||||||
|
if err := json.Unmarshal(decryptData, &data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
|
||||||
|
}
|
||||||
|
if err := validator.Validate(data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
|
||||||
|
}
|
||||||
|
return map[string]interface{}{
|
||||||
|
"mobile_no": data.MobileNo,
|
||||||
|
"id_card": data.IDCard,
|
||||||
|
"bank_card": data.BankCard,
|
||||||
|
"name": data.Name,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runVerifyBankBlackReq(l *QueryServiceLogic, decryptData []byte, product string) (map[string]interface{}, error) {
|
||||||
|
var data types.TocVerifyBankBlackReq
|
||||||
|
if err := json.Unmarshal(decryptData, &data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", err)
|
||||||
|
}
|
||||||
|
if err := validator.Validate(data); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, err.Error()), "查询服务, 参数不正确: %+v", err)
|
||||||
|
}
|
||||||
|
return map[string]interface{}{
|
||||||
|
"mobile_no": data.MobileNo,
|
||||||
|
"id_card": data.IDCard,
|
||||||
|
"name": data.Name,
|
||||||
|
"bank_card": data.BankCard,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
164
app/main/api/internal/logic/tianyuan/vehiclecallbacklogic.go
Normal file
164
app/main/api/internal/logic/tianyuan/vehiclecallbacklogic.go
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
package tianyuan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tyc-server/app/main/api/internal/service"
|
||||||
|
"tyc-server/app/main/api/internal/svc"
|
||||||
|
"tyc-server/app/main/model"
|
||||||
|
"tyc-server/common/xerr"
|
||||||
|
"tyc-server/pkg/lzkit/crypto"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VehicleCallbackLogic 处理天远车辆类接口的异步回调
|
||||||
|
// 设计目标:
|
||||||
|
// - 按 order_no + api_id 找到对应的查询记录
|
||||||
|
// - 解密原有 query_data([]APIResponseData),更新或追加当前 api_id 的结果
|
||||||
|
// - 再次加密写回 query.query_data,必要时将 query_state 从 pending 更新为 success
|
||||||
|
type VehicleCallbackLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVehicleCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *VehicleCallbackLogic {
|
||||||
|
return &VehicleCallbackLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 与 paySuccessNotify 中一致:仅通过异步回调回写结果的车辆接口
|
||||||
|
var asyncVehicleApiIDs = map[string]bool{
|
||||||
|
"QCXG1U4U": true, "QCXG3Y6B": true, "QCXG3Z3L": true, "QCXGP00W": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// allAsyncVehicleReceived 判断 apiList 中所有“异步车辆接口”是否均已收到回调(Success 为 true)
|
||||||
|
func allAsyncVehicleReceived(apiList []service.APIResponseData) bool {
|
||||||
|
for _, item := range apiList {
|
||||||
|
if asyncVehicleApiIDs[item.ApiID] && !item.Success {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle 入口:直接接收原始 *http.Request,方便读取 query / body
|
||||||
|
func (l *VehicleCallbackLogic) Handle(r *http.Request) error {
|
||||||
|
apiID := r.URL.Query().Get("api_id")
|
||||||
|
orderNo := r.URL.Query().Get("order_no")
|
||||||
|
|
||||||
|
if apiID == "" || orderNo == "" {
|
||||||
|
return errors.Wrapf(
|
||||||
|
xerr.NewErrMsg("缺少 api_id 或 order_no"),
|
||||||
|
"tianyuan vehicle callback, api_id=%s, order_no=%s", apiID, orderNo,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyBytes, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "读取回调 Body 失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 根据订单号找到订单
|
||||||
|
order, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, orderNo)
|
||||||
|
if err != nil {
|
||||||
|
if err == model.ErrNotFound {
|
||||||
|
return errors.Wrapf(xerr.NewErrMsg("未找到订单"), "order_no=%s", orderNo)
|
||||||
|
}
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询订单失败, order_no=%s, err=%v", orderNo, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 根据订单ID找到对应查询记录
|
||||||
|
query, err := l.svcCtx.QueryModel.FindOneByOrderId(l.ctx, order.Id)
|
||||||
|
if err != nil {
|
||||||
|
if err == model.ErrNotFound {
|
||||||
|
return errors.Wrapf(xerr.NewErrMsg("未找到查询记录"), "order_no=%s, order_id=%d", orderNo, order.Id)
|
||||||
|
}
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询 query 记录失败, order_id=%d, err=%v", order.Id, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 获取加密密钥
|
||||||
|
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||||
|
key, decodeErr := hex.DecodeString(secretKey)
|
||||||
|
if decodeErr != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解析 AES 密钥失败: %v", decodeErr)
|
||||||
|
}
|
||||||
|
// 4. 解密 query_data,反序列化为 []APIResponseData
|
||||||
|
var apiList []service.APIResponseData
|
||||||
|
if query.QueryData.Valid && query.QueryData.String != "" {
|
||||||
|
decrypted, decErr := crypto.AesDecrypt(query.QueryData.String, key)
|
||||||
|
if decErr != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解密 query_data 失败: %v", decErr)
|
||||||
|
}
|
||||||
|
if len(decrypted) > 0 {
|
||||||
|
if err := json.Unmarshal(decrypted, &apiList); err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解析 query_data 为 APIResponseData 列表失败: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 更新或追加当前 api_id 的结果
|
||||||
|
nowStr := time.Now().Format("2006-01-02 15:04:05")
|
||||||
|
updated := false
|
||||||
|
for i := range apiList {
|
||||||
|
if apiList[i].ApiID == apiID {
|
||||||
|
apiList[i].Data = json.RawMessage(bodyBytes)
|
||||||
|
apiList[i].Success = true
|
||||||
|
apiList[i].Timestamp = nowStr
|
||||||
|
apiList[i].Error = ""
|
||||||
|
updated = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !updated {
|
||||||
|
apiList = append(apiList, service.APIResponseData{
|
||||||
|
ApiID: apiID,
|
||||||
|
Data: json.RawMessage(bodyBytes),
|
||||||
|
Success: true,
|
||||||
|
Timestamp: nowStr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 重新序列化并加密,写回查询记录
|
||||||
|
merged, err := json.Marshal(apiList)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "序列化合并后的 query_data 失败: %v", err)
|
||||||
|
}
|
||||||
|
enc, encErr := crypto.AesEncrypt(merged, key)
|
||||||
|
if encErr != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密合并后的 query_data 失败: %v", encErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
query.QueryData.String = enc
|
||||||
|
query.QueryData.Valid = true
|
||||||
|
// 仅当所有异步车辆接口均已有回调结果时,才将 pending 置为 success,并触发代理结算
|
||||||
|
wasPending := query.QueryState == "pending"
|
||||||
|
didSetSuccess := wasPending && allAsyncVehicleReceived(apiList)
|
||||||
|
if didSetSuccess {
|
||||||
|
query.QueryState = "success"
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := l.svcCtx.QueryModel.UpdateWithVersion(l.ctx, nil, query); err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新查询记录失败, query_id=%d, err=%v", query.Id, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if didSetSuccess {
|
||||||
|
if agentErr := l.svcCtx.AgentService.AgentProcess(l.ctx, order); agentErr != nil {
|
||||||
|
l.Errorf("tianyuan vehicle callback, AgentProcess failed, order_no=%s, err=%v", orderNo, agentErr)
|
||||||
|
// 不因代理处理失败而整体失败,回调已成功落库
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Infof("tianyuan vehicle callback handled, order_no=%s, api_id=%s, query_id=%d", orderNo, apiID, query.Id)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
66
app/main/api/internal/logic/upload/serveuploadedfilelogic.go
Normal file
66
app/main/api/internal/logic/upload/serveuploadedfilelogic.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package upload
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"tyc-server/app/main/api/internal/svc"
|
||||||
|
"tyc-server/app/main/api/internal/types"
|
||||||
|
"tyc-server/common/xerr"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServeUploadedFileLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServeUploadedFileLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ServeUploadedFileLogic {
|
||||||
|
return &ServeUploadedFileLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ServeUploadedFileLogic) ServeUploadedFile(req *types.ServeUploadedFileReq) (resp *types.ServeUploadedFileResp, err error) {
|
||||||
|
fileName := strings.TrimSpace(req.FileName)
|
||||||
|
if fileName == "" {
|
||||||
|
return nil, errors.Wrap(xerr.NewErrMsg("缺少文件名"), "fileName empty")
|
||||||
|
}
|
||||||
|
// 只允许文件名,禁止路径穿越
|
||||||
|
if strings.Contains(fileName, "..") || filepath.Base(fileName) != fileName {
|
||||||
|
return nil, errors.Wrap(xerr.NewErrMsg("非法文件名"), fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates := []string{
|
||||||
|
"data/uploads",
|
||||||
|
"../data/uploads",
|
||||||
|
"../../data/uploads",
|
||||||
|
"../../../data/uploads",
|
||||||
|
}
|
||||||
|
for _, c := range candidates {
|
||||||
|
abs, _ := filepath.Abs(c)
|
||||||
|
fullPath := filepath.Join(abs, fileName)
|
||||||
|
if info, err := os.Stat(fullPath); err == nil && !info.IsDir() {
|
||||||
|
contentType := "image/jpeg"
|
||||||
|
if strings.HasSuffix(strings.ToLower(fileName), ".png") {
|
||||||
|
contentType = "image/png"
|
||||||
|
} else if strings.HasSuffix(strings.ToLower(fileName), ".gif") {
|
||||||
|
contentType = "image/gif"
|
||||||
|
} else if strings.HasSuffix(strings.ToLower(fileName), ".webp") {
|
||||||
|
contentType = "image/webp"
|
||||||
|
}
|
||||||
|
return &types.ServeUploadedFileResp{
|
||||||
|
FilePath: fullPath,
|
||||||
|
ContentType: contentType,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("文件不存在"), "fileName=%s", fileName)
|
||||||
|
}
|
||||||
138
app/main/api/internal/logic/upload/uploadimagelogic.go
Normal file
138
app/main/api/internal/logic/upload/uploadimagelogic.go
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
package upload
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tyc-server/app/main/api/internal/svc"
|
||||||
|
"tyc-server/app/main/api/internal/types"
|
||||||
|
"tyc-server/common/xerr"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
const maxImageSize = 3 * 1024 * 1024 // 3MB
|
||||||
|
const defaultTempFileMaxAgeH = 24
|
||||||
|
|
||||||
|
type UploadImageLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUploadImageLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UploadImageLogic {
|
||||||
|
return &UploadImageLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *UploadImageLogic) UploadImage(req *types.UploadImageReq) (resp *types.UploadImageResp, err error) {
|
||||||
|
decoded, decErr := base64.StdEncoding.DecodeString(req.ImageBase64)
|
||||||
|
if decErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("图片 base64 格式错误"), "%v", decErr)
|
||||||
|
}
|
||||||
|
if len(decoded) > maxImageSize {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("图片不能超过 3M"), "size=%d", len(decoded))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按文件内容 hash 命名,相同文件复用同一 URL,避免重复传输与刷流量
|
||||||
|
hashSum := sha256.Sum256(decoded)
|
||||||
|
hashHex := hex.EncodeToString(hashSum[:])
|
||||||
|
fileName := hashHex + ".jpg"
|
||||||
|
|
||||||
|
dir := l.uploadStoragePath()
|
||||||
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "创建上传目录失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := filepath.Join(dir, fileName)
|
||||||
|
// 若已存在同 hash 文件,直接返回 URL,不重复写入
|
||||||
|
if _, statErr := os.Stat(filePath); statErr == nil {
|
||||||
|
url := l.buildURL(fileName)
|
||||||
|
logx.Infof("upload image dedup by hash, file=%s", fileName)
|
||||||
|
return &types.UploadImageResp{Url: url}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(filePath, decoded, 0644); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "保存图片失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 异步清理过期临时文件,不阻塞响应
|
||||||
|
go l.deleteOldUploads(dir)
|
||||||
|
|
||||||
|
url := l.buildURL(fileName)
|
||||||
|
logx.Infof("upload image ok, file=%s", fileName)
|
||||||
|
return &types.UploadImageResp{Url: url}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *UploadImageLogic) buildURL(fileName string) string {
|
||||||
|
baseURL := l.svcCtx.Config.Upload.FileBaseURL
|
||||||
|
if baseURL == "" {
|
||||||
|
baseURL = l.svcCtx.Config.AdminPromotion.URLDomain
|
||||||
|
if baseURL != "" {
|
||||||
|
baseURL = baseURL + "/api/v1/upload/file"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if baseURL == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s/%s", baseURL, fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *UploadImageLogic) uploadStoragePath() string {
|
||||||
|
candidates := []string{
|
||||||
|
"data/uploads",
|
||||||
|
"../data/uploads",
|
||||||
|
"../../data/uploads",
|
||||||
|
"../../../data/uploads",
|
||||||
|
}
|
||||||
|
for _, c := range candidates {
|
||||||
|
abs, _ := filepath.Abs(c)
|
||||||
|
if err := os.MkdirAll(abs, 0755); err == nil {
|
||||||
|
return abs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
abs, _ := filepath.Abs(candidates[0])
|
||||||
|
return abs
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteOldUploads 删除目录下超过保留时长的临时文件
|
||||||
|
func (l *UploadImageLogic) deleteOldUploads(dir string) {
|
||||||
|
maxAgeH := l.svcCtx.Config.Upload.TempFileMaxAgeH
|
||||||
|
if maxAgeH <= 0 {
|
||||||
|
maxAgeH = defaultTempFileMaxAgeH
|
||||||
|
}
|
||||||
|
cutoff := time.Now().Add(-time.Duration(maxAgeH) * time.Hour)
|
||||||
|
|
||||||
|
entries, err := os.ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorf("deleteOldUploads ReadDir: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, e := range entries {
|
||||||
|
if e.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
path := filepath.Join(dir, e.Name())
|
||||||
|
info, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if info.ModTime().Before(cutoff) {
|
||||||
|
if err := os.Remove(path); err != nil {
|
||||||
|
l.Errorf("deleteOldUploads Remove %s: %v", path, err)
|
||||||
|
} else {
|
||||||
|
l.Infof("deleteOldUploads removed %s", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,6 +36,26 @@ var payload struct {
|
|||||||
OrderID int64 `json:"order_id"`
|
OrderID int64 `json:"order_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 仅通过异步回调回写结果的车辆接口 ApiID
|
||||||
|
var asyncVehicleApiIDs = map[string]bool{
|
||||||
|
"QCXG1U4U": true, // 车辆里程混合
|
||||||
|
"QCXG3Y6B": true, // 车辆维保简版
|
||||||
|
"QCXG3Z3L": true, // 车辆维保详细版
|
||||||
|
"QCXGP00W": true, // 车辆出险详版
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAllAsyncVehicleQuery(responseData []service.APIResponseData) bool {
|
||||||
|
if len(responseData) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, r := range responseData {
|
||||||
|
if !asyncVehicleApiIDs[r.ApiID] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq.Task) error {
|
func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq.Task) error {
|
||||||
// 从任务的负载中解码数据
|
// 从任务的负载中解码数据
|
||||||
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
|
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
|
||||||
@@ -152,7 +172,8 @@ func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq.
|
|||||||
responseData = []service.APIResponseData{}
|
responseData = []service.APIResponseData{}
|
||||||
} else {
|
} else {
|
||||||
var processErr error
|
var processErr error
|
||||||
responseData, processErr = l.svcCtx.ApiRequestService.ProcessRequests(decryptData, product.Id)
|
// 传入订单号,用于在 ApiRequestService 中为异步车辆接口生成回调地址(return_url)
|
||||||
|
responseData, processErr = l.svcCtx.ApiRequestService.ProcessRequests(decryptData, product.Id, order.OrderNo)
|
||||||
if processErr != nil {
|
if processErr != nil {
|
||||||
return l.handleError(ctx, processErr, order, query)
|
return l.handleError(ctx, processErr, order, query)
|
||||||
}
|
}
|
||||||
@@ -160,7 +181,6 @@ func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq.
|
|||||||
|
|
||||||
// 计算成功模块的总成本价
|
// 计算成功模块的总成本价
|
||||||
totalCostPrice := 0.0
|
totalCostPrice := 0.0
|
||||||
if responseData != nil {
|
|
||||||
for _, item := range responseData {
|
for _, item := range responseData {
|
||||||
if item.Success {
|
if item.Success {
|
||||||
// 根据API ID查找功能模块
|
// 根据API ID查找功能模块
|
||||||
@@ -173,7 +193,6 @@ func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq.
|
|||||||
totalCostPrice += feature.CostPrice
|
totalCostPrice += feature.CostPrice
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 更新订单的销售成本
|
// 更新订单的销售成本
|
||||||
order.SalesCost = totalCostPrice
|
order.SalesCost = totalCostPrice
|
||||||
@@ -204,12 +223,15 @@ func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq.
|
|||||||
return l.handleError(ctx, err, order, query)
|
return l.handleError(ctx, err, order, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 若当前产品全部为异步车辆接口(结果通过回调回写),则保持 pending,由回调再置为 success
|
||||||
|
if !isAllAsyncVehicleQuery(responseData) {
|
||||||
query.QueryState = "success"
|
query.QueryState = "success"
|
||||||
updateQueryErr := l.svcCtx.QueryModel.UpdateWithVersion(ctx, nil, query)
|
updateQueryErr := l.svcCtx.QueryModel.UpdateWithVersion(ctx, nil, query)
|
||||||
if updateQueryErr != nil {
|
if updateQueryErr != nil {
|
||||||
updateQueryErr = fmt.Errorf("修改查询状态失败: %v", updateQueryErr)
|
updateQueryErr = fmt.Errorf("修改查询状态失败: %v", updateQueryErr)
|
||||||
return l.handleError(ctx, updateQueryErr, order, query)
|
return l.handleError(ctx, updateQueryErr, order, query)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = l.svcCtx.AgentService.AgentProcess(ctx, order)
|
err = l.svcCtx.AgentService.AgentProcess(ctx, order)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -60,7 +60,8 @@ type APIResponseData struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ProcessRequests 处理请求
|
// ProcessRequests 处理请求
|
||||||
func (a *ApiRequestService) ProcessRequests(params []byte, productID int64) ([]APIResponseData, error) {
|
// orderNo: 当前查询对应的订单号,用于为异步车辆类接口生成回调地址(return_url)
|
||||||
|
func (a *ApiRequestService) ProcessRequests(params []byte, productID int64, orderNo string) ([]APIResponseData, error) {
|
||||||
var ctx, cancel = context.WithCancel(context.Background())
|
var ctx, cancel = context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
build := a.productFeatureModel.SelectBuilder().Where(squirrel.Eq{
|
build := a.productFeatureModel.SelectBuilder().Where(squirrel.Eq{
|
||||||
@@ -87,6 +88,19 @@ func (a *ApiRequestService) ProcessRequests(params []byte, productID int64) ([]A
|
|||||||
if len(featureList) == 0 {
|
if len(featureList) == 0 {
|
||||||
return nil, errors.New("处理请求错误,产品无对应接口功能")
|
return nil, errors.New("处理请求错误,产品无对应接口功能")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 在原始 params 上附加 order_no,供异步车辆类接口自动生成回调地址使用
|
||||||
|
var baseParams map[string]interface{}
|
||||||
|
if err := json.Unmarshal(params, &baseParams); err != nil {
|
||||||
|
return nil, fmt.Errorf("解析查询参数失败: %w", err)
|
||||||
|
}
|
||||||
|
if orderNo != "" {
|
||||||
|
baseParams["order_no"] = orderNo
|
||||||
|
}
|
||||||
|
paramsWithOrder, err := json.Marshal(baseParams)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("序列化查询参数失败: %w", err)
|
||||||
|
}
|
||||||
var (
|
var (
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
resultsCh = make(chan APIResponseData, len(featureList))
|
resultsCh = make(chan APIResponseData, len(featureList))
|
||||||
@@ -120,7 +134,7 @@ func (a *ApiRequestService) ProcessRequests(params []byte, productID int64) ([]A
|
|||||||
tryCount := 0
|
tryCount := 0
|
||||||
for {
|
for {
|
||||||
tryCount++
|
tryCount++
|
||||||
resp, preprocessErr = a.PreprocessRequestApi(params, feature.ApiId)
|
resp, preprocessErr = a.PreprocessRequestApi(paramsWithOrder, feature.ApiId)
|
||||||
if preprocessErr == nil {
|
if preprocessErr == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -213,6 +227,23 @@ var requestProcessors = map[string]func(*ApiRequestService, []byte) ([]byte, err
|
|||||||
"QCXG3Z3L": (*ApiRequestService).ProcessQCXG3Z3LRequest,
|
"QCXG3Z3L": (*ApiRequestService).ProcessQCXG3Z3LRequest,
|
||||||
"QCXGP00W": (*ApiRequestService).ProcessQCXGP00WRequest,
|
"QCXGP00W": (*ApiRequestService).ProcessQCXGP00WRequest,
|
||||||
"QCXG6B4E": (*ApiRequestService).ProcessQCXG6B4ERequest,
|
"QCXG6B4E": (*ApiRequestService).ProcessQCXG6B4ERequest,
|
||||||
|
// 核验工具(verify feature.md)
|
||||||
|
"IVYZ9K7F": (*ApiRequestService).ProcessIVYZ9K7FRequest,
|
||||||
|
"IVYZA1B3": (*ApiRequestService).ProcessIVYZA1B3Request,
|
||||||
|
"IVYZ6M8P": (*ApiRequestService).ProcessIVYZ6M8PRequest,
|
||||||
|
"JRZQ8B3C": (*ApiRequestService).ProcessJRZQ8B3CRequest,
|
||||||
|
"YYSY3M8S": (*ApiRequestService).ProcessYYSY3M8SRequest,
|
||||||
|
"YYSYK9R4": (*ApiRequestService).ProcessYYSYK9R4Request,
|
||||||
|
"YYSYF2T7": (*ApiRequestService).ProcessYYSYF2T7Request,
|
||||||
|
"YYSYK8R3": (*ApiRequestService).ProcessYYSYK8R3Request,
|
||||||
|
"YYSYS9W1": (*ApiRequestService).ProcessYYSYS9W1Request,
|
||||||
|
"YYSYE7V5": (*ApiRequestService).ProcessYYSYE7V5Request,
|
||||||
|
"YYSYP0T4": (*ApiRequestService).ProcessYYSYP0T4Request,
|
||||||
|
"YYSY6F2B": (*ApiRequestService).ProcessYYSY6F2BRequest,
|
||||||
|
"YYSY9E4A": (*ApiRequestService).ProcessYYSY9E4ARequest,
|
||||||
|
"QYGL5F6A": (*ApiRequestService).ProcessQYGL5F6ARequest,
|
||||||
|
"JRZQACAB": (*ApiRequestService).ProcessJRZQACABRequest,
|
||||||
|
"JRZQ0B6Y": (*ApiRequestService).ProcessJRZQ0B6YRequest,
|
||||||
}
|
}
|
||||||
|
|
||||||
// PreprocessRequestApi 调用指定的请求处理函数
|
// PreprocessRequestApi 调用指定的请求处理函数
|
||||||
@@ -1235,10 +1266,12 @@ func (a *ApiRequestService) ProcessQCXG5U0ZRequest(params []byte) ([]byte, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *ApiRequestService) ProcessQCXG1U4URequest(params []byte) ([]byte, error) {
|
func (a *ApiRequestService) ProcessQCXG1U4URequest(params []byte) ([]byte, error) {
|
||||||
body := buildVehicleBody(params, []string{"vin_code", "return_url", "image_url"}, nil)
|
body := buildVehicleBody(params, []string{"vin_code", "image_url"}, nil)
|
||||||
if body["vin_code"] == nil || body["return_url"] == nil || body["image_url"] == nil {
|
orderNo := gjson.GetBytes(params, "order_no").String()
|
||||||
return nil, errors.New("api请求, QCXG1U4U, 缺少必填参数 vin_code/return_url/image_url")
|
if body["vin_code"] == nil || body["image_url"] == nil || orderNo == "" {
|
||||||
|
return nil, errors.New("api请求, QCXG1U4U, 缺少必填参数 vin_code/image_url/order_no")
|
||||||
}
|
}
|
||||||
|
body["return_url"] = a.buildVehicleCallbackURL(orderNo, "QCXG1U4U")
|
||||||
resp, err := a.tianyuanapi.CallInterface("QCXG1U4U", body)
|
resp, err := a.tianyuanapi.CallInterface("QCXG1U4U", body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1283,10 +1316,12 @@ func (a *ApiRequestService) ProcessQCXG4I1ZRequest(params []byte) ([]byte, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *ApiRequestService) ProcessQCXG3Y6BRequest(params []byte) ([]byte, error) {
|
func (a *ApiRequestService) ProcessQCXG3Y6BRequest(params []byte) ([]byte, error) {
|
||||||
body := buildVehicleBody(params, []string{"vin_code", "return_url"}, nil)
|
body := buildVehicleBody(params, []string{"vin_code"}, nil)
|
||||||
if body["vin_code"] == nil || body["return_url"] == nil {
|
orderNo := gjson.GetBytes(params, "order_no").String()
|
||||||
return nil, errors.New("api请求, QCXG3Y6B, 缺少必填参数 vin_code/return_url")
|
if body["vin_code"] == nil || orderNo == "" {
|
||||||
|
return nil, errors.New("api请求, QCXG3Y6B, 缺少必填参数 vin_code/order_no")
|
||||||
}
|
}
|
||||||
|
body["return_url"] = a.buildVehicleCallbackURL(orderNo, "QCXG3Y6B")
|
||||||
resp, err := a.tianyuanapi.CallInterface("QCXG3Y6B", body)
|
resp, err := a.tianyuanapi.CallInterface("QCXG3Y6B", body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1295,10 +1330,12 @@ func (a *ApiRequestService) ProcessQCXG3Y6BRequest(params []byte) ([]byte, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *ApiRequestService) ProcessQCXG3Z3LRequest(params []byte) ([]byte, error) {
|
func (a *ApiRequestService) ProcessQCXG3Z3LRequest(params []byte) ([]byte, error) {
|
||||||
body := buildVehicleBody(params, []string{"vin_code", "return_url"}, nil)
|
body := buildVehicleBody(params, []string{"vin_code"}, nil)
|
||||||
if body["vin_code"] == nil || body["return_url"] == nil {
|
orderNo := gjson.GetBytes(params, "order_no").String()
|
||||||
return nil, errors.New("api请求, QCXG3Z3L, 缺少必填参数 vin_code/return_url")
|
if body["vin_code"] == nil || orderNo == "" {
|
||||||
|
return nil, errors.New("api请求, QCXG3Z3L, 缺少必填参数 vin_code/order_no")
|
||||||
}
|
}
|
||||||
|
body["return_url"] = a.buildVehicleCallbackURL(orderNo, "QCXG3Z3L")
|
||||||
resp, err := a.tianyuanapi.CallInterface("QCXG3Z3L", body)
|
resp, err := a.tianyuanapi.CallInterface("QCXG3Z3L", body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1308,10 +1345,10 @@ func (a *ApiRequestService) ProcessQCXG3Z3LRequest(params []byte) ([]byte, error
|
|||||||
|
|
||||||
func (a *ApiRequestService) ProcessQCXGP00WRequest(params []byte) ([]byte, error) {
|
func (a *ApiRequestService) ProcessQCXGP00WRequest(params []byte) ([]byte, error) {
|
||||||
vin := gjson.GetBytes(params, "vin_code")
|
vin := gjson.GetBytes(params, "vin_code")
|
||||||
returnURL := gjson.GetBytes(params, "return_url")
|
orderNo := gjson.GetBytes(params, "order_no").String()
|
||||||
vlphoto := gjson.GetBytes(params, "vlphoto_data")
|
vlphoto := gjson.GetBytes(params, "vlphoto_data")
|
||||||
if !vin.Exists() || vin.String() == "" || !returnURL.Exists() || returnURL.String() == "" || !vlphoto.Exists() || vlphoto.String() == "" {
|
if !vin.Exists() || vin.String() == "" || orderNo == "" || !vlphoto.Exists() || vlphoto.String() == "" {
|
||||||
return nil, errors.New("api请求, QCXGP00W, 缺少必填参数 vin_code/return_url/vlphoto_data")
|
return nil, errors.New("api请求, QCXGP00W, 缺少必填参数 vin_code/order_no/vlphoto_data")
|
||||||
}
|
}
|
||||||
key, err := hex.DecodeString(a.config.Encrypt.SecretKey)
|
key, err := hex.DecodeString(a.config.Encrypt.SecretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1323,7 +1360,7 @@ func (a *ApiRequestService) ProcessQCXGP00WRequest(params []byte) ([]byte, error
|
|||||||
}
|
}
|
||||||
resp, err := a.tianyuanapi.CallInterface("QCXGP00W", map[string]interface{}{
|
resp, err := a.tianyuanapi.CallInterface("QCXGP00W", map[string]interface{}{
|
||||||
"vin_code": vin.String(),
|
"vin_code": vin.String(),
|
||||||
"return_url": returnURL.String(),
|
"return_url": a.buildVehicleCallbackURL(orderNo, "QCXGP00W"),
|
||||||
"data": encData,
|
"data": encData,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1334,14 +1371,17 @@ func (a *ApiRequestService) ProcessQCXGP00WRequest(params []byte) ([]byte, error
|
|||||||
|
|
||||||
func (a *ApiRequestService) ProcessQCXG6B4ERequest(params []byte) ([]byte, error) {
|
func (a *ApiRequestService) ProcessQCXG6B4ERequest(params []byte) ([]byte, error) {
|
||||||
vin := gjson.GetBytes(params, "vin_code")
|
vin := gjson.GetBytes(params, "vin_code")
|
||||||
auth := gjson.GetBytes(params, "authorized")
|
if !vin.Exists() || vin.String() == "" {
|
||||||
if !vin.Exists() || vin.String() == "" || !auth.Exists() {
|
return nil, errors.New("api请求, QCXG6B4E, 缺少 vin_code")
|
||||||
return nil, errors.New("api请求, QCXG6B4E, 缺少 vin_code 或 authorized")
|
}
|
||||||
|
auth := gjson.GetBytes(params, "authorized").String()
|
||||||
|
if auth == "" {
|
||||||
|
auth = "1"
|
||||||
}
|
}
|
||||||
// 天远文档字段名为 VINCode、Authorized
|
// 天远文档字段名为 VINCode、Authorized
|
||||||
resp, err := a.tianyuanapi.CallInterface("QCXG6B4E", map[string]interface{}{
|
resp, err := a.tianyuanapi.CallInterface("QCXG6B4E", map[string]interface{}{
|
||||||
"VINCode": vin.String(),
|
"VINCode": vin.String(),
|
||||||
"Authorized": auth.String(),
|
"Authorized": auth,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1349,6 +1389,68 @@ func (a *ApiRequestService) ProcessQCXG6B4ERequest(params []byte) ([]byte, error
|
|||||||
return convertTianyuanResponse(resp)
|
return convertTianyuanResponse(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// processVerifyPassThrough 核验类接口:缓存 params 已含 mobile_no/id_card/name 等,原样传天远
|
||||||
|
func (a *ApiRequestService) processVerifyPassThrough(params []byte, apiID string) ([]byte, error) {
|
||||||
|
var m map[string]interface{}
|
||||||
|
if err := json.Unmarshal(params, &m); err != nil {
|
||||||
|
return nil, fmt.Errorf("api请求, %s, 解析参数失败: %w", apiID, err)
|
||||||
|
}
|
||||||
|
resp, err := a.tianyuanapi.CallInterface(apiID, m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return convertTianyuanResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiRequestService) ProcessIVYZ9K7FRequest(params []byte) ([]byte, error) {
|
||||||
|
return a.processVerifyPassThrough(params, "IVYZ9K7F")
|
||||||
|
}
|
||||||
|
func (a *ApiRequestService) ProcessIVYZA1B3Request(params []byte) ([]byte, error) {
|
||||||
|
return a.processVerifyPassThrough(params, "IVYZA1B3")
|
||||||
|
}
|
||||||
|
func (a *ApiRequestService) ProcessIVYZ6M8PRequest(params []byte) ([]byte, error) {
|
||||||
|
return a.processVerifyPassThrough(params, "IVYZ6M8P")
|
||||||
|
}
|
||||||
|
func (a *ApiRequestService) ProcessJRZQ8B3CRequest(params []byte) ([]byte, error) {
|
||||||
|
return a.processVerifyPassThrough(params, "JRZQ8B3C")
|
||||||
|
}
|
||||||
|
func (a *ApiRequestService) ProcessYYSY3M8SRequest(params []byte) ([]byte, error) {
|
||||||
|
return a.processVerifyPassThrough(params, "YYSY3M8S")
|
||||||
|
}
|
||||||
|
func (a *ApiRequestService) ProcessYYSYK9R4Request(params []byte) ([]byte, error) {
|
||||||
|
return a.processVerifyPassThrough(params, "YYSYK9R4")
|
||||||
|
}
|
||||||
|
func (a *ApiRequestService) ProcessYYSYF2T7Request(params []byte) ([]byte, error) {
|
||||||
|
return a.processVerifyPassThrough(params, "YYSYF2T7")
|
||||||
|
}
|
||||||
|
func (a *ApiRequestService) ProcessYYSYK8R3Request(params []byte) ([]byte, error) {
|
||||||
|
return a.processVerifyPassThrough(params, "YYSYK8R3")
|
||||||
|
}
|
||||||
|
func (a *ApiRequestService) ProcessYYSYS9W1Request(params []byte) ([]byte, error) {
|
||||||
|
return a.processVerifyPassThrough(params, "YYSYS9W1")
|
||||||
|
}
|
||||||
|
func (a *ApiRequestService) ProcessYYSYE7V5Request(params []byte) ([]byte, error) {
|
||||||
|
return a.processVerifyPassThrough(params, "YYSYE7V5")
|
||||||
|
}
|
||||||
|
func (a *ApiRequestService) ProcessYYSYP0T4Request(params []byte) ([]byte, error) {
|
||||||
|
return a.processVerifyPassThrough(params, "YYSYP0T4")
|
||||||
|
}
|
||||||
|
func (a *ApiRequestService) ProcessYYSY6F2BRequest(params []byte) ([]byte, error) {
|
||||||
|
return a.processVerifyPassThrough(params, "YYSY6F2B")
|
||||||
|
}
|
||||||
|
func (a *ApiRequestService) ProcessYYSY9E4ARequest(params []byte) ([]byte, error) {
|
||||||
|
return a.processVerifyPassThrough(params, "YYSY9E4A")
|
||||||
|
}
|
||||||
|
func (a *ApiRequestService) ProcessQYGL5F6ARequest(params []byte) ([]byte, error) {
|
||||||
|
return a.processVerifyPassThrough(params, "QYGL5F6A")
|
||||||
|
}
|
||||||
|
func (a *ApiRequestService) ProcessJRZQACABRequest(params []byte) ([]byte, error) {
|
||||||
|
return a.processVerifyPassThrough(params, "JRZQACAB")
|
||||||
|
}
|
||||||
|
func (a *ApiRequestService) ProcessJRZQ0B6YRequest(params []byte) ([]byte, error) {
|
||||||
|
return a.processVerifyPassThrough(params, "JRZQ0B6Y")
|
||||||
|
}
|
||||||
|
|
||||||
// buildVehicleBody 从 params 中取 required 与 optional 键,仅非空才写入 body
|
// buildVehicleBody 从 params 中取 required 与 optional 键,仅非空才写入 body
|
||||||
func buildVehicleBody(params []byte, required, optional []string) map[string]interface{} {
|
func buildVehicleBody(params []byte, required, optional []string) map[string]interface{} {
|
||||||
body := make(map[string]interface{})
|
body := make(map[string]interface{})
|
||||||
@@ -1367,6 +1469,18 @@ func buildVehicleBody(params []byte, required, optional []string) map[string]int
|
|||||||
return body
|
return body
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// buildVehicleCallbackURL 生成车辆类接口的异步回调地址
|
||||||
|
// 当前使用 AdminPromotion.URLDomain 作为域名配置,路径固定为 /api/v1/tianyuan/vehicle/callback
|
||||||
|
// 并通过查询参数携带 order_no 与 api_id 以便后端识别具体查询与模块。
|
||||||
|
func (a *ApiRequestService) buildVehicleCallbackURL(orderNo, apiID string) string {
|
||||||
|
base := strings.TrimRight(a.config.AdminPromotion.URLDomain, "/")
|
||||||
|
if base == "" {
|
||||||
|
// 兜底:如果未配置 URLDomain,则使用相对路径,交给网关/部署层补全域名
|
||||||
|
return fmt.Sprintf("/api/v1/tianyuan/vehicle/callback?order_no=%s&api_id=%s", orderNo, apiID)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s/api/v1/tianyuan/vehicle/callback?order_no=%s&api_id=%s", base, orderNo, apiID)
|
||||||
|
}
|
||||||
|
|
||||||
// ProcessQCXG7A2BRequest 名下车辆
|
// ProcessQCXG7A2BRequest 名下车辆
|
||||||
func (a *ApiRequestService) ProcessQCXG7A2BRequest(params []byte) ([]byte, error) {
|
func (a *ApiRequestService) ProcessQCXG7A2BRequest(params []byte) ([]byte, error) {
|
||||||
idCard := gjson.GetBytes(params, "id_card")
|
idCard := gjson.GetBytes(params, "id_card")
|
||||||
|
|||||||
@@ -129,23 +129,24 @@ type TocVehiclesUnderNameCountReq struct {
|
|||||||
IDCard string `json:"id_card" validate:"required,idCard"`
|
IDCard string `json:"id_card" validate:"required,idCard"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 车辆静态信息/过户详版等 仅 vin_code
|
// 车辆静态信息/过户详版等 仅 vin_code(车辆类不要求手机号与验证码)
|
||||||
type TocVehicleVinCodeReq struct {
|
type TocVehicleVinCodeReq struct {
|
||||||
VinCode string `json:"vin_code" validate:"required"`
|
VinCode string `json:"vin_code" validate:"required"`
|
||||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
Mobile string `json:"mobile"`
|
||||||
Code string `json:"code" validate:"required"`
|
Code string `json:"code"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 车辆里程记录(混合) QCXG1U4U
|
// 车辆里程记录(混合) QCXG1U4U
|
||||||
type TocVehicleMileageMixedReq struct {
|
type TocVehicleMileageMixedReq struct {
|
||||||
VinCode string `json:"vin_code" validate:"required"`
|
VinCode string `json:"vin_code" validate:"required"`
|
||||||
PlateNo string `json:"plate_no"`
|
PlateNo string `json:"plate_no"`
|
||||||
ReturnURL string `json:"return_url" validate:"required"`
|
// 回调地址由后端在调用第三方接口时自动生成,不再由前端透传
|
||||||
|
ReturnURL string `json:"return_url"`
|
||||||
ImageURL string `json:"image_url" validate:"required"`
|
ImageURL string `json:"image_url" validate:"required"`
|
||||||
RegURL string `json:"reg_url"`
|
RegURL string `json:"reg_url"`
|
||||||
EngineNumber string `json:"engine_number"`
|
EngineNumber string `json:"engine_number"`
|
||||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
Mobile string `json:"mobile"`
|
||||||
Code string `json:"code" validate:"required"`
|
Code string `json:"code"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 二手车VIN估值 QCXGY7F2
|
// 二手车VIN估值 QCXGY7F2
|
||||||
@@ -155,60 +156,136 @@ type TocVehicleVinValuationReq struct {
|
|||||||
VehicleLocation string `json:"vehicle_location" validate:"required"`
|
VehicleLocation string `json:"vehicle_location" validate:"required"`
|
||||||
FirstRegistrationDate string `json:"first_registrationdate" validate:"required"` // yyyy-MM
|
FirstRegistrationDate string `json:"first_registrationdate" validate:"required"` // yyyy-MM
|
||||||
Color string `json:"color"`
|
Color string `json:"color"`
|
||||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
Mobile string `json:"mobile"`
|
||||||
Code string `json:"code" validate:"required"`
|
Code string `json:"code"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 车辆过户简版 QCXG1H7Y
|
// 车辆过户简版 QCXG1H7Y
|
||||||
type TocVehicleTransferSimpleReq struct {
|
type TocVehicleTransferSimpleReq struct {
|
||||||
VinCode string `json:"vin_code" validate:"required"`
|
VinCode string `json:"vin_code" validate:"required"`
|
||||||
PlateNo string `json:"plate_no"`
|
PlateNo string `json:"plate_no"`
|
||||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
Mobile string `json:"mobile"`
|
||||||
Code string `json:"code" validate:"required"`
|
Code string `json:"code"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 车辆维保简版 QCXG3Y6B
|
// 车辆维保简版 QCXG3Y6B
|
||||||
type TocVehicleMaintenanceSimpleReq struct {
|
type TocVehicleMaintenanceSimpleReq struct {
|
||||||
VinCode string `json:"vin_code" validate:"required"`
|
VinCode string `json:"vin_code" validate:"required"`
|
||||||
PlateNo string `json:"plate_no"`
|
PlateNo string `json:"plate_no"`
|
||||||
ReturnURL string `json:"return_url" validate:"required"`
|
// 回调地址由后端在调用第三方接口时自动生成,不再由前端透传
|
||||||
|
ReturnURL string `json:"return_url"`
|
||||||
ImageURL string `json:"image_url"`
|
ImageURL string `json:"image_url"`
|
||||||
RegURL string `json:"reg_url"`
|
RegURL string `json:"reg_url"`
|
||||||
EngineNumber string `json:"engine_number"`
|
EngineNumber string `json:"engine_number"`
|
||||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
Mobile string `json:"mobile"`
|
||||||
Code string `json:"code" validate:"required"`
|
Code string `json:"code"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 车辆维保详细版 QCXG3Z3L
|
// 车辆维保详细版 QCXG3Z3L
|
||||||
type TocVehicleMaintenanceDetailReq struct {
|
type TocVehicleMaintenanceDetailReq struct {
|
||||||
VinCode string `json:"vin_code" validate:"required"`
|
VinCode string `json:"vin_code" validate:"required"`
|
||||||
PlateNo string `json:"plate_no"`
|
PlateNo string `json:"plate_no"`
|
||||||
ReturnURL string `json:"return_url" validate:"required"`
|
// 回调地址由后端在调用第三方接口时自动生成,不再由前端透传
|
||||||
|
ReturnURL string `json:"return_url"`
|
||||||
ImageURL string `json:"image_url"`
|
ImageURL string `json:"image_url"`
|
||||||
EngineNumber string `json:"engine_number"`
|
EngineNumber string `json:"engine_number"`
|
||||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
Mobile string `json:"mobile"`
|
||||||
Code string `json:"code" validate:"required"`
|
Code string `json:"code"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 车辆出险详版 QCXGP00W,vlphoto_data 加密后以 data 字段提交
|
// 车辆出险详版 QCXGP00W,vlphoto_data 加密后以 data 字段提交
|
||||||
type TocVehicleClaimDetailReq struct {
|
type TocVehicleClaimDetailReq struct {
|
||||||
VinCode string `json:"vin_code" validate:"required"`
|
VinCode string `json:"vin_code" validate:"required"`
|
||||||
PlateNo string `json:"plate_no"`
|
PlateNo string `json:"plate_no"`
|
||||||
ReturnURL string `json:"return_url" validate:"required"`
|
// 回调地址由后端在调用第三方接口时自动生成,不再由前端透传
|
||||||
|
ReturnURL string `json:"return_url"`
|
||||||
VlphotoData string `json:"vlphoto_data" validate:"required"` // 行驶证图片 base64,加密后传 API 的 data
|
VlphotoData string `json:"vlphoto_data" validate:"required"` // 行驶证图片 base64,加密后传 API 的 data
|
||||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
Mobile string `json:"mobile"`
|
||||||
Code string `json:"code" validate:"required"`
|
Code string `json:"code"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 车辆出险记录核验 QCXG6B4E,VINCode + Authorized(0/1)
|
// 车辆出险记录核验 QCXG6B4E,VINCode;Authorized 由后端默认传 1
|
||||||
type TocVehicleClaimVerifyReq struct {
|
type TocVehicleClaimVerifyReq struct {
|
||||||
VINCode string `json:"vin_code" validate:"required"`
|
VINCode string `json:"vin_code" validate:"required"`
|
||||||
Authorized string `json:"authorized" validate:"required"` // 0:否 1:是
|
Authorized string `json:"authorized"` // 可选,后端默认 1
|
||||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
Mobile string `json:"mobile"`
|
||||||
Code string `json:"code" validate:"required"`
|
Code string `json:"code"`
|
||||||
}
|
}
|
||||||
type AgentIdentifier struct {
|
type AgentIdentifier struct {
|
||||||
Product string `json:"product"`
|
Product string `json:"product"`
|
||||||
AgentID int64 `json:"agent_id"`
|
AgentID int64 `json:"agent_id"`
|
||||||
Price string `json:"price"`
|
Price string `json:"price"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------- 核验工具(verify feature.md)---------------
|
||||||
|
// 公安二要素 IVYZ9K7F:请求用 mobile,缓存/API 用 mobile_no
|
||||||
|
type TocVerifyAuthTwoReq struct {
|
||||||
|
MobileNo string `json:"mobile" validate:"required,mobile"` // 前端传 mobile
|
||||||
|
IDCard string `json:"id_card" validate:"required,idCard"`
|
||||||
|
Name string `json:"name" validate:"required,name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 公安三要素 IVYZA1B3:photo_data, id_card, name
|
||||||
|
type TocVerifyAuthThreeReq struct {
|
||||||
|
PhotoData string `json:"photo_data" validate:"required"` // 人像 base64
|
||||||
|
IDCard string `json:"id_card" validate:"required,idCard"`
|
||||||
|
Name string `json:"name" validate:"required,name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 职业资格证书 IVYZ6M8P:id_card, name
|
||||||
|
type TocVerifyCertReq struct {
|
||||||
|
IDCard string `json:"id_card" validate:"required,idCard"`
|
||||||
|
Name string `json:"name" validate:"required,name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 个人消费能力 JRZQ8B3C
|
||||||
|
type TocVerifyConsumptionReq struct {
|
||||||
|
MobileNo string `json:"mobile" validate:"required,mobile"`
|
||||||
|
IDCard string `json:"id_card" validate:"required,idCard"`
|
||||||
|
Name string `json:"name" validate:"required,name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运营商二要素 YYSY3M8S
|
||||||
|
type TocVerifyYysTwoReq struct {
|
||||||
|
MobileNo string `json:"mobile" validate:"required,mobile"`
|
||||||
|
Name string `json:"name" validate:"required,name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全网手机三要素 YYSYK9R4
|
||||||
|
type TocVerifyYysThreeReq struct {
|
||||||
|
MobileNo string `json:"mobile" validate:"required,mobile"`
|
||||||
|
IDCard string `json:"id_card" validate:"required,idCard"`
|
||||||
|
Name string `json:"name" validate:"required,name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 仅手机号:YYSYF2T7/YYSYK8R3/YYSYS9W1/YYSYE7V5/YYSYP0T4/YYSY9E4A
|
||||||
|
type TocVerifyMobileOnlyReq struct {
|
||||||
|
MobileNo string `json:"mobile" validate:"required,mobile"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 手机消费区间 YYSY6F2B
|
||||||
|
type TocVerifyYysConsumptionReq struct {
|
||||||
|
MobileNo string `json:"mobile" validate:"required,mobile"`
|
||||||
|
Authorized string `json:"authorized" validate:"required"` // 0/1
|
||||||
|
}
|
||||||
|
|
||||||
|
// 名下企业关联 QYGL5F6A:id_card 选填
|
||||||
|
type TocVerifyEntRelationReq struct {
|
||||||
|
IDCard string `json:"id_card"` // 可选
|
||||||
|
}
|
||||||
|
|
||||||
|
// 银行卡四要素 JRZQACAB
|
||||||
|
type TocVerifyBankFourReq struct {
|
||||||
|
MobileNo string `json:"mobile" validate:"required,mobile"`
|
||||||
|
IDCard string `json:"id_card" validate:"required,idCard"`
|
||||||
|
BankCard string `json:"bank_card" validate:"required"`
|
||||||
|
Name string `json:"name" validate:"required,name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 银行卡黑名单 JRZQ0B6Y
|
||||||
|
type TocVerifyBankBlackReq struct {
|
||||||
|
MobileNo string `json:"mobile" validate:"required,mobile"`
|
||||||
|
IDCard string `json:"id_card" validate:"required,idCard"`
|
||||||
|
Name string `json:"name" validate:"required,name"`
|
||||||
|
BankCard string `json:"bank_card" validate:"required"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -2115,6 +2115,15 @@ type SaveAgentMembershipUserConfigReq struct {
|
|||||||
PriceRatio float64 `json:"price_ratio"`
|
PriceRatio float64 `json:"price_ratio"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ServeUploadedFileReq struct {
|
||||||
|
FileName string `path:"fileName"` // 文件名,如 uuid.jpg
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServeUploadedFileResp struct {
|
||||||
|
FilePath string `json:"-"` // 内部:本地文件路径
|
||||||
|
ContentType string `json:"-"` // 内部:Content-Type
|
||||||
|
}
|
||||||
|
|
||||||
type TimeRangeReport struct {
|
type TimeRangeReport struct {
|
||||||
Commission float64 `json:"commission"` // 佣金
|
Commission float64 `json:"commission"` // 佣金
|
||||||
Report int `json:"report"` // 报告量
|
Report int `json:"report"` // 报告量
|
||||||
@@ -2170,6 +2179,14 @@ type UpdateRoleResp struct {
|
|||||||
Success bool `json:"success"` // 是否成功
|
Success bool `json:"success"` // 是否成功
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UploadImageReq struct {
|
||||||
|
ImageBase64 string `json:"image_base64" validate:"required"` // 图片 base64(不含 data URL 前缀)
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadImageResp struct {
|
||||||
|
Url string `json:"url"` // 可公网访问的图片 URL
|
||||||
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
Mobile string `json:"mobile"`
|
Mobile string `json:"mobile"`
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
285
deploy/sql/repair_order_refund_q176967208700018143d9f6.sql
Normal file
285
deploy/sql/repair_order_refund_q176967208700018143d9f6.sql
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
-- =============================================================================
|
||||||
|
-- 修复订单 Q_176967208700018143d9f6:支付宝已退款成功,但库内未完成
|
||||||
|
-- =============================================================================
|
||||||
|
--
|
||||||
|
-- 【原因简述】
|
||||||
|
-- 后台发起支付宝退款后,支付宝侧已退款成功,但创建退款记录时因 unique_refund_no
|
||||||
|
-- 冲突(Duplicate entry 'refund-Q_176967208700018143d9f6')导致 createRefundRecordAndUpdateOrder
|
||||||
|
-- 失败,后续流程未执行:订单未置为 refunded、未写退款记录、未扣代理佣金与钱包。
|
||||||
|
--
|
||||||
|
-- 【本单实际退款金额】支付宝已退 99.5 元(非整单金额)
|
||||||
|
--
|
||||||
|
-- 【与 adminrefundorderlogic 退款链路对照】
|
||||||
|
-- 代码路径:AdminRefundOrder -> handleAlipayRefund -> createRefundRecordAndUpdateOrder + HandleCommissionAndWalletDeduction
|
||||||
|
--
|
||||||
|
-- handleAlipayRefund 成功分支:
|
||||||
|
-- 1) createRefundRecordAndUpdateOrder(order, req, refundNo, refundResp.TradeNo, ...)
|
||||||
|
-- -> 事务内:Insert order_refund(refund_no, platform_refund_id=TradeNo, order_id, user_id, product_id, refund_amount, status=success, refund_time=NOW())
|
||||||
|
-- -> 事务内:Update order(仅 code 中赋了 status=refunded,未显式设 refund_time/version)
|
||||||
|
-- 2) HandleCommissionAndWalletDeduction(ctx, svcCtx, nil, order, req.RefundAmount)
|
||||||
|
-- -> 该订单下 agent_commission:按 refundAmount 比例冲减,refunded_amount 增加,满额则 status=2(UpdateWithVersion)
|
||||||
|
-- -> 按代理汇总扣减额,agent_wallet 先扣冻结再扣可用(UpdateWithVersion)
|
||||||
|
-- -> agent_wallet_transaction 插入 type=refund、金额为负的流水
|
||||||
|
--
|
||||||
|
-- 本 SQL 对应关系:
|
||||||
|
-- Step 2 = createRefundRecordAndUpdateOrder 的 Insert order_refund(platform_refund_id 修复时无支付宝 TradeNo 填 NULL,可从支付宝补)
|
||||||
|
-- Step 3 = createRefundRecordAndUpdateOrder 的 Update order,并显式补全 refund_time、version+1
|
||||||
|
-- Step 4 = HandleCommissionAndWalletDeduction 对 agent_commission 的冲减(按 99.5 比例)
|
||||||
|
-- Step 5 = HandleCommissionAndWalletDeduction 对 agent_wallet 扣减 + agent_wallet_transaction 插入
|
||||||
|
--
|
||||||
|
-- 【执行前请确认】
|
||||||
|
-- 1)该订单在支付宝侧已退款成功;2)已备份相关表或先在测试环境执行。
|
||||||
|
-- Step 2~5 已包在事务中:任一步报错请执行 ROLLBACK;若 step 1 未查到订单(_order_not_found=1)勿执行后续。
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
-- 与表字段 collation 一致,避免 #1267 Illegal mix of collations(若表为 utf8mb4_general_ci 则改为 COLLATE utf8mb4_general_ci)
|
||||||
|
SET
|
||||||
|
@order_no = CONVERT(
|
||||||
|
'Q_176967208700018143d9f6' USING utf8mb4
|
||||||
|
) COLLATE utf8mb4_general_ci;
|
||||||
|
-- 本单实际退款金额(元)
|
||||||
|
SET @repair_refund_amount = 99.5;
|
||||||
|
|
||||||
|
-- 1) 取订单信息
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
user_id,
|
||||||
|
product_id,
|
||||||
|
amount,
|
||||||
|
version INTO @order_id,
|
||||||
|
@user_id,
|
||||||
|
@product_id,
|
||||||
|
@order_amount,
|
||||||
|
@order_version
|
||||||
|
FROM `order`
|
||||||
|
WHERE
|
||||||
|
order_no = @order_no
|
||||||
|
AND del_state = 0
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
SET @refund_amount = @repair_refund_amount;
|
||||||
|
|
||||||
|
-- 若无记录则说明订单号错误,终止
|
||||||
|
SELECT IF(@order_id IS NULL, 1, 0) AS _order_not_found;
|
||||||
|
-- 若 _order_not_found=1 请勿继续执行后续语句
|
||||||
|
|
||||||
|
-- ---------- 以下为事务:任一步报错请执行 ROLLBACK ----------
|
||||||
|
START TRANSACTION;
|
||||||
|
|
||||||
|
-- 2) 补写退款记录(若该订单尚无成功状态的退款记录)
|
||||||
|
-- 代码中 platform_refund_id 来自支付宝 refundResp.TradeNo;修复时无则填 NULL,如有支付宝退款单号可事后 UPDATE 补上
|
||||||
|
INSERT INTO
|
||||||
|
order_refund (
|
||||||
|
refund_no,
|
||||||
|
order_id,
|
||||||
|
user_id,
|
||||||
|
product_id,
|
||||||
|
platform_refund_id,
|
||||||
|
refund_amount,
|
||||||
|
refund_reason,
|
||||||
|
status,
|
||||||
|
del_state,
|
||||||
|
version,
|
||||||
|
refund_time,
|
||||||
|
close_time,
|
||||||
|
delete_time
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
CONCAT(
|
||||||
|
'refund-',
|
||||||
|
@order_no,
|
||||||
|
'-repair'
|
||||||
|
),
|
||||||
|
@order_id,
|
||||||
|
@user_id,
|
||||||
|
@product_id,
|
||||||
|
NULL,
|
||||||
|
@refund_amount,
|
||||||
|
NULL,
|
||||||
|
'success',
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
NOW(),
|
||||||
|
NULL,
|
||||||
|
NULL
|
||||||
|
FROM (
|
||||||
|
SELECT 1
|
||||||
|
) _one
|
||||||
|
WHERE
|
||||||
|
NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM order_refund
|
||||||
|
WHERE
|
||||||
|
order_id = @order_id
|
||||||
|
AND status = 'success'
|
||||||
|
AND del_state = 0
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 3) 订单状态改为已退款
|
||||||
|
UPDATE `order`
|
||||||
|
SET
|
||||||
|
status = 'refunded',
|
||||||
|
refund_time = NOW(),
|
||||||
|
version = version + 1,
|
||||||
|
update_time = NOW()
|
||||||
|
WHERE
|
||||||
|
id = @order_id
|
||||||
|
AND del_state = 0
|
||||||
|
AND status = 'paid';
|
||||||
|
|
||||||
|
-- 4) 代理佣金:按本次退款 99.5 元在该订单的佣金上比例冲减(refunded_amount 增加,满额则 status=2)
|
||||||
|
SET
|
||||||
|
@total_available = (
|
||||||
|
SELECT COALESCE(
|
||||||
|
SUM(amount - refunded_amount), 0
|
||||||
|
)
|
||||||
|
FROM agent_commission
|
||||||
|
WHERE
|
||||||
|
order_id = @order_id
|
||||||
|
AND del_state = 0
|
||||||
|
);
|
||||||
|
|
||||||
|
UPDATE agent_commission
|
||||||
|
SET
|
||||||
|
refunded_amount = LEAST(
|
||||||
|
amount,
|
||||||
|
refunded_amount + @repair_refund_amount * (amount - refunded_amount) / NULLIF(@total_available, 0)
|
||||||
|
),
|
||||||
|
status = CASE
|
||||||
|
WHEN LEAST(
|
||||||
|
amount,
|
||||||
|
refunded_amount + @repair_refund_amount * (amount - refunded_amount) / NULLIF(@total_available, 0)
|
||||||
|
) >= amount THEN 2
|
||||||
|
ELSE status
|
||||||
|
END,
|
||||||
|
version = version + 1,
|
||||||
|
update_time = NOW()
|
||||||
|
WHERE
|
||||||
|
order_id = @order_id
|
||||||
|
AND del_state = 0
|
||||||
|
AND @total_available > 0;
|
||||||
|
|
||||||
|
-- 5) 代理钱包扣减 + 流水(仅对尚未存在本单退款流水的代理扣减,避免重复执行导致重复扣款)
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS _repair_wallet_snapshot;
|
||||||
|
|
||||||
|
CREATE TEMPORARY TABLE _repair_wallet_snapshot (
|
||||||
|
agent_id BIGINT NOT NULL,
|
||||||
|
balance_before DECIMAL(20, 4) NOT NULL,
|
||||||
|
frozen_before DECIMAL(20, 4) NOT NULL,
|
||||||
|
total_deduct DECIMAL(20, 4) NOT NULL,
|
||||||
|
PRIMARY KEY (agent_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 按 99.5 元在该订单各代理间按“可退佣金”比例分配扣减额(与 step 4 一致),仅扣减额>0 的代理
|
||||||
|
INSERT INTO
|
||||||
|
_repair_wallet_snapshot (
|
||||||
|
agent_id,
|
||||||
|
balance_before,
|
||||||
|
frozen_before,
|
||||||
|
total_deduct
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
agent_id,
|
||||||
|
balance_before,
|
||||||
|
frozen_before,
|
||||||
|
total_deduct
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
c.agent_id, w.balance AS balance_before, w.frozen_balance AS frozen_before, @repair_refund_amount * COALESCE(
|
||||||
|
SUM(c.amount - c.refunded_amount), 0
|
||||||
|
) / NULLIF(@total_available, 0) AS total_deduct
|
||||||
|
FROM
|
||||||
|
agent_commission c
|
||||||
|
JOIN agent_wallet w ON w.agent_id = c.agent_id
|
||||||
|
AND w.del_state = 0
|
||||||
|
WHERE
|
||||||
|
c.order_id = @order_id
|
||||||
|
AND c.del_state = 0
|
||||||
|
AND @total_available > 0
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM agent_wallet_transaction t
|
||||||
|
WHERE
|
||||||
|
t.agent_id = c.agent_id
|
||||||
|
AND t.transaction_id = @order_no
|
||||||
|
AND t.transaction_type = 'refund'
|
||||||
|
AND t.del_state = 0
|
||||||
|
)
|
||||||
|
GROUP BY
|
||||||
|
c.agent_id, w.balance, w.frozen_balance
|
||||||
|
) _agent_deduct
|
||||||
|
WHERE
|
||||||
|
total_deduct > 0;
|
||||||
|
|
||||||
|
-- 从钱包扣减:优先扣冻结余额,不足再扣可用余额
|
||||||
|
UPDATE agent_wallet w
|
||||||
|
INNER JOIN _repair_wallet_snapshot r ON w.agent_id = r.agent_id
|
||||||
|
SET
|
||||||
|
w.frozen_balance = w.frozen_balance - LEAST(
|
||||||
|
r.total_deduct,
|
||||||
|
w.frozen_balance
|
||||||
|
),
|
||||||
|
w.balance = w.balance - (
|
||||||
|
r.total_deduct - LEAST(
|
||||||
|
r.total_deduct,
|
||||||
|
w.frozen_balance
|
||||||
|
)
|
||||||
|
),
|
||||||
|
w.version = w.version + 1,
|
||||||
|
w.update_time = NOW()
|
||||||
|
WHERE
|
||||||
|
w.del_state = 0;
|
||||||
|
|
||||||
|
-- 插入退款流水(金额为负数)
|
||||||
|
INSERT INTO
|
||||||
|
agent_wallet_transaction (
|
||||||
|
delete_time,
|
||||||
|
del_state,
|
||||||
|
version,
|
||||||
|
agent_id,
|
||||||
|
transaction_type,
|
||||||
|
amount,
|
||||||
|
balance_before,
|
||||||
|
balance_after,
|
||||||
|
frozen_balance_before,
|
||||||
|
frozen_balance_after,
|
||||||
|
transaction_id,
|
||||||
|
related_user_id,
|
||||||
|
remark
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
NULL,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
r.agent_id,
|
||||||
|
'refund',
|
||||||
|
- r.total_deduct,
|
||||||
|
r.balance_before,
|
||||||
|
r.balance_before - (
|
||||||
|
r.total_deduct - LEAST(
|
||||||
|
r.total_deduct,
|
||||||
|
r.frozen_before
|
||||||
|
)
|
||||||
|
),
|
||||||
|
r.frozen_before,
|
||||||
|
r.frozen_before - LEAST(
|
||||||
|
r.total_deduct,
|
||||||
|
r.frozen_before
|
||||||
|
),
|
||||||
|
@order_no,
|
||||||
|
NULL,
|
||||||
|
'订单退款修复(支付宝已退,库内补单)'
|
||||||
|
FROM _repair_wallet_snapshot r;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS _repair_wallet_snapshot;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
-- ---------- 事务结束 ----------
|
||||||
|
|
||||||
|
-- 6) 校验(可选)
|
||||||
|
-- 订单应为 refunded
|
||||||
|
-- SELECT id, order_no, status, refund_time FROM `order` WHERE id = @order_id;
|
||||||
|
-- 应有 success 退款记录,refund_amount = 99.5
|
||||||
|
-- SELECT * FROM order_refund WHERE order_id = @order_id AND del_state = 0;
|
||||||
|
-- 佣金 refunded_amount 按 99.5 比例增加,满额则 status=2
|
||||||
|
-- SELECT id, agent_id, order_id, amount, refunded_amount, status FROM agent_commission WHERE order_id = @order_id;
|
||||||
Reference in New Issue
Block a user