This commit is contained in:
lwl0608 2024-03-13 20:35:18 +08:00
parent 3273330c76
commit b833ecdc9e
11 changed files with 267 additions and 25 deletions

View File

@ -46,3 +46,11 @@ type DealOneCoinOrderReq struct {
type DealOneOrderReq struct {
OrderId string `json:"order_id"`
}
type RefundOrderReq struct {
OrderId string `json:"order_id"`
Operator string `json:"operator"`
}
type RefundOrderData struct {
}

View File

@ -232,6 +232,7 @@ func Init(r *gin.Engine) {
opVasPayGroup := r.Group("/op/vas", PrepareOp())
opVasPayGroup.POST("create_order", middleware.JSONParamValidator(vasproto.OpCreateOrderReq{}), OpCreateOrder)
opVasPayGroup.POST("coin_order_list", middleware.JSONParamValidator(vasproto.OpCoinOrderListReq{}), OpOrderList)
opVasPayGroup.POST("refund_order", middleware.JSONParamValidator(vasproto.RefundOrderReq{}), RefundOrder)
// 验证码
opVeriCodeGroup := r.Group("/op/veri_code", PrepareOp())

View File

@ -308,3 +308,19 @@ func DealOneOrder(ctx *gin.Context) {
}
ReplyOk(ctx, nil)
}
// 订单退款
func RefundOrder(ctx *gin.Context) {
req := ctx.MustGet("client_req").(*vasproto.RefundOrderReq)
ec, err := service.DefaultService.RefundOrder(ctx, req)
if ec != errcode.ErrCodeVasSrvOk {
logger.Error("RefundOrder fail, req: %v, ec: %v", util.ToJson(req), ec)
if err != nil {
ReplyErrorMsg(ctx, err.Error())
return
}
ReplyErrCodeMsg(ctx, ec)
return
}
ReplyOk(ctx, nil)
}

View File

@ -701,6 +701,21 @@ func (m *Mysql) GetUserVasMembershipUnlock(ctx *gin.Context, tx *sqlx.Tx, mid in
return
}
// 删除会员解锁记录
func (m *Mysql) DeleteUserVasMembershipUnlock(ctx *gin.Context, tx *sqlx.Tx, mid int64) (err error) {
sqlStr := fmt.Sprintf("delete from %s where mid=?", TableVasUserMembershipUnlock)
if tx != nil {
_, err = tx.ExecContext(ctx, sqlStr, mid)
} else {
db := m.getDBVas()
_, err = db.ExecContext(ctx, sqlStr, mid)
}
if err != nil {
return
}
return
}
// 获取解锁记录
func (m *Mysql) GetUnlockWechatList(ctx *gin.Context, tx *sqlx.Tx, mid int64, offset, limit int) (list []*dbstruct.UserVasUnlock, err error) {
list = make([]*dbstruct.UserVasUnlock, 0)
@ -751,6 +766,40 @@ func (m *Mysql) GetIncomeCHList(ctx *gin.Context, tx *sqlx.Tx, orderId string) (
return
}
// 获取消费历史
func (m *Mysql) GetCostCHList(ctx *gin.Context, tx *sqlx.Tx, orderId string) (list []*dbstruct.ConsumeHistory, err error) {
list = make([]*dbstruct.ConsumeHistory, 0)
tableName, err := m.ChTableName(&dbstruct.ConsumeHistory{Type: goproto.Int32(dbstruct.CHTypeCost)})
sqlStr := fmt.Sprintf("select * from %s where order_id=?", tableName)
if tx != nil {
err = tx.SelectContext(ctx, &list, sqlStr, orderId)
} else {
db := m.getDBVas()
err = db.SelectContext(ctx, &list, sqlStr, orderId)
}
if err != nil {
return
}
return
}
// 获取充值历史
func (m *Mysql) GetChargeCHList(ctx *gin.Context, tx *sqlx.Tx, orderId string) (list []*dbstruct.ConsumeHistory, err error) {
list = make([]*dbstruct.ConsumeHistory, 0)
tableName, err := m.ChTableName(&dbstruct.ConsumeHistory{Type: goproto.Int32(dbstruct.CHTypeCharge)})
sqlStr := fmt.Sprintf("select * from %s where order_id=?", tableName)
if tx != nil {
err = tx.SelectContext(ctx, &list, sqlStr, orderId)
} else {
db := m.getDBVas()
err = db.SelectContext(ctx, &list, sqlStr, orderId)
}
if err != nil {
return
}
return
}
// 获取订单
func (m *Mysql) GetOrderCountGroupByStatus(ctx *gin.Context, tx *sqlx.Tx, orderStatuses []int32, ctStart *int64, ctEnd *int64) (list []*dbstruct.VasOrderStatusCount, err error) {
var sql strings.Builder
@ -988,7 +1037,7 @@ func (m *Mysql) DeleteSuccessXxlJobLogs(ctx *gin.Context, tx *sqlx.Tx, ids []int
return
}
// 创建提现订单
// 创建提现钻石历史
func (m *Mysql) CreateWithdrawDiamondsHis(ctx *gin.Context, tx *sqlx.Tx, h *dbstruct.WithdrawDiamondsHis) error {
var err error
sqlStr := "insert into " + TableWithdrawDiamondsHis +

View File

@ -2426,8 +2426,8 @@ func (v *Vas) UnlockMembership(ctx *gin.Context, mid int64, product *dbstruct.Pr
}
// 订单退款,只退充值
func (v *Vas) RefundOrder(ctx *gin.Context, orderId string) error {
order, err := v.store.GetOrderById(ctx, nil, orderId)
func (v *Vas) RefundOrder(ctx *gin.Context, req *vasproto.RefundOrderReq) error {
order, err := v.store.GetOrderById(ctx, nil, req.OrderId)
if err != nil {
return err
}
@ -2437,13 +2437,13 @@ func (v *Vas) RefundOrder(ctx *gin.Context, orderId string) error {
switch order.GetProductId() {
case dbstruct.ProductIdMembership:
return v.refundMembership(ctx, order, req)
default:
return errors.New("invalid product")
}
return nil
}
func (v *Vas) refundMembership(ctx *gin.Context, order *dbstruct.Order) error {
func (v *Vas) refundMembership(ctx *gin.Context, order *dbstruct.Order, req *vasproto.RefundOrderReq) error {
switch order.GetOrderStatus() {
case dbstruct.VasOrderStatusNone, dbstruct.VasOrderStatusInit:
return errors.New("订单还未支付,无法退款")
@ -2468,11 +2468,11 @@ func (v *Vas) refundMembership(ctx *gin.Context, order *dbstruct.Order) error {
ctx,
&dbstruct.OplogOrder{
OrderId: orderId,
Action: dbstruct.OrderOpLogActionAdd,
Action: dbstruct.OrderOpLogActionUpdate,
Operator: req.Operator,
Detail: fmt.Sprintf("后台充值%d金币", coins),
BeforeStatus: dbstruct.VasOrderStatusNone,
AfterStatus: util.DerefInt32(order.OrderStatus),
Detail: fmt.Sprintf("会员退款"),
BeforeStatus: order.GetOrderStatus(),
AfterStatus: dbstruct.VasOrderStatusRefund,
},
err,
)
@ -2488,9 +2488,75 @@ func (v *Vas) refundMembership(ctx *gin.Context, order *dbstruct.Order) error {
}
}()
// 用户的消费退款记录
costChList, err := v.store.GetCostCHList(ctx, tx, orderId)
if err != nil {
logger.Error("GetCostCHList fail, err: %v", err)
return err
}
if len(costChList) <= 0 || len(costChList) > 1 {
err = errors.New("invalid cost ch list")
logger.Error("invalid cost ch list, orderId: %v, err: %v", orderId, err)
return err
}
costCh := costChList[0]
costChNew := &dbstruct.ConsumeHistory{
Mid: goproto.Int64(costCh.GetMid()),
Uid: goproto.Int64(costCh.GetUid()),
Did: goproto.String(costCh.GetDid()),
Type: goproto.Int32(costCh.GetType()),
SType: goproto.Int32(dbstruct.CHSTypeCostRefundMembership),
TypeId: goproto.String(costCh.GetTypeId()),
OrderId: goproto.String(costCh.GetOrderId()),
Change: goproto.Int64(-costCh.GetChange()),
Ct: goproto.Int64(time.Now().Unix()),
}
err = v.store.CreateConsumeHistory(ctx, tx, costChNew)
if err != nil {
logger.Error("CreateConsumeHistory fail, costChNew: %v, err: %v", util.ToJson(costChNew), err)
return err
}
// 用户的充值退款记录
chargeChList, err := v.store.GetChargeCHList(ctx, tx, orderId)
if err != nil {
logger.Error("GetChargeCHList fail, orderId: %v, err: %v", orderId, err)
return err
}
if len(chargeChList) <= 0 || len(chargeChList) > 1 {
err = errors.New("invalid charge ch list")
logger.Error("invalid charge ch list, orderId: %v, err: %v", orderId, err)
return err
}
chargeCh := chargeChList[0]
chargeChNew := &dbstruct.ConsumeHistory{
Mid: goproto.Int64(chargeCh.GetMid()),
Uid: goproto.Int64(chargeCh.GetUid()),
Did: goproto.String(chargeCh.GetDid()),
Type: goproto.Int32(chargeCh.GetType()),
SType: goproto.Int32(dbstruct.CHSTypeChargeRefundMembership),
TypeId: goproto.String(chargeCh.GetTypeId()),
OrderId: goproto.String(chargeCh.GetOrderId()),
Change: goproto.Int64(-chargeCh.GetChange()),
Ct: goproto.Int64(time.Now().Unix()),
}
err = v.store.CreateConsumeHistory(ctx, tx, chargeChNew)
if err != nil {
logger.Error("CreateConsumeHistory fail, chargeChNew: %v, err: %v", util.ToJson(chargeChNew), err)
return err
}
// 修改订单状态 xx -> 已退款
err = v.store.UpdateOrderStatus(ctx, tx, orderId, order.GetOrderStatus(), dbstruct.VasOrderStatusRefund)
if err != nil {
logger.Error("UpdateOrderStatus fail, err: %v", err)
return err
}
// 删除会员解锁记录
err = v.store.DeleteUserVasMembershipUnlock(ctx, tx, order.GetMid())
if err != nil {
logger.Error("DeleteUserVasMembershipUnlock fail, mid: %v, err: %v", order.GetMid(), err)
return err
}
@ -2499,21 +2565,21 @@ func (v *Vas) refundMembership(ctx *gin.Context, order *dbstruct.Order) error {
if err != nil {
return err
}
chList := make([]*dbstruct.ConsumeHistory, 0)
incomeChList := make([]*dbstruct.ConsumeHistory, 0)
for _, ch := range chListTmp {
if ch.GetMid() == common.OfficialMid {
continue
}
chList = append(chList, ch)
incomeChList = append(incomeChList, ch)
}
if len(chList) > 1 {
if len(incomeChList) > 1 {
err = errors.New("收入记录错误,请找开发同学排查")
return err
}
// 有分成的情况
if len(chList) > 0 {
ch := chList[0]
if len(incomeChList) > 0 {
ch := incomeChList[0]
uid := ch.GetUid()
if uid <= 0 {
err = errors.New("收入uid错误")
@ -2563,9 +2629,49 @@ func (v *Vas) refundMembership(ctx *gin.Context, order *dbstruct.Order) error {
return err
}
// 提现钻石记录
wh := &dbstruct.WithdrawDiamondsHis{
Mid: goproto.Int64(uid),
IncomeChId: goproto.Int64(ch.GetId()),
OrderId: goproto.String(ch.GetOrderId()),
Ct: goproto.Int64(time.Now().Unix()),
BeforeWithdrawDiamonds: goproto.Int64(wallet.GetWithdrawDiamonds()),
AfterWithdrawDiamonds: goproto.Int64(wallet.GetWithdrawDiamonds() - change),
Change: goproto.Int64(-change),
ProductId: goproto.String(order.GetProductId()),
}
err = v.store.CreateWithdrawDiamondsHis(ctx, tx, wh)
if err != nil {
logger.Error("CreateWithdrawDiamondsHis fail, wh: %v, err: %v", util.ToJson(wh), err)
return err
}
}
}
// 扣主播金币
v.store.DecDiamonds(ctx, tx)
// 退款
switch order.GetPayType() {
case vasproto.PayTypeAlipay, vasproto.PayTypeAlipayH5:
alipayCli := alipaycli.GetAlipayClientByAppId(order.GetOid3())
resp, err := alipayCli.RefundOne(ctx, &alipaycli.RefundOneParam{
OutTradeNo: orderId,
RefundAmount: order.GetPayAmount(),
RefundReason: "发货D退款",
})
if err != nil {
logger.Error("alipayCli.RefundOne fail, orderId: %v, resp: %v, err: %v", orderId, util.ToJson(resp), err)
return err
}
case vasproto.PayTypeWxpayNative, vasproto.PayTypeWxpayJsapi, vasproto.PayTypeWxpayH5:
wxpayCli := wxpaycli.GetDefaultWxpayClient()
resp, err := wxpayCli.RefundOne(ctx, &wxpaycli.RefundOneParam{
OutTradeNo: orderId,
RefundAmount: order.GetPayAmount(),
RefundReason: "发货D退款",
})
if err != nil {
logger.Error("wxpayCli.RefundOne fail, orderId: %v, resp: %v, err: %v", orderId, util.ToJson(resp), err)
return err
}
}
return nil
}

View File

@ -523,3 +523,14 @@ func (s *Service) DealOneOrder(ctx *gin.Context, req *vasproto.DealOneOrderReq)
}
return
}
func (s *Service) RefundOrder(ctx *gin.Context, req *vasproto.RefundOrderReq) (ec errcode.ErrCode, err error) {
ec = errcode.ErrCodeVasSrvOk
err = _DefaultVas.RefundOrder(ctx, req)
if err != nil {
logger.Error("RefundOrder fail, req: %v, err: %v", util.ToJson(req), err)
ec = errcode.ErrCodeVasSrvFail
return
}
return
}

View File

@ -156,6 +156,7 @@ CREATE INDEX ix_mid ON vas_withdraw_diamonds_his (mid);
CREATE INDEX ix_ct ON vas_withdraw_diamonds_his (ct);
CREATE INDEX ix_order_id ON vas_withdraw_diamonds_his (order_id);
CREATE INDEX ix_chid ON vas_withdraw_diamonds_his (income_ch_id);
CREATE TABLE `vas_user_membership_unlock`
(
`id` bigint AUTO_INCREMENT COMMENT 'id',
@ -166,4 +167,5 @@ CREATE TABLE `vas_user_membership_unlock`
`order_id` varchar(128) DEFAULT NULL COMMENT '订单id',
PRIMARY KEY (`id`)
);
CREATE INDEX uni_idx_mid_product_id ON vas_user_unlock (mid, product_id);
CREATE INDEX ix_mid_product_id ON vas_user_membership_unlock (mid, product_id);
CREATE INDEX ix_orderid ON vas_user_membership_unlock (order_id);

View File

@ -490,13 +490,15 @@ const (
CHTypeIncome = 3 // 收入明细(钻石)
CHTypeWithdraw = 4 // 提现明细(钻石)
CHSTypeCostContact = 10001 // 消费明细,联系方式
CHSTypeCostRefund = 10002 // 消费明细,金币退款
CHSTypeCostMembership = 10003 // 消费明细,会员资格解锁(伪金币记录,会员资格解锁中间无转金币过程)
CHSTypeCostContact = 10001 // 消费明细,联系方式
CHSTypeCostRefund = 10002 // 消费明细,金币退款
CHSTypeCostMembership = 10003 // 消费明细,会员资格解锁(伪金币记录,会员资格解锁中间无转金币过程)
CHSTypeCostRefundMembership = 10004 // 消费明细,会员资格解锁退款(伪金币记录,会员资格解锁中间无转金币过程)
CHSTypeChargeUser = 20001 // 充值明细,用户自己冲
CHSTypeChargeOp = 20002 // 充值明细OP充值
CHSTypeChargeRefund = 20003 // 充值明细,现金退款
CHSTypeChargeUser = 20001 // 充值明细,用户自己冲
CHSTypeChargeOp = 20002 // 充值明细OP充值
CHSTypeChargeRefund = 20003 // 充值明细,现金退款
CHSTypeChargeRefundMembership = 20004 // 充值明细,会员退款
CHSTypeIncomeContact = 30001 // 收入明细,联系方式
CHSTypeIncomeInvite = 30002 // 收入明细,邀请分成

View File

@ -47,6 +47,7 @@ const (
NodeDailyStatement // node 每日报表表
NodeWithdrawOrderId // node 提现订单
NodeAppConfig // node 应用配置表
NodeWxpayRefund // node 微信支付退款
)
func GenIdInt64(node int64) (int64, error) {
@ -210,3 +211,9 @@ func GenAppConfigId() int64 {
id, _ := GenIdInt64(NodeAppConfig)
return id
}
// wxpay refund
func GenWxpayRefundId() string {
id, _ := GenIdString(NodeWxpayRefund)
return id
}

View File

@ -14,6 +14,7 @@ import (
"fmt"
"net/http"
"os"
"service/library/idgenerator"
"time"
"github.com/go-pay/gopay"
@ -286,3 +287,28 @@ func (c *WxpayClient) H5Pay(ctx context.Context, param *H5PayParam) (wxpayH5Para
logger.Info("wxpayv3 H5 success, code: %v, error: %v, response: %v", resp.Code, resp.Error, util.ToJson(resp.Response))
return
}
// 退款
type RefundOneParam struct {
OutTradeNo string // 商家订单id我们自己的订单id
RefundAmount int64 // 退款金额,单位:分
RefundReason string // 退款理由
}
func (c *WxpayClient) RefundOne(ctx context.Context, param *RefundOneParam) (resp *wxpayv3.RefundRsp, err error) {
bm := gopay.BodyMap{
"out_trade_no": param.OutTradeNo,
"out_refund_no": idgenerator.GenWxpayRefundId(),
"reason": param.RefundReason,
"amount": gopay.BodyMap{
"refund": param.RefundAmount,
"total": param.RefundAmount,
"currency": "CNY",
},
}
resp, err = c.clientV3.V3Refund(ctx, bm)
if err != nil {
return
}
return
}

View File

@ -113,3 +113,17 @@ func TestWxpayClient_H5Pay(t *testing.T) {
}
t.Log(resp)
}
func TestWxpayClient_RefundOne(t *testing.T) {
cli := GetDefaultWxpayClient()
resp, err := cli.RefundOne(context.Background(), &RefundOneParam{
OutTradeNo: "1767889021582716928",
RefundAmount: 100,
RefundReason: "发货D退款",
})
if err != nil {
t.Log(err.Error())
return
}
t.Log(util.ToJson(resp))
}