Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"binlog.go",
"binlog_elec.go",
"elec_syncdb.go",
"limited_list.go",
"repaire.go",
"service.go",
"task.go",
"task_account_biz.go",
"task_account_user.go",
"task_bill_daily.go",
"task_bill_monthly.go",
"task_shell_recharge.go",
"tool.go",
],
importpath = "go-common/app/job/main/ugcpay/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/ugcpay/conf:go_default_library",
"//app/job/main/ugcpay/dao:go_default_library",
"//app/job/main/ugcpay/model:go_default_library",
"//app/job/main/ugcpay/service/pay:go_default_library",
"//library/database/sql:go_default_library",
"//library/log:go_default_library",
"//library/queue/databus:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/github.com/robfig/cron:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/job/main/ugcpay/service/pay:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = ["service_test.go"],
embed = [":go_default_library"],
tags = ["automanaged"],
)

View File

@@ -0,0 +1,79 @@
package service
import (
"context"
"encoding/json"
"runtime/debug"
"go-common/app/job/main/ugcpay/model"
"go-common/library/log"
"github.com/pkg/errors"
)
const (
_tableOrderUser = "order_user"
_tableAsset = "asset"
_tableAssetRelation = "asset_relation"
)
func (s *Service) binlogproc() (err error) {
defer func() {
if x := recover(); x != nil {
log.Error("binlogproc panic(%+v) :\n %s", x, debug.Stack())
go s.binlogproc()
}
}()
var (
c = context.Background()
)
for res := range s.binlogMQ.Messages() {
if err != nil {
log.Error("%+v", err)
err = nil
}
msg := &model.Message{}
if err = json.Unmarshal(res.Value, msg); err != nil {
err = errors.WithStack(err)
continue
}
switch msg.Table {
case _tableOrderUser:
ms := &model.BinlogOrderUser{}
if err = json.Unmarshal(msg.New, ms); err != nil {
err = errors.Wrapf(err, "%s", msg.New)
continue
}
log.Info("Delete order_user cache : %+v", ms)
if err = s.dao.DelCacheOrderUser(c, ms.OrderID); err != nil {
continue
}
case _tableAsset:
ms := &model.BinlogAsset{}
if err = json.Unmarshal(msg.New, ms); err != nil {
err = errors.Wrapf(err, "%s", msg.New)
continue
}
log.Info("Delete asset cache : %+v", ms)
if err = s.dao.DelCacheAsset(c, ms.OID, ms.OType, ms.Currency); err != nil {
continue
}
case _tableAssetRelation:
ms := &model.BinlogAssetRelation{}
if err = json.Unmarshal(msg.New, ms); err != nil {
err = errors.Wrapf(err, "%s", msg.New)
continue
}
log.Info("Delete asset_relation cache : %+v", ms)
if err = s.dao.DelCacheAssetRelationState(c, ms.OID, ms.OType, ms.MID); err != nil {
continue
}
}
if err = res.Commit(); err != nil {
err = errors.Wrapf(err, "binlogproc commit")
continue
}
log.Info("binlogproc consume key:%v, topic: %v, part:%v, offset:%v, message %s,", res.Key, res.Topic, res.Partition, res.Offset, res.Value)
}
return
}

View File

@@ -0,0 +1,249 @@
package service
import (
"context"
"encoding/json"
"runtime/debug"
"strconv"
"time"
"go-common/app/job/main/ugcpay/model"
// "go-common/app/service/main/ugcpay-rank/internal/service/rank"
"go-common/library/log"
"github.com/pkg/errors"
)
const (
_tableOldElecOrder = "elec_pay_order"
_tableOldElecMessage = "elec_message"
_tableOldElecUserSetting = "elec_user_setting"
)
var (
_ctx = context.Background()
)
func (s *Service) elecbinlogproc() {
defer func() {
if x := recover(); x != nil {
log.Error("binlogproc panic(%+v) :\n %s", x, debug.Stack())
go s.binlogproc()
}
}()
log.Info("Start binlogproc")
var (
err error
)
for res := range s.elecBinlogMQ.Messages() {
if err != nil {
log.Error("binlogproc consume key:%v, topic: %v, part:%v, offset:%v, message %s, err: %+v", res.Key, res.Topic, res.Partition, res.Offset, res.Value, err)
err = nil
}
if time.Since(time.Unix(res.Timestamp, 0)) >= time.Hour*24 {
log.Error("binlogproc consume expired msg, key:%v, topic: %v, part:%v, offset:%v, message %s", res.Key, res.Topic, res.Partition, res.Offset, res.Value)
continue
}
msg := &model.Message{}
if err = json.Unmarshal(res.Value, msg); err != nil {
err = errors.WithStack(err)
continue
}
switch msg.Table {
case _tableOldElecOrder:
data := &model.DBOldElecPayOrder{}
if err = json.Unmarshal(msg.New, data); err != nil {
err = errors.Wrapf(err, "%s", msg.New)
continue
}
if err = s.handleElecOrder(_ctx, data); err != nil {
continue
}
case _tableOldElecMessage:
data := &model.DBOldElecMessage{}
if err = json.Unmarshal(msg.New, data); err != nil {
err = errors.Wrapf(err, "%s", msg.New)
continue
}
if err = s.handleOldElecMessage(_ctx, data); err != nil {
continue
}
case _tableOldElecUserSetting:
data := &model.DBOldElecUserSetting{}
if err = json.Unmarshal(msg.New, data); err != nil {
err = errors.Wrapf(err, "%s", msg.New)
continue
}
if err = s.handleElecUserSetting(_ctx, data); err != nil {
continue
}
default:
log.Error("binlogproc unknown table: %s", msg.Table)
}
if err = res.Commit(); err != nil {
err = errors.Wrapf(err, "binlogproc commit")
continue
}
log.Info("binlogproc consume msg, key:%v, topic: %v, part:%v, offset:%v, message %s", res.Key, res.Topic, res.Partition, res.Offset, res.Value)
}
log.Info("End binlogproc")
}
func (s *Service) handleOldElecMessage(ctx context.Context, msg *model.DBOldElecMessage) (err error) {
log.Info("handleOldElecMessage message: %+v", msg)
var (
ver int64
verTime time.Time
avID int64
)
if verTime, err = time.Parse("2006-01", msg.DateVer); err != nil {
err = errors.WithStack(err)
return
}
ver = monthlyBillVer(verTime)
if msg.AVID != "" {
if avID, err = strconv.ParseInt(msg.AVID, 10, 64); err != nil {
log.Error("%+v", errors.WithStack(err))
avID = 0
err = nil
}
}
switch msg.Type {
// 用户对up主留言
case 1:
dbMSG := &model.DBElecMessage{
ID: msg.ID,
Ver: ver,
AVID: avID,
UPMID: msg.RefMID,
PayMID: msg.MID,
Message: msg.Message,
Replied: msg.State == 1,
Hidden: msg.State == 2,
CTime: msg.ParseCTime(),
MTime: msg.ParseMTime(),
}
if err = s.dao.UpsertElecMessage(ctx, dbMSG); err != nil {
return
}
if err = s.dao.RankElecUpdateMessage(ctx, dbMSG.AVID, dbMSG.UPMID, dbMSG.PayMID, dbMSG.Ver, dbMSG.Message, dbMSG.Hidden); err != nil {
return
}
// up主回复用户
case 2:
dbReply := &model.DBElecReply{
ID: msg.ID,
MSGID: msg.RefID,
Reply: msg.Message,
Hidden: msg.State == 2,
CTime: msg.ParseCTime(),
MTime: msg.ParseMTime(),
}
if err = s.dao.UpsertElecReply(ctx, dbReply); err != nil {
return
}
default:
log.Error("old_ele_message unknown type: %+v", msg)
}
return
}
func (s *Service) handleElecOrder(ctx context.Context, order *model.DBOldElecPayOrder) (err error) {
log.Info("handleElecOrder order: %+v", order)
if !order.IsPaid() {
return
}
var ok bool
ok, err = s.dao.AddCacheOrderID(ctx, order.OrderID)
if err != nil {
return
}
// 重复消费
if !ok {
log.Info("handleElecOrder order: %+v, has consumed before", order)
err = nil
return
}
if order.IsHiddnRank() {
log.Info("handleElecOrder order: %+v which app_id == 19", order)
return
}
tradeInfo, err := s.dao.RawOldElecTradeInfo(ctx, order.OrderID)
if err != nil {
return
}
avID := int64(0)
if tradeInfo != nil {
// log.Info("RawOldElecTradeInfo data not found, order: %+v", order)
if avID, err = strconv.ParseInt(tradeInfo.AVID, 10, 64); err != nil {
log.Error("handleElecOrder cant convert avID from: %s, err: %+v", tradeInfo.AVID, err)
avID = 0
}
}
var (
ver = monthlyBillVer(order.ParseMTime())
hidden = false
)
// 更新DB
tx, err := s.dao.BeginTranRank(ctx)
if err != nil {
return
}
rollbackFN := func() {
if theErr := s.dao.DelCacheOrderID(ctx, order.OrderID); theErr != nil {
log.Error("%+v", theErr)
}
tx.Rollback()
}
if avID != 0 {
if err = s.dao.TXUpsertElecAVRank(ctx, tx, 0, avID, order.UPMID, order.PayMID, order.ElecNum, hidden); err != nil {
rollbackFN()
return
}
if err = s.dao.TXUpsertElecAVRank(ctx, tx, ver, avID, order.UPMID, order.PayMID, order.ElecNum, hidden); err != nil {
rollbackFN()
return
}
}
if err = s.dao.TXUpsertElecUPRank(ctx, tx, 0, order.UPMID, order.PayMID, order.ElecNum, hidden); err != nil {
rollbackFN()
return
}
if err = s.dao.TXUpsertElecUPRank(ctx, tx, ver, order.UPMID, order.PayMID, order.ElecNum, hidden); err != nil {
rollbackFN()
return
}
if err = tx.Commit(); err != nil {
return
}
err = s.dao.RankElecUpdateOrder(ctx, avID, order.UPMID, order.PayMID, ver, order.ElecNum)
return
}
func (s *Service) handleElecUserSetting(ctx context.Context, setting *model.DBOldElecUserSetting) (err error) {
if setting.Status > 0 {
log.Info("handleElecUserSetting add setting: %+v", setting)
err = s.dao.ElecAddSetting(ctx, model.DefaultUserSetting, setting.MID, setting.BitValue())
} else {
log.Info("handleElecUserSetting delete setting: %+v", setting)
err = s.dao.ElecDeleteSetting(ctx, model.DefaultUserSetting, setting.MID, setting.BitValue())
}
if err != nil {
return
}
// 清理缓存
if err = s.dao.DelCacheUserSetting(ctx, setting.MID); err != nil {
log.Error("DelCacheUserSetting: %d, err: %+v", setting.MID, err)
err = nil
}
return
}

