package httpapi import ( "context" "encoding/json" "errors" "io" "mime" "net/http" "strconv" "strings" "unicode/utf8" "git.nianxx.cn/wangxuming/NianAIGC/backend/internal/payments" ) type PaymentRefundService interface { AdminList(context.Context, string, int, int) (payments.AdminOrdersPage, error) CreateRefund(context.Context, string, string, string, string, int64) (payments.Refund, error) GetRefund(context.Context, string) (payments.Refund, error) ListRefunds(context.Context, string) ([]payments.Refund, error) RefundNotify(context.Context, *http.Request) error } func (h *paymentHandler) adminPayments(w http.ResponseWriter, r *http.Request) { parts := strings.Split(strings.TrimPrefix(r.URL.Path, "/api/payments/admin/"), "/") list := len(parts) == 1 && parts[0] == "orders" order := len(parts) == 3 && parts[0] == "orders" && parts[2] == "refunds" && len(parts[1]) <= 32 && paymentIdentifier.MatchString(parts[1]) refund := len(parts) == 2 && parts[0] == "refunds" && len(parts[1]) <= 64 && paymentIdentifier.MatchString(parts[1]) if !list && !order && !refund { http.NotFound(w, r) return } if r.Method != http.MethodGet && !(order && r.Method == http.MethodPost) { w.WriteHeader(http.StatusMethodNotAllowed) return } session, err := h.authorizer.Authorize(r, PlatformSuperAdmin) if err != nil { writePaymentError(w, err) return } service, ok := h.service.(PaymentRefundService) if !ok { writeRefundError(w, payments.ErrUnavailable) return } if list { limit, offset := 50, 0 if raw := r.URL.Query().Get("limit"); raw != "" { limit, err = strconv.Atoi(raw) if err != nil || limit < 1 || limit > 100 { writeAPIError(w, 400, "分页参数无效。") return } } if raw := r.URL.Query().Get("offset"); raw != "" { offset, err = strconv.Atoi(raw) if err != nil || offset < 0 || offset > 10000000 { writeAPIError(w, 400, "分页参数无效。") return } } org := r.URL.Query().Get("organizationId") if len(org) > 128 { writeAPIError(w, 400, "组织参数无效。") return } page, err := service.AdminList(r.Context(), org, limit, offset) if err != nil { writeRefundError(w, err) return } if page.Orders == nil { page.Orders = []payments.AdminOrder{} } for i := range page.Orders { page.Orders[i].CodeURL = "" } writeJSON(w, 200, page) return } if refund { value, err := service.GetRefund(r.Context(), parts[1]) if err != nil { writeRefundError(w, err) return } writeJSON(w, 200, map[string]any{"refund": value}) return } if r.Method == http.MethodGet { values, err := service.ListRefunds(r.Context(), parts[1]) if err != nil { writeRefundError(w, err) return } if values == nil { values = []payments.Refund{} } writeJSON(w, 200, map[string]any{"refunds": values}) return } if !h.sameOrigin(r) { writeAPIError(w, 403, "请从本站发起退款。") return } mediaType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) if err != nil || mediaType != "application/json" { writeAPIError(w, 415, "请使用 JSON 请求。") return } var body struct { AmountFen int64 `json:"amountFen"` Reason string `json:"reason"` RequestKey string `json:"requestKey"` } r.Body = http.MaxBytesReader(w, r.Body, 4096) decoder := json.NewDecoder(r.Body) decoder.DisallowUnknownFields() if err := decoder.Decode(&body); err != nil { writeAPIError(w, 400, "退款参数无效,金额必须是整数分。") return } body.Reason = strings.TrimSpace(body.Reason) if err := decoder.Decode(&struct{}{}); err != io.EOF || body.AmountFen <= 0 || !paymentIdentifier.MatchString(body.RequestKey) || body.Reason == "" || len(body.Reason) > 80 || !utf8.ValidString(body.Reason) { writeAPIError(w, 400, "退款金额、请求标识或原因无效(原因最多 80 字节)。") return } value, err := service.CreateRefund(r.Context(), parts[1], session.User.ID, body.RequestKey, body.Reason, body.AmountFen) if err != nil { writeRefundError(w, err) return } writeJSON(w, 200, map[string]any{"refund": value}) } func (h *paymentHandler) refundNotify(w http.ResponseWriter, r *http.Request) { if !allow(w, r, http.MethodPost) { return } service, ok := h.service.(PaymentRefundService) if !ok { writeJSON(w, 503, map[string]string{"code": "FAIL", "message": "退款服务未启用"}) return } r.Body = http.MaxBytesReader(w, r.Body, 64<<10) if err := service.RefundNotify(r.Context(), r); err != nil { status := http.StatusInternalServerError if errors.Is(err, payments.ErrVerification) { status = http.StatusBadRequest } writeJSON(w, status, map[string]string{"code": "FAIL", "message": "退款通知处理失败"}) return } w.WriteHeader(http.StatusNoContent) } func writeRefundError(w http.ResponseWriter, err error) { status, message := 500, "退款服务暂时不可用,请稍后刷新记录。" switch { case errors.Is(err, payments.ErrNotFound): status, message = 404, "充值或退款记录不存在。" case errors.Is(err, payments.ErrConflict): status, message = 409, "订单状态、可退金额或退款请求已变化,请刷新记录后重试。" case errors.Is(err, payments.ErrInvalidAmount): status, message = 400, "退款金额超过可退金额或参数无效。" case errors.Is(err, payments.ErrUnavailable): status, message = 503, "微信退款尚未启用。" } writeAPIError(w, status, message) }