package app import ( "bindbox-game/internal/pkg/core" "bindbox-game/internal/repository/mysql/model" "fmt" "time" ) // applyCouponWithCap 优惠券抵扣(含50%封顶与金额券部分使用) // 功能:在订单上应用一张用户券,实施总价50%封顶;金额券支持“部分使用”,在 remark 记录明细 // 参数: // - ctx:请求上下文 // - userID:用户ID // - order:待更新的订单对象(入参引用,被本函数更新 discount_amount/actual_amount/remark) // - activityID:活动ID用于范围校验 // - userCouponID:用户持券ID // // 返回:本次实际应用的抵扣金额(分);若不适用或受封顶为0则返回0 func (h *handler) applyCouponWithCap(ctx core.Context, userID int64, order *model.Orders, activityID int64, userCouponID int64) int64 { uc, _ := h.readDB.UserCoupons.WithContext(ctx.RequestContext()).Where(h.readDB.UserCoupons.ID.Eq(userCouponID), h.readDB.UserCoupons.UserID.Eq(userID), h.readDB.UserCoupons.Status.Eq(1)).First() if uc == nil { return 0 } sc, _ := h.readDB.SystemCoupons.WithContext(ctx.RequestContext()).Where(h.readDB.SystemCoupons.ID.Eq(uc.CouponID), h.readDB.SystemCoupons.Status.Eq(1)).First() now := time.Now() if sc == nil { return 0 } if uc.ValidStart.After(now) { return 0 } if !uc.ValidEnd.IsZero() && uc.ValidEnd.Before(now) { return 0 } scopeOK := (sc.ScopeType == 1) || (sc.ScopeType == 2 && sc.ActivityID == activityID) if !scopeOK { return 0 } if order.TotalAmount < sc.MinSpend { return 0 } cap := order.TotalAmount / 2 remainingCap := cap - order.DiscountAmount if remainingCap <= 0 { return 0 } applied := int64(0) switch sc.DiscountType { case 1: var bal int64 _ = h.repo.GetDbR().Raw("SELECT COALESCE(balance_amount,0) FROM user_coupons WHERE id=?", userCouponID).Scan(&bal).Error if bal <= 0 { bal = sc.DiscountValue } if bal > 0 { if bal > remainingCap { applied = remainingCap } else { applied = bal } } case 2: applied = sc.DiscountValue if applied > remainingCap { applied = remainingCap } case 3: rate := sc.DiscountValue if rate < 0 { rate = 0 } if rate > 1000 { rate = 1000 } newAmt := order.ActualAmount * rate / 1000 d := order.ActualAmount - newAmt if d > remainingCap { applied = remainingCap } else { applied = d } } if applied > order.ActualAmount { applied = order.ActualAmount } if applied <= 0 { return 0 } order.DiscountAmount += applied order.ActualAmount -= applied order.Remark = order.Remark + fmt.Sprintf("|c:%d:%d", userCouponID, applied) return applied } // updateUserCouponAfterApply 应用后更新用户券(扣减余额或核销) // 功能:根据订单 remark 中记录的 applied_amount, // // 对直金额券扣减余额并在余额为0时核销;满减/折扣券一次性核销 // // 参数: // - ctx:请求上下文 // - userID:用户ID // - order:订单(用于读取 remark 和写入 used_order_id) // - userCouponID:用户持券ID // // 返回:无 func (h *handler) updateUserCouponAfterApply(ctx core.Context, userID int64, order *model.Orders, userCouponID int64) { uc, _ := h.readDB.UserCoupons.WithContext(ctx.RequestContext()).Where(h.readDB.UserCoupons.ID.Eq(userCouponID), h.readDB.UserCoupons.UserID.Eq(userID), h.readDB.UserCoupons.Status.Eq(1)).First() if uc == nil { return } sc, _ := h.readDB.SystemCoupons.WithContext(ctx.RequestContext()).Where(h.readDB.SystemCoupons.ID.Eq(uc.CouponID), h.readDB.SystemCoupons.Status.Eq(1)).First() if sc == nil { return } applied := int64(0) remark := order.Remark p := 0 for i := 0; i < len(remark); i++ { if remark[i] == '|' { seg := remark[p:i] if len(seg) > 2 && seg[:2] == "c:" { j := 2 var id int64 for j < len(seg) && seg[j] >= '0' && seg[j] <= '9' { id = id*10 + int64(seg[j]-'0') j++ } if j < len(seg) && seg[j] == ':' { j++ var amt int64 for j < len(seg) && seg[j] >= '0' && seg[j] <= '9' { amt = amt*10 + int64(seg[j]-'0') j++ } if id == userCouponID { applied = amt } } } p = i + 1 } } if sc.DiscountType == 1 { var bal int64 _ = h.repo.GetDbR().Raw("SELECT COALESCE(balance_amount,0) FROM user_coupons WHERE id=?", userCouponID).Scan(&bal).Error newBal := bal - applied if newBal < 0 { newBal = 0 } if newBal == 0 { _, _ = h.writeDB.UserCoupons.WithContext(ctx.RequestContext()).Where(h.writeDB.UserCoupons.ID.Eq(userCouponID), h.writeDB.UserCoupons.UserID.Eq(userID)).Updates(map[string]any{"balance_amount": newBal, h.writeDB.UserCoupons.Status.ColumnName().String(): 2, h.writeDB.UserCoupons.UsedOrderID.ColumnName().String(): order.ID, h.writeDB.UserCoupons.UsedAt.ColumnName().String(): time.Now()}) } else { _, _ = h.writeDB.UserCoupons.WithContext(ctx.RequestContext()).Where(h.writeDB.UserCoupons.ID.Eq(userCouponID), h.writeDB.UserCoupons.UserID.Eq(userID)).Updates(map[string]any{"balance_amount": newBal, h.writeDB.UserCoupons.UsedOrderID.ColumnName().String(): order.ID, h.writeDB.UserCoupons.UsedAt.ColumnName().String(): time.Now()}) } } else { _, _ = h.writeDB.UserCoupons.WithContext(ctx.RequestContext()).Where(h.writeDB.UserCoupons.ID.Eq(userCouponID), h.writeDB.UserCoupons.UserID.Eq(userID)).Updates(map[string]any{h.writeDB.UserCoupons.Status.ColumnName().String(): 2, h.writeDB.UserCoupons.UsedOrderID.ColumnName().String(): order.ID, h.writeDB.UserCoupons.UsedAt.ColumnName().String(): time.Now()}) } } // markOrderPaid 将订单标记为已支付 // 功能:用于0元订单直接置为已支付并写入支付时间 // 参数: // - ctx:请求上下文 // - orderNo:订单号 // // 返回:无 func (h *handler) markOrderPaid(ctx core.Context, orderNo string) { now := time.Now() _, _ = h.writeDB.Orders.WithContext(ctx.RequestContext()).Where(h.writeDB.Orders.OrderNo.Eq(orderNo)).Updates(map[string]any{h.writeDB.Orders.Status.ColumnName().String(): 2, h.writeDB.Orders.PaidAt.ColumnName().String(): now}) } func (h *handler) randomID(prefix string) string { now := time.Now() return prefix + now.Format("20060102150405") } func (h *handler) orderModel(userID int64, orderNo string, amount int64, activityID int64, issueID int64, count int64) *model.Orders { return &model.Orders{UserID: userID, OrderNo: orderNo, SourceType: 2, TotalAmount: amount, DiscountAmount: 0, PointsAmount: 0, ActualAmount: amount, Status: 1, IsConsumed: 0, Remark: fmt.Sprintf("lottery:activity:%d|issue:%d|count:%d", activityID, issueID, count)} } func parseSlotFromRemark(remark string) int64 { if remark == "" { return -1 } p := 0 for i := 0; i < len(remark); i++ { if remark[i] == '|' { seg := remark[p:i] if len(seg) > 5 && seg[:5] == "slot:" { var n int64 for j := 5; j < len(seg); j++ { c := seg[j] if c < '0' || c > '9' { break } n = n*10 + int64(c-'0') } return n } p = i + 1 } } if p < len(remark) { seg := remark[p:] if len(seg) > 5 && seg[:5] == "slot:" { var n int64 for j := 5; j < len(seg); j++ { c := seg[j] if c < '0' || c > '9' { break } n = n*10 + int64(c-'0') } return n } } return -1 } func parseItemCardIDFromRemark(remark string) int64 { // remark segments separated by '|', find segment starting with "itemcard:" p := 0 n := len(remark) for i := 0; i <= n; i++ { if i == n || remark[i] == '|' { seg := remark[p:i] if len(seg) > 9 && seg[:9] == "itemcard:" { var val int64 valid := true for j := 9; j < len(seg); j++ { c := seg[j] if c < '0' || c > '9' { valid = false break } val = val*10 + int64(c-'0') } if valid && val > 0 { return val } } p = i + 1 } } return 0 } func (h *handler) joinSigPayload(userID int64, issueID int64, ts int64, nonce int64) string { return fmt.Sprintf("%d|%d|%d|%d", userID, issueID, ts, nonce) } func buildSlotsRemarkWithScalarCount(slots []int64) string { s := "" for i := range slots { if i > 0 { s += "," } s += fmt.Sprintf("%d:%d", slots[i]-1, 1) } return s } func parseSlotsCountsFromRemark(remark string) ([]int64, []int64) { if remark == "" { return nil, nil } p := 0 for i := 0; i < len(remark); i++ { if remark[i] == '|' { seg := remark[p:i] if len(seg) > 6 && seg[:6] == "slots:" { pairs := seg[6:] idxs := make([]int64, 0) cnts := make([]int64, 0) start := 0 for start <= len(pairs) { end := start for end < len(pairs) && pairs[end] != ',' { end++ } if end > start { a := pairs[start:end] // a format: num:num x, y := int64(0), int64(0) j := 0 for j < len(a) && a[j] >= '0' && a[j] <= '9' { x = x*10 + int64(a[j]-'0') j++ } if j < len(a) && a[j] == ':' { j++ for j < len(a) && a[j] >= '0' && a[j] <= '9' { y = y*10 + int64(a[j]-'0') j++ } } if y > 0 { idxs = append(idxs, x+1) cnts = append(cnts, y) } } start = end + 1 } return idxs, cnts } p = i + 1 } } return nil, nil } func parseIssueIDFromRemark(remark string) int64 { if remark == "" { return 0 } // Try "issue:" or "matching_game:issue:" // Split by | segs := make([]string, 0) last := 0 for i := 0; i < len(remark); i++ { if remark[i] == '|' { segs = append(segs, remark[last:i]) last = i + 1 } } if last < len(remark) { segs = append(segs, remark[last:]) } for _, seg := range segs { // handle 'issue:123' if len(seg) > 6 && seg[:6] == "issue:" { var n int64 for j := 6; j < len(seg); j++ { c := seg[j] if c < '0' || c > '9' { break } n = n*10 + int64(c-'0') } return n } // handle 'matching_game:issue:123' if len(seg) > 20 && seg[:20] == "matching_game:issue:" { var n int64 for j := 20; j < len(seg); j++ { c := seg[j] if c < '0' || c > '9' { break } n = n*10 + int64(c-'0') } return n } } return 0 } func parseCountFromRemark(remark string) int64 { if remark == "" { return 1 } p := 0 for i := 0; i < len(remark); i++ { if remark[i] == '|' { seg := remark[p:i] if len(seg) > 6 && seg[:6] == "count:" { var n int64 for j := 6; j < len(seg); j++ { c := seg[j] if c < '0' || c > '9' { break } n = n*10 + int64(c-'0') } if n <= 0 { return 1 } return n } p = i + 1 } } return 1 }