Files
kami_gateway/internal/service/pay_solve.go
danial e88ff05a14 refactor(trace): 重命名 otel 包为 otelTrace并更新相关引用
- 将内部使用的 otel 包重命名为 otelTrace
- 更新了所有引用该包的文件中的导入路径
- 修改了部分函数和变量名称以适应新的包名
2025-02-23 21:56:29 +08:00

554 lines
21 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 service
import (
"context"
"errors"
"fmt"
"gateway/internal/config"
"gateway/internal/consts"
"gateway/internal/models/accounts"
"gateway/internal/models/merchant"
"gateway/internal/models/notify"
"gateway/internal/models/order"
"gateway/internal/models/road"
"gateway/internal/otelTrace"
"gateway/internal/service/message"
"gateway/internal/utils"
"net/url"
"strconv"
"time"
"github.com/bytedance/gopkg/util/gopool"
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/pointer"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"github.com/beego/beego/v2/client/orm"
)
var (
orderNotify = gopool.NewPool("orderNotify", 20, gopool.NewConfig())
sendMessageNotify = gopool.NewPool("sendMessageNotify", 20, gopool.NewConfig())
)
// SolvePaySuccess 处理支付成功的加款等各项操作
func SolvePaySuccess(ctx context.Context, bankOrderId string, factAmount float64, trxNo string, cardReturnData string) bool {
ctx, cancel := otelTrace.Span(ctx, "订单处理", "SolvePayFail", trace.WithAttributes(
attribute.String("bankOrderId", bankOrderId),
attribute.String("cardReturnData", cardReturnData),
))
defer cancel()
o := orm.NewOrm()
err := o.DoTxWithCtx(ctx, func(ctx context.Context, txOrm orm.TxOrmer) error {
// 查找订单
var orderInfo order.OrderInfo
if err := txOrm.RawWithCtx(ctx, "select * from order_info where bank_order_id = ? for update", bankOrderId).
QueryRow(&orderInfo); err != nil || orderInfo.BankOrderId == "" {
otelTrace.Logger.WithContext(ctx).Error("不存在该订单或者select for update出错")
return err
}
if orderInfo.Status == config.SUCCESS {
otelTrace.Logger.WithContext(ctx).Error("该订单已经处理,订单号=", zap.String("bankOrderId", bankOrderId))
return errors.New(fmt.Sprintf("该订单已经处理,订单号= %s", bankOrderId))
}
orderInfo.FactAmount = factAmount
orderInfo.Status = config.SUCCESS
orderInfo.BankTransId = trxNo
orderInfo.CardReturnData = cardReturnData
orderInfo.UpdateTime = time.Now()
if _, err := txOrm.UpdateWithCtx(ctx, &orderInfo); err != nil || orderInfo.BankOrderId == "" {
otelTrace.Logger.WithContext(ctx).Error(fmt.Sprintf("solve pay success, update order info fail: %s, bankOrderId = %s", err, bankOrderId))
return err
}
record, _ := GetRelateRecordByTargetOrderNo(ctx, bankOrderId)
if !pointer.IsNil(record) && record.Id != 0 {
// TODO: 判断是否偷卡(隐藏订单)
if err2 := UpdateRelateRecordSucceedStatus(ctx, bankOrderId, factAmount); err2 != nil {
otelTrace.Logger.WithContext(ctx).Error("【偷卡】更新偷卡记录失败", zap.Error(err2))
}
return nil
}
// 查找订单表
var orderProfitInfo order.OrderProfitInfo
if err := txOrm.RawWithCtx(ctx,
"select * from order_profit_info where bank_order_id = ? for update",
bankOrderId,
).QueryRow(&orderProfitInfo); err != nil || orderProfitInfo.BankOrderId == "" {
otelTrace.Logger.WithContext(ctx).Error("select order_profit_info for update fail: ", zap.Error(err))
return err
}
if orderProfitInfo.BankOrderId == "" {
otelTrace.Logger.WithContext(ctx).Error("solve pay success, get orderProfit fail, bankOrderId = ", zap.String("bankOrderId", bankOrderId))
return errors.New(fmt.Sprintf("solve pay success, get orderProfit fail, bankOrderId = %s", bankOrderId))
}
orderSettleInfo := order.OrderSettleInfo{
PayTypeCode: orderInfo.PayTypeCode,
PayProductCode: orderInfo.PayProductCode,
RoadUid: orderInfo.RoadUid,
PayProductName: orderInfo.PayProductName,
PayTypeName: orderInfo.PayTypeName,
MerchantUid: orderInfo.MerchantUid,
MerchantOrderId: orderInfo.MerchantOrderId,
MerchantName: orderInfo.MerchantName,
BankOrderId: bankOrderId,
SettleAmount: factAmount,
IsAllowSettle: config.YES,
IsCompleteSettle: config.NO,
CreateTime: time.Now(),
UpdateTime: time.Now(),
}
if _, err := txOrm.InsertWithCtx(ctx, &orderSettleInfo); err != nil {
otelTrace.Logger.WithContext(ctx).Error(
fmt.Sprintf("solve pay successinsert order settle info fail: %s, bankOrderId = %s",
err, bankOrderId),
)
return err
}
// 做账户的加款操作,最重要的一步
var accountInfo accounts.AccountInfo
if err := txOrm.RawWithCtx(ctx, "select * from account_info where account_uid = ? for update", orderInfo.MerchantUid).QueryRow(&accountInfo); err != nil || accountInfo.AccountUid == "" {
otelTrace.Logger.WithContext(ctx).Error(fmt.Sprintf("solve pay success, raw account info fail: %s, bankOrderId = %s", err, bankOrderId))
return err
}
if _, err := txOrm.QueryTable(accounts.ACCOUNT_INFO).Filter("account_uid", orderInfo.MerchantUid).
UpdateWithCtx(ctx, orm.Params{
"balance": factAmount + accountInfo.Balance,
"wait_amount": factAmount + accountInfo.WaitAmount,
}); err != nil {
otelTrace.Logger.WithContext(ctx).Error(fmt.Sprintf("solve pay success, update account info fail: %s, bankOrderId = %s", err, bankOrderId))
return err
}
// 添加一条动账记录
accountHistory := accounts.AccountHistoryInfo{
AccountUid: orderInfo.MerchantUid,
AccountName: orderInfo.MerchantName,
Type: config.PLUS_AMOUNT,
OrderId: orderInfo.MerchantOrderId,
Amount: factAmount,
Balance: factAmount + accountInfo.Balance,
CreateTime: time.Now(),
UpdateTime: time.Now(),
}
if _, err := txOrm.InsertWithCtx(ctx, &accountHistory); err != nil {
otelTrace.Logger.WithContext(ctx).Error(fmt.Sprintf("solve pay successinsert account history fail%s, bankOrderId = %s", err, bankOrderId))
return err
}
// 更新通道信息
roadInfo := road.GetRoadInfoByRoadUid(ctx, orderInfo.RoadUid)
roadInfo.RequestSuccess += 1
roadInfo.TodayRequestSuccess += 1 // 今日成功
roadInfo.TotalIncome += orderInfo.FactAmount
roadInfo.TodayIncome += orderInfo.FactAmount
roadInfo.TodayProfit += orderProfitInfo.PlatformProfit + orderProfitInfo.AgentProfit
roadInfo.TotalProfit += orderProfitInfo.PlatformProfit + orderProfitInfo.AgentProfit
roadInfo.UpdateTime = time.Now()
if _, err := txOrm.UpdateWithCtx(ctx, &roadInfo); err != nil {
otelTrace.Logger.WithContext(ctx).Error(fmt.Sprintf("solve pay success, update road info fail: %s, bankOrderId = %s", err, bankOrderId))
return err
}
// 更新订单利润表
orderProfitInfo.Status = config.SUCCESS
orderProfitInfo.UpdateTime = time.Now()
if _, err := txOrm.UpdateWithCtx(ctx, &orderProfitInfo); err != nil {
otelTrace.Logger.WithContext(ctx).Error(fmt.Sprintf("solve pay success, update order profit info fail: %s, bankOrderId = %s", err, bankOrderId))
return err
}
// 给下游发送回调通知
return nil
})
orderNotify.Go(func() {
// 创建一个5分钟超时的上下文
ctx2, span := otelTrace.NewSchedulerTrace().Start(otelTrace.InitCtx, "SolvePaySuccess")
defer span.End()
CreateOrderNotifyInfo(ctx2, bankOrderId, config.SUCCESS)
})
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("SolvePaySuccess失败", zap.Error(err))
return true
}
otelTrace.Logger.WithContext(ctx).Info("SolvePaySuccess处理成功")
return true
}
// SolvePayFail 处理支付失败
func SolvePayFail(ctx context.Context, bankOrderId, transId string, cardReturnData string) bool {
ctx, cancel := otelTrace.Span(ctx, "订单处理", "SolvePayFail", trace.WithAttributes(
attribute.String("bankOrderId", bankOrderId),
attribute.String("cardReturnData", cardReturnData),
))
defer cancel()
o := orm.NewOrm()
err := o.DoTxWithCtx(ctx, func(ctx context.Context, txOrm orm.TxOrmer) error {
var orderTmp order.OrderInfo
if err := txOrm.RawWithCtx(ctx, "select * from order_info where bank_order_id = ?", bankOrderId).QueryRow(&orderTmp); err != nil || orderTmp.BankOrderId == "" {
return err
}
if orderTmp.Status != config.WAIT {
return nil
// return errors.New("订单已经处理,不要重复加款")
}
if _, err := txOrm.QueryTable(order.ORDER_INFO).Filter("bank_order_id", bankOrderId).UpdateWithCtx(ctx, orm.Params{
"status": config.FAIL,
"card_return_data": cardReturnData,
"bank_trans_id": transId,
}); err != nil {
otelTrace.Logger.WithContext(ctx).Error("更改订单状态失败:", zap.Error(err))
return err
}
if _, err := txOrm.QueryTable(order.ORDER_PROFIT_INFO).Filter("bank_order_id", bankOrderId).UpdateWithCtx(ctx, orm.Params{"status": config.FAIL, "bank_trans_id": transId}); err != nil {
otelTrace.Logger.WithContext(ctx).Error("更改订单状态失败:", zap.Error(err))
return err
}
// TODO: 判断是否偷卡(隐藏订单)
// 如果是偷卡,就不计算利润什么的,也不回调
hiddenCfg, _ := GetRelateRecordByTargetOrderNo(ctx, bankOrderId)
if !pointer.IsNil(hiddenCfg) && hiddenCfg.Id != 0 {
_ = UpdateRelateRecordStatus(ctx, bankOrderId, 0, int(consts.StealRuleStatusFail))
return nil
}
return nil
})
orderNotify.Go(func() {
// 创建一个5分钟超时的上下文
ctx2, span := otelTrace.NewSchedulerTrace().Start(otelTrace.InitCtx, "SolvePayFail")
defer span.End()
CreateOrderNotifyInfo(ctx2, bankOrderId, config.FAIL)
})
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("SolvePayFail", zap.Error(err))
}
otelTrace.Logger.WithContext(ctx).Info("SolvePayFail成功")
return true
}
// SolveOrderFreeze 处理订单冻结
func SolveOrderFreeze(ctx context.Context, bankOrderId string) bool {
o := orm.NewOrm()
err := o.DoTxWithCtx(ctx, func(ctx context.Context, txOrm orm.TxOrmer) error {
var orderInfo order.OrderInfo
if err := txOrm.Raw("select * from order_info where bank_order_id = ? for update", bankOrderId).QueryRow(&orderInfo); err != nil || orderInfo.BankOrderId == "" {
otelTrace.Logger.WithContext(ctx).Error("solve order freeze 不存在这样的订单记录bankOrderId = ", zap.String("bankOrderId", bankOrderId))
return err
}
// 如果订单不是失败或者成功状态,订单不能冻结
if orderInfo.Status != config.SUCCESS && orderInfo.Status != config.FAIL {
otelTrace.Logger.WithContext(ctx).Error("非成功或失败订单不能进行冻结")
return errors.New("非成功或失败订单不能进行冻结")
}
orderInfo.Freeze = config.YES
orderInfo.FreezeTime = time.Now().Format("2006-01-02 15:04:05")
orderInfo.UpdateTime = time.Now()
if _, err := txOrm.Update(&orderInfo); err != nil {
otelTrace.Logger.WithContext(ctx).Error("solve order freeze fail: ", zap.Error(err))
return err
}
// 账户的冻结金额里面加入相应的金额
orderProfitInfo := order.GetOrderProfitByBankOrderId(ctx, bankOrderId)
var accountInfo accounts.AccountInfo
if err := txOrm.Raw("select * from account_info where account_uid = ? for update", orderInfo.MerchantUid).QueryRow(&accountInfo); err != nil || accountInfo.AccountUid == "" {
otelTrace.Logger.WithContext(ctx).Error(fmt.Sprintf("solve pay fail select acount fail%s", err))
return err
}
accountInfo.UpdateTime = time.Now()
accountInfo.FreezeAmount = orderProfitInfo.UserInAmount + accountInfo.FreezeAmount
if _, err := txOrm.Update(&accountInfo); err != nil {
otelTrace.Logger.WithContext(ctx).Error("solve order freeze fail: ", zap.Error(err))
return err
}
// 插入一条动账记录
accountHistoryInfo := accounts.AccountHistoryInfo{
AccountName: accountInfo.AccountName,
AccountUid: accountInfo.AccountUid,
Type: config.FREEZE_AMOUNT,
OrderId: orderInfo.MerchantOrderId,
Amount: orderProfitInfo.UserInAmount,
Balance: accountInfo.Balance,
CreateTime: time.Now(),
UpdateTime: time.Now(),
}
if _, err := txOrm.Insert(&accountHistoryInfo); err != nil {
otelTrace.Logger.WithContext(ctx).Error("solve order freeze fail: ", zap.Error(err))
return err
}
return nil
})
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("SolveOrderFreeze", zap.Error(err))
return false
}
otelTrace.Logger.WithContext(ctx).Info("SolveOrderFreeze")
return true
}
// SolveOrderUnfreeze 订单解冻
func SolveOrderUnfreeze(ctx context.Context, bankOrderId string) bool {
o := orm.NewOrm()
if err := o.DoTx(func(ctx context.Context, txOrm orm.TxOrmer) error {
orderInfo := new(order.OrderInfo)
if err := txOrm.Raw("select * from order_info where bank_order_id = ? for update", bankOrderId).QueryRow(orderInfo); err != nil || orderInfo.BankOrderId == "" {
otelTrace.Logger.WithContext(ctx).Error("solve order unfreeze 不存在这样的订单记录bankOrderId = ", zap.String("bankOrderId", bankOrderId))
return err
}
orderInfo.Freeze = ""
orderInfo.Unfreeze = config.YES
orderInfo.UnfreezeTime = utils.GetBasicDateTime()
orderInfo.UpdateTime = time.Now()
if _, err := txOrm.Update(orderInfo); err != nil {
otelTrace.Logger.WithContext(ctx).Error("solve order unfreeze fail: ", zap.Error(err))
return err
}
orderProfitInfo := order.GetOrderProfitByBankOrderId(ctx, bankOrderId)
accountInfo := new(accounts.AccountInfo)
if err := txOrm.Raw("select * from account_info where account_uid = ? for update", orderInfo.MerchantUid).QueryRow(accountInfo); err != nil || accountInfo.AccountUid == "" {
otelTrace.Logger.WithContext(ctx).Error(fmt.Sprintf("unfreeze select account fail: %s", err))
return err
}
accountInfo.UpdateTime = time.Now()
accountInfo.FreezeAmount = accountInfo.FreezeAmount - orderProfitInfo.UserInAmount
if _, err := txOrm.Update(accountInfo); err != nil {
otelTrace.Logger.WithContext(ctx).Error("solve order unfreeze fail: ", zap.Error(err))
return err
}
accountHistoryInfo := accounts.AccountHistoryInfo{
AccountUid: accountInfo.AccountUid,
AccountName: accountInfo.AccountName,
OrderId: orderInfo.MerchantOrderId,
Type: config.UNFREEZE_AMOUNT,
Amount: orderProfitInfo.UserInAmount,
Balance: accountInfo.Balance,
CreateTime: time.Now(),
UpdateTime: time.Now(),
}
if _, err := txOrm.Insert(&accountHistoryInfo); err != nil {
return err
}
return nil
}); err != nil {
otelTrace.Logger.WithContext(ctx).Error("SolveOrderUnfreeze失败", zap.Error(err))
return false
}
return true
}
func SolveRefund(ctx context.Context, bankOrderId string) bool {
o := orm.NewOrm()
if err := o.DoTx(func(ctx context.Context, txOrm orm.TxOrmer) error {
orderInfo := new(order.OrderInfo)
if err := txOrm.Raw("select * from order_info where bank_order_id = ? for update", bankOrderId).QueryRow(orderInfo); err != nil || orderInfo.BankOrderId == "" {
otelTrace.Logger.WithContext(ctx).Error("solve refund 不存在这样的订单bankOrderId = " + bankOrderId)
return err
}
orderInfo.UpdateTime = time.Now()
orderInfo.Refund = config.YES
orderInfo.RefundTime = utils.GetBasicDateTime()
orderProfitInfo := order.GetOrderProfitByBankOrderId(ctx, bankOrderId)
account := new(accounts.AccountInfo)
if err := txOrm.Raw("select * from account_info where account_uid = ? for update", orderInfo.MerchantUid).QueryRow(account); err != nil || account.AccountUid == "" {
return err
}
account.UpdateTime = time.Now()
account.SettleAmount = account.SettleAmount - orderProfitInfo.UserInAmount
account.Balance = account.Balance - orderProfitInfo.UserInAmount
if orderInfo.Freeze == config.YES {
account.FreezeAmount = account.FreezeAmount - orderProfitInfo.UserInAmount
if account.FreezeAmount < 0 {
account.FreezeAmount = config.ZERO
}
orderInfo.Freeze = ""
}
if _, err := txOrm.Update(orderInfo); err != nil {
otelTrace.Logger.WithContext(ctx).Error("solve order refund update order info fail: ", zap.Error(err))
return err
}
if _, err := txOrm.Update(account); err != nil {
otelTrace.Logger.WithContext(ctx).Error("solve order refund update account fail: ", zap.Error(err))
return err
}
accountHistoryInfo := accounts.AccountHistoryInfo{
AccountName: account.AccountName,
AccountUid: account.AccountUid,
Type: config.REFUND,
OrderId: orderInfo.MerchantOrderId,
Amount: orderProfitInfo.UserInAmount,
Balance: account.Balance,
CreateTime: time.Now(),
UpdateTime: time.Now(),
}
if _, err := txOrm.Insert(&accountHistoryInfo); err != nil {
otelTrace.Logger.WithContext(ctx).Error("solve order refund insert account history fail: ", zap.Error(err))
return err
}
return nil
}); err != nil {
otelTrace.Logger.WithContext(ctx).Error("SolveRefund 成功:", zap.Error(err))
return false
}
return true
}
func SolveOrderRoll(ctx context.Context, bankOrderId string) bool {
o := orm.NewOrm()
if err := o.DoTx(func(ctx context.Context, txOrm orm.TxOrmer) error {
orderInfo := new(order.OrderInfo)
if err := txOrm.Raw("select * from order_info where bank_order_id = ? for update", bankOrderId).QueryRow(orderInfo); err != nil {
otelTrace.Logger.WithContext(ctx).Error("solve order roll fail ", zap.Error(err))
return err
}
if orderInfo.Status != config.SUCCESS {
otelTrace.Logger.WithContext(ctx).Error("solve order roll 订单不存在或者订单状态不是success, bankOrderId=", zap.String("bankOrderId", bankOrderId))
return errors.New("solve order roll failed")
}
orderInfo.UpdateTime = time.Now()
orderProfitInfo := order.GetOrderProfitByBankOrderId(ctx, bankOrderId)
account := new(accounts.AccountInfo)
if err := txOrm.Raw("select * from account_info where account_uid = ? for update", orderInfo.MerchantUid).QueryRow(account); err != nil || account.AccountUid == "" {
return err
}
account.UpdateTime = time.Now()
if orderInfo.Refund == config.YES {
account.Balance = account.Balance + orderProfitInfo.UserInAmount
account.SettleAmount = account.SettleAmount + orderProfitInfo.UserInAmount
orderInfo.Refund = config.NO
}
if _, err := txOrm.Update(orderInfo); err != nil {
otelTrace.Logger.WithContext(ctx).Error("solve order roll fail update order info fail: ", zap.Error(err))
return err
}
if _, err := txOrm.Update(account); err != nil {
otelTrace.Logger.WithContext(ctx).Error("solve order roll update account fail: ", zap.Error(err))
return err
}
accountHistoryInfo := accounts.AccountHistoryInfo{
AccountUid: account.AccountUid,
AccountName: account.AccountName,
Type: config.PLUS_AMOUNT,
OrderId: orderInfo.MerchantOrderId,
Amount: orderProfitInfo.UserInAmount,
Balance: account.Balance,
CreateTime: time.Now(),
UpdateTime: time.Now(),
}
if _, err := txOrm.Insert(&accountHistoryInfo); err != nil {
otelTrace.Logger.WithContext(ctx).Error("solve order roll insert account history fail: ", zap.Error(err))
return err
}
return nil
}); err != nil {
otelTrace.Logger.WithContext(ctx).Error("SolveOrderRoll处理失败", zap.Error(err))
return false
}
return true
}
// CompareOrderAndFactAmount 比较订单金额和实际支付金额的大小
func CompareOrderAndFactAmount(factAmount float64, orderInfo order.OrderInfo) int {
orderAmount := orderInfo.OrderAmount
// 将金额放大1000倍
oa := int64(orderAmount * 1000)
fa := int64(factAmount * 1000)
if oa > fa {
// 如果实际金额大返回1
return 1
} else if oa == fa {
return 0
} else {
return 2
}
}
// CreateOrderNotifyInfo 支付完成后,处理给商户的回调信息
func CreateOrderNotifyInfo(ctx context.Context, bankOrderId string, tradeStatus string) {
orderInfo := order.GetOneOrder(ctx, bankOrderId)
if orderInfo.Status != config.SUCCESS && orderInfo.Status != config.FAIL {
return
}
merchantInfo := merchant.GetMerchantByUid(ctx, orderInfo.MerchantUid)
statusCode := 0
if tradeStatus == "success" {
statusCode = 1
}
params := map[string]any{
"orderNo": orderInfo.MerchantOrderId, // 商户订单号
"orderPrice": strconv.FormatFloat(orderInfo.OrderAmount, 'f', 2, 64),
"factPrice": strconv.FormatFloat(orderInfo.FactAmount, 'f', 2, 64),
"orderTime": utils.GetNowTimesTamp(),
"trxNo": orderInfo.BankOrderId,
"payKey": merchantInfo.MerchantKey,
"statusCode": statusCode,
"failReason": orderInfo.CardReturnData,
}
params["sign"] = utils.GetMD5SignMF(params, merchantInfo.MerchantSecret)
u := url.Values{}
for k, v := range params {
u.Add(k, convertor.ToString(v))
}
if notify.InsertNotifyInfo(ctx, notify.NotifyInfo{
Type: "order",
BankOrderId: orderInfo.BankOrderId,
MerchantOrderId: orderInfo.MerchantOrderId,
Status: "wait",
Times: 0,
CreateTime: time.Now(),
UpdateTime: time.Now(),
Url: orderInfo.NotifyUrl + "?" + u.Encode(),
}) {
otelTrace.Logger.WithContext(ctx).Info(fmt.Sprintf("订单bankOrderId=%s已经将回调地址插入数据库", orderInfo.BankOrderId))
} else {
otelTrace.Logger.WithContext(ctx).Error(fmt.Sprintf("订单bankOrderId=%s插入回调数据库失败", orderInfo.BankOrderId))
}
// 将订单发送到消息队列,给下面的商户进行回调
sendMessageNotify.Go(func() {
// 创建一个5分钟超时的上下文
ctx2, span := otelTrace.NewSchedulerTrace().Start(otelTrace.InitCtx, "CreateOrderNotifyInfo")
defer span.End()
message.SendMessage(ctx2, config.MqOrderNotify, orderInfo.BankOrderId)
})
}