package callback import ( "context" "encoding/xml" "fmt" "net/http" "net/url" "strings" "github.com/gofiber/fiber/v2" "github.com/valyala/fasthttp/fasthttpadaptor" "github.com/break/junhong_cmp_fiber/internal/model" orderService "github.com/break/junhong_cmp_fiber/internal/service/order" rechargeService "github.com/break/junhong_cmp_fiber/internal/service/recharge" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/break/junhong_cmp_fiber/pkg/errors" "github.com/break/junhong_cmp_fiber/pkg/fuiou" "github.com/break/junhong_cmp_fiber/pkg/response" "github.com/break/junhong_cmp_fiber/pkg/wechat" ) // AgentRechargeServiceInterface 代理充值服务接口 type AgentRechargeServiceInterface interface { HandlePaymentCallback(ctx context.Context, rechargeNo string, paymentMethod string, paymentTransactionID string) error } type PaymentHandler struct { orderService *orderService.Service rechargeService *rechargeService.Service agentRechargeService AgentRechargeServiceInterface wechatPayment wechat.PaymentServiceInterface } func NewPaymentHandler( orderService *orderService.Service, rechargeService *rechargeService.Service, agentRechargeService AgentRechargeServiceInterface, wechatPayment wechat.PaymentServiceInterface, ) *PaymentHandler { return &PaymentHandler{ orderService: orderService, rechargeService: rechargeService, agentRechargeService: agentRechargeService, wechatPayment: wechatPayment, } } // WechatPayCallback 微信支付回调(带签名验证) // POST /api/callback/wechat-pay func (h *PaymentHandler) WechatPayCallback(c *fiber.Ctx) error { if h.wechatPayment == nil { return errors.New(errors.CodeWechatCallbackInvalid, "微信支付服务未配置") } var httpReq http.Request fasthttpadaptor.ConvertRequest(c.Context(), &httpReq, true) ctx := context.Background() _, err := h.wechatPayment.HandlePaymentNotify(&httpReq, func(result *wechat.PaymentNotifyResult) error { if result.TradeState != "SUCCESS" { return nil } // TODO: 按 payment_config_id 加载配置验签(当前留桩,仍用 wechatPayment 单例验签) // 按订单号前缀分发 outTradeNo := result.OutTradeNo switch { case strings.HasPrefix(outTradeNo, "ORD"): return h.orderService.HandlePaymentCallback(ctx, outTradeNo, model.PaymentMethodWechat) case strings.HasPrefix(outTradeNo, constants.AssetRechargeOrderPrefix): return h.rechargeService.HandlePaymentCallback(ctx, outTradeNo, model.PaymentMethodWechat, result.TransactionID) case strings.HasPrefix(outTradeNo, constants.AgentRechargeOrderPrefix): if h.agentRechargeService != nil { return h.agentRechargeService.HandlePaymentCallback(ctx, outTradeNo, model.PaymentMethodWechat, result.TransactionID) } return fmt.Errorf("代理充值服务未配置,无法处理订单: %s", outTradeNo) default: return fmt.Errorf("未知订单号前缀: %s", outTradeNo) } }) if err != nil { return errors.Wrap(errors.CodeWechatCallbackInvalid, err, "处理微信支付回调失败") } return response.Success(c, map[string]string{"return_code": "SUCCESS"}) } type AlipayCallbackRequest struct { OrderNo string `json:"out_trade_no" form:"out_trade_no"` } // AlipayCallback 支付宝回调 // POST /api/callback/alipay func (h *PaymentHandler) AlipayCallback(c *fiber.Ctx) error { var req AlipayCallbackRequest if err := c.BodyParser(&req); err != nil { return errors.New(errors.CodeInvalidParam, "请求参数解析失败") } if req.OrderNo == "" { return errors.New(errors.CodeInvalidParam, "订单号不能为空") } ctx := c.UserContext() // 按订单号前缀分发 switch { case strings.HasPrefix(req.OrderNo, "ORD"): if err := h.orderService.HandlePaymentCallback(ctx, req.OrderNo, model.PaymentMethodAlipay); err != nil { return err } case strings.HasPrefix(req.OrderNo, constants.AssetRechargeOrderPrefix): if err := h.rechargeService.HandlePaymentCallback(ctx, req.OrderNo, model.PaymentMethodAlipay, ""); err != nil { return err } case strings.HasPrefix(req.OrderNo, constants.AgentRechargeOrderPrefix): if h.agentRechargeService != nil { if err := h.agentRechargeService.HandlePaymentCallback(ctx, req.OrderNo, model.PaymentMethodAlipay, ""); err != nil { return err } } default: return errors.New(errors.CodeInvalidParam, "未知订单号前缀") } return c.SendString("success") } // FuiouPayCallback 富友支付回调 // POST /api/callback/fuiou-pay func (h *PaymentHandler) FuiouPayCallback(c *fiber.Ctx) error { body := c.Body() if len(body) == 0 { return errors.New(errors.CodeFuiouCallbackInvalid, "回调请求体为空") } ctx := c.UserContext() // TODO: 按 payment_config_id 加载配置创建 fuiou.Client 验签 // 当前留桩:解析但不验签 // 解析 req= 参数 formValue := string(body) if strings.HasPrefix(formValue, "req=") { formValue = formValue[4:] } decoded, err := url.QueryUnescape(formValue) if err != nil { return errors.New(errors.CodeFuiouCallbackInvalid, "回调数据解码失败") } utf8Data, err := fuiou.GBKToUTF8([]byte(decoded)) if err != nil { return errors.New(errors.CodeFuiouCallbackInvalid, "GBK 转 UTF-8 失败") } xmlStr := strings.Replace(string(utf8Data), `encoding="GBK"`, `encoding="UTF-8"`, 1) var notify fuiou.NotifyRequest if err := xml.Unmarshal([]byte(xmlStr), ¬ify); err != nil { return errors.New(errors.CodeFuiouCallbackInvalid, "解析回调 XML 失败") } if notify.ResultCode != "000000" { c.Set("Content-Type", "text/xml; charset=gbk") return c.Send(fuiou.BuildNotifySuccessResponse()) } // 按订单号前缀分发 orderNo := notify.MchntOrderNo switch { case strings.HasPrefix(orderNo, "ORD"): if err := h.orderService.HandlePaymentCallback(ctx, orderNo, "fuiou"); err != nil { c.Set("Content-Type", "text/xml; charset=gbk") return c.Send(fuiou.BuildNotifyFailResponse(err.Error())) } case strings.HasPrefix(orderNo, constants.AssetRechargeOrderPrefix): if err := h.rechargeService.HandlePaymentCallback(ctx, orderNo, "fuiou", notify.TransactionId); err != nil { c.Set("Content-Type", "text/xml; charset=gbk") return c.Send(fuiou.BuildNotifyFailResponse(err.Error())) } case strings.HasPrefix(orderNo, constants.AgentRechargeOrderPrefix): if h.agentRechargeService != nil { if err := h.agentRechargeService.HandlePaymentCallback(ctx, orderNo, "fuiou", notify.TransactionId); err != nil { c.Set("Content-Type", "text/xml; charset=gbk") return c.Send(fuiou.BuildNotifyFailResponse(err.Error())) } } default: c.Set("Content-Type", "text/xml; charset=gbk") return c.Send(fuiou.BuildNotifyFailResponse("unknown order prefix")) } c.Set("Content-Type", "text/xml; charset=gbk") return c.Send(fuiou.BuildNotifySuccessResponse()) }