Files
mengyastore/mengyastore-backend-go/internal/payment/mengya.go
2026-05-13 12:11:46 +08:00

144 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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*元`),
}
// AmountEpsilonWebhook 到账金额与订单快照金额对比时的容差。
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, "; ")
}