mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
283 lines
8.4 KiB
Go
283 lines
8.4 KiB
Go
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
|
|
}
|