View File

@@ -0,0 +1,82 @@
package service
import (
"context"
"runtime/debug"
"time"
"go-common/app/job/main/ugcpay/model"
"go-common/library/log"
)
// SyncElecOrderList 同步老充电订单
func (s *Service) SyncElecOrderList(c context.Context) {
defer func() {
if x := recover(); x != nil {
log.Error("syncElecOrderSync panic(%+v) :\n %s", x, debug.Stack())
}
}()
var (
limit = 100
list = make([]*model.DBOldElecPayOrder, limit)
startID = int64(0)
err error
)
log.Info("Start syncElecOrderSync from elec_pay_order")
for len(list) >= limit {
log.Info("sync progress elec_pay_order fromID (%d)", startID)
// 1. load old data
if startID, list, err = s.dao.OldElecOrderList(_ctx, startID, limit); err != nil {
log.Error("%+v", err)
return
}
// 2. save new data
for _, ele := range list {
if err = s.handleElecOrder(_ctx, ele); err != nil {
log.Error("s.handleElecOrder: %+v, err: %+v", ele, err)
return
}
}
// 3. give db a break time
time.Sleep(time.Millisecond * 20)
}
log.Info("End syncElecOrderSync from elec_pay_order")
}
// SyncElecMessageList 同步老充电留言
func (s *Service) SyncElecMessageList(c context.Context) {
defer func() {
if x := recover(); x != nil {
log.Error("syncElecMessageList panic(%+v) :\n %s", x, debug.Stack())
}
}()
var (
limit = 100
list = make([]*model.DBOldElecMessage, limit)
startID = int64(0)
err error
)
log.Info("Start syncElecMessageList from elec_message")
for len(list) >= limit {
log.Info("sync progress elec_message fromID (%d)", startID)
// 1. load old data
if startID, list, err = s.dao.OldElecMessageList(_ctx, startID, limit); err != nil {
log.Error("%+v", err)
return
}
// 2. save new data
for _, ele := range list {
if err = s.handleOldElecMessage(_ctx, ele); err != nil {
log.Error("s.handleOldElecMessage: %+v, err: %+v", ele, err)
return
}
}
// 3. give db a break time
time.Sleep(time.Millisecond * 20)
}
log.Info("End syncElecMessageList from elec_message")
}

View File

@@ -0,0 +1,177 @@
package service
import (
"context"
"time"
"go-common/app/job/main/ugcpay/dao"
"go-common/app/job/main/ugcpay/model"
"go-common/library/log"
)
type limitedList interface {
LimitSize() int
BeginID(ctx context.Context) (id int64, err error)
List(ctx context.Context, beginID int64) (maxID int64, list []interface{}, err error)
}
func runLimitedList(ctx context.Context, ll limitedList, sleep time.Duration, handler func(ctx context.Context, ele interface{}) error) (err error) {
if ll.LimitSize() <= 0 {
return
}
var (
id int64
list = make([]interface{}, ll.LimitSize())
)
if id, err = ll.BeginID(ctx); err != nil {
return
}
for len(list) >= ll.LimitSize() {
if id, list, err = ll.List(ctx, id); err != nil {
return
}
for _, ele := range list {
if err = handler(ctx, ele); err != nil {
log.Error("handle failed, ele: %+v, err: %+v", ele, err)
err = nil
continue
}
if sleep > 0 {
time.Sleep(sleep)
}
}
}
return
}
type orderPaidLL struct {
beginTime time.Time
endTime time.Time
limit int
dao *dao.Dao
}
func (o *orderPaidLL) LimitSize() int {
return o.limit
}
func (o *orderPaidLL) BeginID(ctx context.Context) (id int64, err error) {
return o.dao.MinIDOrderPaid(ctx, o.beginTime)
}
func (o *orderPaidLL) List(ctx context.Context, beginID int64) (maxID int64, list []interface{}, err error) {
var rawList []*model.Order
if maxID, rawList, err = o.dao.OrderPaidList(ctx, o.beginTime, o.endTime, beginID, o.limit); err != nil {
return
}
log.Info("orderPaidLL beginID: %d, beginTime: %+v, endTime: %+v, limit: %d, size: %d", beginID, o.beginTime, o.endTime, o.limit, len(rawList))
for _, r := range rawList {
list = append(list, r)
}
return
}
type orderRefundedLL struct {
beginTime time.Time
endTime time.Time
limit int
dao *dao.Dao
}
func (o *orderRefundedLL) LimitSize() int {
return o.limit
}
func (o *orderRefundedLL) BeginID(ctx context.Context) (id int64, err error) {
return o.dao.MinIDOrderRefunded(ctx, o.beginTime)
}
func (o *orderRefundedLL) List(ctx context.Context, beginID int64) (maxID int64, list []interface{}, err error) {
var rawList []*model.Order
if maxID, rawList, err = o.dao.OrderRefundedList(ctx, o.beginTime, o.endTime, beginID, o.limit); err != nil {
return
}
log.Info("orderRefundedLL beginID: %d, beginTime: %+v, endTime: %+v, limit: %d, size: %d", beginID, o.beginTime, o.endTime, o.limit, len(rawList))
for _, r := range rawList {
list = append(list, r)
}
return
}
type dailyBillLLByVer struct {
ver int64
limit int
dao *dao.Dao
}
func (d *dailyBillLLByVer) LimitSize() int {
return d.limit
}
func (d *dailyBillLLByVer) BeginID(ctx context.Context) (id int64, err error) {
return d.dao.MinIDDailyBillByVer(ctx, d.ver)
}
func (d *dailyBillLLByVer) List(ctx context.Context, beginID int64) (maxID int64, list []interface{}, err error) {
var rawList []*model.DailyBill
if maxID, rawList, err = d.dao.DailyBillListByVer(ctx, d.ver, beginID, d.limit); err != nil {
return
}
log.Info("dailyBillLLByVer beginID: %d, ver: %d, limit: %d, size: %d", beginID, d.ver, d.limit, len(rawList))
for _, r := range rawList {
list = append(list, r)
}
return
}
type dailyBillLLByMonthVer struct {
monthVer int64
limit int
dao *dao.Dao
}
func (d *dailyBillLLByMonthVer) LimitSize() int {
return d.limit
}
func (d *dailyBillLLByMonthVer) BeginID(ctx context.Context) (id int64, err error) {
return d.dao.MinIDDailyBillByMonthVer(ctx, d.monthVer)
}
func (d *dailyBillLLByMonthVer) List(ctx context.Context, beginID int64) (maxID int64, list []interface{}, err error) {
var rawList []*model.DailyBill
if maxID, rawList, err = d.dao.DailyBillListByMonthVer(ctx, d.monthVer, beginID, d.limit); err != nil {
return
}
log.Info("dailyBillLLByMonthVer beginID: %d, monthVer: %d, limit: %d, size: %d", beginID, d.monthVer, d.limit, len(rawList))
for _, r := range rawList {
list = append(list, r)
}
return
}
type monthlyBillLL struct {
ver int64
limit int
dao *dao.Dao
}
func (m *monthlyBillLL) LimitSize() int {
return m.limit
}
func (m *monthlyBillLL) BeginID(ctx context.Context) (id int64, err error) {
return m.dao.MinIDMonthlyBill(ctx, m.ver)
}
func (m *monthlyBillLL) List(ctx context.Context, beginID int64) (maxID int64, list []interface{}, err error) {
var rawList []*model.Bill
if maxID, rawList, err = m.dao.MonthlyBillList(ctx, m.ver, beginID, m.limit); err != nil {
return
}
log.Info("monthlyBillLL beginID: %d, ver: %d, limit: %d, size: %d", beginID, m.ver, m.limit, len(rawList))
for _, r := range rawList {
list = append(list, r)
}
return
}

