package handlers import ( "io" "net/http" "strings" "attune-heart-therapy/internal/services" "github.com/gin-gonic/gin" ) type PaymentHandler struct { paymentService services.PaymentService } func NewPaymentHandler(paymentService services.PaymentService) *PaymentHandler { return &PaymentHandler{ paymentService: paymentService, } } // CreatePaymentIntent handles POST /api/payments/intent for payment intent creation func (h *PaymentHandler) CreatePaymentIntent(c *gin.Context) { var req services.CreatePaymentIntentRequest // Bind JSON request to struct if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "Invalid request format", "details": err.Error(), }) return } // Set default currency if not provided if req.Currency == "" { req.Currency = "usd" } // Create payment intent paymentIntent, err := h.paymentService.CreatePaymentIntent(req.Amount, req.Currency) if err != nil { if strings.Contains(err.Error(), "amount must be greater than 0") { c.JSON(http.StatusBadRequest, gin.H{ "error": "Invalid amount", "details": err.Error(), }) return } c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to create payment intent", "details": err.Error(), }) return } c.JSON(http.StatusCreated, gin.H{ "message": "Payment intent created successfully", "client_secret": paymentIntent.ClientSecret, "payment_intent": paymentIntent.ID, "amount": paymentIntent.Amount, "currency": paymentIntent.Currency, "status": paymentIntent.Status, }) } // ConfirmPayment handles POST /api/payments/confirm for payment confirmation func (h *PaymentHandler) ConfirmPayment(c *gin.Context) { var req services.ConfirmPaymentRequest // Bind JSON request to struct if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "Invalid request format", "details": err.Error(), }) return } // Confirm payment paymentIntent, err := h.paymentService.ConfirmPayment(req.PaymentIntentID) if err != nil { if strings.Contains(err.Error(), "payment intent ID is required") { c.JSON(http.StatusBadRequest, gin.H{ "error": "Payment intent ID is required", "details": err.Error(), }) return } // Check if it's a Stripe error (payment failed, card declined, etc.) if strings.Contains(err.Error(), "Your card was declined") || strings.Contains(err.Error(), "insufficient_funds") || strings.Contains(err.Error(), "card_declined") { c.JSON(http.StatusPaymentRequired, gin.H{ "error": "Payment failed", "details": err.Error(), }) return } c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to confirm payment", "details": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "message": "Payment confirmed successfully", "payment_intent": paymentIntent.ID, "status": paymentIntent.Status, "amount": paymentIntent.Amount, "currency": paymentIntent.Currency, }) } // HandleWebhook handles POST /api/payments/webhook for Stripe webhooks func (h *PaymentHandler) HandleWebhook(c *gin.Context) { // Get the raw body body, err := io.ReadAll(c.Request.Body) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "Failed to read request body", "details": err.Error(), }) return } // Get Stripe signature from header signature := c.GetHeader("Stripe-Signature") if signature == "" { c.JSON(http.StatusBadRequest, gin.H{ "error": "Missing Stripe signature header", }) return } // Process webhook err = h.paymentService.HandleWebhook(body, signature) if err != nil { if strings.Contains(err.Error(), "webhook signature") { c.JSON(http.StatusBadRequest, gin.H{ "error": "Invalid webhook signature", "details": err.Error(), }) return } if strings.Contains(err.Error(), "webhook payload is empty") { c.JSON(http.StatusBadRequest, gin.H{ "error": "Empty webhook payload", "details": err.Error(), }) return } c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to process webhook", "details": err.Error(), }) return } // Return 200 to acknowledge receipt of the webhook c.JSON(http.StatusOK, gin.H{ "message": "Webhook processed successfully", }) }