refactor(order): 优化订单金额校验与接口调用参数传递

- 调整SendCard方法参数,增加orderInfo传递订单金额信息
- 完善苹果卡、BatchSix供应商发送卡片时的金额使用,改用订单实际金额替换面额字段
- 增加CompleteRedeemExValue中订单金额一致性校验,防止面额数据不符错误
- 支付通知处理中校验实际支付金额与订单金额,金额不符时调用专用处理函数
- 修正扫描控制器调用CompleteRedeemExValue时传入的订单金额参数以确保准确校验
This commit is contained in:
danial
2025-12-16 13:14:24 +08:00
parent fe8b732da4
commit 69dcfd06a8
4 changed files with 197 additions and 187 deletions

View File

@@ -179,9 +179,7 @@ func (c *ScanController) Scan() {
return
}
p.Params["exValue"], err = service.CompleteRedeemExValue(
convertor.ToString(p.Params["exValue"]), strconv.FormatFloat(pm.ShowLabel, 'f', 0, 64),
)
p.Params["exValue"], err = service.CompleteRedeemExValue(ctx, orderPrice, 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())

View File

@@ -1,169 +1,172 @@
package service
import (
"context"
"encoding/json"
"errors"
"gateway/internal/config"
"gateway/internal/otelTrace"
"gateway/internal/service/supplier"
"gateway/internal/models/merchant"
"gateway/internal/schema/response"
"strconv"
"github.com/duke-git/lancet/v2/convertor"
"go.uber.org/zap"
)
// GetMerchantInfoByPayKey 通过payKey获取商户信息
func GetMerchantInfoByPayKey(ctx context.Context, payKey string) (merchantInfo merchant.MerchantInfo, err error) {
merchantInfo = merchant.GetMerchantByPasskey(ctx, payKey)
if merchantInfo.MerchantUid == "" || len(merchantInfo.MerchantUid) == 0 {
err = errors.New("商户不存在,请联系商户")
return
} else if merchantInfo.Status != config.ACTIVE {
err = errors.New("商户状态已经被冻结或者被删除,请联系商户!")
return
}
return
}
// GetMerchantInfo 获取商户信息
func GetMerchantInfo(ctx context.Context, params map[string]any) *response.PayBaseResp {
c := new(response.PayBaseResp)
c.Params = params
merchantInfo := merchant.GetMerchantByPasskey(ctx, convertor.ToString(params["payKey"]))
if merchantInfo.MerchantUid == "" || len(merchantInfo.MerchantUid) == 0 {
c.Code = -1
c.Msg = "商户不存在或者paykey有误请联系管理员"
} else if merchantInfo.Status != config.ACTIVE {
c.Code = -1
c.Msg = "商户状态已经被冻结或者被删除,请联系管理员!"
} else {
c.MerchantInfo = merchantInfo
}
return c
}
func JudgeParams(ctx context.Context, c *response.PayBaseResp) *response.PayBaseResp {
c = OrderIsValid(ctx, c)
c = NotifyUrlIsValid(ctx, c)
c = OrderPeriodIsValid(ctx, c)
c = OrderPriceIsValid(ctx, c)
c = ExValueIsValid(ctx, c)
return c
}
func ExValueIsValid(ctx context.Context, c *response.PayBaseResp) *response.PayBaseResp {
if c.Params["exValue"] == "" || len(convertor.ToString(c.Params["exValue"])) == 0 {
c.Code = -1
c.Msg = "扩展参数不能为空"
}
isRedeemValid := true
exRedeemValue := supplier.RedeemCardInfo{}
if err := json.Unmarshal([]byte(convertor.ToString(c.Params["exValue"])), &exRedeemValue); err != nil {
otelTrace.Logger.WithContext(ctx).Error("提交卡密格式错误,请检查", zap.Any("exValue", c.Params["exValue"]), zap.Error(err))
isRedeemValid = false
}
if exRedeemValue.Data == "" || len(exRedeemValue.Data) == 0 {
isRedeemValid = false
}
if !isRedeemValid {
c.Code = -1
c.Msg = "提交卡密格式错误,请检查"
}
return c
}
func CompleteRedeemExValue(exValueStr string, faceValue string) (string, error) {
exValue := supplier.RedeemCardInfo{}
if err := json.Unmarshal([]byte(exValueStr), &exValue); err != nil {
return "", err
}
exValue.FaceType = faceValue
res, err := json.Marshal(&exValue)
return string(res), err
}
func GetCompleteRedeemExValue(exValueStr string, faceValue string) (exValue supplier.RedeemCardInfo, err error) {
exValue = supplier.RedeemCardInfo{}
if err = json.Unmarshal([]byte(exValueStr), &exValue); err != nil {
return exValue, err
}
exValue.FaceType = faceValue
return
}
func CompleteRechargeExValue(exValueStr string, faceValue string) (string, error) {
exValue := supplier.RechargeCardInfo{}
if err := json.Unmarshal([]byte(exValueStr), &exValue); err != nil {
return "", err
}
exValue.FaceType = faceValue
res, err := json.Marshal(&exValue)
return string(res), err
}
// NotifyUrlIsValid 判断回调地址是否符合规则
func NotifyUrlIsValid(ctx context.Context, c *response.PayBaseResp) *response.PayBaseResp {
if c.Params["notifyUrl"] == "" || len(convertor.ToString(c.Params["notifyUrl"])) == 0 {
c.Code = -1
c.Msg = "订单回调不能为空"
}
return c
}
// OsTypeIsValid 判断设备类型是否符合规则
func OsTypeIsValid(c *response.PayBaseResp) *response.PayBaseResp {
if c.Params["osType"] == "" || len(convertor.ToString(c.Params["osType"])) == 0 {
c.Code = -1
c.Msg = "支付设备系统类型不能为空,默认填写\"1\"即可"
}
return c
}
func OrderPeriodIsValid(ctx context.Context, c *response.PayBaseResp) *response.PayBaseResp {
if c.Params["orderPeriod"] == "" || len(convertor.ToString(c.Params["orderPeriod"])) == 0 {
c.Code = -1
c.Msg = "订单过期时间不能为空,默认填写\"1\"即可"
}
return c
}
// OrderPriceIsValid 判断订单金额
func OrderPriceIsValid(ctx context.Context, c *response.PayBaseResp) *response.PayBaseResp {
if c.Params["orderPrice"] == "" || len(convertor.ToString(c.Params["orderPrice"])) == 0 {
c.Code = -1
c.Msg = "订单金额不能为空"
return c
}
a, err := strconv.ParseFloat(convertor.ToString(c.Params["orderPrice"]), 64)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("order price is invalid ", zap.Any("orderPrice", c.Params["orderPrice"]), zap.Error(err))
c.Code = -1
c.Msg = "订单金额非法"
}
c.OrderAmount = a
return c
}
// OrderIsValid 判断金额订单号是否为空或者有重复
func OrderIsValid(ctx context.Context, c *response.PayBaseResp) *response.PayBaseResp {
if c.Params["orderNo"] == "" || len(convertor.ToString(c.Params["orderNo"])) == 0 {
c.Code = -1
c.Msg = "商户订单号不能为空"
return c
}
return c
}
// IpIsWhite 判断ip是否在白名单中
func IpIsWhite() bool {
// TODO
return true
}
package service
import (
"context"
"encoding/json"
"errors"
"gateway/internal/config"
"gateway/internal/otelTrace"
"gateway/internal/service/supplier"
"gateway/internal/models/merchant"
"gateway/internal/schema/response"
"strconv"
"github.com/duke-git/lancet/v2/convertor"
"go.uber.org/zap"
)
// GetMerchantInfoByPayKey 通过payKey获取商户信息
func GetMerchantInfoByPayKey(ctx context.Context, payKey string) (merchantInfo merchant.MerchantInfo, err error) {
merchantInfo = merchant.GetMerchantByPasskey(ctx, payKey)
if merchantInfo.MerchantUid == "" || len(merchantInfo.MerchantUid) == 0 {
err = errors.New("商户不存在,请联系商户")
return
} else if merchantInfo.Status != config.ACTIVE {
err = errors.New("商户状态已经被冻结或者被删除,请联系商户!")
return
}
return
}
// GetMerchantInfo 获取商户信息
func GetMerchantInfo(ctx context.Context, params map[string]any) *response.PayBaseResp {
c := new(response.PayBaseResp)
c.Params = params
merchantInfo := merchant.GetMerchantByPasskey(ctx, convertor.ToString(params["payKey"]))
if merchantInfo.MerchantUid == "" || len(merchantInfo.MerchantUid) == 0 {
c.Code = -1
c.Msg = "商户不存在或者paykey有误请联系管理员"
} else if merchantInfo.Status != config.ACTIVE {
c.Code = -1
c.Msg = "商户状态已经被冻结或者被删除,请联系管理员!"
} else {
c.MerchantInfo = merchantInfo
}
return c
}
func JudgeParams(ctx context.Context, c *response.PayBaseResp) *response.PayBaseResp {
c = OrderIsValid(ctx, c)
c = NotifyUrlIsValid(ctx, c)
c = OrderPeriodIsValid(ctx, c)
c = OrderPriceIsValid(ctx, c)
c = ExValueIsValid(ctx, c)
return c
}
func ExValueIsValid(ctx context.Context, c *response.PayBaseResp) *response.PayBaseResp {
if c.Params["exValue"] == "" || len(convertor.ToString(c.Params["exValue"])) == 0 {
c.Code = -1
c.Msg = "扩展参数不能为空"
}
isRedeemValid := true
exRedeemValue := supplier.RedeemCardInfo{}
if err := json.Unmarshal([]byte(convertor.ToString(c.Params["exValue"])), &exRedeemValue); err != nil {
otelTrace.Logger.WithContext(ctx).Error("提交卡密格式错误,请检查", zap.Any("exValue", c.Params["exValue"]), zap.Error(err))
isRedeemValid = false
}
if exRedeemValue.Data == "" || len(exRedeemValue.Data) == 0 {
isRedeemValid = false
}
if !isRedeemValid {
c.Code = -1
c.Msg = "提交卡密格式错误,请检查"
}
return c
}
func CompleteRedeemExValue(ctx context.Context, orderAmount float64, exValueStr string, faceValue string) (string, error) {
exValue := supplier.RedeemCardInfo{}
if err := json.Unmarshal([]byte(exValueStr), &exValue); err != nil {
return "", err
}
exValue.FaceType = faceValue
res, err := json.Marshal(&exValue)
if orderAmount != exValue.GetFaceTypeFloat(ctx) {
return "", errors.New("订单金额与提交的金额不一致")
}
return string(res), err
}
func GetCompleteRedeemExValue(exValueStr string, faceValue string) (exValue supplier.RedeemCardInfo, err error) {
exValue = supplier.RedeemCardInfo{}
if err = json.Unmarshal([]byte(exValueStr), &exValue); err != nil {
return exValue, err
}
exValue.FaceType = faceValue
return
}
func CompleteRechargeExValue(exValueStr string, faceValue string) (string, error) {
exValue := supplier.RechargeCardInfo{}
if err := json.Unmarshal([]byte(exValueStr), &exValue); err != nil {
return "", err
}
exValue.FaceType = faceValue
res, err := json.Marshal(&exValue)
return string(res), err
}
// NotifyUrlIsValid 判断回调地址是否符合规则
func NotifyUrlIsValid(ctx context.Context, c *response.PayBaseResp) *response.PayBaseResp {
if c.Params["notifyUrl"] == "" || len(convertor.ToString(c.Params["notifyUrl"])) == 0 {
c.Code = -1
c.Msg = "订单回调不能为空"
}
return c
}
// OsTypeIsValid 判断设备类型是否符合规则
func OsTypeIsValid(c *response.PayBaseResp) *response.PayBaseResp {
if c.Params["osType"] == "" || len(convertor.ToString(c.Params["osType"])) == 0 {
c.Code = -1
c.Msg = "支付设备系统类型不能为空,默认填写\"1\"即可"
}
return c
}
func OrderPeriodIsValid(ctx context.Context, c *response.PayBaseResp) *response.PayBaseResp {
if c.Params["orderPeriod"] == "" || len(convertor.ToString(c.Params["orderPeriod"])) == 0 {
c.Code = -1
c.Msg = "订单过期时间不能为空,默认填写\"1\"即可"
}
return c
}
// OrderPriceIsValid 判断订单金额
func OrderPriceIsValid(ctx context.Context, c *response.PayBaseResp) *response.PayBaseResp {
if c.Params["orderPrice"] == "" || len(convertor.ToString(c.Params["orderPrice"])) == 0 {
c.Code = -1
c.Msg = "订单金额不能为空"
return c
}
a, err := strconv.ParseFloat(convertor.ToString(c.Params["orderPrice"]), 64)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("order price is invalid ", zap.Any("orderPrice", c.Params["orderPrice"]), zap.Error(err))
c.Code = -1
c.Msg = "订单金额非法"
}
c.OrderAmount = a
return c
}
// OrderIsValid 判断金额订单号是否为空或者有重复
func OrderIsValid(ctx context.Context, c *response.PayBaseResp) *response.PayBaseResp {
if c.Params["orderNo"] == "" || len(convertor.ToString(c.Params["orderNo"])) == 0 {
c.Code = -1
c.Msg = "商户订单号不能为空"
return c
}
return c
}
// IpIsWhite 判断ip是否在白名单中
func IpIsWhite() bool {
// TODO
return true
}

