feat: 业务逻辑补全 — 佣金待审记录、C端订单重构、支付抽象、富友支付、卡设备状态联动
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
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 而非省略
This commit is contained in:
@@ -2,14 +2,13 @@ package callback
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"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"
|
||||
@@ -26,11 +25,18 @@ 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(
|
||||
@@ -38,12 +44,16 @@ func NewPaymentHandler(
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +140,7 @@ func (h *PaymentHandler) AlipayCallback(c *fiber.Ctx) error {
|
||||
return c.SendString("success")
|
||||
}
|
||||
|
||||
// FuiouPayCallback 富友支付回调
|
||||
// FuiouPayCallback 富友支付回调(带签名验证)
|
||||
// POST /api/callback/fuiou-pay
|
||||
func (h *PaymentHandler) FuiouPayCallback(c *fiber.Ctx) error {
|
||||
body := c.Body()
|
||||
@@ -139,63 +149,60 @@ func (h *PaymentHandler) FuiouPayCallback(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
ctx := c.UserContext()
|
||||
c.Set("Content-Type", "text/xml; charset=gbk")
|
||||
|
||||
// TODO: 按 payment_config_id 加载配置创建 fuiou.Client 验签
|
||||
// 当前留桩:解析但不验签
|
||||
|
||||
// 解析 req= 参数
|
||||
formValue := string(body)
|
||||
if strings.HasPrefix(formValue, "req=") {
|
||||
formValue = formValue[4:]
|
||||
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"))
|
||||
}
|
||||
|
||||
decoded, err := url.QueryUnescape(formValue)
|
||||
client, err := fuiou.NewClient(
|
||||
activeConfig.FyInsCd,
|
||||
activeConfig.FyMchntCd,
|
||||
activeConfig.FyTermID,
|
||||
activeConfig.FyAPIURL,
|
||||
activeConfig.FyNotifyURL,
|
||||
activeConfig.FyPrivateKey,
|
||||
activeConfig.FyPublicKey,
|
||||
h.logger,
|
||||
)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeFuiouCallbackInvalid, "回调数据解码失败")
|
||||
h.logger.Error("富友回调:构造客户端失败", zap.Error(err))
|
||||
return c.Send(fuiou.BuildNotifyFailResponse("client init failed"))
|
||||
}
|
||||
|
||||
utf8Data, err := fuiou.GBKToUTF8([]byte(decoded))
|
||||
notify, err := client.VerifyNotify(body)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeFuiouCallbackInvalid, "GBK 转 UTF-8 失败")
|
||||
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()))
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user