Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
F-1: 佣金链断裂时创建 status=99 零额待审记录,新增修正接口 POST /commission-records/:id/resolve F-4: C端订单查询从 Handler 迁移至 Service 层,移除 SkipPermissionCtx J-1: 富友支付 JSAPI/MiniApp 预下单实现,回调补全签名验证 J-2: 平台钱包代购支持(buyerType 为空时使用资产所属代理钱包) J-3: 套餐激活后自动更新卡/设备 status=3,最后套餐过期后更新 status=4 支付抽象: 引入 PaymentProvider 接口 + 微信/富友适配器,CreateOrder 支持多支付渠道 修复: 设备/IoT卡响应 DTO 移除 omitempty,空值字段返回 null 而非省略
209 lines
7.0 KiB
Go
209 lines
7.0 KiB
Go
package callback
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/valyala/fasthttp/fasthttpadaptor"
|
|
"go.uber.org/zap"
|
|
|
|
"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
|
|
}
|
|
|
|
// WechatConfigServiceInterface 支付配置服务接口
|
|
type WechatConfigServiceInterface interface {
|
|
GetActiveConfig(ctx context.Context) (*model.WechatConfig, error)
|
|
}
|
|
|
|
type PaymentHandler struct {
|
|
orderService *orderService.Service
|
|
rechargeService *rechargeService.Service
|
|
agentRechargeService AgentRechargeServiceInterface
|
|
wechatPayment wechat.PaymentServiceInterface
|
|
wechatConfigService WechatConfigServiceInterface
|
|
logger *zap.Logger
|
|
}
|
|
|
|
func NewPaymentHandler(
|
|
orderService *orderService.Service,
|
|
rechargeService *rechargeService.Service,
|
|
agentRechargeService AgentRechargeServiceInterface,
|
|
wechatPayment wechat.PaymentServiceInterface,
|
|
wechatConfigService WechatConfigServiceInterface,
|
|
logger *zap.Logger,
|
|
) *PaymentHandler {
|
|
return &PaymentHandler{
|
|
orderService: orderService,
|
|
rechargeService: rechargeService,
|
|
agentRechargeService: agentRechargeService,
|
|
wechatPayment: wechatPayment,
|
|
wechatConfigService: wechatConfigService,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// 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()
|
|
c.Set("Content-Type", "text/xml; charset=gbk")
|
|
|
|
activeConfig, err := h.wechatConfigService.GetActiveConfig(ctx)
|
|
if err != nil || activeConfig == nil {
|
|
h.logger.Error("富友回调:加载支付配置失败", zap.Error(err))
|
|
return c.Send(fuiou.BuildNotifyFailResponse("payment config unavailable"))
|
|
}
|
|
|
|
client, err := fuiou.NewClient(
|
|
activeConfig.FyInsCd,
|
|
activeConfig.FyMchntCd,
|
|
activeConfig.FyTermID,
|
|
activeConfig.FyAPIURL,
|
|
activeConfig.FyNotifyURL,
|
|
activeConfig.FyPrivateKey,
|
|
activeConfig.FyPublicKey,
|
|
h.logger,
|
|
)
|
|
if err != nil {
|
|
h.logger.Error("富友回调:构造客户端失败", zap.Error(err))
|
|
return c.Send(fuiou.BuildNotifyFailResponse("client init failed"))
|
|
}
|
|
|
|
notify, err := client.VerifyNotify(body)
|
|
if err != nil {
|
|
if notify != nil {
|
|
h.logger.Warn("富友回调:非成功结果",
|
|
zap.String("result_code", notify.ResultCode),
|
|
zap.String("result_msg", notify.ResultMsg))
|
|
return c.Send(fuiou.BuildNotifySuccessResponse())
|
|
}
|
|
h.logger.Error("富友回调:验签或解析失败", zap.Error(err))
|
|
return c.Send(fuiou.BuildNotifyFailResponse(err.Error()))
|
|
}
|
|
|
|
orderNo := notify.MchntOrderNo
|
|
switch {
|
|
case strings.HasPrefix(orderNo, "ORD"):
|
|
if err := h.orderService.HandlePaymentCallback(ctx, orderNo, "fuiou"); err != nil {
|
|
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 {
|
|
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 {
|
|
return c.Send(fuiou.BuildNotifyFailResponse(err.Error()))
|
|
}
|
|
}
|
|
default:
|
|
return c.Send(fuiou.BuildNotifyFailResponse("unknown order prefix"))
|
|
}
|
|
|
|
return c.Send(fuiou.BuildNotifySuccessResponse())
|
|
}
|