View File

@@ -6,6 +6,7 @@ import (
"crypto/cipher"
"encoding/base64"
"encoding/json"
"fmt"
"gateway/internal/config"
"gateway/internal/models/merchant"
"gateway/internal/models/order"
@@ -13,7 +14,6 @@ import (
"gateway/internal/models/road"
"gateway/internal/models/supply_model"
"gateway/internal/otelTrace"
"gateway/internal/service"
"gateway/internal/service/supplier"
"gateway/internal/utils"
@@ -77,14 +77,14 @@ func (c *AppleCardImpl) HasDependencyHTML() bool {
return false
}
func (c *AppleCardImpl) SendCard(ctx context.Context, jsonStr string, cardInfo supplier.RedeemCardInfo, attach string, merchantId string) (bool, string) {
func (c *AppleCardImpl) SendCard(ctx context.Context, jsonStr string, orderInfo order.OrderInfo, cardInfo supplier.RedeemCardInfo, attach string, merchantId string) (bool, string) {
cfg := config.Config{}
params := make(map[string]string)
params["callbackUrl"], _ = cfg.GetAppleNotifyUrl() // 回调地址
params["attach"] = attach // 附带参数
params["faceValue"] = cardInfo.FaceType // 面额
params["cardNo"] = cardInfo.CardNo // 卡号
params["callbackUrl"], _ = cfg.GetAppleNotifyUrl() // 回调地址
params["attach"] = attach // 附带参数
params["faceValue"] = fmt.Sprintf("%.2f", orderInfo.OrderAmount) // 面额
params["cardNo"] = cardInfo.CardNo // 卡号
params["cardPass"] = cardInfo.Data
params["merchantId"] = merchantId
@@ -156,7 +156,7 @@ func (c *AppleCardImpl) Scan(ctx context.Context, orderInfo order.OrderInfo, roa
otelTrace.Logger.WithContext(ctx).Error("格式化数据失败", zap.String("ExValue", orderInfo.ExValue))
return supplier.ScanData{Status: "-1", Msg: "订单有误,请稍后再试"}
}
ok, str := c.SendCard(ctx, roadInfo.Params, cdata, orderInfo.BankOrderId, orderInfo.MerchantOrderId)
ok, str := c.SendCard(ctx, roadInfo.Params, orderInfo, cdata, orderInfo.BankOrderId, orderInfo.MerchantOrderId)
var scanData supplier.ScanData
if !ok {
scanData = supplier.ScanData{
@@ -215,13 +215,22 @@ func (c *AppleCardImpl) PayNotify() {
}
orderInfo.BankTransId = params["merchantId"]
if params["status"] == "1" {
// TODO 订单支付成功
isOk := service.SolvePaySuccess(ctx, orderInfo.BankOrderId, orderInfo.FactAmount, params["merchantId"], "支付成功")
if isOk {
c.Ctx.WriteString("SUCCESS")
} else {
amount, err := strconv.ParseFloat(c.GetString("amount"), 64)
if err != nil {
c.Ctx.WriteString("FAIL")
}
if orderInfo.FactAmount != amount {
SolvePaySuccessByAmountDifferent(ctx, orderInfo.BankOrderId, amount, orderInfo.BankOrderId, fmt.Sprintf("金额不一致,实际金额:%.2f,拉单金额:%.2f", amount, orderInfo.OrderAmount))
return
} else {
isOk := service.SolvePaySuccess(ctx, orderInfo.BankOrderId, orderInfo.FactAmount, params["merchantId"], "支付成功")
if isOk {
c.Ctx.WriteString("SUCCESS")
} else {
c.Ctx.WriteString("FAIL")
}
}
return
}
isOk := service.SolvePayFail(ctx, orderInfo.BankOrderId, "", params["remark"])

View File

@@ -37,7 +37,7 @@ func (c *BatchSixImpl) HasDependencyHTML() bool {
return false
}
func (c *BatchSixImpl) SendCard(ctx context.Context, jsonStr string, cardInfo supplier.RedeemCardInfo, attach string) (bool, string) {
func (c *BatchSixImpl) SendCard(ctx context.Context, orderInfo order.OrderInfo, jsonStr string, cardInfo supplier.RedeemCardInfo, attach string) (bool, string) {
ctx, span := otelTrace.Span(ctx, "BatchSixImpl", "sendCard", trace.WithAttributes(
attribute.String("jsonStr", jsonStr),
attribute.String("attach", attach),
@@ -68,7 +68,7 @@ func (c *BatchSixImpl) SendCard(ctx context.Context, jsonStr string, cardInfo su
Url: submitUrl,
ChannelID: queue.ChannelBatchSix,
ChannelCode: idInt,
FaceValue: cardInfo.GetFaceTypeFloat(ctx),
FaceValue: orderInfo.OrderAmount,
CardNo: cardInfo.CardNo,
CardPwd: cardInfo.Data,
Mobile: gojson.Json(jsonStr).Get("username").Tostring(),
@@ -105,7 +105,7 @@ func (c *BatchSixImpl) Scan(ctx context.Context, orderInfo order.OrderInfo, road
otelTrace.Logger.WithContext(ctx).Error("格式化数据失败", zap.String("ExValue", orderInfo.ExValue))
return supplier.ScanData{Status: "-1", Msg: "订单有误,请稍后再试"}
}
ok, str := c.SendCard(ctx, roadInfo.Params, cdata, orderInfo.BankOrderId)
ok, str := c.SendCard(ctx, orderInfo, roadInfo.Params, cdata, orderInfo.BankOrderId)
var scanData supplier.ScanData
if !ok {
scanData = supplier.ScanData{