View File

@@ -0,0 +1,45 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["pay_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/service/main/ugcpay/conf:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["pay.go"],
importpath = "go-common/app/job/main/ugcpay/service/pay",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/log:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,196 @@
package pay
import (
"bytes"
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"net/url"
"strconv"
"time"
"github.com/pkg/errors"
"go-common/library/log"
)
// Pay is.
type Pay struct {
ID string
Token string
RechargeShellNotifyURL string
}
// TraceID .
func (p *Pay) TraceID() string {
return strconv.FormatInt(time.Now().UnixNano(), 10)
}
// RechargeShellReq .
type RechargeShellReq struct {
CustomerID string `json:"customerId"`
ProductName string `json:"productName"`
Rate string `json:"rate"`
NotifyURL string `json:"notifyUrl"`
Timestamp int64 `json:"timestamp"`
SignType string `json:"signType"`
Sign string `json:"sign"`
Data []RechargeShellData `json:"data"`
}
// RechargeShellData .
type RechargeShellData struct {
ThirdOrderNo string `json:"thirdOrderNo"`
MID int64 `json:"mid"`
ThirdCoin string `json:"thirdCoin"`
Brokerage string `json:"brokerage"`
ThirdCtime int64 `json:"thirdCtime"`
}
// RechargeShell 转入贝壳
func (p *Pay) RechargeShell(orderID string, mid int64, assetBP int64, shell int64) (params url.Values, jsonData string, err error) {
var (
productName = "UGC付费"
rate = "1.00"
timestamp = time.Now().Unix() * 1000
thirdCoin = float64(assetBP) / 100
brokerage = float64(shell) / 100
)
params = make(url.Values)
params.Set("customerId", p.ID)
params.Set("productName", productName)
params.Set("rate", rate)
params.Set("notifyUrl", p.RechargeShellNotifyURL)
params.Set("timestamp", strconv.FormatInt(timestamp, 10))
params.Set("data", fmt.Sprintf("[{brokerage=%.2f&mid=%d&thirdCoin=%.2f&thirdCtime=%d&thirdOrderNo=%s}]", brokerage, mid, thirdCoin, timestamp, orderID))
p.Sign(params)
data := RechargeShellData{
ThirdOrderNo: orderID,
MID: mid,
ThirdCoin: fmt.Sprintf("%.2f", thirdCoin),
Brokerage: fmt.Sprintf("%.2f", brokerage),
ThirdCtime: timestamp,
}
req := RechargeShellReq{
CustomerID: p.ID,
ProductName: productName,
Rate: rate,
NotifyURL: p.RechargeShellNotifyURL,
Timestamp: timestamp,
SignType: params.Get("signType"),
Sign: params.Get("sign"),
Data: []RechargeShellData{data},
}
payBytes, err := json.Marshal(req)
if err != nil {
err = errors.Wrapf(err, "pay.RechargeShell.ToJSON : %s", params.Encode())
return
}
jsonData = string(payBytes)
return
}
// CheckOrder 对账param
func (p Pay) CheckOrder(txID string) (params url.Values) {
params = make(url.Values)
params.Set("customerId", p.ID)
params.Set("txIds", txID)
return
}
// CheckRefundOrder 退款对账param
func (p Pay) CheckRefundOrder(txID string) (params url.Values) {
params = make(url.Values)
params.Set("customerId", p.ID)
params.Set("txIds", txID)
return
}
// Query 返回订单查询param
func (p *Pay) Query(orderID string) (params url.Values) {
params = make(url.Values)
params.Set("customerId", p.ID)
params.Set("orderIds", orderID)
params.Set("timestamp", strconv.FormatInt(time.Now().Unix()*1000, 10))
params.Set("traceId", p.TraceID())
params.Set("version", "1.0")
return
}
// ToJSON param to json
func (p *Pay) ToJSON(params url.Values) (j string, err error) {
var (
payBytes []byte
pmap = make(map[string]string)
)
for k, v := range params {
if len(v) > 0 {
pmap[k] = v[0]
}
}
if payBytes, err = json.Marshal(pmap); err != nil {
err = errors.Wrapf(err, "pay.ToJSON : %s", params.Encode())
return
}
j = string(payBytes)
return
}
// DeviceType 支付平台DeviceType
func (p *Pay) DeviceType(platform string) (t int64) {
// 支付设备渠道类型, 1 pc 2 webapp 3 app 4jsapi 5 server 6小程序支付 7聚合二维码支付
switch platform {
case "ios", "android":
return 3
default:
return 1
}
}
// Sign 支付平台接口签名
func (p *Pay) Sign(params url.Values) (err error) {
params.Set("signType", "MD5")
sortedStr := params.Encode()
if sortedStr, err = url.QueryUnescape(sortedStr); err != nil {
return
}
b := bytes.Buffer{}
b.WriteString(sortedStr)
b.WriteString("&token=" + p.Token)
signMD5 := md5.Sum(b.Bytes())
sign := hex.EncodeToString(signMD5[:])
params.Set("sign", sign)
return
}
// Verify 支付平台返回param校验
func (p *Pay) Verify(params url.Values) (ok bool) {
var (
rs = params.Get("sign")
s string
)
ok = false
defer func() {
if !ok {
params.Set("sign", rs)
log.Error("Verify pay sign error, expect : %s, actual : %s, params : %s", s, rs, params.Encode())
}
}()
if rs == "" {
return
}
params.Del("sign")
if err := p.Sign(params); err != nil {
log.Error("Verify pay sign error : %+v", err)
return
}
s = params.Get("sign")
if rs == s {
ok = true
return
}
return
}

View File

@@ -0,0 +1,131 @@
package pay
import (
"encoding/json"
"flag"
"net/url"
"os"
"testing"
"go-common/app/service/main/ugcpay/conf"
. "github.com/smartystreets/goconvey/convey"
)
var (
p *Pay
)
func TestMain(m *testing.M) {
flag.Set("conf", "../../cmd/test.toml")
if err := conf.Init(); err != nil {
panic(err)
}
p = &Pay{
ID: conf.Conf.Biz.Pay.ID,
Token: conf.Conf.Biz.Pay.Token,
RechargeShellNotifyURL: "http://api.bilibili.co/x/internal/ugcpay/trade/recharge/callback",
}
m.Run()
os.Exit(0)
}
func TestCheckOrder(t *testing.T) {
Convey("", t, func() {
param := p.CheckOrder("3059753508505497600")
p.Sign(param)
t.Log(p.ToJSON(param))
})
}
func TestCheckRefundOrder(t *testing.T) {
Convey("", t, func() {
param := p.CheckRefundOrder("3059753508505497600")
p.Sign(param)
t.Log(p.ToJSON(param))
})
}
func TestRechargeShell(t *testing.T) {
var (
orderID = "123"
mid = int64(46333)
assetBP = int64(1)
shell = int64(1)
)
Convey("", t, func() {
_, json, err := p.RechargeShell(orderID, mid, assetBP, shell)
So(err, ShouldBeNil)
t.Log(json)
})
}
func TestSign(t *testing.T) {
Convey("", t, func() {
var (
param = url.Values{
"customerId": []string{"10017"},
"deviceType": []string{"3"},
"notifyUrl": []string{"http://api.bilibili.co/x/internal/ugcpay/trade/pay/callback"},
"orderCreateTime": []string{"1539935981000"},
"orderExpire": []string{"1800"},
"orderId": []string{"224"},
"originalAmount": []string{"2000"},
"payAmount": []string{"2000"},
"productId": []string{"10110688"},
"serviceType": []string{"99"},
"showTitle": []string{"传点什么好呢?"},
"timestamp": []string{"1539935981000"},
"traceId": []string{"1539935981967342977"},
"uid": []string{"27515244"},
"version": []string{"1.0"},
"feeType": []string{"CNY"},
}
)
err := p.Sign(param)
So(err, ShouldBeNil)
pmap := make(map[string]string)
var payBytes []byte
for k, v := range param {
if len(v) > 0 {
pmap[k] = v[0]
}
}
if payBytes, err = json.Marshal(pmap); err != nil {
return
}
t.Log(string(payBytes))
})
}
func TestSignVerify(t *testing.T) {
Convey("", t, func() {
var (
param = url.Values{
"customerId": []string{"10017"},
"deviceType": []string{"3"},
"notifyUrl": []string{"http://api.bilibili.co/x/internal/ugcpay/trade/pay/callback"},
"orderCreateTime": []string{"1539935981000"},
"orderExpire": []string{"1800"},
"orderId": []string{"15"},
"originalAmount": []string{"2000"},
"payAmount": []string{"2000"},
"productId": []string{"10110688"},
"serviceType": []string{"99"},
"showTitle": []string{"传点什么好呢?"},
"timestamp": []string{"1539935981000"},
"traceId": []string{"1539935981967342977"},
"uid": []string{"27515244"},
"version": []string{"1.0"},
"feeType": []string{"CNY"},
}
)
err := p.Sign(param)
So(err, ShouldBeNil)
ok := p.Verify(param)
So(ok, ShouldBeTrue)
})
}

View File

@@ -0,0 +1,85 @@
package service
// import (
// "context"
// "fmt"
// "net/url"
// "time"
// "go-common/app/job/main/ugcpay/model"
// "go-common/library/log"
// )
// func (s *Service) repairOrderUser() {
// var (
// ctx = context.Background()
// beginTime = time.Date(2018, time.October, 1, 0, 0, 0, 0, time.Local)
// endTime = time.Now()
// state = "paid"
// limit = 1000
// )
// maxID, orderList, err := s.dao.OrderList(ctx, beginTime, endTime, state, 0, limit)
// if err != nil {
// log.Error("%+v", err)
// return
// }
// log.Info("repaireOrderUser got list: %d, maxID: %d", len(orderList), maxID)
// for _, order := range orderList {
// // 修复 pay_time
// order.PayTime = order.CTime
// // 修复 real_fee
// asset, err := s.dao.Asset(ctx, order.OID, order.OType, order.Currency)
// if err != nil {
// log.Error("order:%+v, err: %+v", order, err)
// continue
// }
// order.RealFee = asset.Price
// // 修复 pay_id
// if len(order.PayID) < 2 {
// order.PayID, err = s.tradeQuery(ctx, order.OrderID)
// if err != nil {
// log.Error("order:%+v, err: %+v", order, err)
// continue
// }
// }
// if _, err = s.dao.UpdateOrder(ctx, order); err != nil {
// log.Error("order:%+v, err: %+v", order, err)
// err = nil
// }
// log.Info("repaireOrderUser success, order: %+v", order)
// }
// }
// func (s *Service) tradeQuery(ctx context.Context, orderID string) (payID string, err error) {
// // 2. 从支付平台获取订单状态
// var (
// params url.Values
// jsonData string
// orders map[string][]*model.PayOrder
// payOrders []*model.PayOrder
// ok bool
// )
// params = s.pay.Query(orderID)
// if err = s.pay.Sign(params); err != nil {
// return
// }
// if jsonData, err = s.pay.ToJSON(params); err != nil {
// return
// }
// if orders, err = s.dao.PayQuery(ctx, jsonData); err != nil {
// return
// }
// if payOrders, ok = orders[orderID]; !ok || len(payOrders) == 0 {
// log.Info("tradeQuery from pay platform not found order: %s", orderID)
// return
// }
// for _, po := range payOrders {
// if po.TXID > 0 {
// log.Info("tradeQuery order: %s, txID: %d", orderID, po.TXID)
// payID = fmt.Sprintf("%d", po.TXID)
// return
// }
// }
// return
// }

View File

@@ -0,0 +1,132 @@
package service
import (
"context"
"math/rand"
"time"
"go-common/app/job/main/ugcpay/conf"
"go-common/app/job/main/ugcpay/dao"
"go-common/app/job/main/ugcpay/service/pay"
"go-common/library/queue/databus"
"github.com/robfig/cron"
)
var (
ctx = context.Background()
)
// Service struct
type Service struct {
c *conf.Config
dao *dao.Dao
binlogMQ *databus.Databus
elecBinlogMQ *databus.Databus
cron *cron.Cron
pay *pay.Pay
taskLog *taskLog
}
// New init
func New(c *conf.Config) (s *Service) {
s = &Service{
c: c,
dao: dao.New(c),
binlogMQ: databus.New(c.BinlogMQ),
elecBinlogMQ: databus.New(c.ElecBinlogMQ),
cron: cron.New(),
pay: &pay.Pay{
ID: conf.Conf.Biz.Pay.ID,
Token: conf.Conf.Biz.Pay.Token,
RechargeShellNotifyURL: conf.Conf.Biz.Pay.RechargeCallbackURL,
},
}
s.taskLog = &taskLog{
d: s.dao,
}
// 创建日账单任务
taskBillDaily := &taskBillDaily{
dao: s.dao,
pay: s.pay,
rnd: rand.New(rand.NewSource(time.Now().Unix())),
dayOffset: conf.Conf.Biz.Task.DailyBillOffset,
namePrefix: conf.Conf.Biz.Task.DailyBillPrefix,
tl: s.taskLog,
}
if err := s.cron.AddFunc(conf.Conf.Biz.Cron.TaskDailyBill, s.wrapDisProc(taskBillDaily)); err != nil {
panic(err)
}
// 创建up虚拟账户入账任务
taskAccountUser := &taskAccountUser{
dao: s.dao,
taskPre: taskBillDaily, // 前置任务
dayOffset: conf.Conf.Biz.Task.DailyBillOffset,
namePrefix: conf.Conf.Biz.Task.AccountUserPrefix,
tl: s.taskLog,
}
if err := s.cron.AddFunc(conf.Conf.Biz.Cron.TaskAccountUser, s.wrapDisProc(taskAccountUser)); err != nil {
panic(err)
}
// 创建资金池入账任务
taskAccountBiz := &taskAccountBiz{
dao: s.dao,
taskPre: taskBillDaily, // 前置任务
dayOffset: conf.Conf.Biz.Task.DailyBillOffset,
namePrefix: conf.Conf.Biz.Task.AccountBizPrefix,
tl: s.taskLog,
}
if err := s.cron.AddFunc(conf.Conf.Biz.Cron.TaskAccountBiz, s.wrapDisProc(taskAccountBiz)); err != nil {
panic(err)
}
// 创建月账单任务
taskBillMonthly := &taskBillMonthly{
dao: s.dao,
rnd: rand.New(rand.NewSource(time.Now().Unix())),
monthOffset: conf.Conf.Biz.Task.MonthBillOffset,
namePrefix: conf.Conf.Biz.Task.MonthBillPrefix,
tl: s.taskLog,
}
if err := s.cron.AddFunc(conf.Conf.Biz.Cron.TaskMonthlyBill, s.wrapDisProc(taskBillMonthly)); err != nil {
panic(err)
}
// 创建转贝壳任务
taskRechargeShell := &taskRechargeShell{
dao: s.dao,
pay: s.pay,
rnd: rand.New(rand.NewSource(time.Now().Unix())),
monthOffset: conf.Conf.Biz.Task.RechargeShellOffset,
namePrefix: conf.Conf.Biz.Task.RechargeShellPrefix,
tl: s.taskLog,
}
if err := s.cron.AddFunc(conf.Conf.Biz.Cron.TaskRechargeShell, s.wrapDisProc(taskRechargeShell)); err != nil {
panic(err)
}
s.cron.Start()
go s.binlogproc()
go s.elecbinlogproc()
// go s.repairOrderUser() 修复订单用
return s
}
// Ping Service
func (s *Service) Ping(c context.Context) (err error) {
return s.dao.Ping(c)
}
// Close Service
func (s *Service) Close() {
s.dao.Close()
}

View File

@@ -0,0 +1,59 @@
package service
import (
"testing"
)
type c struct {
balance int64
loss int64
expectUserRefund int64
expectBizRefund int64
}
func TestCalcRefundFee(t *testing.T) {
var min int64 = -20000
cases := []c{
c{
balance: 10000,
loss: 20000,
expectUserRefund: 20000,
expectBizRefund: 0,
}, c{
balance: 0,
loss: 10000,
expectUserRefund: 10000,
expectBizRefund: 0,
}, c{
balance: -1,
loss: 20000,
expectUserRefund: 19999,
expectBizRefund: 1,
}, c{
balance: -19999,
loss: 20000,
expectUserRefund: 1,
expectBizRefund: 19999,
}, c{
balance: -20000,
loss: 20000,
expectUserRefund: 0,
expectBizRefund: 20000,
}, c{
balance: -30000,
loss: 20000,
expectUserRefund: 0,
expectBizRefund: 20000,
},
}
for _, c := range cases {
bizRefund, userRefund := calcRefundFee(c.balance, c.loss, min)
if userRefund != c.expectUserRefund {
t.Fatalf("TestCalcRefundFee case: %+v expectUserRefund not right, actual: %d\n", c, userRefund)
}
if bizRefund != c.expectBizRefund {
t.Fatalf("TestCalcRefundFee case: %+v expectBizRefund not right, actual: %d\n", c, bizRefund)
}
}
}

View File

@@ -0,0 +1,155 @@
package service
import (
"context"
"runtime/debug"
"go-common/app/job/main/ugcpay/dao"
"go-common/app/job/main/ugcpay/model"
xsql "go-common/library/database/sql"
"go-common/library/log"
"github.com/pkg/errors"
)
func (s *Service) wrapDisProc(tp TaskProcess) func() {
return func() {
defer func() {
if x := recover(); x != nil {
log.Error("task : %s, panic(%+v): %s", tp.Name(), x, debug.Stack())
}
}()
var (
ok bool
err error
)
if ok, err = s.taskCreate(tp.Name(), tp.TTL()); err != nil {
log.Info("s.taskCreate err: %+v", err)
return
}
if !ok {
log.Info("task : %s end, other task is running", tp.Name())
return
}
defer func() {
if err = s.taskDone(tp.Name()); err != nil {
log.Error("task : %s, taskDone error: %+v", tp.Name(), err)
}
}()
log.Info("task : %s, task start", tp.Name())
if err = tp.Run(); err != nil {
log.Error("task : %s end, error: %+v", tp.Name(), err)
}
}
}
// TaskProcess .
type TaskProcess interface {
Run() error // 运行任务
TTL() int32 // 任务的最长生命周期
Name() string // 任务名称
}
func (s *Service) taskCreate(task string, ttl int32) (ok bool, err error) {
log.Info("task create: %s, ttl: %d", task, ttl)
return s.dao.AddCacheTask(context.Background(), task, ttl)
}
func (s *Service) taskDone(task string) (err error) {
// return s.dao.DelCacheTask(context.Background(), task)
return
}
func checkOrCreateTaskFromLog(ctx context.Context, task TaskProcess, tl *taskLog, expectFN func(context.Context) (int64, error)) (finished bool, err error) {
var (
taskCreated bool
expect int64
)
if taskCreated, finished = tl.checkTask(task); finished {
log.Info("%s already finished", task.Name())
return
}
if !taskCreated {
if expect, err = expectFN(ctx); err != nil {
return
}
if _, err = tl.createTask(ctx, task, expect); err != nil {
return
}
}
return
}
func runTXCASTaskWithLog(ctx context.Context, task TaskProcess, tl *taskLog, biz func(context.Context, *xsql.Tx) (bool, error)) (err error) {
fn := func(ctx context.Context) (affected bool, err error) {
affected = true
tx, err := tl.d.BeginTran(ctx)
if err != nil {
return
}
if affected, err = biz(ctx, tx); err != nil {
// 业务报错不主动rollback
return
}
if err = tl.recordTaskSuccess(ctx, tx, task); err != nil {
tx.Rollback()
return
}
err = tx.Commit()
return
}
if err = runCAS(ctx, fn); err != nil {
tl.recordTaskFailure(ctx, task)
}
return
}
type taskLog struct {
d *dao.Dao
}
func (t *taskLog) createTask(ctx context.Context, task TaskProcess, expect int64) (logTask *model.LogTask, err error) {
logTask = &model.LogTask{
Name: task.Name(),
Expect: expect,
State: "created",
}
logTask.ID, err = t.d.InsertLogTask(ctx, logTask)
return
}
func (t *taskLog) recordTaskSuccess(ctx context.Context, tx *xsql.Tx, task TaskProcess) (err error) {
_, err = t.d.TXIncrLogTaskSuccess(ctx, tx, task.Name())
if err != nil {
err = errors.Wrapf(err, "taskLog recordTaskSuccess: %s", task.Name())
}
return
}
func (t *taskLog) recordTaskFailure(ctx context.Context, task TaskProcess) {
_, err := t.d.IncrLogTaskFailure(ctx, task.Name())
if err != nil {
err = errors.Wrapf(err, "taskLog recordTaskFailure: %s", task.Name())
log.Error("%+v", err)
}
}
func (t *taskLog) checkTask(task TaskProcess) (created, finished bool) {
data, err := t.d.LogTask(ctx, task.Name())
if err != nil {
return
}
if data == nil {
return
}
log.Info("checkTask: %s, data: %+v", task.Name(), data)
created = true
if data.State == "success" {
finished = true
return
}
if data.Expect == data.Success {
finished = true
}
return
}

View File

@@ -0,0 +1,143 @@
package service
import (
"context"
"fmt"
"time"
"go-common/app/job/main/ugcpay/dao"
"go-common/app/job/main/ugcpay/model"
xsql "go-common/library/database/sql"
"go-common/library/log"
"github.com/pkg/errors"
)
type taskAccountBiz struct {
dao *dao.Dao
taskPre TaskProcess
dayOffset int
namePrefix string
tl *taskLog
}
func (s *taskAccountBiz) Run() (err error) {
// 检查日账单任务是否完成
if _, finished := s.tl.checkTask(s.taskPre); !finished {
log.Info("taskAccountBiz check task: %s not finished", s.taskPre.Name())
return nil
}
var (
ctx = context.Background()
finished bool
expectFN = func(ctx context.Context) (expect int64, err error) {
expect = 1
return
}
)
if finished, err = checkOrCreateTaskFromLog(ctx, s, s.tl, expectFN); err != nil || finished {
return
}
return runTXCASTaskWithLog(ctx, s, s.tl, s.run)
}
func (s *taskAccountBiz) TTL() int32 {
return 3600 * 2
}
func (s *taskAccountBiz) Name() string {
return fmt.Sprintf("%s_%d", s.namePrefix, dailyBillVer(time.Now()))
}
func (s *taskAccountBiz) run(ctx context.Context, tx *xsql.Tx) (affected bool, err error) {
var (
timeFrom, timeTo time.Time = dayRange(s.dayOffset)
ver = dailyBillVer(timeFrom)
bizAccount *model.BizAccount
bizAccountLog *model.AccountLog
sumPaidOrderRealfee int64
sumRefundedOrderRealfee int64
sumBillDailyIn int64
sumBillDailyOut int64
bizProfit int64
)
affected = true
if sumPaidOrderRealfee, err = s.dao.SumPaidOrderUserRealFee(ctx, timeFrom, timeTo); err != nil {
return
}
if sumRefundedOrderRealfee, err = s.dao.SumRefundedOrderUserRealFee(ctx, timeFrom, timeTo); err != nil {
return
}
if sumBillDailyIn, sumBillDailyOut, err = s.dao.SumDailyBill(ctx, ver); err != nil {
return
}
log.Info("taskAccountBiz: %s, sumPaidOrderRealfee: %d, sumRefundedOrderRealfee: %d, sumBillDailyIn: %d, sumBillDailyOut: %d", s.Name(), sumPaidOrderRealfee, sumRefundedOrderRealfee, sumBillDailyIn, sumBillDailyOut)
if sumPaidOrderRealfee < sumBillDailyIn {
err = errors.Errorf("taskAccountBiz find sumPaidOrderRealfee(%d) < sumBillDailyIn(%d), ver: %d", sumPaidOrderRealfee, sumBillDailyIn, ver)
return
}
if sumRefundedOrderRealfee < sumBillDailyOut {
err = errors.Errorf("taskAccountBiz find sumRefundedOrderRealfee(%d) < sumBillDailyOut(%d), ver: %d", sumRefundedOrderRealfee, sumBillDailyOut, ver)
return
}
// 日收益 - 日支出
bizProfit = (sumPaidOrderRealfee - sumBillDailyIn) - (sumRefundedOrderRealfee - sumBillDailyOut)
// 获得 biz_account
if bizAccount, err = s.dao.BizAccount(ctx, model.BizAsset, model.CurrencyBP); err != nil {
return
}
// 初始化 biz_account
if bizAccount == nil {
if bizAccount, err = initBizAccount(ctx, model.BizAsset, model.CurrencyBP, s.dao); err != nil {
return
}
}
bizAccountLog = &model.AccountLog{
AccountID: bizAccount.ID,
Name: s.Name(),
From: bizAccount.Balance,
To: bizAccount.Balance + bizProfit,
Ver: bizAccount.Ver + 1,
State: model.AccountStateProfit,
}
bizAccount.Balance = bizAccount.Balance + bizProfit
// 更新 biz account
rowAffected, err := s.dao.TXUpdateBizAccount(ctx, tx, bizAccount)
if err != nil {
tx.Rollback()
return
}
if rowAffected <= 0 {
log.Error("TXUpdateBizAccount no affected biz account: %+v", bizAccount)
tx.Rollback()
affected = false
return
}
// 添加资金池账户 log
err = s.dao.TXInsertBizAccountLog(ctx, tx, bizAccountLog)
if err != nil {
tx.Rollback()
return
}
log.Info("taskAccountBiz: %+v ", bizAccount)
return
}
func initBizAccount(ctx context.Context, biz, currency string, dao *dao.Dao) (bizAccount *model.BizAccount, err error) {
bizAccount = &model.BizAccount{
Biz: biz,
Currency: currency,
State: model.StateValid,
Ver: 1,
}
if bizAccount.ID, err = dao.InsertBizAccount(ctx, bizAccount); err != nil {
return
}
return
}

View File

@@ -0,0 +1,201 @@
package service
import (
"context"
"fmt"
"time"
"go-common/app/job/main/ugcpay/conf"
"go-common/app/job/main/ugcpay/dao"
"go-common/app/job/main/ugcpay/model"
xsql "go-common/library/database/sql"
"go-common/library/log"
"github.com/pkg/errors"
)
type taskAccountUser struct {
dao *dao.Dao
taskPre TaskProcess
dayOffset int
namePrefix string
tl *taskLog
}
func (s *taskAccountUser) Run() (err error) {
// 检查日账单任务是否完成
if _, finished := s.tl.checkTask(s.taskPre); !finished {
log.Info("taskAccountUser check task: %s not finished", s.taskPre.Name())
return nil
}
var (
ctx = context.Background()
finished bool
expectFN = func(ctx context.Context) (expect int64, err error) {
var (
beginTime, _ = dayRange(s.dayOffset)
ver = dailyBillVer(beginTime)
)
if expect, err = s.dao.CountDailyBillByVer(ctx, ver); err != nil {
return
}
return
}
)
if finished, err = checkOrCreateTaskFromLog(ctx, s, s.tl, expectFN); err != nil || finished {
return
}
return s.run(ctx)
}
func (s *taskAccountUser) TTL() int32 {
return 3600 * 2
}
func (s *taskAccountUser) Name() string {
return fmt.Sprintf("%s_%d", s.namePrefix, dailyBillVer(time.Now()))
}
func (s *taskAccountUser) run(ctx context.Context) (err error) {
ll := &dailyBillLLByVer{
limit: 1000,
dao: s.dao,
}
beginTime, _ := dayRange(s.dayOffset)
ll.ver = dailyBillVer(beginTime)
return runLimitedList(ctx, ll, time.Millisecond*2, s.runDailyBill)
}
func (s *taskAccountUser) runDailyBill(ctx context.Context, ele interface{}) (err error) {
dailyBill, ok := ele.(*model.DailyBill)
if !ok {
err = errors.Errorf("taskAccountUser convert ele: %+v failed", dailyBill)
return
}
log.Info("taskAccountUser handle dailyBill: %+v", dailyBill)
fn := func(ctx context.Context, tx *xsql.Tx) (affected bool, err error) {
var (
account *model.UserAccount
accountLog *model.AccountLog
userProfit = dailyBill.In - dailyBill.Out
)
affected = true
// 获得该 mid 的 account
if account, err = s.dao.UserAccount(ctx, dailyBill.MID, dailyBill.Biz, dailyBill.Currency); err != nil {
return
}
// 初始化 biz_account
if account == nil {
if account, err = s.initUserAccount(ctx, dailyBill.MID, dailyBill.Biz, dailyBill.Currency); err != nil {
return
}
}
// 虚拟账户平账,低于一定阈值由虚拟账户转出
if userProfit < 0 {
bizRefund, userRefund := calcRefundFee(account.Balance, -userProfit, conf.Conf.Biz.AccountUserMin)
userProfit = -userRefund
if bizRefund > 0 {
// 获得 biz_account
var bizAccount *model.BizAccount
if bizAccount, err = s.dao.BizAccount(ctx, model.BizAsset, model.CurrencyBP); err != nil {
return
}
// 初始化 biz_account
if bizAccount == nil {
if bizAccount, err = initBizAccount(ctx, model.BizAsset, model.CurrencyBP, s.dao); err != nil {
return
}
}
bizAccountLog := &model.AccountLog{
AccountID: bizAccount.ID,
Name: s.Name(),
From: bizAccount.Balance,
To: bizAccount.Balance - bizRefund,
Ver: bizAccount.Ver + 1,
State: model.AccountStateLoss,
}
bizAccount.Balance = bizAccount.Balance - bizRefund
// 更新 biz account
var rowAffected int64
if rowAffected, err = s.dao.TXUpdateBizAccount(ctx, tx, bizAccount); err != nil {
tx.Rollback()
return
}
if rowAffected <= 0 {
log.Error("TXUpdateBizAccount no affected biz account: %+v", bizAccount)
tx.Rollback()
affected = false
return
}
// 添加资金池账户 log
if err = s.dao.TXInsertBizAccountLog(ctx, tx, bizAccountLog); err != nil {
tx.Rollback()
return
}
}
}
accountLog = &model.AccountLog{
AccountID: account.ID,
Name: fmt.Sprintf("%s_%d", s.Name(), dailyBill.MID),
From: account.Balance,
To: account.Balance + userProfit,
Ver: account.Ver + 1,
State: model.AccountStateIncome,
}
account.Balance = account.Balance + userProfit
// 更新 user account
rowAffected, err := s.dao.TXUpdateUserAccount(ctx, tx, account)
if err != nil {
tx.Rollback()
return
}
if rowAffected <= 0 {
log.Error("TXUpdateUserAccount no affected user account: %+v", account)
tx.Rollback()
affected = false
return
}
// 添加资金池账户 log
err = s.dao.TXInsertUserAccountLog(ctx, tx, accountLog)
if err != nil {
tx.Rollback()
return
}
log.Info("taskAccountUser: %+v ", account)
return
}
return runTXCASTaskWithLog(ctx, s, s.tl, fn)
}
func (s *taskAccountUser) initUserAccount(ctx context.Context, mid int64, biz, currency string) (account *model.UserAccount, err error) {
account = &model.UserAccount{}
account.MID = mid
account.Biz = biz
account.Currency = currency
account.State = model.StateValid
account.Ver = 1
if account.ID, err = s.dao.InsertUserAccount(ctx, account); err != nil {
return
}
return
}
// 虚拟账户平账
func calcRefundFee(balance int64, loss int64, minBalance int64) (bizRefund int64, userRefund int64) {
if balance-loss >= minBalance {
userRefund = loss
bizRefund = 0
return
}
if balance > minBalance {
userRefund = balance - minBalance
}
bizRefund = loss - userRefund
return
}

View File

@@ -0,0 +1,471 @@
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
}

