feat: 业务逻辑补全 — 佣金待审记录、C端订单重构、支付抽象、富友支付、卡设备状态联动
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:
2026-03-28 16:57:39 +08:00
parent 65e461eff7
commit 623a622298
26 changed files with 970 additions and 447 deletions

View File

@@ -311,28 +311,8 @@ func (s *Service) GetRealtimeStatus(ctx context.Context, assetType string, id ui
resp.DeviceProtectStatus = s.getDeviceProtectStatus(ctx, id)
// 实时查询 Gateway 设备状态不缓存per D-05
// Gateway 失败不阻断主流程DeviceRealtime 返回 nullper D-06
if s.gatewayClient != nil {
device, devErr := s.deviceStore.GetByID(ctx, id)
if devErr == nil {
// IMEI 优先,无则用 SN与 Refresh 中 updateDeviceFromSyncInfo 保持一致)
cardNo := device.IMEI
if cardNo == "" {
cardNo = device.SN
}
if cardNo != "" {
if syncResp, syncErr := s.gatewayClient.SyncDeviceInfo(ctx, &gateway.SyncDeviceInfoReq{
CardNo: cardNo,
}); syncErr == nil {
resp.DeviceRealtime = mapSyncRespToDeviceGatewayInfo(syncResp)
} else {
logger.GetAppLogger().Warn("GetRealtimeStatus: sync-info 调用失败DeviceRealtime 返回 null",
zap.Uint("device_id", id),
zap.Error(syncErr))
}
}
}
}
// Gateway 失败不阻断主流程DeviceRealtime 始终非 nil失败时仅填 GatewayMsgper D-06
resp.DeviceRealtime = s.fetchDeviceGatewayInfo(ctx, id)
default:
return nil, errors.New(errors.CodeInvalidParam, "不支持的资产类型,仅支持 card 或 device")
@@ -341,6 +321,44 @@ func (s *Service) GetRealtimeStatus(ctx context.Context, assetType string, id ui
return resp, nil
}
// fetchDeviceGatewayInfo 查询设备 Gateway 实时状态,始终返回非 nil 对象
// Gateway 成功时填充全量字段,失败时仅填充 GatewayMsg 供前端展示错误原因
func (s *Service) fetchDeviceGatewayInfo(ctx context.Context, deviceID uint) *dto.DeviceGatewayInfo {
if s.gatewayClient == nil {
msg := "Gateway 客户端未配置"
return &dto.DeviceGatewayInfo{GatewayMsg: &msg}
}
device, err := s.deviceStore.GetByID(ctx, deviceID)
if err != nil {
msg := "查询设备信息失败: " + err.Error()
return &dto.DeviceGatewayInfo{GatewayMsg: &msg}
}
// IMEI 优先,无则用 SN与 Refresh 中 updateDeviceFromSyncInfo 保持一致)
cardNo := device.IMEI
if cardNo == "" {
cardNo = device.SN
}
if cardNo == "" {
msg := "设备缺少 IMEI 和 SN无法查询 Gateway 实时状态"
return &dto.DeviceGatewayInfo{GatewayMsg: &msg}
}
syncResp, syncErr := s.gatewayClient.SyncDeviceInfo(ctx, &gateway.SyncDeviceInfoReq{
CardNo: cardNo,
})
if syncErr != nil {
msg := syncErr.Error()
logger.GetAppLogger().Warn("fetchDeviceGatewayInfo: sync-info 调用失败",
zap.Uint("device_id", deviceID),
zap.Error(syncErr))
return &dto.DeviceGatewayInfo{GatewayMsg: &msg}
}
return mapSyncRespToDeviceGatewayInfo(syncResp)
}
// mapSyncRespToDeviceGatewayInfo 将 Gateway SyncDeviceInfoResp 映射为 B 端 DeviceGatewayInfo DTO
// 使用指针字段,未赋值的字段保持 nilomitempty
func mapSyncRespToDeviceGatewayInfo(r *gateway.SyncDeviceInfoResp) *dto.DeviceGatewayInfo {