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
|
||
}
|