View File

@@ -0,0 +1,143 @@
package service
import (
"bytes"
"context"
"fmt"
"math/rand"
"time"
"go-common/app/job/main/ugcpay/dao"
"go-common/app/job/main/ugcpay/model"
xsql "go-common/library/database/sql"
"go-common/library/log"
"github.com/pkg/errors"
)
type taskBillMonthly struct {
dao *dao.Dao
rnd *rand.Rand
monthOffset int
namePrefix string
tl *taskLog
}
func (s *taskBillMonthly) Run() (err error) {
var (
ctx = context.Background()
finished bool
expectFN = func(ctx context.Context) (expect int64, err error) {
var (
beginTime, _ = monthRange(s.monthOffset)
monthVer = monthlyBillVer(beginTime)
)
if expect, err = s.dao.CountDailyBillByMonthVer(ctx, monthVer); err != nil {
return
}
return
}
)
if finished, err = checkOrCreateTaskFromLog(ctx, s, s.tl, expectFN); err != nil || finished {
return
}
return s.run(ctx)
}
func (s *taskBillMonthly) TTL() int32 {
return 3600 * 2
}
func (s *taskBillMonthly) Name() string {
return fmt.Sprintf("%s_%d", s.namePrefix, monthlyBillVer(time.Now()))
}
// 月账单生成
func (s *taskBillMonthly) run(ctx context.Context) (err error) {
ll := &dailyBillLLByMonthVer{
limit: 1000,
dao: s.dao,
}
beginTime, _ := monthRange(s.monthOffset)
ll.monthVer = monthlyBillVer(beginTime)
return runLimitedList(ctx, ll, time.Millisecond*2, s.runDailyBill)
}
func (s *taskBillMonthly) runDailyBill(ctx context.Context, ele interface{}) (err error) {
dailyBill, ok := ele.(*model.DailyBill)
if !ok {
return errors.Errorf("taskBillMonthly convert ele: %+v failed", dailyBill)
}
log.Info("taskBillMonthly start handle daily biil: %+v", dailyBill)
fn := func(ctx context.Context, tx *xsql.Tx) (affected bool, err error) {
var (
monthlyBill *model.Bill
monthVer = dailyBill.MonthVer
monthlyBillLog *model.LogBillMonthly
)
affected = true
// 获得该 mid 的 daily_bill
if monthlyBill, err = s.dao.MonthlyBill(ctx, dailyBill.MID, model.BizAsset, model.CurrencyBP, monthVer); err != nil {
return
}
if monthlyBill == nil {
if monthlyBill, err = s.initMonthlyBill(ctx, dailyBill.MID, dailyBill.Biz, dailyBill.Currency, dailyBill.MonthVer); err != nil {
return
}
}
monthlyBillLog = &model.LogBillMonthly{
BillID: monthlyBill.BillID,
FromIn: monthlyBill.In,
ToIn: monthlyBill.In + dailyBill.In,
FromOut: monthlyBill.Out,
ToOut: monthlyBill.Out + dailyBill.Out,
BillUserDailyID: dailyBill.BillID,
}
monthlyBill.In += dailyBill.In
monthlyBill.Out += dailyBill.Out
// 添加 monthly bill log , uk : daily_bill_id
_, err = s.dao.TXInsertLogMonthlyBill(ctx, tx, monthlyBillLog)
if err != nil {
tx.Rollback()
return
}
// 更新 monthly bill
_, err = s.dao.TXUpdateMonthlyBill(ctx, tx, monthlyBill)
if err != nil {
tx.Rollback()
return
}
log.Info("taskBillMonthly: %+v,from daily bill: %+v", monthlyBill, dailyBill)
return
}
return runTXCASTaskWithLog(ctx, s, s.tl, fn)
}
func (s *taskBillMonthly) initMonthlyBill(ctx context.Context, mid int64, biz, currency string, ver int64) (data *model.Bill, err error) {
data = &model.Bill{
BillID: orderID(s.rnd),
MID: mid,
Biz: biz,
Currency: currency,
In: 0,
Out: 0,
Ver: ver,
Version: 1,
}
if data.ID, err = s.dao.InsertMonthlyBill(ctx, data); err != nil {
return
}
return
}
func orderID(rnd *rand.Rand) string {
var b bytes.Buffer
b.WriteString(fmt.Sprintf("%05d", rnd.Int63n(99999)))
b.WriteString(fmt.Sprintf("%03d", time.Now().UnixNano()/1e6%1000))
b.WriteString(time.Now().Format("060102150405"))
return b.String()
}

