package payment import ( "bytes" "encoding/json" "fmt" "math" "regexp" "strconv" "strings" ) // 依次尝试:微信等常带 ¥;支付宝等到账文案多为「收款x.xx元」无货币符号。 var mengyaNoticeAmountPatterns = []*regexp.Regexp{ regexp.MustCompile(`到账[¥¥]\s*([\d]+(?:\.[\d]+)?)`), regexp.MustCompile(`到账\s*([\d]+(?:\.[\d]+)?)\s*元`), regexp.MustCompile(`收款[¥¥]\s*([\d]+(?:\.[\d]+)?)(?:\s*元)?`), regexp.MustCompile(`收款\s*([\d]+(?:\.[\d]+)?)\s*元`), } // AmountEpsilon:Webhook 到账金额与订单快照金额对比时的容差。 const AmountEpsilon = 0.005 func AmountAlmostEqual(expected, actual float64) bool { return math.Abs(expected-actual) < AmountEpsilon } // ParseWebhookJSON 从转发通知体中解析支付金额: // 优先 body.notice,其次顶层 notice,否则在序列化后的 JSON 字符串中扫描。 func ParseWebhookJSON(raw []byte) (float64, bool) { if len(bytes.TrimSpace(raw)) == 0 { return 0, false } var top map[string]json.RawMessage if err := json.Unmarshal(raw, &top); err != nil { return scanAmount(string(raw)) } if v, ok := top["notice"]; ok { if s := jsonString(v); s != "" { return scanAmount(s) } } if inner, ok := top["body"]; ok { var nested map[string]json.RawMessage if json.Unmarshal(inner, &nested) == nil { if v, ok := nested["notice"]; ok { if s := jsonString(v); s != "" { return scanAmount(s) } } if amt, ok := scanAmount(jsonString(inner)); ok { return amt, ok } return scanAmount(string(inner)) } if amt, ok := scanAmount(strings.Trim(strings.Trim(string(inner), "\""), `"`)); ok { return amt, ok } return scanAmount(string(inner)) } buf, _ := json.Marshal(top) return scanAmount(string(buf)) } func jsonString(raw json.RawMessage) string { var s string if json.Unmarshal(raw, &s) == nil { return s } return "" } func scanAmount(text string) (float64, bool) { if amt, ok := parseAmountRegex(text); ok { return amt, true } return parseAmountRegex(strings.ReplaceAll(text, `\n`, "\n")) } func parseAmountRegex(text string) (float64, bool) { for _, re := range mengyaNoticeAmountPatterns { match := re.FindStringSubmatch(text) if len(match) < 2 { continue } s := strings.ReplaceAll(match[1], ",", "") yuan, err := strconv.ParseFloat(s, 64) if err != nil { continue } return yuan, true } return 0, false } // NoticeFieldsForLog 从常见萌芽/转发 JSON 里抽出 notice 等字段,仅供后台日志排查(如微信/支付宝文案差异)。 func NoticeFieldsForLog(raw []byte) string { if len(bytes.TrimSpace(raw)) == 0 { return "" } var top map[string]json.RawMessage if err := json.Unmarshal(raw, &top); err != nil { return "" } var parts []string add := func(k, v string) { if v == "" { return } if len(v) > 600 { v = v[:600] + "…" } parts = append(parts, fmt.Sprintf("%s=%s", k, strconv.Quote(v))) } if v, ok := top["notice"]; ok { add("notice", jsonString(v)) } if inner, ok := top["body"]; ok { var nested map[string]json.RawMessage if json.Unmarshal(inner, &nested) == nil { if v, ok := nested["notice"]; ok { add("body.notice", jsonString(v)) } if v, ok := nested["content"]; ok { add("body.content", jsonString(v)) } if v, ok := nested["text"]; ok { add("body.text", jsonString(v)) } } else if s := jsonString(inner); s != "" { add("body(string)", s) } } for _, k := range []string{"title", "msg", "message", "desc", "description"} { if v, ok := top[k]; ok { add(k, jsonString(v)) } } if len(parts) == 0 { return "" } return strings.Join(parts, "; ") }