472 lines
12 KiB
Go
472 lines
12 KiB
Go
|
package service
|
|||
|
|
|||
|
import (
|
|||
|
"context"
|
|||
|
"fmt"
|
|||
|
"math/rand"
|
|||
|
"time"
|
|||
|
|
|||
|
"go-common/app/job/main/ugcpay/dao"
|
|||
|
"go-common/app/job/main/ugcpay/model"
|
|||
|
"go-common/app/job/main/ugcpay/service/pay"
|
|||
|
xsql "go-common/library/database/sql"
|
|||
|
"go-common/library/log"
|
|||
|
|
|||
|
"github.com/pkg/errors"
|
|||
|
)
|
|||
|
|
|||
|
type taskBillDaily struct {
|
|||
|
dao *dao.Dao
|
|||
|
pay *pay.Pay
|
|||
|
rnd *rand.Rand
|
|||
|
dayOffset int
|
|||
|
namePrefix string
|
|||
|
tl *taskLog
|
|||
|
}
|
|||
|
|
|||
|
func (s *taskBillDaily) Run() (err error) {
|
|||
|
var (
|
|||
|
ctx = context.Background()
|
|||
|
finished bool
|
|||
|
expectFN = func(ctx context.Context) (expect int64, err error) {
|
|||
|
var (
|
|||
|
beginTime, endTime = dayRange(s.dayOffset)
|
|||
|
expectPaid, expectRefunded int64
|
|||
|
)
|
|||
|
if expectPaid, err = s.dao.CountPaidOrderUser(ctx, beginTime, endTime); err != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
if expectRefunded, err = s.dao.CountRefundedOrderUser(ctx, beginTime, endTime); err != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
expect = expectPaid + expectRefunded
|
|||
|
return
|
|||
|
}
|
|||
|
)
|
|||
|
if finished, err = checkOrCreateTaskFromLog(ctx, s, s.tl, expectFN); err != nil || finished {
|
|||
|
return
|
|||
|
}
|
|||
|
return s.run(ctx)
|
|||
|
}
|
|||
|
|
|||
|
func (s *taskBillDaily) TTL() int32 {
|
|||
|
return 3600 * 2
|
|||
|
}
|
|||
|
|
|||
|
func (s *taskBillDaily) Name() string {
|
|||
|
return fmt.Sprintf("%s_%d", s.namePrefix, dailyBillVer(time.Now()))
|
|||
|
}
|
|||
|
|
|||
|
// 日账单生成
|
|||
|
func (s *taskBillDaily) run(ctx context.Context) (err error) {
|
|||
|
// 已支付成功订单入账
|
|||
|
paidLL := &orderPaidLL{
|
|||
|
limit: 1000,
|
|||
|
dao: s.dao,
|
|||
|
}
|
|||
|
paidLL.beginTime, paidLL.endTime = dayRange(s.dayOffset)
|
|||
|
if err = runLimitedList(ctx, paidLL, time.Millisecond*5, s.runPaidOrder); err != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// 已退款订单入账
|
|||
|
refundLL := &orderRefundedLL{
|
|||
|
limit: 1000,
|
|||
|
dao: s.dao,
|
|||
|
}
|
|||
|
refundLL.beginTime, refundLL.endTime = dayRange(s.dayOffset)
|
|||
|
return runLimitedList(ctx, refundLL, time.Millisecond*5, s.runRefundedOrder)
|
|||
|
}
|
|||
|
|
|||
|
// 处理退款order
|
|||
|
func (s *taskBillDaily) runRefundedOrder(ctx context.Context, ele interface{}) (err error) {
|
|||
|
order, ok := ele.(*model.Order)
|
|||
|
if !ok {
|
|||
|
return errors.Errorf("refundedOrderHandler convert ele: %+v failed", order)
|
|||
|
}
|
|||
|
log.Info("runRefundedOrder handle order: %+v", order)
|
|||
|
|
|||
|
logOrder := &model.LogOrder{
|
|||
|
OrderID: order.OrderID,
|
|||
|
FromState: order.State,
|
|||
|
ToState: model.OrderStateRefundFinished,
|
|||
|
}
|
|||
|
order.State = model.OrderStateRefundFinished
|
|||
|
|
|||
|
fn := func(ctx context.Context, tx *xsql.Tx) (affected bool, err error) {
|
|||
|
var (
|
|||
|
bill *model.DailyBill
|
|||
|
asset *model.Asset
|
|||
|
ver = dailyBillVer(order.RefundTime)
|
|||
|
monthVer = monthlyBillVer(order.RefundTime)
|
|||
|
billDailyLog *model.LogBillDaily
|
|||
|
userIncome, _ = calcAssetIncome(order.RealFee) // 收入计算结果
|
|||
|
)
|
|||
|
affected = true
|
|||
|
|
|||
|
// 获得订单对应的asset
|
|||
|
if asset, err = s.dao.Asset(ctx, order.OID, order.OType, order.Currency); err != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
if asset == nil {
|
|||
|
err = errors.Errorf("dailyBillHander find invalid asset order, order: %+v", order)
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// 获得该mid对应的日账单
|
|||
|
if bill, err = s.dao.DailyBill(ctx, asset.MID, model.BizAsset, model.CurrencyBP, ver); err != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
if bill == nil {
|
|||
|
if bill, err = s.initDailyBill(ctx, asset.MID, model.BizAsset, model.CurrencyBP, ver, monthVer); err != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
}
|
|||
|
// 计算日账单
|
|||
|
billDailyLog = &model.LogBillDaily{
|
|||
|
BillID: bill.BillID,
|
|||
|
FromIn: bill.In,
|
|||
|
ToIn: bill.In,
|
|||
|
FromOut: bill.Out + userIncome,
|
|||
|
ToOut: bill.Out,
|
|||
|
OrderID: order.OrderID + "_r",
|
|||
|
}
|
|||
|
bill.Out += userIncome
|
|||
|
|
|||
|
// 更新order
|
|||
|
rowAffected, err := s.dao.TXUpdateOrder(ctx, tx, order)
|
|||
|
if err != nil {
|
|||
|
tx.Rollback()
|
|||
|
return
|
|||
|
}
|
|||
|
if rowAffected <= 0 {
|
|||
|
tx.Rollback()
|
|||
|
log.Error("UpdateOrder no affected from order: %+v", order)
|
|||
|
affected = false
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// 添加 order log
|
|||
|
_, err = s.dao.TXInsertOrderUserLog(ctx, tx, logOrder)
|
|||
|
if err != nil {
|
|||
|
tx.Rollback()
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// 更新daily_bill
|
|||
|
rowAffected, err = s.dao.TXUpdateDailyBill(ctx, tx, bill)
|
|||
|
if err != nil {
|
|||
|
tx.Rollback()
|
|||
|
return
|
|||
|
}
|
|||
|
if rowAffected <= 0 {
|
|||
|
log.Error("TXUpdateDailyBill no affected bill: %+v", bill)
|
|||
|
tx.Rollback()
|
|||
|
affected = false
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// 添加 daily bill log , uk order_id
|
|||
|
_, err = s.dao.TXInsertLogDailyBill(ctx, tx, billDailyLog)
|
|||
|
if err != nil {
|
|||
|
tx.Rollback()
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// 更新 aggr
|
|||
|
aggrMonthlyAsset := &model.AggrIncomeUserAsset{
|
|||
|
MID: bill.MID,
|
|||
|
Currency: bill.Currency,
|
|||
|
Ver: monthVer,
|
|||
|
OID: order.OID,
|
|||
|
OType: order.OType,
|
|||
|
}
|
|||
|
aggrAllAsset := &model.AggrIncomeUserAsset{
|
|||
|
MID: bill.MID,
|
|||
|
Currency: bill.Currency,
|
|||
|
Ver: 0,
|
|||
|
OID: order.OID,
|
|||
|
OType: order.OType,
|
|||
|
}
|
|||
|
aggrUser := &model.AggrIncomeUser{
|
|||
|
MID: bill.MID,
|
|||
|
Currency: bill.Currency,
|
|||
|
}
|
|||
|
_, err = s.dao.TXUpsertDeltaAggrIncomeUserAsset(ctx, tx, aggrAllAsset, 0, 1, 0, userIncome)
|
|||
|
if err != nil {
|
|||
|
tx.Rollback()
|
|||
|
return
|
|||
|
}
|
|||
|
_, err = s.dao.TXUpsertDeltaAggrIncomeUserAsset(ctx, tx, aggrMonthlyAsset, 0, 1, 0, userIncome)
|
|||
|
if err != nil {
|
|||
|
tx.Rollback()
|
|||
|
return
|
|||
|
}
|
|||
|
_, err = s.dao.TXUpsertDeltaAggrIncomeUser(ctx, tx, aggrUser, 0, 1, 0, userIncome)
|
|||
|
if err != nil {
|
|||
|
tx.Rollback()
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
log.Info("Settle daily bill: %+v, aggrAllAsset: %+v, aggrMonthlyAsset: %+v, aggrUser: %+v, from refunded order: %+v", bill, aggrAllAsset, aggrMonthlyAsset, aggrUser, order)
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
return runTXCASTaskWithLog(ctx, s, s.tl, fn)
|
|||
|
}
|
|||
|
|
|||
|
func (s *taskBillDaily) runPaidOrder(ctx context.Context, ele interface{}) (err error) {
|
|||
|
order, ok := ele.(*model.Order)
|
|||
|
if !ok {
|
|||
|
return errors.Errorf("runPaidOrder convert ele: %+v failed", order)
|
|||
|
}
|
|||
|
log.Info("runPaidOrder handle order: %+v", order)
|
|||
|
|
|||
|
checkOK, payDesc, err := s.checkOrder(ctx, order) // 对支付订单对账
|
|||
|
if err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
logOrder := &model.LogOrder{
|
|||
|
OrderID: order.OrderID,
|
|||
|
FromState: order.State,
|
|||
|
Desc: payDesc,
|
|||
|
}
|
|||
|
var fn func(context.Context, *xsql.Tx) (affected bool, err error)
|
|||
|
|
|||
|
if checkOK { // 对账成功
|
|||
|
logOrder.ToState = model.OrderStateSettled
|
|||
|
order.State = model.OrderStateSettled
|
|||
|
fn = func(ctx context.Context, tx *xsql.Tx) (affected bool, err error) {
|
|||
|
var (
|
|||
|
bill *model.DailyBill
|
|||
|
asset *model.Asset
|
|||
|
ver = dailyBillVer(order.PayTime)
|
|||
|
monthVer = monthlyBillVer(order.PayTime)
|
|||
|
billDailyLog *model.LogBillDaily
|
|||
|
userIncome, _ = calcAssetIncome(order.RealFee) // 收入计算结果
|
|||
|
)
|
|||
|
affected = true
|
|||
|
|
|||
|
// 获得订单对应的asset
|
|||
|
if asset, err = s.dao.Asset(ctx, order.OID, order.OType, order.Currency); err != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
if asset == nil {
|
|||
|
log.Error("runPaidOrder find invalid asset order, order: %+v", order)
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// 获得该mid对应的日账单
|
|||
|
if bill, err = s.dao.DailyBill(ctx, asset.MID, model.BizAsset, model.CurrencyBP, ver); err != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
if bill == nil {
|
|||
|
if bill, err = s.initDailyBill(ctx, asset.MID, model.BizAsset, model.CurrencyBP, ver, monthVer); err != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
}
|
|||
|
// 计算日账单
|
|||
|
billDailyLog = &model.LogBillDaily{
|
|||
|
BillID: bill.BillID,
|
|||
|
FromIn: bill.In,
|
|||
|
ToIn: bill.In + userIncome,
|
|||
|
FromOut: bill.Out,
|
|||
|
ToOut: bill.Out,
|
|||
|
OrderID: order.OrderID,
|
|||
|
}
|
|||
|
bill.In += userIncome
|
|||
|
|
|||
|
// 更新order
|
|||
|
rowAffected, err := s.dao.TXUpdateOrder(ctx, tx, order)
|
|||
|
if err != nil {
|
|||
|
tx.Rollback()
|
|||
|
return
|
|||
|
}
|
|||
|
if rowAffected <= 0 {
|
|||
|
tx.Rollback()
|
|||
|
log.Error("UpdateOrder no affected from order: %+v", order)
|
|||
|
affected = false
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// 添加 order log
|
|||
|
_, err = s.dao.TXInsertOrderUserLog(ctx, tx, logOrder)
|
|||
|
if err != nil {
|
|||
|
tx.Rollback()
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// 更新daily_bill
|
|||
|
rowAffected, err = s.dao.TXUpdateDailyBill(ctx, tx, bill)
|
|||
|
if err != nil {
|
|||
|
tx.Rollback()
|
|||
|
return
|
|||
|
}
|
|||
|
if rowAffected <= 0 {
|
|||
|
log.Error("TXUpsertDeltaDailyBill no affected bill: %+v", bill)
|
|||
|
tx.Rollback()
|
|||
|
affected = false
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// 添加 daily bill log , uk order_id
|
|||
|
_, err = s.dao.TXInsertLogDailyBill(ctx, tx, billDailyLog)
|
|||
|
if err != nil {
|
|||
|
tx.Rollback()
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// 更新 aggr
|
|||
|
aggrMonthlyAsset := &model.AggrIncomeUserAsset{
|
|||
|
MID: bill.MID,
|
|||
|
Currency: bill.Currency,
|
|||
|
Ver: monthVer,
|
|||
|
OID: order.OID,
|
|||
|
OType: order.OType,
|
|||
|
}
|
|||
|
aggrAllAsset := &model.AggrIncomeUserAsset{
|
|||
|
MID: bill.MID,
|
|||
|
Currency: bill.Currency,
|
|||
|
Ver: 0,
|
|||
|
OID: order.OID,
|
|||
|
OType: order.OType,
|
|||
|
}
|
|||
|
aggrUser := &model.AggrIncomeUser{
|
|||
|
MID: bill.MID,
|
|||
|
Currency: bill.Currency,
|
|||
|
}
|
|||
|
_, err = s.dao.TXUpsertDeltaAggrIncomeUserAsset(ctx, tx, aggrAllAsset, 1, 0, userIncome, 0)
|
|||
|
if err != nil {
|
|||
|
tx.Rollback()
|
|||
|
return
|
|||
|
}
|
|||
|
_, err = s.dao.TXUpsertDeltaAggrIncomeUserAsset(ctx, tx, aggrMonthlyAsset, 1, 0, userIncome, 0)
|
|||
|
if err != nil {
|
|||
|
tx.Rollback()
|
|||
|
return
|
|||
|
}
|
|||
|
_, err = s.dao.TXUpsertDeltaAggrIncomeUser(ctx, tx, aggrUser, 1, 0, userIncome, 0)
|
|||
|
if err != nil {
|
|||
|
tx.Rollback()
|
|||
|
return
|
|||
|
}
|
|||
|
log.Info("taskBillDaily: %+v, aggrAllAsset: %+v, aggrMonthlyAsset: %+v, aggrUser: %+v, from paid order: %+v", bill, aggrAllAsset, aggrMonthlyAsset, aggrUser, order)
|
|||
|
return
|
|||
|
}
|
|||
|
} else { // 对账失败
|
|||
|
logOrder.ToState = model.OrderStateBadDebt
|
|||
|
order.State = model.OrderStateBadDebt
|
|||
|
fn = func(ctx context.Context, tx *xsql.Tx) (affected bool, err error) {
|
|||
|
var (
|
|||
|
orderBadDebt *model.OrderBadDebt
|
|||
|
)
|
|||
|
affected = true
|
|||
|
|
|||
|
if orderBadDebt, err = s.dao.OrderBadDebt(ctx, order.OrderID); err != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
if orderBadDebt == nil {
|
|||
|
if orderBadDebt, err = s.initBadDebt(ctx, order.OrderID); err != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
}
|
|||
|
orderBadDebt.Type = "unknown"
|
|||
|
orderBadDebt.State = "failed"
|
|||
|
|
|||
|
// 更新order
|
|||
|
rowAffected, theErr := s.dao.TXUpdateOrder(ctx, tx, order)
|
|||
|
if theErr != nil {
|
|||
|
tx.Rollback()
|
|||
|
return
|
|||
|
}
|
|||
|
if rowAffected <= 0 {
|
|||
|
tx.Rollback()
|
|||
|
log.Error("UpdateOrder no affected from order: %+v", order)
|
|||
|
affected = false
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// 添加order log
|
|||
|
_, theErr = s.dao.TXInsertOrderUserLog(ctx, tx, logOrder)
|
|||
|
if theErr != nil {
|
|||
|
tx.Rollback()
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// 添加坏账表
|
|||
|
_, err = s.dao.TXUpdateOrderBadDebt(ctx, tx, orderBadDebt)
|
|||
|
if err != nil {
|
|||
|
tx.Rollback()
|
|||
|
return
|
|||
|
}
|
|||
|
log.Info("Add bad debt: %+v", orderBadDebt)
|
|||
|
return
|
|||
|
}
|
|||
|
}
|
|||
|
return runTXCASTaskWithLog(ctx, s, s.tl, fn)
|
|||
|
}
|
|||
|
|
|||
|
func (s *taskBillDaily) checkOrder(ctx context.Context, order *model.Order) (ok bool, payDesc string, err error) {
|
|||
|
ok = false
|
|||
|
if order == nil {
|
|||
|
return
|
|||
|
}
|
|||
|
if order.PayID == "" {
|
|||
|
log.Error("Check order found baddebt order: %+v", order)
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
payParam := s.pay.CheckOrder(order.PayID)
|
|||
|
s.pay.Sign(payParam)
|
|||
|
payJSON, err := s.pay.ToJSON(payParam)
|
|||
|
if err != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
orders, err := s.dao.PayCheckOrder(ctx, payJSON)
|
|||
|
if err != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
result, ok := orders[order.PayID]
|
|||
|
if !ok {
|
|||
|
return
|
|||
|
}
|
|||
|
payDesc = result.RecoStatusDesc
|
|||
|
switch result.RecoStatusDesc {
|
|||
|
case model.PayCheckOrderStateSuccess:
|
|||
|
ok = true
|
|||
|
default:
|
|||
|
ok = false
|
|||
|
}
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
func (s *taskBillDaily) initDailyBill(ctx context.Context, mid int64, biz, currency string, ver, monthVer int64) (bill *model.DailyBill, err error) {
|
|||
|
bill = &model.DailyBill{}
|
|||
|
bill.BillID = orderID(s.rnd)
|
|||
|
bill.MID = mid
|
|||
|
bill.Biz = model.BizAsset
|
|||
|
bill.Currency = model.CurrencyBP
|
|||
|
bill.In = 0
|
|||
|
bill.Out = 0
|
|||
|
bill.Ver = ver
|
|||
|
bill.MonthVer = monthVer
|
|||
|
bill.Version = 1
|
|||
|
|
|||
|
if bill.ID, err = s.dao.InsertDailyBill(ctx, bill); err != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
func (s *taskBillDaily) initBadDebt(ctx context.Context, orderID string) (data *model.OrderBadDebt, err error) {
|
|||
|
data = &model.OrderBadDebt{
|
|||
|
OrderID: orderID,
|
|||
|
Type: "",
|
|||
|
State: "",
|
|||
|
}
|
|||
|
if data.ID, err = s.dao.InsertOrderBadDebt(ctx, data); err != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
return
|
|||
|
}
|