View File

@@ -0,0 +1,223 @@
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 taskRechargeShell struct {
dao *dao.Dao
pay *pay.Pay
rnd *rand.Rand
monthOffset int
namePrefix string
tl *taskLog
}
func (s *taskRechargeShell) Run() (err error) {
var (
ctx = context.Background()
finished bool
expectFN = func(ctx context.Context) (expect int64, err error) {
var (
beginTime, _ = monthRange(s.monthOffset)
monthVer = monthlyBillVer(beginTime)
)
if expect, err = s.dao.CountMonthlyBillByVer(ctx, monthVer); err != nil {
return
}
return
}
)
if finished, err = checkOrCreateTaskFromLog(ctx, s, s.tl, expectFN); err != nil || finished {
return
}
return s.run(ctx)
}
func (s *taskRechargeShell) TTL() int32 {
return 3600 * 2
}
func (s *taskRechargeShell) Name() string {
return fmt.Sprintf("%s_%d", s.namePrefix, monthlyBillVer(time.Now()))
}
func (s *taskRechargeShell) run(ctx context.Context) (err error) {
ll := &monthlyBillLL{
limit: 1000,
dao: s.dao,
}
beginTime, _ := monthRange(s.monthOffset)
ll.ver = monthlyBillVer(beginTime)
return runLimitedList(ctx, ll, time.Millisecond*2, s.runMonthlyBill)
}
func (s *taskRechargeShell) runMonthlyBill(ctx context.Context, ele interface{}) (err error) {
monthlyBill, ok := ele.(*model.Bill)
if !ok {
return errors.Errorf("runMonthlyBill convert ele: %+v failed", monthlyBill)
}
log.Info("taskRechargeShell start handle monthly bill: %+v", monthlyBill)
var fn func(ctx context.Context, tx *xsql.Tx) (affected bool, err error)
if monthlyBill.In-monthlyBill.Out > 0 {
fn = s.fnRechargeShell(ctx, monthlyBill)
} else {
fn = s.fnRecordRecharge(ctx, monthlyBill)
}
if err = runTXCASTaskWithLog(ctx, s, s.tl, fn); err != nil {
return
}
return
}
func (s *taskRechargeShell) fnRecordRecharge(ctx context.Context, monthlyBill *model.Bill) func(ctx context.Context, tx *xsql.Tx) (affected bool, err error) {
return func(ctx context.Context, tx *xsql.Tx) (affected bool, err error) {
affected = true
var (
readyRecharge = monthlyBill.In - monthlyBill.Out
)
// 记录贝壳订单
orderRechargeShell := &model.OrderRechargeShell{
MID: monthlyBill.MID,
OrderID: orderID(s.rnd),
Biz: model.BizAsset,
Amount: readyRecharge,
State: "finished",
Ver: monthlyBill.Ver,
}
var (
orderRechargeShellLog = &model.OrderRechargeShellLog{
OrderID: orderRechargeShell.OrderID,
FromState: "finished",
ToState: "finished",
BillUserMonthlyID: monthlyBill.BillID,
}
)
_, err = s.dao.TXInsertOrderRechargeShell(ctx, tx, orderRechargeShell)
if err != nil {
tx.Rollback()
return
}
// 插入 order_recharge_shell_log, uk: bill_monthly_bill_id
_, err = s.dao.TXInsertOrderRechargeShellLog(ctx, tx, orderRechargeShellLog)
if err != nil {
tx.Rollback()
return
}
log.Info("fnRecordRecharge : %+v, orderRechargeShell: %+v", monthlyBill, orderRechargeShell)
return
}
}
func (s *taskRechargeShell) fnRechargeShell(ctx context.Context, monthlyBill *model.Bill) func(ctx context.Context, tx *xsql.Tx) (affected bool, err error) {
return func(ctx context.Context, tx *xsql.Tx) (affected bool, err error) {
affected = true
var (
account *model.UserAccount
readyRecharge = monthlyBill.In - monthlyBill.Out
accountLog *model.AccountLog
)
// 获得该 mid 的 虚拟账户
if account, err = s.dao.UserAccount(ctx, monthlyBill.MID, model.BizAsset, model.CurrencyBP); err != nil {
return
}
if account == nil {
err = errors.Errorf("runMonthlyBill not found valid user_account, monthly_bill: %+v", monthlyBill)
return
}
// 检查虚拟账户余额是否足够
if account.Balance-readyRecharge < 0 {
err = errors.Errorf("runMonthlyBill failed, account.Balance - readyRecharge < 0 !!!! account: %+v, monthly bill: %+v", account, monthlyBill)
return
}
accountLog = &model.AccountLog{
AccountID: account.ID,
Name: s.Name(),
From: account.Balance,
To: account.Balance - readyRecharge,
Ver: account.Ver + 1,
State: model.AccountStateWithdraw,
}
account.Balance -= readyRecharge
// 扣减虚拟账户余额
rowAffected, err := s.dao.TXUpdateUserAccount(ctx, tx, account)
if err != nil {
tx.Rollback()
return
}
if rowAffected <= 0 {
log.Error("TXUpdateUserAccount no affected user account: %+v", account)
affected = false
tx.Rollback()
return
}
err = s.dao.TXInsertUserAccountLog(ctx, tx, accountLog)
if err != nil {
tx.Rollback()
return
}
// 开始转贝壳
orderRechargeShell := &model.OrderRechargeShell{
MID: monthlyBill.MID,
OrderID: orderID(s.rnd),
Biz: model.BizAsset,
Amount: readyRecharge,
State: "created",
Ver: monthlyBill.Ver,
}
var (
orderRechargeShellLog = &model.OrderRechargeShellLog{
OrderID: orderRechargeShell.OrderID,
FromState: "created",
ToState: "created",
BillUserMonthlyID: monthlyBill.BillID,
}
)
_, err = s.dao.TXInsertOrderRechargeShell(ctx, tx, orderRechargeShell)
if err != nil {
tx.Rollback()
return
}
// 插入 order_recharge_shell_log, uk: bill_monthly_bill_id
_, err = s.dao.TXInsertOrderRechargeShellLog(ctx, tx, orderRechargeShellLog)
if err != nil {
tx.Rollback()
return
}
// 请求支付中心转贝壳
_, payJSON, err := s.pay.RechargeShell(orderRechargeShell.OrderID, orderRechargeShell.MID, orderRechargeShell.Amount, orderRechargeShell.Amount)
if err != nil {
tx.Rollback()
return
}
if err = s.dao.PayRechargeShell(ctx, payJSON); err != nil {
tx.Rollback()
return
}
log.Info("fnRechargeShell: %+v, account: %+v, orderRechargeShell: %+v", monthlyBill, account, orderRechargeShell)
return
}
}

