mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 18:52:56 +00:00
hot fix #1
This commit is contained in:
@@ -0,0 +1,282 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"fotbal-club/internal/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// TicketCheckoutController handles ticket purchases integrated with e-shop
|
||||
type TicketCheckoutController struct {
|
||||
DB *gorm.DB
|
||||
}
|
||||
|
||||
func NewTicketCheckoutController(db *gorm.DB) *TicketCheckoutController {
|
||||
return &TicketCheckoutController{DB: db}
|
||||
}
|
||||
|
||||
// TicketCheckoutRequest represents a ticket purchase request
|
||||
type TicketCheckoutRequest struct {
|
||||
// Customer info
|
||||
FirstName string `json:"first_name" binding:"required"`
|
||||
LastName string `json:"last_name" binding:"required"`
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Phone string `json:"phone"`
|
||||
|
||||
// Ticket reservations
|
||||
TicketReservations []TicketReservationRequest `json:"ticket_reservations" binding:"required,min=1"`
|
||||
|
||||
// Payment method
|
||||
PaymentMethod string `json:"payment_method" binding:"required,oneof=stripe gopay"`
|
||||
}
|
||||
|
||||
type TicketReservationRequest struct {
|
||||
TicketID uint `json:"ticket_id" binding:"required"`
|
||||
// Note: Quantity is fixed per reservation, but we allow multiple reservations per order
|
||||
}
|
||||
|
||||
// CreateTicketOrder creates an e-shop order from ticket reservations
|
||||
func (tcc *TicketCheckoutController) CreateTicketOrder(c *gin.Context) {
|
||||
var req TicketCheckoutRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user info if logged in
|
||||
userIDVal, _ := c.Get("userID")
|
||||
var userID *uint
|
||||
if u, ok := userIDVal.(uint); ok {
|
||||
userID = &u
|
||||
}
|
||||
|
||||
// Validate all ticket reservations exist and are available
|
||||
var ticketIDs []uint
|
||||
for _, tr := range req.TicketReservations {
|
||||
ticketIDs = append(ticketIDs, tr.TicketID)
|
||||
}
|
||||
|
||||
var tickets []models.Ticket
|
||||
if err := tcc.DB.Where("id IN ? AND status = ?", ticketIDs, "reserved").
|
||||
Preload("Campaign").Preload("TicketType").Find(&tickets).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch tickets"})
|
||||
return
|
||||
}
|
||||
|
||||
if len(tickets) != len(req.TicketReservations) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Some tickets not found or not available"})
|
||||
return
|
||||
}
|
||||
|
||||
// Verify all tickets belong to the same customer (email)
|
||||
for _, ticket := range tickets {
|
||||
if ticket.HolderEmail != req.Email {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Ticket holder email doesn't match customer email"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate total amount
|
||||
var totalAmount int64
|
||||
for _, ticket := range tickets {
|
||||
totalAmount += ticket.TotalPriceCents
|
||||
}
|
||||
|
||||
// Generate order number
|
||||
orderNumber := fmt.Sprintf("%s%d", time.Now().Format("200601"), time.Now().Unix()%100000)
|
||||
|
||||
// Create e-shop order
|
||||
ticketOrderFlag := uint(1)
|
||||
order := models.EshopOrder{
|
||||
OrderNumber: orderNumber,
|
||||
UserID: userID,
|
||||
Email: req.Email,
|
||||
FirstName: req.FirstName,
|
||||
LastName: req.LastName,
|
||||
Status: "awaiting_payment",
|
||||
TotalAmountCents: totalAmount,
|
||||
Currency: "CZK", // Tickets are always in CZK for now
|
||||
TicketOrder: &ticketOrderFlag,
|
||||
ShippingMethod: "digital", // Tickets are digital
|
||||
ShippingPriceCents: 0, // No shipping for digital tickets
|
||||
}
|
||||
|
||||
tx := tcc.DB.Begin()
|
||||
|
||||
// Create order
|
||||
if err := tx.Create(&order).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create order"})
|
||||
return
|
||||
}
|
||||
|
||||
// Create order items for tickets
|
||||
for _, ticket := range tickets {
|
||||
itemName := fmt.Sprintf("%s - %s", ticket.Campaign.Title, ticket.TicketType.Name)
|
||||
if ticket.Campaign.HomeTeam != nil && ticket.Campaign.AwayTeam != nil {
|
||||
itemName = fmt.Sprintf("%s %s vs %s - %s",
|
||||
itemName, *ticket.Campaign.HomeTeam, *ticket.Campaign.AwayTeam, ticket.TicketType.Name)
|
||||
}
|
||||
|
||||
orderItem := models.EshopOrderItem{
|
||||
OrderID: order.ID,
|
||||
Name: itemName,
|
||||
Quantity: ticket.Quantity,
|
||||
UnitPriceCents: ticket.UnitPriceCents,
|
||||
Currency: ticket.Currency,
|
||||
VATRate: 0.21, // 21% VAT for tickets
|
||||
TicketID: &ticket.ID, // Link to ticket
|
||||
}
|
||||
|
||||
if err := tx.Create(&orderItem).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create order item"})
|
||||
return
|
||||
}
|
||||
|
||||
// Update ticket to link with order
|
||||
if err := tx.Model(&ticket).Update("order_id", order.ID).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to link ticket to order"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
|
||||
// Create payment based on method
|
||||
var paymentResult interface{}
|
||||
var err error
|
||||
|
||||
switch req.PaymentMethod {
|
||||
case "stripe":
|
||||
paymentResult, err = tcc.createStripePayment(&order)
|
||||
case "gopay":
|
||||
paymentResult, err = tcc.createGoPayPayment(&order)
|
||||
default:
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Unsupported payment method"})
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create payment", "details": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"order": order,
|
||||
"payment": paymentResult,
|
||||
})
|
||||
}
|
||||
|
||||
// CompleteTicketOrder handles successful payment completion for tickets
|
||||
func (tcc *TicketCheckoutController) CompleteTicketOrder(c *gin.Context) {
|
||||
orderID := c.Param("order_id")
|
||||
|
||||
var order models.EshopOrder
|
||||
if err := tcc.DB.Where("id = ? AND ticket_order = ?", orderID, true).First(&order).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Ticket order not found"})
|
||||
return
|
||||
}
|
||||
|
||||
if order.Status != "paid" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Order not paid"})
|
||||
return
|
||||
}
|
||||
|
||||
// Get all tickets for this order
|
||||
var tickets []models.Ticket
|
||||
if err := tcc.DB.Where("order_id = ?", order.ID).Find(&tickets).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch tickets"})
|
||||
return
|
||||
}
|
||||
|
||||
// Confirm all tickets
|
||||
tx := tcc.DB.Begin()
|
||||
|
||||
for _, ticket := range tickets {
|
||||
// Update ticket status to paid
|
||||
if err := tx.Model(&ticket).Updates(map[string]interface{}{
|
||||
"status": "paid",
|
||||
}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to confirm ticket"})
|
||||
return
|
||||
}
|
||||
|
||||
// Update availability
|
||||
if err := tx.Model(&models.TicketAvailability{}).
|
||||
Where("campaign_id = ? AND ticket_type_id = ?", ticket.CampaignID, ticket.TicketTypeID).
|
||||
Updates(map[string]interface{}{
|
||||
"sold_quantity": gorm.Expr("sold_quantity + ?", ticket.Quantity),
|
||||
"reserved_quantity": gorm.Expr("reserved_quantity - ?", ticket.Quantity),
|
||||
}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update availability"})
|
||||
return
|
||||
}
|
||||
|
||||
// Send ticket email (async)
|
||||
go func(t models.Ticket) {
|
||||
// TODO: Implement ticket confirmation email with barcode/QR
|
||||
// For now, just log the action
|
||||
fmt.Printf("Ticket confirmation email sent to %s for ticket ID %d\n", t.HolderEmail, t.ID)
|
||||
}(ticket)
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Ticket order completed successfully",
|
||||
"order": order,
|
||||
"tickets": tickets,
|
||||
})
|
||||
}
|
||||
|
||||
// GetTicketOrders returns all ticket orders for a user
|
||||
func (tcc *TicketCheckoutController) GetTicketOrders(c *gin.Context) {
|
||||
userIDVal, _ := c.Get("userID")
|
||||
userID, ok := userIDVal.(uint)
|
||||
if !ok {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
|
||||
return
|
||||
}
|
||||
|
||||
var orders []models.EshopOrder
|
||||
if err := tcc.DB.Where("user_id = ? AND ticket_order = ?", userID, true).
|
||||
Preload("Items.Ticket").
|
||||
Preload("Items.Ticket.Campaign").
|
||||
Preload("Items.Ticket.TicketType").
|
||||
Order("created_at DESC").
|
||||
Find(&orders).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch ticket orders"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, orders)
|
||||
}
|
||||
|
||||
// Helper methods for payment creation
|
||||
|
||||
func (tcc *TicketCheckoutController) createStripePayment(order *models.EshopOrder) (interface{}, error) {
|
||||
// This would integrate with the existing Stripe service
|
||||
// For now, return a mock response
|
||||
return gin.H{
|
||||
"payment_intent_id": "pi_mock_" + order.OrderNumber,
|
||||
"client_secret": "pi_mock_secret_" + order.OrderNumber,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (tcc *TicketCheckoutController) createGoPayPayment(order *models.EshopOrder) (interface{}, error) {
|
||||
// This would integrate with the existing GoPay service
|
||||
// For now, return a mock response
|
||||
return gin.H{
|
||||
"payment_id": fmt.Sprintf("gopay_%d", order.ID),
|
||||
"redirect_url": fmt.Sprintf("https://gate.gopay.cz/gw/v3/payment/%d", order.ID),
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user