go-common/app/job/main/ugcpay/service/task_bill_daily.go

472 lines
12 KiB
Go
Raw Normal View History

2019-04-22 10:49:16 +00:00
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
}