View File

@@ -0,0 +1,62 @@
package service
import (
"context"
"time"
"go-common/app/job/main/ugcpay/conf"
"go-common/library/log"
)
func dayRange(offset int) (from, to time.Time) {
tmp := time.Now().AddDate(0, 0, offset)
from = time.Date(tmp.Year(), tmp.Month(), tmp.Day(), 0, 0, 0, 0, time.Local)
to = from.Add(24*time.Hour - 1)
return
}
func monthRange(offset int) (from, to time.Time) {
tmp := time.Now().AddDate(0, offset, 0)
from = time.Date(tmp.Year(), tmp.Month(), 1, 0, 0, 0, 0, time.Local)
to = from.AddDate(0, 1, 0).Add(-1)
return
}
func dailyBillVer(t time.Time) int64 {
// 2006-01-02 15:04:05
return int64(t.Year()*10000 + int(t.Month())*100 + t.Day())
}
func monthlyBillVer(t time.Time) int64 {
return int64(t.Year()*100 + int(t.Month()))
}
func runCAS(ctx context.Context, fn func(ctx context.Context) (effected bool, err error)) (err error) {
times := conf.Conf.Biz.RunCASTimes
if times <= 0 {
times = 2
}
effected := false
for times > 0 {
times--
if effected, err = fn(ctx); err != nil {
return
}
if effected {
return
}
}
if times <= 0 {
log.Error("runCAS failed!!!")
}
return
}
func calcAssetIncome(fee int64) (userIncome int64, bizIncome int64) {
if fee <= 0 {
return 0, 0
}
userIncome = int64((1.0 - conf.Conf.Biz.Tax.AssetRate) * float64(fee))
bizIncome = fee - userIncome
return
}