- Add PaymentHandler with methods for creating payment intents, confirming payments, and handling webhooks - Implement PaymentService interface with Stripe payment processing logic - Create DTOs for payment-related requests in services/interfaces.go - Add error handling and validation for payment-related operations - Configure Stripe API key and support for automatic payment methods - Implement webhook signature verification and event processing - Enhance error responses with detailed error messages and appropriate HTTP status codes Enables full payment flow using Stripe, supporting intent creation, payment confirmation, and webhook handling for robust transaction management.
172 lines
4.3 KiB
Go
172 lines
4.3 KiB
Go
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",
|
|
})
|
|
}
|