package payment import ( "encoding/json" "errors" "fmt" "net/url" "strings" "kra/internal/biz" "kra/internal/utils/paymentutil" ) func callbackFields(callback *biz.PaymentCallback) map[string]string { fields := map[string]string{} if callback == nil { return fields } for key, value := range callback.Query { fields[key] = value } contentType := "" for key, value := range callback.Headers { if strings.EqualFold(key, "Content-Type") { contentType = strings.ToLower(value) break } } if strings.Contains(contentType, "application/x-www-form-urlencoded") { if values, err := url.ParseQuery(string(callback.Body)); err == nil { for key, value := range values { if len(value) > 0 { fields[key] = value[0] } } } } var object map[string]any if json.Unmarshal(callback.Body, &object) == nil { for key, value := range object { if stringValue, ok := value.(string); ok { fields[key] = stringValue } } } return fields } func first(values map[string]string, keys ...string) string { for _, key := range keys { if values[key] != "" { return values[key] } } return "" } func text(values map[string]any, key string) string { value, _ := values[key].(string) return value } func firstAny(values map[string]any, keys ...string) string { for _, key := range keys { if value, ok := values[key].(string); ok && value != "" { return value } } return "" } // Compatibility shims keep channel adapters focused on protocol and // persistence concerns while stateless parsing rules live in paymentutil. func normalizePaymentStatus(value, fallback string) string { return paymentutil.NormalizeStatus(value, fallback) } func parseIntegerAmount(value string) (int64, error) { return paymentutil.ParseIntegerAmount(value) } func parseDecimalAmount(value string, scale int64) (int64, error) { return paymentutil.ParseDecimalAmount(value, scale) } func formatDecimalAmount(amount, scale int64) (string, error) { return paymentutil.FormatDecimalAmount(amount, scale) } func configuredInt64(values map[string]any, key string, fallback int64) int64 { return paymentutil.ConfiguredInt64(values, key, fallback) } func jsonObject(raw []byte) map[string]any { return paymentutil.JSONObject(raw) } func nestedString(value any, keys ...string) string { return paymentutil.NestedString(value, keys...) } func valueAtPath(value any, path string) any { return paymentutil.ValueAtPath(value, path) } func stringAtPath(value any, path string) string { return paymentutil.StringAtPath(value, path) } func configuredValues(values map[string]any, key string) []string { return paymentutil.ConfiguredValues(values, key) } func containsFold(values []string, value string) bool { return paymentutil.ContainsFold(values, value) } // parseConfiguredAmount reads a provider amount field using an explicit unit // conversion. Missing fields are reported separately so callers can avoid // manufacturing a complete breakdown from incomplete provider responses. func parseConfiguredAmount(object any, config map[string]any, fieldKey, scaleKey string, fallbackScale int64) (int64, bool, error) { path := strings.TrimSpace(text(config, fieldKey)) if path == "" { return 0, false, nil } raw := valueAtPath(object, path) if raw == nil || strings.TrimSpace(fmt.Sprint(raw)) == "" || strings.EqualFold(strings.TrimSpace(fmt.Sprint(raw)), "") { return 0, false, nil } value := strings.TrimSpace(fmt.Sprint(raw)) scale := configuredInt64(config, scaleKey, fallbackScale) if scale <= 0 { return 0, false, errors.New("支付金额换算比例无效") } amount, err := parseIntegerAmount(value) if scale != 1 { amount, err = parseDecimalAmount(value, scale) } if err != nil { return 0, true, err } if amount < 0 { return 0, true, errors.New("支付金额不能为负数") } return amount, true, nil } func amountFromDecimalField(values map[string]any, key string, fallback int64) (int64, bool, error) { value := strings.TrimSpace(fmt.Sprint(values[key])) if value == "" || value == "" { return 0, false, nil } amount, err := parseDecimalAmount(value, fallback) return amount, true, err }