refund
This commit is contained in:
parent
3273330c76
commit
b833ecdc9e
|
@ -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 {
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 +
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
|
@ -493,10 +493,12 @@ const (
|
|||
CHSTypeCostContact = 10001 // 消费明细,联系方式
|
||||
CHSTypeCostRefund = 10002 // 消费明细,金币退款
|
||||
CHSTypeCostMembership = 10003 // 消费明细,会员资格解锁(伪金币记录,会员资格解锁中间无转金币过程)
|
||||
CHSTypeCostRefundMembership = 10004 // 消费明细,会员资格解锁退款(伪金币记录,会员资格解锁中间无转金币过程)
|
||||
|
||||
CHSTypeChargeUser = 20001 // 充值明细,用户自己冲
|
||||
CHSTypeChargeOp = 20002 // 充值明细,OP充值
|
||||
CHSTypeChargeRefund = 20003 // 充值明细,现金退款
|
||||
CHSTypeChargeRefundMembership = 20004 // 充值明细,会员退款
|
||||
|
||||
CHSTypeIncomeContact = 30001 // 收入明细,联系方式
|
||||
CHSTypeIncomeInvite = 30002 // 收入明细,邀请分成
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue