package fadada import ( "encoding/json" "fmt" "net/http" "strings" ) // 常见回调事件名(以官方实际推送为准,解析时兼容多种字段) const ( CallbackEventSignTaskSigned = "sign-task-signed" CallbackEventSignTaskFinished = "sign-task-finished" CallbackEventSignTaskCanceled = "sign-task-canceled" CallbackEventCorpAuthorize = "corp-authorize" CallbackEventCorpAuthSuccess = "corp-authorize-success" ) // SuccessCallbackBody 回调应答,约定返回成功 var SuccessCallbackBody = []byte(`{"msg":"success"}`) // VerifyCallback 校验法大大回调签名(HMAC-SHA256,与出站请求同一套算法) func (c *Client) VerifyCallback(headers map[string]string, bizContent string) error { if c.config == nil || c.config.AppSecret == "" { return fmt.Errorf("法大大 app_secret 未配置,无法验签") } normalized := normalizeHeaderMap(headers) sign := firstHeader(normalized, "X-FASC-Sign", "X-Fasc-Sign") if sign == "" { return fmt.Errorf("缺少回调签名头 X-FASC-Sign") } timestamp := firstHeader(normalized, "X-FASC-Timestamp", "X-Fasc-Timestamp") if timestamp == "" { return fmt.Errorf("缺少回调时间戳头 X-FASC-Timestamp") } headMap := map[string]string{ "X-FASC-App-Id": firstHeader(normalized, "X-FASC-App-Id", "X-Fasc-App-Id"), "X-FASC-Sign-Type": firstHeader(normalized, "X-FASC-Sign-Type", "X-Fasc-Sign-Type"), "X-FASC-Timestamp": timestamp, "X-FASC-Nonce": firstHeader(normalized, "X-FASC-Nonce", "X-Fasc-Nonce"), "X-FASC-Api-SubVersion": firstHeader(normalized, "X-FASC-Api-SubVersion", "X-Fasc-Api-SubVersion"), "bizContent": bizContent, } if event := firstHeader(normalized, "X-FASC-Event", "X-Fasc-Event"); event != "" { headMap["X-FASC-Event"] = event } if token := firstHeader(normalized, "X-FASC-AccessToken", "X-Fasc-AccessToken"); token != "" { headMap["X-FASC-AccessToken"] = token } expected := SignByMap(headMap, timestamp, c.config.AppSecret) if !strings.EqualFold(expected, sign) { return fmt.Errorf("法大大回调签名校验失败") } appID := firstHeader(normalized, "X-FASC-App-Id", "X-Fasc-App-Id") if appID != "" && c.config.AppID != "" && appID != c.config.AppID { return fmt.Errorf("法大大回调 AppId 不匹配") } return nil } // VerifyCallbackFromHTTP 从 http.Header 验签 func (c *Client) VerifyCallbackFromHTTP(h http.Header, bizContent string) error { headers := make(map[string]string, len(h)) for k, vals := range h { if len(vals) > 0 { headers[k] = vals[0] } } return c.VerifyCallback(headers, bizContent) } // ParseCallback 解析 bizContent 为统一 CallbackEvent func (c *Client) ParseCallback(headers map[string]string, bizContent string) (*CallbackEvent, error) { bizContent = strings.TrimSpace(bizContent) if bizContent == "" { return nil, fmt.Errorf("回调 bizContent 不能为空") } raw := make(map[string]interface{}) if err := json.Unmarshal([]byte(bizContent), &raw); err != nil { return nil, fmt.Errorf("解析回调 bizContent 失败: %w", err) } normalized := normalizeHeaderMap(headers) event := firstHeader(normalized, "X-FASC-Event", "X-Fasc-Event") if event == "" { event = stringValue(raw, "event", "eventType", "type") } ev := &CallbackEvent{ Event: event, SignTaskID: stringValue(raw, "signTaskId", "sign_task_id"), SignTaskStatus: stringValue(raw, "signTaskStatus", "sign_task_status"), ClientCorpID: stringValue(raw, "clientCorpId", "client_corp_id"), OpenCorpID: stringValue(raw, "openCorpId", "open_corp_id"), AuthResult: stringValue(raw, "authResult", "auth_result", "result"), EventTime: stringValue(raw, "eventTime", "event_time", "timestamp"), Raw: raw, } if data, ok := raw["data"].(map[string]interface{}); ok { if ev.SignTaskID == "" { ev.SignTaskID = stringValue(data, "signTaskId", "sign_task_id") } if ev.SignTaskStatus == "" { ev.SignTaskStatus = stringValue(data, "signTaskStatus", "sign_task_status") } if ev.ClientCorpID == "" { ev.ClientCorpID = stringValue(data, "clientCorpId", "client_corp_id") } if ev.OpenCorpID == "" { ev.OpenCorpID = stringValue(data, "openCorpId", "open_corp_id") } if ev.AuthResult == "" { ev.AuthResult = stringValue(data, "authResult", "auth_result", "result") } } return ev, nil } // IsSignCompletedCallback 是否签署完成类回调 func (e *CallbackEvent) IsSignCompletedCallback() bool { if e == nil { return false } if IsSignTaskCompletedStatus(e.SignTaskStatus) { return true } switch e.Event { case CallbackEventSignTaskFinished, CallbackEventSignTaskSigned: return true default: return false } } func normalizeHeaderMap(headers map[string]string) map[string]string { out := make(map[string]string, len(headers)) for k, v := range headers { out[http.CanonicalHeaderKey(k)] = v out[k] = v } return out } func firstHeader(headers map[string]string, keys ...string) string { for _, key := range keys { if v := strings.TrimSpace(headers[key]); v != "" { return v } if v := strings.TrimSpace(headers[http.CanonicalHeaderKey(key)]); v != "" { return v } } return "" } func stringValue(m map[string]interface{}, keys ...string) string { for _, key := range keys { if v, ok := m[key]; ok && v != nil { switch t := v.(type) { case string: if strings.TrimSpace(t) != "" { return strings.TrimSpace(t) } case float64: return fmt.Sprintf("%.0f", t) case json.Number: return t.String() default: s := strings.TrimSpace(fmt.Sprint(t)) if s != "" && s != "" { return s } } } } return "" }