mh_server/controller/mall.go

1371 lines
38 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package controller
import (
"fmt"
"github.com/codinl/go-logger"
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"mh-server/lib/auth"
"mh-server/lib/status"
"mh-server/lib/utils"
"mh-server/lib/wxpay"
"mh-server/model"
"strconv"
"strings"
)
// MallGoodsList 商品列表
// @Summary 商品列表
// @Tags 商城, V1.4.5
// @Produce json
// @Accept json
// @Param request body model.GoodsListReq true "新建商品列表模型"
// @Success 200 {object} model.GoodsListResp
// @Router /api/v1/mall/goods/list [post]
func MallGoodsList(c *gin.Context) {
req := model.GoodsListReq{}
if err := c.ShouldBindJSON(&req); err != nil {
logger.Error(err)
RespJson(c, status.BadRequest, nil)
return
}
if req.NearestStoreId != 0 {
if store, err := model.GetStore(req.NearestStoreId); err != nil || store == nil {
RespJson(c, status.BadRequest, "未查询到门店信息")
return
}
}
list, total, err := req.GoodsList()
if err != nil {
logger.Error("err:", err)
RespJson(c, status.InternalServerError, nil)
return
}
var ids []uint32
for _, goods := range list {
ids = append(ids, goods.GoodsId)
}
if len(ids) > 0 {
cm := model.GetGoodsFirstSkuCombo(ids)
if cm != nil {
for j, combo := range cm {
for i, goods := range list {
if combo.GoodsId == goods.GoodsId {
list[i].Combo = &cm[j]
}
}
}
}
}
ret := model.GoodsListResp{
List: list,
CurPage: req.PageIdx,
TotalPage: total,
}
RespOK(c, ret)
return
}
func MallGoodsDetail(c *gin.Context) {
req := model.GoodsDetailReq{}
if err := c.ShouldBindJSON(&req); err != nil {
logger.Error(err)
RespJson(c, status.BadRequest, nil)
return
}
detail, err := req.GoodsDetail()
if err != nil {
logger.Error("err:", err)
RespJson(c, status.InternalServerError, nil)
return
}
cm := model.GetGoodsFirstSkuCombo([]uint32{detail.GoodsId})
if len(cm) > 0 {
detail.Combo = &cm[0]
}
RespOK(c, detail)
return
}
// GetCombinedCouponCode 定义一个函数来根据 number 和 couponType 组合结果
func GetCombinedCouponCode(goods *model.Goods, attribute *model.GoodsAttribute) (uint32, error) {
// 确保 goods 和 attribute 以及 SpecValues 不为空
if goods == nil || attribute == nil || attribute.SpecValues == nil {
return 0, fmt.Errorf("goods or attribute or SpecValues is nil")
}
// 确定 goods.Name 中的数字
var number int
switch {
case strings.Contains(goods.Name, "100"):
number = 100
case strings.Contains(goods.Name, "50"):
number = 50
case strings.Contains(goods.Name, "20"):
number = 20
case strings.Contains(goods.Name, "10"):
number = 10
case strings.Contains(goods.Name, "5"):
number = 5
default:
return 0, fmt.Errorf("优惠券金额有误")
}
// 遍历 SpecValues 查找匹配的 display_value
var couponType int
for _, specValue := range attribute.SpecValues {
if specValue == nil {
continue
}
switch specValue.DisplayValue {
case "周边券":
couponType = 3
case "配件券":
couponType = 2
case "游戏券":
couponType = 1
default:
continue
}
break
}
if couponType == 0 {
return 0, fmt.Errorf("优惠券规格有误")
}
// 生成对应的组合字符串
resultStr := fmt.Sprintf("%d%d", number, couponType)
// 将组合字符串转换为 uint32
result, err := strconv.ParseUint(resultStr, 10, 32)
if err != nil {
return 0, fmt.Errorf("转换为 uint32 时出错: %v", err)
}
return uint32(result), nil
}
//type MallOrderCreateReq struct {
// GoodsId uint32 `json:"goods_id" binding:"required"` // 商品id
// GoodsAttributeId uint32 `json:"goods_attribute_id" binding:"required"` // 规格id
// GoodsAttributeComboId uint32 `json:"goods_attribute_combo_id" binding:"required"` // 套餐id
// Quantity uint32 `json:"quantity" binding:"required"` // 购买数量
// AddressId uint32 `json:"address_id"` // 收货地址
// DeliveryExtraInfo string `json:"delivery_extra_info"` // 收货备注
// CouponCode string `json:"coupon_code"` // 优惠券券码
// NearestStoreId uint32 `json:"nearest_store_id"` // 定位最近的门店id
//}
//
//// MallOrderCreate 新建商城订单
//// @Summary 新建商城订单
//// @Tags 商城, V1.4.5
//// @Produce json
//// @Accept json
//// @Param request body MallOrderCreateReq true "新建商城订单模型"
//// @Success 200 {object} RespRet
//// @Router /api/v1/mall/order/create [post]
//func MallOrderCreate(c *gin.Context) {
// req := MallOrderCreateReq{}
// if err := c.ShouldBindJSON(&req); err != nil {
// logger.Error(err)
// RespJson(c, status.BadRequest, nil)
// return
// }
//
// uc := auth.GetCurrentUser(c)
// if uc == nil {
// RespJson(c, status.Unauthorized, nil)
// return
// }
// user := model.GetUserByUid(uc.Uid)
// err := user.SetVm()
// if err != nil {
// log.Error().Msgf("set vm err:%#v", err)
// RespJson(c, status.InternalServerError, nil)
// return
// }
// // 商品是否存在
// var goods model.Goods
// err = model.NewGoodsQuerySet(model.DB).GoodsIdEq(req.GoodsId).One(&goods)
// if err != nil {
// logger.Error("err:", err)
// RespJson(c, status.BadRequest, nil)
// return
// }
//
// // 商品在售状态
// if goods.SaleStatus != model.SaleStatusYes {
// logger.Error("err:", err)
// RespJson(c, status.GoodsNotSale, nil)
// return
// }
//
// var attribute model.GoodsAttribute
// err = model.NewGoodsAttributeQuerySet(model.DB).IDEq(req.GoodsAttributeId).One(&attribute)
// if err != nil {
// log.Error().Msgf("attribute err:%#v", err)
// RespJson(c, status.InternalServerError, nil)
// return
// }
//
// // 判断商品是否为erp同步商品如果是则直接使用erp库存
// if goods.ErpCommodityId != 0 { // 是erp同步的商品
// // 查询库存是否充足
// var count int64
// err = model.DB.Table("erp_stock_commodity").Where("state = 1 and erp_commodity_id = ?",
// goods.ErpCommodityId).Count(&count).Error
// if err != nil {
// log.Error().Msgf("query erp_stock_commodity err:%#v", err)
// RespJson(c, status.OrderStockOut, "查询商品库存失败")
// return
// }
//
// if uint32(count) < req.Quantity {
// logger.Error("count < req.Quantity:", count, req.Quantity)
// RespJson(c, status.OrderStockOut, nil)
// return
// }
// } else {
// // 库存不足
// if attribute.Stock < req.Quantity {
// logger.Error("err:", err)
// RespJson(c, status.OrderStockOut, nil)
// return
// }
// }
//
// // 如果不是发放优惠券,需要判断用户地址
// if !(strings.Contains(goods.Name, "优惠券") || strings.Contains(goods.Name, "预售")) {
// // 检测收货地址是否正确
// count, _ := model.NewUserAddressQuerySet(model.DB).UidEq(uc.Uid).IDEq(req.AddressId).Count()
// if count != 1 {
// logger.Error("err:", err)
// RespJson(c, status.BadRequest, "收货地址错误")
// return
// }
// }
//
// // 判断是否已经购买或使用过新机预售券
// if strings.Contains(goods.Name, "预售") {
// var newMachineCoupon model.UserCoupon
// err = model.NewUserCouponQuerySet(model.DB).UidEq(user.Uid).ActivityIdEq(model.NewMachineActivityId).
// One(&newMachineCoupon)
// if err != nil {
// log.Error().Msgf("query newMachineCoupon err:%#v, code is:", err, req.CouponCode)
// }
//
// if newMachineCoupon.ID != 0 {
// RespJson(c, status.OutOffCouponLimit, "一个ID只能购买一张预售券")
// return
// }
// }
//
// // 查找用户优惠券,判断是否可用
// var userCoupon model.UserCoupon
// var coupon model.Coupon
// if req.CouponCode != "" {
// err = model.NewUserCouponQuerySet(model.DB).CodeEq(req.CouponCode).One(&userCoupon)
// if err != nil {
// log.Error().Msgf("query userCoupon err:%#v, code is:", err, req.CouponCode)
// } else {
// switch userCoupon.State {
// case 2: // 已使用
// log.Error().Msgf("优惠券已使用, code is:", req.CouponCode)
// RespJson(c, status.InternalServerError, nil)
// case 3: // 已过期
// log.Error().Msgf("优惠券已过期, code is:", req.CouponCode)
// RespJson(c, status.InternalServerError, nil)
// }
// if userCoupon.ActivityId != model.NewMachineActivityId {
// log.Error().Msgf("优惠券类型与商品不匹配, code is:", req.CouponCode)
// RespJson(c, status.InternalServerError, nil)
// }
// }
//
// model.NewCouponQuerySet(model.DB).ActivityIdEq(userCoupon.ActivityId).One(&coupon)
// }
//
// // 读取折扣(是否有会员折扣)
// discount, err := goods.GetUserDiscount(user)
// if err != nil {
// log.Error().Msgf("combo err:%#v", err)
// RespJson(c, status.InternalServerError, nil)
// return
// }
// var combo model.GoodsAttributeCombo
// err = model.NewGoodsAttributeComboQuerySet(model.DB).IDEq(req.GoodsAttributeComboId).One(&combo)
// if err != nil {
// log.Error().Msgf("combo err:%#v", err)
// RespJson(c, status.InternalServerError, nil)
// return
// }
//
// // 计算总金额
// totalVm := combo.PriceVm * req.Quantity
// if combo.PriceVm > 0 && user.UserVm.Vm < totalVm {
// log.Error().Msgf("vm not enough")
// RespJson(c, status.UserVmNotEnough, nil)
// return
// }
//
// totalRm := (combo.PriceRm*req.Quantity*discount + 5) / model.Rmb
// couponDiscount := userCoupon.Value
// if totalRm < couponDiscount {
// log.Error().Msgf("coupon value[%d] is err, code is:", couponDiscount, req.CouponCode)
// RespJson(c, status.InternalServerError, nil)
// return
// }
//
// // 开启事务
// tx := model.TransactionBegin()
// // 订单创建逻辑
// order := model.GoodsOrder{
// OrderId: model.CreateGoodsOrderId(),
// SerialNo: model.CreateGoodsOrderSerialNo(),
// Uid: uc.Uid,
// GoodsId: req.GoodsId,
// Rm: totalRm - couponDiscount,
// Vm: totalVm,
// Quantity: req.Quantity,
// PayStatus: model.PayStatusInit,
// AddressId: req.AddressId,
// DeliveryExtraInfo: req.DeliveryExtraInfo,
// DeliveryFee: goods.DeliveryFee,
// DeliveryStatus: model.DeliveryStatusUnDeliver,
// GoodsAttributeId: req.GoodsAttributeId,
// GoodsAttributeComboId: req.GoodsAttributeComboId,
// Discount: discount,
// State: model.GoodsOrderStateUnPay,
// CouponDiscount: userCoupon.Value,
// CouponCode: userCoupon.Code,
// CouponName: coupon.Name,
// }
//
// // 如果商品人民币价格为0则是积分兑换
// if combo.PriceRm == 0 {
// order.PayTime = time.Now()
// order.PayStatus = model.PayStatusOK
// order.State = model.GoodsOrderStateOnDeliver
//
// // 如果发放的是优惠券,则默认为用户已收货
// if strings.Contains(goods.Name, "优惠券") {
// order.State = model.GoodsOrderStateReceived
// }
//
// err = order.Create(tx)
// if err != nil {
// logger.Error("err:", err)
// tx.Rollback()
// RespJson(c, status.InternalServerError, nil)
// return
// }
//
// err = model.OrderUpdateGoodsStock(req.GoodsAttributeId, order.Quantity, tx)
// if err != nil {
// tx.Rollback()
// logger.Error("err:", err)
// RespJson(c, status.InternalServerError, nil)
// return
// }
//
// err = model.UserVmUpdate(order.Uid, int(order.Vm)*-1, model.VmEventBuyGoods, "购买商品积分抵扣")
// if err != nil {
// tx.Rollback()
// logger.Error("err:", err)
// RespJson(c, status.InternalServerError, nil)
// return
// }
//
// if strings.Contains(goods.Name, "优惠券") {
// // 发放优惠券:判断优惠券金额和种类
// err = json.Unmarshal([]byte(attribute.SpecValueList), &attribute.SpecValues)
// if err != nil {
// log.Error().Msgf("spec value err:%#v", err)
// }
//
// nActivityType, err := GetCombinedCouponCode(&goods, &attribute)
// if err != nil {
// logger.Error("GetCombinedCouponCode err:", err)
// RespJson(c, status.NoSpecValue, nil)
// return
// }
//
// var coupons []model.Coupon
// err = model.NewCouponQuerySet(model.DB).ActivityIdEq(model.VmActivityId).ActivityTypeEq(nActivityType).All(&coupons)
// if err != nil {
// logger.Error("coupons err:", err)
// RespJson(c, status.InternalServerError, nil)
// return
// }
//
// for i, _ := range coupons {
// couponCode, err := utils.GenerateRandomNumber19()
// if err != nil {
// logger.Error("GenerateRandomNumber19err:", err)
// }
// userCoupon := &model.UserCoupon{
// Uid: user.Uid,
// CouponId: coupons[i].ID,
// CouponType: coupons[i].CouponType,
// ActivityType: coupons[i].ActivityType,
// ActivityId: coupons[i].ActivityId,
// Value: coupons[i].Value,
// State: 1,
// ActiveStart: time.Now(),
// ActiveEnd: time.Now().AddDate(0, 0, 7),
// UseTime: time.Time{},
// MemberLevel: coupons[i].MemberLevel,
// Approach: 0,
// PromotionalSales: 0,
// RedeemCode: "",
// CategoryNumber: coupons[i].CategoryNumber,
// Code: couponCode,
// }
//
// err = model.DB.Create(userCoupon).Error
// if err != nil {
// logger.Error("user coupon err:", err)
// continue
// }
// }
// }
//
// err = tx.Commit().Error
// if err != nil {
// tx.Rollback()
// logger.Error("err:", err)
// RespJson(c, status.InternalServerError, nil)
// return
// }
//
// sub := model.DeliverTaskSub{
// Uid: order.Uid,
// UserAddressId: order.AddressId,
// OrderType: 2,
// OrderId: order.OrderId,
// StoreId: 13,
// }
// err = sub.Add()
// if err != nil {
// logger.Error("deliver task sub add err:", err)
// }
//
// ret := map[string]interface{}{
// "order_id": order.ID,
// "order": order,
// }
//
// RespOK(c, ret)
// return
// }
//
// err = order.Create(tx)
// if err != nil {
// logger.Error("err:", err)
// tx.Rollback()
// RespJson(c, status.InternalServerError, nil)
// return
// }
//
// err = tx.Commit().Error
// if err != nil {
// tx.Rollback()
// logger.Error("err:", err)
// RespJson(c, status.InternalServerError, nil)
// return
// }
//
// configInfo, err := model.PayConfigInfo()
// if err != nil {
// logger.Error(err)
// RespJson(c, status.InternalServerError, nil)
// return
// }
//
// err = model.UserOpenMemberRecord{Uid: uc.Uid, OpenNo: order.SerialNo, OrderId: order.OrderId, OrderType: 6, Attach: wxpay.WxPayBuyGoods}.Insert()
// if err != nil {
// logger.Error(errors.New("WebPay err"))
// RespJson(c, status.InternalServerError, nil)
// return
// }
//
// if goods.GoodsAccountNum == model.AccountForDw { // 迪为账户收费
// webPay, err := wxpay.WebPay(order.SerialNo, order.Rm, user.WxOpenID, "N", wxpay.WxPayBuyGoods, configInfo.NotifyUrl, true)
// if err != nil {
// logger.Error(errors.New("WebPay err"))
// RespJson(c, status.InternalServerError, nil)
// return
// }
//
// ret := map[string]interface{}{
// "web_pay": webPay,
// "order_id": order.ID,
// "order": order,
// }
//
// RespOK(c, ret)
// } else { // 其他则默认明慧河马付账户收费
// webPay, err := wxpay.HmJsPayUnifiedOrderForBuyGoods(order.SerialNo, order.Rm, user.WxOpenID, configInfo.NotifyUrl)
// if err != nil {
// logger.Error(errors.New("WebPay err"))
// RespJson(c, status.InternalServerError, nil)
// return
// }
//
// ret := map[string]interface{}{
// "web_pay": webPay,
// "order_id": order.ID,
// "order": order,
// }
//
// RespOK(c, ret)
// }
//
// return
//}
// MallOrderCreateReq 新建商城订单请求结构
type MallOrderCreateReq struct {
NearestStoreId uint32 `json:"nearest_store_id"` // 用户选择的门店ID
Commodities []struct {
GoodsId uint32 `json:"goods_id"`
Quantity uint32 `json:"quantity"`
GoodsAttributeId uint32 `json:"goods_attribute_id"`
GoodsAttributeComboId uint32 `json:"goods_attribute_combo_id"`
CouponCode string `json:"coupon_code"`
} `json:"commodities"`
AddressId uint32 `json:"address_id"`
DeliveryExtraInfo string `json:"delivery_extra_info"`
}
// MallOrderCreate 新建商城订单
// @Summary 新建商城订单
// @Tags 商城, V1.4.5
// @Produce json
// @Accept json
// @Param request body MallOrderCreateReq true "新建商城订单模型"
// @Success 200 {object} RespRet
// @Router /api/v1/mall/order/create [post]
func MallOrderCreate(c *gin.Context) {
req := MallOrderCreateReq{}
if err := c.ShouldBindJSON(&req); err != nil {
logger.Error(err)
RespJson(c, status.BadRequest, nil)
return
}
if req.NearestStoreId == 0 {
RespJson(c, status.BadRequest, "请选择门店")
return
}
userStore, err := model.GetStore(req.NearestStoreId)
if err != nil || userStore == nil {
RespJson(c, status.BadRequest, "未查询到门店信息,请选择门店")
return
}
uc := auth.GetCurrentUser(c)
if uc == nil {
RespJson(c, status.Unauthorized, nil)
return
}
user := model.GetUserByUid(uc.Uid)
if err := user.SetVm(); err != nil {
log.Error().Msgf("set vm err:%#v", err)
RespJson(c, status.InternalServerError, nil)
return
}
tx := model.TransactionBegin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
logger.Error("panic occurred:", r)
RespJson(c, status.InternalServerError, nil)
}
}()
var erpCommodities []model.GoodsOrderCommodity
var onlineCommodities []model.GoodsOrderCommodity
// ⚡ 记录全局收款账户(初始化为 0
var orderAccountNum uint32
// 全局支付类型0-未定1-RMB2-VM
var paymentType int = 0
// 先检查库存 + 收款账户一致性
for _, item := range req.Commodities {
var goods model.Goods
if err := model.NewGoodsQuerySet(model.DB).GoodsIdEq(item.GoodsId).One(&goods); err != nil {
RespJson(c, status.BadRequest, fmt.Sprintf("商品ID %d 不存在", item.GoodsId))
return
}
if goods.SaleStatus != model.SaleStatusYes {
RespJson(c, status.GoodsNotSale, fmt.Sprintf("商品 %s 不在售", goods.Name))
return
}
// ---------- 支付方式检查 (来自 Goods 表) ----------
var itemPayType int
if goods.PriceRm != 0 && goods.PriceVm == 0 {
itemPayType = 1 // RMB
} else if goods.PriceVm != 0 && goods.PriceRm == 0 {
itemPayType = 2 // VM
} else {
RespJson(c, status.BadRequest, fmt.Sprintf("商品 %s 支付配置错误PriceRm/PriceVm 必须且仅有一个非0", goods.Name))
return
}
if paymentType == 0 {
paymentType = itemPayType
} else if paymentType != itemPayType {
RespJson(c, status.BadRequest, "不能混合使用人民币和积分购买,请分开下单")
return
}
// ⚡ 收款账户检查
if orderAccountNum == 0 {
orderAccountNum = goods.GoodsAccountNum
} else if orderAccountNum != goods.GoodsAccountNum {
RespJson(c, status.BadRequest, "下单失败:多个商品收款账户不一致,请分开下单")
return
}
if goods.ErpCommodityId != 0 {
// ERP商品检查指定门店库存
var count int64
if err := model.DB.Table("erp_stock_commodity").
Where("state = 1 AND erp_commodity_id = ? AND store_id = ?", goods.ErpCommodityId, req.NearestStoreId).
Count(&count).Error; err != nil {
RespJson(c, status.OrderStockOut, "查询ERP库存失败")
return
}
if uint32(count) < item.Quantity {
RespJson(c, status.OrderStockOut, fmt.Sprintf("商品 %s 在当前门店库存不足", goods.Name))
return
}
} else {
// 线上商品检查本地库存
var attribute model.GoodsAttribute
if err := model.NewGoodsAttributeQuerySet(model.DB).IDEq(item.GoodsAttributeId).One(&attribute); err != nil {
RespJson(c, status.InternalServerError, nil)
return
}
if attribute.Stock < item.Quantity {
RespJson(c, status.OrderStockOut, fmt.Sprintf("商品 %s 库存不足", goods.Name))
return
}
}
}
// 创建订单对象(只创建一次)
order := model.GoodsOrder{
OrderId: model.CreateGoodsOrderId(),
SerialNo: model.CreateGoodsOrderSerialNo(),
NearestStoreId: req.NearestStoreId,
NearestStoreName: userStore.Name,
Uid: uc.Uid,
PayStatus: model.PayStatusInit,
AddressId: req.AddressId,
DeliveryExtraInfo: req.DeliveryExtraInfo,
DeliveryStatus: model.DeliveryStatusUnDeliver,
GoodsOrderCommodities: []model.GoodsOrderCommodity{},
State: model.GoodsOrderStateUnPay,
TotalDiscount: 0,
}
var totalVm, totalRm float64
for _, item := range req.Commodities {
// 商品是否存在
var goods model.Goods
if err := model.NewGoodsQuerySet(model.DB).GoodsIdEq(item.GoodsId).One(&goods); err != nil {
tx.Rollback()
RespJson(c, status.BadRequest, fmt.Sprintf("商品ID %d 不存在", item.GoodsId))
return
}
// 获取商品属性
var attribute model.GoodsAttribute
if err := model.NewGoodsAttributeQuerySet(model.DB).IDEq(item.GoodsAttributeId).One(&attribute); err != nil {
tx.Rollback()
RespJson(c, status.InternalServerError, nil)
return
}
// 收货地址检查
if !(strings.Contains(goods.Name, "优惠券") || strings.Contains(goods.Name, "预售")) {
count, _ := model.NewUserAddressQuerySet(model.DB).UidEq(user.Uid).IDEq(req.AddressId).Count()
if count != 1 {
tx.Rollback()
RespJson(c, status.BadRequest, "收货地址错误")
return
}
}
// 预售券限制
if strings.Contains(goods.Name, "预售") {
var newMachineCoupon model.UserCoupon
_ = model.NewUserCouponQuerySet(model.DB).UidEq(user.Uid).ActivityIdEq(model.NewMachineActivityId).One(&newMachineCoupon)
if newMachineCoupon.ID != 0 {
tx.Rollback()
RespJson(c, status.OutOffCouponLimit, "一个ID只能购买一张预售券")
return
}
}
// 用户优惠券
var userCoupon model.UserCoupon
var coupon model.Coupon
if item.CouponCode != "" {
_ = model.NewUserCouponQuerySet(model.DB).CodeEq(item.CouponCode).One(&userCoupon)
if userCoupon.ID != 0 {
model.NewCouponQuerySet(model.DB).ActivityIdEq(userCoupon.ActivityId).One(&coupon)
}
}
// 会员折扣
discount, _ := goods.GetUserDiscount(user)
// 套餐信息
var combo model.GoodsAttributeCombo
if err := model.NewGoodsAttributeComboQuerySet(model.DB).IDEq(item.GoodsAttributeComboId).One(&combo); err != nil {
tx.Rollback()
RespJson(c, status.InternalServerError, nil)
return
}
// 金额计算
vm := combo.PriceVm * item.Quantity
rm := (combo.PriceRm * item.Quantity) * discount
if rm < userCoupon.Value {
tx.Rollback()
RespJson(c, status.InternalServerError, "优惠券金额大于订单金额")
return
}
totalVm += float64(vm)
totalRm += float64(rm) - float64(userCoupon.Value)
// 2) 支付方式全局校验:如果是 VM 支付,则用户积分总额是否足够
if paymentType == 2 {
// totalVm 是积分总数(以 combo.PriceVm 单位),比较 user.UserVm.Vm
if uint32(totalVm) > user.UserVm.Vm {
RespJson(c, status.UserVmNotEnough, nil)
return
}
}
// 构建订单商品明细
orderCommodity := model.GoodsOrderCommodity{
GoodsId: item.GoodsId,
ErpCommodityId: goods.ErpCommodityId,
Quantity: item.Quantity,
PriceRm: combo.PriceRm,
PriceVm: combo.PriceVm,
Discount: discount,
GoodsAttributeId: item.GoodsAttributeId,
GoodsAttributeComboId: item.GoodsAttributeComboId,
CouponID: userCoupon.ID,
CouponCode: userCoupon.Code,
CouponName: coupon.Name,
CouponDiscount: userCoupon.Value,
}
order.GoodsOrderCommodities = append(order.GoodsOrderCommodities, orderCommodity)
if goods.ErpCommodityId != 0 {
erpCommodities = append(erpCommodities, orderCommodity)
} else {
onlineCommodities = append(onlineCommodities, orderCommodity)
}
}
// 订单总额
order.Vm = uint32(totalVm)
order.Rm = uint32(totalRm)
order.Amount = uint32(totalVm)
// 写入订单
if err := order.Create(tx); err != nil {
tx.Rollback()
RespJson(c, status.InternalServerError, nil)
return
}
// 批量写入商品明细
for i := range order.GoodsOrderCommodities {
order.GoodsOrderCommodities[i].GoodsOrderId = order.ID
if err := tx.Create(&order.GoodsOrderCommodities[i]).Error; err != nil {
tx.Rollback()
RespJson(c, status.InternalServerError, fmt.Sprintf("写入商品明细失败: %v", err))
return
}
}
// 积分扣减
if paymentType == 2 && totalVm > 0 {
if err := model.UserVmUpdate(user.Uid, int(totalVm*-1), model.VmEventBuyGoods, "购买商品积分抵扣"); err != nil {
tx.Rollback()
RespJson(c, status.InternalServerError, nil)
return
}
}
// 处理优惠券类商品直接发放
for _, com := range order.GoodsOrderCommodities {
var goods model.Goods
_ = model.NewGoodsQuerySet(model.DB).GoodsIdEq(com.GoodsId).One(&goods)
if strings.Contains(goods.Name, "优惠券") {
_ = model.NewGoodsOrderQuerySet(model.DB).IDEq(order.ID).GetUpdater().
SetState(model.GoodsOrderStateReceived).Update()
}
}
// 记录用户会员购买信息
_ = model.UserOpenMemberRecord{
Uid: uc.Uid,
OpenNo: order.SerialNo,
OrderId: order.OrderId,
OrderType: 6,
Attach: wxpay.WxPayBuyGoods,
}.Insert()
// 提交事务
if err := tx.Commit().Error; err != nil {
tx.Rollback()
RespJson(c, status.InternalServerError, nil)
return
}
// 支付
configInfo, err := model.PayConfigInfo()
if err != nil {
logger.Error(err)
RespJson(c, status.InternalServerError, nil)
return
}
if orderAccountNum == model.AccountForDw { // 迪为账户收费
webPay, err := wxpay.WebPay(order.SerialNo, order.Rm, user.WxOpenID, "N", wxpay.WxPayBuyGoods, configInfo.NotifyUrl, true)
if err != nil {
logger.Error(errors.New("WebPay err"))
RespJson(c, status.InternalServerError, nil)
return
}
ret := map[string]interface{}{
"web_pay": webPay,
"order_id": order.ID,
"order": order,
}
RespOK(c, ret)
} else { // 其他则默认明慧河马付账户收费
webPay, err := wxpay.HmJsPayUnifiedOrderForBuyGoods(order.SerialNo, order.Rm, user.WxOpenID, configInfo.NotifyUrl)
if err != nil {
logger.Error(errors.New("HmPay err"))
RespJson(c, status.InternalServerError, nil)
return
}
ret := map[string]interface{}{
"web_pay": webPay,
"order_id": order.ID,
"order": order,
}
RespOK(c, ret)
}
return
}
// 订单支付
// 暂时只支持积分支付, 以后再考虑人民币支付
func MallOrderPay(c *gin.Context) {
req := struct {
OrderId uint32 `json:"order_id" binding:"required"`
}{}
if err := c.ShouldBindJSON(&req); err != nil {
logger.Error(err)
RespJson(c, status.BadRequest, nil)
return
}
uc := auth.GetCurrentUser(c)
if uc == nil {
RespJson(c, status.Unauthorized, nil)
return
}
// 订单是否存在
var order model.GoodsOrder
err := model.NewGoodsOrderQuerySet(model.DB).
OrderIdEq(req.OrderId).
UidEq(uc.Uid).
One(&order)
if err != nil {
logger.Error("err:", err)
RespJson(c, status.BadRequest, nil)
return
}
// 订单状态不可以支付
if order.PayStatus != model.PayStatusInit {
logger.Error("err:", err)
RespJson(c, status.BadRequest, nil)
return
}
tx := model.TransactionBegin()
// 减少库存
// 确认下是在支付后减少,还是下单后?
// 减少用户积分
// 更新支付状态
err = model.NewGoodsOrderQuerySet(tx).
OrderIdEq(req.OrderId).
VersionIdEq(order.VersionId).
GetUpdater().
SetPayStatus(model.PayStatusOK).
SetVersionId(order.VersionId + 1).
Update()
if err != nil {
logger.Error("err:", err)
tx.Rollback()
RespJson(c, status.BadRequest, nil)
return
}
tx.Commit()
RespOK(c, nil)
return
}
func MallOrderList(c *gin.Context) {
req := model.GoodsOrderListReq{}
if err := c.ShouldBindJSON(&req); err != nil {
logger.Error(err)
RespJson(c, status.BadRequest, nil)
return
}
uc := auth.GetCurrentUser(c)
if uc == nil {
RespJson(c, status.Unauthorized, nil)
return
}
list, total, err := req.OrderList(uc.Uid)
if err != nil {
logger.Error("err:", err)
RespJson(c, status.InternalServerError, nil)
return
}
ret := map[string]interface{}{
"list": list,
"cur_page": req.PageIdx,
"total_page": total,
}
RespOK(c, ret)
return
}
func MallOrderDetail(c *gin.Context) {
req := model.GoodsOrderDetailReq{}
if err := c.ShouldBindJSON(&req); err != nil {
logger.Error(err)
RespJson(c, status.BadRequest, nil)
return
}
uc := auth.GetCurrentUser(c)
if uc == nil {
RespJson(c, status.Unauthorized, nil)
return
}
detail, err := req.OrderDetail(uc.Uid)
if err != nil {
logger.Error("err:", err)
RespJson(c, status.InternalServerError, nil)
return
}
RespOK(c, detail)
return
}
func MallOrderRefund(c *gin.Context) {
req := struct {
GoodsOrderId uint32 `json:"goods_order_id"`
RefundReason string `json:"refund_reason"`
}{}
if err := c.ShouldBindJSON(&req); err != nil {
logger.Error(err)
RespJson(c, status.BadRequest, nil)
return
}
uc := auth.GetCurrentUser(c)
if uc == nil {
RespJson(c, status.Unauthorized, nil)
return
}
//model.GoodsOrder{}
var goodsOrder model.GoodsOrder
err := model.NewGoodsOrderQuerySet(model.DB).OrderIdEq(req.GoodsOrderId).One(&goodsOrder)
if err != nil {
log.Error().Msgf("goods order err:%#v", err)
RespJson(c, status.InternalServerError, nil)
return
}
if goodsOrder.Amount == 0 && goodsOrder.DeliverStoreId == 0 { // 新机预售券
// 查询新机预售券状态,只有未使用才能退
var newMachineCoupon model.UserCoupon
err = model.NewUserCouponQuerySet(model.DB).UidEq(goodsOrder.Uid).ActivityIdEq(model.NewMachineActivityId).
One(&newMachineCoupon)
if err != nil {
logger.Error(err)
RespJson(c, status.BadRequest, nil)
return
}
switch newMachineCoupon.State {
case 2:
RespJson(c, status.NewMachineCouponUsed, nil)
return
case 3:
RespJson(c, status.NewMachineCouponOutOfTime, nil)
return
case 4:
RespJson(c, status.NewMachineCouponDisabled, nil)
return
}
} else {
if goodsOrder.CreatedAt.AddDate(0, 0, 15).Before(utils.Now()) ||
(!goodsOrder.ReceivedTime.IsZero() && goodsOrder.ReceivedTime.AddDate(0, 0, 7).Before(utils.Now())) {
log.Error().Msg("goods order refund exceed the time limit")
RespJson(c, status.InternalServerError, nil)
return
}
}
if goodsOrder.State != model.GoodsOrderStateDelivered &&
goodsOrder.State != model.GoodsOrderStateReceived &&
goodsOrder.State != model.GoodsOrderStateRefundedCancel {
log.Error().Msg("goods order state err")
RespJson(c, status.OrderDelivered, nil)
return
}
if goodsOrder.DeliverStoreId != 0 { // 线上购买预售券时没有发货门店
store, err := model.GetStore(goodsOrder.DeliverStoreId)
if err != nil {
log.Error().Msgf("get store err:%#v", err)
RespJson(c, status.InternalServerError, nil)
return
}
goodsOrder.DeliverStore = store
}
err = model.NewGoodsOrderQuerySet(model.DB).OrderIdEq(req.GoodsOrderId).GetUpdater().
SetRefundReason(req.RefundReason).SetState(model.GoodsOrderStateOnRefund).Update()
if err != nil {
log.Error().Msgf("update goods order err:%#v", err)
RespJson(c, status.InternalServerError, nil)
return
}
RespOK(c, goodsOrder)
return
}
func MallOrderRefundCancel(c *gin.Context) {
req := struct {
GoodsOrderId uint32 `json:"goods_order_id"`
}{}
if err := c.ShouldBindJSON(&req); err != nil {
logger.Error(err)
RespJson(c, status.BadRequest, nil)
return
}
var goodsOrder model.GoodsOrder
err := model.NewGoodsOrderQuerySet(model.DB).OrderIdEq(req.GoodsOrderId).One(&goodsOrder)
if err != nil {
log.Error().Msgf("goods order err:%#v", err)
RespJson(c, status.InternalServerError, nil)
return
}
if goodsOrder.State != model.GoodsOrderStateOnRefund {
log.Error().Msgf("state err")
RespJson(c, status.StateNotCancel, nil)
return
}
err = model.NewGoodsOrderQuerySet(model.DB).OrderIdEq(req.GoodsOrderId).GetUpdater().
SetState(model.GoodsOrderStateRefundedCancel).Update()
if err != nil {
log.Error().Msgf("update goods order err:%#v", err)
RespJson(c, status.InternalServerError, nil)
return
}
RespOK(c, nil)
return
}
func MallOrderRefundSend(c *gin.Context) {
req := struct {
GoodsOrderId uint32 `json:"goods_order_id"`
RefundExpressCompany string `json:"refund_express_company"` // 退货物流公司
RefundExpressCompanyNo string `json:"refund_express_company_no"` // 退货物流公司编号
RefundExpressNo string `json:"refund_express_no"` // 退货物流单号
}{}
if err := c.ShouldBindJSON(&req); err != nil {
logger.Error(err)
RespJson(c, status.BadRequest, nil)
return
}
var goodsOrder model.GoodsOrder
err := model.NewGoodsOrderQuerySet(model.DB).OrderIdEq(req.GoodsOrderId).One(&goodsOrder)
if err != nil {
log.Error().Msgf("goods order err:%#v", err)
RespJson(c, status.InternalServerError, nil)
return
}
if goodsOrder.State != model.GoodsOrderStateOnRefund {
log.Error().Msgf("state err")
RespJson(c, status.StateNotCancel, nil)
return
}
err = model.NewGoodsOrderQuerySet(model.DB).OrderIdEq(req.GoodsOrderId).GetUpdater().
SetRefundExpressNo(req.RefundExpressNo).
SetRefundExpressCompany(req.RefundExpressCompany).
SetRefundExpressCompanyNo(req.RefundExpressCompanyNo).Update()
if err != nil {
log.Error().Msgf("update goods order err:%#v", err)
RespJson(c, status.InternalServerError, nil)
return
}
RespOK(c, nil)
return
}
func MallOrderCancel(c *gin.Context) {
req := struct {
GoodsOrderId uint32 `json:"goods_order_id"`
}{}
if err := c.ShouldBindJSON(&req); err != nil {
logger.Error(err)
RespJson(c, status.BadRequest, nil)
return
}
uc := auth.GetCurrentUser(c)
if uc == nil {
RespJson(c, status.Unauthorized, nil)
return
}
var goodsOrder model.GoodsOrder
err := model.NewGoodsOrderQuerySet(model.DB).OrderIdEq(req.GoodsOrderId).One(&goodsOrder)
if err != nil {
log.Error().Msgf("goods order err:%#v", err)
RespJson(c, status.InternalServerError, nil)
return
}
if goodsOrder.State != model.GoodsOrderStateOnDeliver {
log.Error().Msg("goods order state err")
RespJson(c, status.OrderDelivered, nil)
return
}
if goodsOrder.PayStatus != 2 {
log.Error().Msg("not pay")
RespJson(c, status.InternalServerError, nil)
return
}
if goodsOrder.Rm != 0 {
outTradeNo, err := model.GetWxPayExpressFeeRefundRecord(goodsOrder.OrderId)
if err != nil {
logger.Error("err:", err)
RespJson(c, status.InternalServerError, nil)
return
}
//m.OpenNo = model.GetOrderSn()
memberRecord := &model.UserOpenMemberRecord{OpenNo: model.GetOrderSn(), OrderType: 7, GoodsOrder: &goodsOrder}
err = memberRecord.MallGoodsOrderRefund(outTradeNo)
if err != nil {
logger.Error("err:", err)
RespJson(c, status.InternalServerError, nil)
return
}
}
//tx := model.DB.Begin()
err = model.NewGoodsOrderQuerySet(model.DB).OrderIdEq(req.GoodsOrderId).GetUpdater().
SetState(model.GoodsOrderStateCancel).Update()
if err != nil {
//tx.Rollback()
log.Error().Msgf("update goods order err:%#v", err)
RespJson(c, status.InternalServerError, nil)
return
}
//err = tx.Commit().Error
//if err != nil {
// tx.Rollback()
// log.Error().Msgf("commit err:%#v", err)
// RespJson(c, status.InternalServerError, nil)
// return
//}
if goodsOrder.Vm != 0 {
err = model.UserVmUpdate(goodsOrder.Uid, int(goodsOrder.Vm), model.VmEventBuyGoodsReject, "购买商品积分抵扣取消")
if err != nil {
logger.Error("err:", err)
RespJson(c, status.InternalServerError, nil)
return
}
}
model.UpdateDeliverTaskSubStateCancel(goodsOrder.OrderId)
RespOK(c, "")
return
}
// MallUserVmRecord 用户积分记录
// @Summary 用户积分记录
// @Tags 用户信息
// @Produce json
// @Accept json
// @Param request body model.MallUserVmRecordReq true "用户登陆模型"
// @Success 200 {object} model.MallUserVmRecordResp
// @Router /api/v1/mall/user/vm_record [post]
func MallUserVmRecord(c *gin.Context) {
req := model.MallUserVmRecordReq{}
if err := c.ShouldBindJSON(&req); err != nil {
logger.Error(err)
RespJson(c, status.BadRequest, nil)
return
}
uc := auth.GetCurrentUser(c)
if uc == nil {
RespJson(c, status.Unauthorized, nil)
return
}
list, total, err := req.UserVmRecordList(uc.Uid)
if err != nil {
logger.Error("err:", err)
RespJson(c, status.InternalServerError, nil)
return
}
ret := model.MallUserVmRecordResp{
List: list,
CurPage: req.PageIdx,
TotalPage: total,
}
RespOK(c, ret)
return
}
func MallGoodsOrderConfirmReceipt(c *gin.Context) {
req := model.MallGoodsOrderConfirmReceiptReq{}
if err := c.ShouldBindJSON(&req); err != nil {
logger.Error(err)
RespJson(c, status.BadRequest, nil)
return
}
uc := auth.GetCurrentUser(c)
if uc == nil {
RespJson(c, status.Unauthorized, nil)
return
}
err := req.MallGoodsOrderConfirmReceipt(uc.Uid)
if err != nil {
logger.Error("err:", err)
RespJson(c, status.InternalServerError, nil)
return
}
RespOK(c, nil)
return
}
func MallGoodsSpec(c *gin.Context) {
req := model.MallGoodsSpecReq{}
if err := c.ShouldBindJSON(&req); err != nil {
logger.Error(err)
RespJson(c, status.BadRequest, nil)
return
}
err := req.Spec()
if err != nil {
logger.Error("err:", err)
if err.Error() == "not_found" {
RespJson(c, status.OrderStockOut, nil)
return
}
RespJson(c, status.InternalServerError, nil)
return
}
RespOK(c, req.GoodsAttribute)
return
}
func MallGoodsCatList(c *gin.Context) {
var goodsCats []model.GoodsCat
err := model.DB.Table("goods_cat").Where("level=?", 1).Where("state=2").
Order("sort DESC").Find(&goodsCats).Error
if err != nil {
logger.Error("goods cat list err:", err)
RespJson(c, status.InternalServerError, nil)
return
}
pids := make([]uint32, 0)
for i, _ := range goodsCats {
pids = append(pids, goodsCats[i].ID)
}
var subCat []model.GoodsCat
err = model.DB.Table("goods_cat").Where("pid in (?)", pids).Where("state=2").
Order("sort DESC").Find(&subCat).Error
if err != nil {
logger.Errorf("pCat err:%#v", err)
RespJson(c, status.InternalServerError, nil)
return
}
pCatMap := make(map[uint32][]model.GoodsCat, 0)
for i, _ := range subCat {
pCatMap[subCat[i].Pid] = append(pCatMap[subCat[i].Pid], subCat[i])
}
for i, _ := range goodsCats {
v, ok := pCatMap[goodsCats[i].ID]
if ok {
goodsCats[i].SubCats = v
}
}
RespOK(c, goodsCats)
return
}