feat: 订单自动过期-查看时检查并取消超时订单,收银台倒计时结束自动取消
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"sale/internal/models"
|
"sale/internal/models"
|
||||||
"sale/internal/schemas"
|
"sale/internal/schemas"
|
||||||
@@ -91,6 +92,41 @@ func NewOrderHandler() *OrderHandler {
|
|||||||
return &OrderHandler{}
|
return &OrderHandler{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkOrderExpired(order *models.Order) {
|
||||||
|
if order.Status != models.OrderStatusPendingPayment || order.PaymentExpiresAt == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if time.Now().After(*order.PaymentExpiresAt) {
|
||||||
|
tx := utils.DB.Begin()
|
||||||
|
tx.Model(order).Update("status", models.OrderStatusCancelled)
|
||||||
|
for _, item := range order.OrderItems {
|
||||||
|
var inventory models.Inventory
|
||||||
|
if err := tx.Where("product_id = ?", item.ProductID).First(&inventory).Error; err == nil {
|
||||||
|
tx.Model(&inventory).UpdateColumn("quantity", inventory.Quantity+item.Quantity)
|
||||||
|
}
|
||||||
|
var product models.Product
|
||||||
|
if err := tx.First(&product, item.ProductID).Error; err == nil {
|
||||||
|
if product.RequireCredit {
|
||||||
|
tx.Model(&models.User{}).Where("id = ?", order.UserID).
|
||||||
|
UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits + ?", product.CreditCost*item.Quantity))
|
||||||
|
}
|
||||||
|
if product.CreditReward > 0 {
|
||||||
|
tx.Model(&models.User{}).Where("id = ?", order.UserID).
|
||||||
|
UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits - ?", product.CreditReward*item.Quantity))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tx.Commit()
|
||||||
|
order.Status = models.OrderStatusCancelled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkOrdersExpired(orders []models.Order) {
|
||||||
|
for i := range orders {
|
||||||
|
checkOrderExpired(&orders[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (h *OrderHandler) List(c *gin.Context) {
|
func (h *OrderHandler) List(c *gin.Context) {
|
||||||
userID := c.GetUint("user_id")
|
userID := c.GetUint("user_id")
|
||||||
role, _ := c.Get("role")
|
role, _ := c.Get("role")
|
||||||
@@ -107,6 +143,8 @@ func (h *OrderHandler) List(c *gin.Context) {
|
|||||||
query.Preload("OrderItems.Product").Preload("ShippingAddress").
|
query.Preload("OrderItems.Product").Preload("ShippingAddress").
|
||||||
Order("created_at DESC").Find(&orders)
|
Order("created_at DESC").Find(&orders)
|
||||||
|
|
||||||
|
checkOrdersExpired(orders)
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{"data": orders})
|
c.JSON(http.StatusOK, gin.H{"data": orders})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,6 +167,8 @@ func (h *OrderHandler) GetByID(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkOrderExpired(&order)
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{"data": order})
|
c.JSON(http.StatusOK, gin.H{"data": order})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,6 +440,8 @@ func (h *OrderHandler) AdminList(c *gin.Context) {
|
|||||||
utils.DB.Preload("OrderItems.Product").Preload("ShippingAddress").Preload("User").
|
utils.DB.Preload("OrderItems.Product").Preload("ShippingAddress").Preload("User").
|
||||||
Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&orders)
|
Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&orders)
|
||||||
|
|
||||||
|
checkOrdersExpired(orders)
|
||||||
|
|
||||||
totalPages := int(math.Ceil(float64(total) / float64(pageSize)))
|
totalPages := int(math.Ceil(float64(total) / float64(pageSize)))
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"data": orders,
|
"data": orders,
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func (h *PaymentHandler) CreatePayment(c *gin.Context) {
|
|||||||
orderID, _ := strconv.Atoi(c.Param("id"))
|
orderID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
|
||||||
var order models.Order
|
var order models.Order
|
||||||
if err := utils.DB.Where("id = ? AND user_id = ?", orderID, userID).First(&order).Error; err != nil {
|
if err := utils.DB.Preload("OrderItems").Where("id = ? AND user_id = ?", orderID, userID).First(&order).Error; err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"})
|
c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -49,6 +49,20 @@ func (h *PaymentHandler) CreatePayment(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if order.PaymentExpiresAt != nil && time.Now().After(*order.PaymentExpiresAt) {
|
||||||
|
tx := utils.DB.Begin()
|
||||||
|
tx.Model(&order).Update("status", models.OrderStatusCancelled)
|
||||||
|
for _, item := range order.OrderItems {
|
||||||
|
var inventory models.Inventory
|
||||||
|
if err := tx.Where("product_id = ?", item.ProductID).First(&inventory).Error; err == nil {
|
||||||
|
tx.Model(&inventory).UpdateColumn("quantity", inventory.Quantity+item.Quantity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tx.Commit()
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "支付已超时,订单已取消"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var channels []models.PaymentChannel
|
var channels []models.PaymentChannel
|
||||||
utils.DB.Where("is_enabled = ? AND type = ?", true, "bepusdt").Order("sort_order ASC").Find(&channels)
|
utils.DB.Where("is_enabled = ? AND type = ?", true, "bepusdt").Order("sort_order ASC").Find(&channels)
|
||||||
if len(channels) == 0 {
|
if len(channels) == 0 {
|
||||||
@@ -169,15 +183,21 @@ func (h *PaymentHandler) CreatePayment(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var remainingSeconds int64
|
var remainingSeconds int64
|
||||||
|
var expiresAt *time.Time
|
||||||
if expiration > 0 {
|
if expiration > 0 {
|
||||||
remainingSeconds = expiration - time.Now().Unix()
|
remainingSeconds = expiration - time.Now().Unix()
|
||||||
if remainingSeconds < 0 {
|
if remainingSeconds < 0 {
|
||||||
remainingSeconds = 0
|
remainingSeconds = 0
|
||||||
}
|
}
|
||||||
|
if remainingSeconds > 0 {
|
||||||
|
t := time.Now().Add(time.Duration(remainingSeconds) * time.Second)
|
||||||
|
expiresAt = &t
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.DB.Model(&order).Updates(map[string]interface{}{
|
utils.DB.Model(&order).Updates(map[string]interface{}{
|
||||||
"payment_method": "bepusdt",
|
"payment_method": "bepusdt",
|
||||||
|
"payment_expires_at": expiresAt,
|
||||||
})
|
})
|
||||||
|
|
||||||
paymentInfo := map[string]interface{}{
|
paymentInfo := map[string]interface{}{
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ type Order struct {
|
|||||||
ExpressPhoto string `gorm:"type:text" json:"express_photo"`
|
ExpressPhoto string `gorm:"type:text" json:"express_photo"`
|
||||||
CustomsPhoto string `gorm:"type:text" json:"customs_photo"`
|
CustomsPhoto string `gorm:"type:text" json:"customs_photo"`
|
||||||
PaymentMethod string `gorm:"size:50" json:"payment_method"`
|
PaymentMethod string `gorm:"size:50" json:"payment_method"`
|
||||||
|
PaymentExpiresAt *time.Time `json:"payment_expires_at,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
|
|||||||
@@ -78,18 +78,19 @@ const paymentError = ref('')
|
|||||||
const remaining = ref(0)
|
const remaining = ref(0)
|
||||||
const totalTime = ref(0)
|
const totalTime = ref(0)
|
||||||
const orderAmount = ref(0)
|
const orderAmount = ref(0)
|
||||||
|
const orderId = ref(0)
|
||||||
let pollTimer: any = null
|
let pollTimer: any = null
|
||||||
let countdownTimer: any = null
|
let countdownTimer: any = null
|
||||||
|
|
||||||
async function createPayment() {
|
async function createPayment() {
|
||||||
try {
|
try {
|
||||||
const orderId = Number(route.params.id)
|
orderId.value = Number(route.params.id)
|
||||||
const res: any = await orderApi.createPayment(orderId)
|
const res: any = await orderApi.createPayment(orderId.value)
|
||||||
paymentInfo.value = res.data
|
paymentInfo.value = res.data
|
||||||
totalTime.value = res.data.expiration_time || 600
|
totalTime.value = res.data.expiration_time || 600
|
||||||
remaining.value = totalTime.value
|
remaining.value = totalTime.value
|
||||||
|
|
||||||
const orderRes: any = await orderApi.getById(orderId)
|
const orderRes: any = await orderApi.getById(orderId.value)
|
||||||
orderAmount.value = orderRes.data?.total_amount || 0
|
orderAmount.value = orderRes.data?.total_amount || 0
|
||||||
|
|
||||||
if (qrCanvas.value) {
|
if (qrCanvas.value) {
|
||||||
@@ -116,20 +117,22 @@ function startCountdown() {
|
|||||||
if (remaining.value <= 0) {
|
if (remaining.value <= 0) {
|
||||||
clearInterval(countdownTimer)
|
clearInterval(countdownTimer)
|
||||||
clearInterval(pollTimer)
|
clearInterval(pollTimer)
|
||||||
|
orderApi.cancel(orderId.value).catch(() => {})
|
||||||
|
paymentError.value = '支付已超时,订单已取消'
|
||||||
|
paymentInfo.value = null
|
||||||
}
|
}
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
function startPolling() {
|
function startPolling() {
|
||||||
const orderId = Number(route.params.id)
|
|
||||||
pollTimer = setInterval(async () => {
|
pollTimer = setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
const res: any = await orderApi.checkPaymentStatus(orderId)
|
const res: any = await orderApi.checkPaymentStatus(orderId.value)
|
||||||
if (res.data?.status === 'pending_confirm' || res.data?.status === 'completed') {
|
if (res.data?.status === 'pending_confirm' || res.data?.status === 'completed') {
|
||||||
clearInterval(pollTimer)
|
clearInterval(pollTimer)
|
||||||
clearInterval(countdownTimer)
|
clearInterval(countdownTimer)
|
||||||
ElMessage.success('支付成功!')
|
ElMessage.success('支付成功!')
|
||||||
setTimeout(() => router.push(`/orders/${orderId}`), 1500)
|
setTimeout(() => router.push(`/orders/${orderId.value}`), 1500)
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
}, 5000)
|
}, 5000)
|
||||||
|
|||||||
Reference in New Issue
Block a user