增加支付逻辑

This commit is contained in:
andy committed 2026-10-12 13:57:23 +08:00
1 parent 4ac786aafb
commit dfb2c91b8d
77 files changed
+6370 -45

No files matched your search

+172
View File
@@ -0,0 +1,172 @@
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)
}