Files

185 lines
5.6 KiB
Go
Raw Permalink Normal View History

2026-07-21 15:53:29 +08:00
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 != "<nil>" {
return s
}
}
}
}
return ""
}