package trade import ( "context" "fmt" "math" "strings" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/util/gconv" ) const ( rechargeStatusPending = iota rechargeStatusPaid rechargeStatusExpired rechargeStatusFailed rechargeChannelAlipay = 1 rechargeType = 0 rechargeIncome = 0 ) type sTrade struct{} func NewTrade() *sTrade { return &sTrade{} } func init() { service.RegisterTrade(NewTrade()) } // CreateRechargePending records the recharge before redirecting the user to Alipay. func (s *sTrade) CreateRechargePending(ctx context.Context, outTradeNo, totalAmount string) error { outTradeNo = strings.TrimSpace(outTradeNo) amount := gconv.Float64(strings.TrimSpace(totalAmount)) if outTradeNo == "" { return gerror.New("商户订单号不能为空") } if amount <= 0 { return gerror.New("充值金额必须大于0") } deptId := contexts.GetDeptId(ctx) tenantId := contexts.GetTenantId(ctx) userId := contexts.GetUserId(ctx) if deptId == 0 || tenantId == "" { return gerror.New("当前用户未绑定组织或租户") } return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { var existing *entity.TBalanceDetail if err := tx.Ctx(ctx).Model(dao.TBalanceDetail.Table()). Where(dao.TBalanceDetail.Columns().OrderSn, outTradeNo). Where(dao.TBalanceDetail.Columns().TransactionChannel, rechargeChannelAlipay). Scan(&existing); err != nil { return gerror.Wrap(err, "查询待支付充值明细失败") } if existing != nil && existing.Id != 0 { return nil } beforeBalance := float64(0) var account *entity.TAgentBalance if err := tx.Ctx(ctx).Model(dao.TAgentBalance.Table()). Where(dao.TAgentBalance.Columns().DeptId, deptId). Where(dao.TAgentBalance.Columns().TenantId, tenantId). Scan(&account); err != nil { return gerror.Wrap(err, "查询账户余额失败") } if account != nil { beforeBalance = account.Balance } now := gtime.Now() _, err := tx.Ctx(ctx).Model(dao.TBalanceDetail.Table()).Data(g.Map{ dao.TBalanceDetail.Columns().DeptId: deptId, dao.TBalanceDetail.Columns().SerialNo: outTradeNo, dao.TBalanceDetail.Columns().TransactionTime: now, dao.TBalanceDetail.Columns().IncomeExpenseType: rechargeIncome, dao.TBalanceDetail.Columns().TransactionType: rechargeType, dao.TBalanceDetail.Columns().TransactionChannel: rechargeChannelAlipay, dao.TBalanceDetail.Columns().TransactionStatus: rechargeStatusPending, dao.TBalanceDetail.Columns().Amount: amount, dao.TBalanceDetail.Columns().BeforeBalance: beforeBalance, dao.TBalanceDetail.Columns().BalanceAfter: beforeBalance, dao.TBalanceDetail.Columns().OrderSn: outTradeNo, dao.TBalanceDetail.Columns().Remark: "支付宝充值,待支付", dao.TBalanceDetail.Columns().TenantId: tenantId, dao.TBalanceDetail.Columns().CreatedDept: deptId, dao.TBalanceDetail.Columns().CreatedBy: userId, dao.TBalanceDetail.Columns().CreatedAt: now, }).Insert() return gerror.Wrap(err, "创建待支付充值明细失败") }) } // Recharge confirms a paid recharge and credits the account exactly once. func (s *sTrade) Recharge(ctx context.Context, in *model.TBalanceDetailRechargeModel) (err error) { if in == nil { return gerror.New("充值参数不能为空") } if in.TradeStatus != "TRADE_SUCCESS" && in.TradeStatus != "TRADE_FINISHED" { return gerror.New("交易状态错误") } outTradeNo := strings.TrimSpace(in.OutTradeNo) amount := gconv.Float64(strings.TrimSpace(in.TotalAmount)) if outTradeNo == "" || amount <= 0 { return gerror.New("充值订单或金额无效") } return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { var detail *entity.TBalanceDetail if err := tx.Ctx(ctx).Model(dao.TBalanceDetail.Table()). Where(dao.TBalanceDetail.Columns().OrderSn, outTradeNo). Where(dao.TBalanceDetail.Columns().TransactionChannel, rechargeChannelAlipay). LockUpdate(). Scan(&detail); err != nil { return gerror.Wrap(err, "查询充值明细失败") } if detail == nil || detail.Id == 0 { return gerror.New("充值订单不存在") } if detail.TransactionStatus == rechargeStatusPaid { return nil } if detail.TransactionStatus != rechargeStatusPending { return gerror.New("充值订单已关闭") } if math.Abs(detail.Amount-amount) > 0.001 { return gerror.New("支付金额与充值订单不一致") } var account *entity.TAgentBalance if err := tx.Ctx(ctx).Model(dao.TAgentBalance.Table()). Where(dao.TAgentBalance.Columns().DeptId, detail.DeptId). Where(dao.TAgentBalance.Columns().TenantId, detail.TenantId). LockUpdate(). Scan(&account); err != nil { return gerror.Wrap(err, "查询账户余额失败") } now := gtime.Now() if account == nil { id, err := tx.Ctx(ctx).Model(dao.TAgentBalance.Table()).Data(g.Map{ dao.TAgentBalance.Columns().DeptId: detail.DeptId, dao.TAgentBalance.Columns().Balance: 0, dao.TAgentBalance.Columns().WithdrawAmount: 0, dao.TAgentBalance.Columns().WithdrawAmountStatus: 1, dao.TAgentBalance.Columns().InvoiceAmount: 0, dao.TAgentBalance.Columns().CanInvoiceAmount: 0, dao.TAgentBalance.Columns().CreditLimit: 0, dao.TAgentBalance.Columns().UsedCredit: 0, dao.TAgentBalance.Columns().PendingRepayment: 0, dao.TAgentBalance.Columns().Status: 0, dao.TAgentBalance.Columns().TenantId: detail.TenantId, dao.TAgentBalance.Columns().CreatedDept: detail.CreatedDept, dao.TAgentBalance.Columns().CreatedBy: detail.CreatedBy, dao.TAgentBalance.Columns().CreatedAt: now, }).InsertAndGetId() if err != nil { return gerror.Wrap(err, "初始化账户余额失败") } account = &entity.TAgentBalance{Id: id, Balance: 0} } before := account.Balance after := before + detail.Amount if _, err := tx.Ctx(ctx).Model(dao.TAgentBalance.Table()).WherePri(account.Id).Data(g.Map{ dao.TAgentBalance.Columns().Balance: after, dao.TAgentBalance.Columns().UpdatedBy: detail.CreatedBy, dao.TAgentBalance.Columns().UpdatedAt: now, }).Update(); err != nil { return gerror.Wrap(err, "更新账户余额失败") } _, err := tx.Ctx(ctx).Model(dao.TBalanceDetail.Table()).WherePri(detail.Id).Data(g.Map{ dao.TBalanceDetail.Columns().TransactionStatus: rechargeStatusPaid, dao.TBalanceDetail.Columns().TransactionTime: now, dao.TBalanceDetail.Columns().BeforeBalance: before, dao.TBalanceDetail.Columns().BalanceAfter: after, dao.TBalanceDetail.Columns().ChannelSerialNo: in.TradeNo, dao.TBalanceDetail.Columns().Remark: fmt.Sprintf("支付宝充值,买家账号: %s", in.BuyerLogonId), dao.TBalanceDetail.Columns().UpdatedBy: detail.CreatedBy, dao.TBalanceDetail.Columns().UpdatedAt: now, }).Update() return gerror.Wrap(err, "更新充值明细状态失败") }) } func (s *sTrade) MarkRechargeFailed(ctx context.Context, outTradeNo string) error { _, err := dao.TBalanceDetail.Ctx(ctx). Where(dao.TBalanceDetail.Columns().OrderSn, outTradeNo). Where(dao.TBalanceDetail.Columns().TransactionStatus, rechargeStatusPending). Data(g.Map{ dao.TBalanceDetail.Columns().TransactionStatus: rechargeStatusFailed, dao.TBalanceDetail.Columns().Remark: "支付宝支付链接创建失败", dao.TBalanceDetail.Columns().UpdatedAt: gtime.Now(), }).Update() return err }