Initial commit
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
package srv_recharge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
"videoweb/pub/common/log"
|
||||
"videoweb/pub/common/srvdef"
|
||||
"videoweb/pub/common/useragent"
|
||||
"videoweb/pub/dataReport"
|
||||
"videoweb/pub/models/cointransmod"
|
||||
"videoweb/pub/models/eventmod"
|
||||
"videoweb/pub/models/rechargepackagemod"
|
||||
"videoweb/pub/models/usermod"
|
||||
"videoweb/pub/models/vipordermod"
|
||||
"videoweb/pub/payment"
|
||||
"videoweb/pub/pubdef"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
// BillCallbackParam 嘉华支付回调参数
|
||||
type BillCallbackParam struct {
|
||||
TradeNo string `json:"transNo" form:"transNo"`
|
||||
Oid string `json:"oid" form:"oid"`
|
||||
RealAmount int64 `json:"payMoney" form:"payMoney"`
|
||||
PaymentAt time.Time `json:"paymentAt" form:"paymentAt"`
|
||||
SuccessAt time.Time `json:"successAt" form:"successAt"`
|
||||
}
|
||||
|
||||
// OnPayCenterCallback 统一支付回调入口
|
||||
// 通过 vip_order 表查询订单,再按 OrderType 分支处理。
|
||||
func OnPayCenterCallback(ctx *gin.Context, param *BillCallbackParam) *srvdef.SrvErr {
|
||||
log.Info("srv_recharge:OnPayCenterCallback",
|
||||
log.Any("tradeNo", param.TradeNo),
|
||||
log.Any("oid", param.Oid),
|
||||
log.Any("realAmount", param.RealAmount),
|
||||
)
|
||||
if param.TradeNo == "" {
|
||||
return srvdef.New(srvdef.ErrParamError, "tradeNo 不能为空", "")
|
||||
}
|
||||
|
||||
order, serr := vipordermod.FindOneVipOrder(ctx, bson.M{
|
||||
vipordermod.FN.VipOrder.TradeNo: param.TradeNo,
|
||||
})
|
||||
if serr != nil {
|
||||
log.Error("OnPayCenterCallback FindOneVipOrder failed",
|
||||
log.Any("tradeNo", param.TradeNo), log.E(serr))
|
||||
return serr
|
||||
}
|
||||
|
||||
if order.Status == pubdef.VipOrderSuccess {
|
||||
log.Info("OnPayCenterCallback already success", log.Any("tradeNo", param.TradeNo))
|
||||
return nil
|
||||
}
|
||||
|
||||
if order.Oid != "" && order.Oid != param.Oid {
|
||||
return srvdef.New(srvdef.ErrParamError,
|
||||
fmt.Sprintf("oid 不匹配: %s vs %s", order.Oid, param.Oid), "")
|
||||
}
|
||||
|
||||
paymentAt := resolvePaymentAt(param)
|
||||
|
||||
if serr = vipordermod.UpdateOrderSuccess(ctx, param.TradeNo, param.Oid, param.RealAmount, paymentAt); serr != nil {
|
||||
log.Error("OnPayCenterCallback UpdateOrderSuccess failed",
|
||||
log.Any("tradeNo", param.TradeNo), log.E(serr))
|
||||
return serr
|
||||
}
|
||||
|
||||
orderSnap := *order
|
||||
realAmount := param.RealAmount
|
||||
oid := param.Oid
|
||||
|
||||
switch order.OrderType {
|
||||
case vipordermod.OrderTypeCoin:
|
||||
go handleCoinOrderSuccess(orderSnap, oid, realAmount)
|
||||
default:
|
||||
go handleVipOrderSuccess(orderSnap, oid, realAmount)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleVipOrderSuccess(order vipordermod.VipOrder, oid string, realAmount int64) {
|
||||
ctx := context.Background()
|
||||
|
||||
pkg, err := findVipPackage(ctx, order.CardId)
|
||||
if err != nil {
|
||||
log.Error("handleVipOrderSuccess find package failed",
|
||||
log.Any("userId", order.UserId), log.Any("packageId", order.CardId), log.E(err))
|
||||
return
|
||||
}
|
||||
|
||||
expire, err := payment.ActivateVip(ctx, order.UserId, pkg)
|
||||
if err != nil {
|
||||
log.Error("handleVipOrderSuccess ActivateVip failed, retrying",
|
||||
log.Any("userId", order.UserId), log.Any("packageId", order.CardId), log.E(err))
|
||||
time.Sleep(3 * time.Second)
|
||||
expire, err = payment.ActivateVip(ctx, order.UserId, pkg)
|
||||
if err != nil {
|
||||
log.Error("handleVipOrderSuccess ActivateVip retry failed",
|
||||
log.Any("userId", order.UserId), log.Any("packageId", order.CardId), log.E(err))
|
||||
}
|
||||
}
|
||||
log.Info("handleVipOrderSuccess done", log.Any("userId", order.UserId), log.Any("expire", expire))
|
||||
|
||||
if pkg.GiftCoin > 0 {
|
||||
_ = payment.WriteCoinTransByUser(ctx, payment.WriteCoinTransOpt{
|
||||
UserId: order.UserId,
|
||||
FlowDir: cointransmod.FlowIn,
|
||||
TranType: cointransmod.TranTypeVipGiftCoin,
|
||||
Amount: pkg.GiftCoin,
|
||||
OrderNo: order.TradeNo,
|
||||
Remark: payment.CoinTransRemark(cointransmod.TranTypeVipGiftCoin, pkg.Name),
|
||||
})
|
||||
}
|
||||
|
||||
reportVipOrderPaid(ctx, &order, oid, realAmount, expire)
|
||||
}
|
||||
|
||||
func handleCoinOrderSuccess(order vipordermod.VipOrder, oid string, realAmount int64) {
|
||||
ctx := context.Background()
|
||||
|
||||
if err := addUserCoins(ctx, order.UserId, order.Coin); err != nil {
|
||||
log.Error("handleCoinOrderSuccess addUserCoins failed, retrying",
|
||||
log.Any("userId", order.UserId), log.Any("coin", order.Coin), log.E(err))
|
||||
time.Sleep(3 * time.Second)
|
||||
if err = addUserCoins(ctx, order.UserId, order.Coin); err != nil {
|
||||
log.Error("handleCoinOrderSuccess addUserCoins retry failed",
|
||||
log.Any("userId", order.UserId), log.Any("coin", order.Coin), log.E(err))
|
||||
}
|
||||
}
|
||||
log.Info("handleCoinOrderSuccess done", log.Any("userId", order.UserId), log.Any("coin", order.Coin))
|
||||
|
||||
_ = payment.WriteCoinTransByUser(ctx, payment.WriteCoinTransOpt{
|
||||
UserId: order.UserId,
|
||||
FlowDir: cointransmod.FlowIn,
|
||||
TranType: cointransmod.TranTypeCoinRecharge,
|
||||
Amount: order.Coin,
|
||||
OrderNo: order.TradeNo,
|
||||
Remark: payment.CoinTransRemark(cointransmod.TranTypeCoinRecharge, ""),
|
||||
})
|
||||
|
||||
reportCoinOrderPaid(ctx, &order, oid, realAmount)
|
||||
}
|
||||
|
||||
func resolvePaymentAt(param *BillCallbackParam) time.Time {
|
||||
if !param.PaymentAt.IsZero() {
|
||||
return param.PaymentAt
|
||||
}
|
||||
if !param.SuccessAt.IsZero() {
|
||||
return param.SuccessAt
|
||||
}
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
func findVipPackage(ctx context.Context, packageId int64) (*rechargepackagemod.RechargePackage, *srvdef.SrvErr) {
|
||||
pkg, serr := rechargepackagemod.FindOneRechargePackage(ctx, bson.M{
|
||||
rechargepackagemod.FN.RechargePackage.Id: packageId,
|
||||
rechargepackagemod.FN.RechargePackage.PackageType: rechargepackagemod.PackageTypeVip,
|
||||
})
|
||||
if serr != nil || pkg == nil {
|
||||
return nil, srvdef.New(srvdef.ErrNoData, "会员套餐不存在", "")
|
||||
}
|
||||
return pkg, nil
|
||||
}
|
||||
|
||||
func addUserCoins(ctx context.Context, userId int64, coin int64) *srvdef.SrvErr {
|
||||
return usermod.UpdateOneUser(ctx,
|
||||
bson.M{usermod.FN.User.Id: userId},
|
||||
bson.M{
|
||||
"$inc": bson.M{usermod.FN.User.Recharge: coin},
|
||||
"$set": bson.M{usermod.FN.User.UpdatedAt: time.Now()},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func reportVipOrderPaid(ctx context.Context, order *vipordermod.VipOrder, oid string, realAmount int64, vipExpire time.Time) {
|
||||
user, _ := usermod.FindOneUser(ctx, bson.M{usermod.FN.User.Id: order.UserId})
|
||||
districtCode, ip := "", order.IPAddr
|
||||
if user != nil {
|
||||
districtCode = user.DistrictCode
|
||||
}
|
||||
|
||||
ua := useragent.UA{SysType: order.SysType}
|
||||
eventId := newEventId(pubdef.TrackerAppId(), order.UserId, order.TradeNo, order.CreatedAt.Unix())
|
||||
cf := dataReport.NewCommonField(eventId, dataReport.EventTypeOrderPaid, districtCode, ua, strconv.FormatInt(user.Id, 10), ip)
|
||||
|
||||
var expireTS int64
|
||||
if !vipExpire.IsZero() {
|
||||
expireTS = vipExpire.Unix()
|
||||
}
|
||||
cf.Payload = dataReport.JsonRawMessage(&dataReport.OrderPaidEvent{
|
||||
OrderID: order.TradeNo,
|
||||
OrderType: "vip_subscription",
|
||||
ProductID: strconv.FormatInt(order.CardId, 10),
|
||||
Amount: dataReport.JsonNumber(realAmount),
|
||||
Currency: "CNY",
|
||||
VIPExpirationTime: dataReport.JsonNumber(expireTS),
|
||||
PayType: order.PayMode,
|
||||
PayChannel: order.PayChannel,
|
||||
TransactionID: oid,
|
||||
CreateTime: dataReport.JsonNumber(order.CreatedAt.Unix()),
|
||||
})
|
||||
insertEvent(ctx, cf, order.TradeNo)
|
||||
}
|
||||
|
||||
func reportCoinOrderPaid(ctx context.Context, order *vipordermod.VipOrder, oid string, realAmount int64) {
|
||||
user, _ := usermod.FindOneUser(ctx, bson.M{usermod.FN.User.Id: order.UserId})
|
||||
districtCode, ip := "", order.IPAddr
|
||||
if user != nil {
|
||||
districtCode = user.DistrictCode
|
||||
}
|
||||
|
||||
ua := useragent.UA{SysType: order.SysType}
|
||||
eventId := newEventId(pubdef.TrackerAppId(), order.UserId, order.TradeNo, order.CreatedAt.Unix())
|
||||
cf := dataReport.NewCommonField(eventId, dataReport.EventTypeOrderPaid, districtCode, ua, strconv.FormatInt(user.Id, 10), ip)
|
||||
|
||||
cf.Payload = dataReport.JsonRawMessage(&dataReport.OrderPaidEvent{
|
||||
OrderID: order.TradeNo,
|
||||
OrderType: "coin_purchase",
|
||||
ProductID: strconv.FormatInt(order.CoinCfgId, 10),
|
||||
Amount: dataReport.JsonNumber(realAmount),
|
||||
Currency: "CNY",
|
||||
CoinQuantity: dataReport.JsonNumber(order.Coin),
|
||||
PayType: order.PayMode,
|
||||
PayChannel: order.PayChannel,
|
||||
TransactionID: oid,
|
||||
CreateTime: dataReport.JsonNumber(order.CreatedAt.Unix()),
|
||||
})
|
||||
insertEvent(ctx, cf, order.TradeNo)
|
||||
}
|
||||
|
||||
func insertEvent(ctx context.Context, cf dataReport.CommonField, tradeNo string) {
|
||||
data, _ := json.Marshal(cf)
|
||||
if err := eventmod.InsertOneEvent(ctx, &eventmod.Event{
|
||||
EventId: cf.EventID,
|
||||
EventType: cf.Event,
|
||||
Data: string(data),
|
||||
Status: eventmod.Default,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}); err != nil {
|
||||
log.Error("insertEvent failed", log.Any("tradeNo", tradeNo), log.E(err))
|
||||
}
|
||||
}
|
||||
|
||||
func newEventId(appId string, uid int64, orderId interface{}, createTime int64) string {
|
||||
input := fmt.Sprintf("%v%v%v%v", appId, uid, orderId, createTime)
|
||||
h := md5.New()
|
||||
h.Write([]byte(input))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
Reference in New Issue
Block a user