This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
BreakPilot Dev 19855efacc
Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
feat: BreakPilot PWA - Full codebase (clean push without large binaries)
All services: admin-v2, studio-v2, website, ai-compliance-sdk,
consent-service, klausur-service, voice-service, and infrastructure.
Large PDFs and compiled binaries excluded via .gitignore.
2026-02-11 13:25:58 +01:00

206 lines
6.1 KiB
Go

package handlers
import (
"io"
"log"
"net/http"
"github.com/breakpilot/billing-service/internal/database"
"github.com/breakpilot/billing-service/internal/services"
"github.com/gin-gonic/gin"
"github.com/stripe/stripe-go/v76/webhook"
)
// WebhookHandler handles Stripe webhook events
type WebhookHandler struct {
db *database.DB
webhookSecret string
subscriptionService *services.SubscriptionService
entitlementService *services.EntitlementService
}
// NewWebhookHandler creates a new WebhookHandler
func NewWebhookHandler(
db *database.DB,
webhookSecret string,
subscriptionService *services.SubscriptionService,
entitlementService *services.EntitlementService,
) *WebhookHandler {
return &WebhookHandler{
db: db,
webhookSecret: webhookSecret,
subscriptionService: subscriptionService,
entitlementService: entitlementService,
}
}
// HandleStripeWebhook handles incoming Stripe webhook events
// POST /api/v1/billing/webhook
func (h *WebhookHandler) HandleStripeWebhook(c *gin.Context) {
// Read the request body
body, err := io.ReadAll(c.Request.Body)
if err != nil {
log.Printf("Webhook: Error reading body: %v", err)
c.JSON(http.StatusBadRequest, gin.H{"error": "cannot read body"})
return
}
// Get the Stripe signature header
sigHeader := c.GetHeader("Stripe-Signature")
if sigHeader == "" {
log.Printf("Webhook: Missing Stripe-Signature header")
c.JSON(http.StatusBadRequest, gin.H{"error": "missing signature"})
return
}
// Verify the webhook signature
event, err := webhook.ConstructEvent(body, sigHeader, h.webhookSecret)
if err != nil {
log.Printf("Webhook: Signature verification failed: %v", err)
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid signature"})
return
}
ctx := c.Request.Context()
// Check if we've already processed this event (idempotency)
processed, err := h.subscriptionService.IsEventProcessed(ctx, event.ID)
if err != nil {
log.Printf("Webhook: Error checking event: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error"})
return
}
if processed {
log.Printf("Webhook: Event %s already processed", event.ID)
c.JSON(http.StatusOK, gin.H{"status": "already_processed"})
return
}
// Mark event as being processed
if err := h.subscriptionService.MarkEventProcessing(ctx, event.ID, string(event.Type)); err != nil {
log.Printf("Webhook: Error marking event: %v", err)
}
// Handle the event based on type
var handleErr error
switch event.Type {
case "checkout.session.completed":
handleErr = h.handleCheckoutSessionCompleted(ctx, event.Data.Raw)
case "customer.subscription.created":
handleErr = h.handleSubscriptionCreated(ctx, event.Data.Raw)
case "customer.subscription.updated":
handleErr = h.handleSubscriptionUpdated(ctx, event.Data.Raw)
case "customer.subscription.deleted":
handleErr = h.handleSubscriptionDeleted(ctx, event.Data.Raw)
case "invoice.paid":
handleErr = h.handleInvoicePaid(ctx, event.Data.Raw)
case "invoice.payment_failed":
handleErr = h.handleInvoicePaymentFailed(ctx, event.Data.Raw)
case "customer.created":
log.Printf("Webhook: Customer created - %s", event.ID)
default:
log.Printf("Webhook: Unhandled event type: %s", event.Type)
}
if handleErr != nil {
log.Printf("Webhook: Error handling %s: %v", event.Type, handleErr)
// Mark event as failed
h.subscriptionService.MarkEventFailed(ctx, event.ID, handleErr.Error())
c.JSON(http.StatusInternalServerError, gin.H{"error": "handler error"})
return
}
// Mark event as processed
if err := h.subscriptionService.MarkEventProcessed(ctx, event.ID); err != nil {
log.Printf("Webhook: Error marking event processed: %v", err)
}
c.JSON(http.StatusOK, gin.H{"status": "processed"})
}
// handleCheckoutSessionCompleted handles successful checkout
func (h *WebhookHandler) handleCheckoutSessionCompleted(ctx interface{}, data []byte) error {
log.Printf("Webhook: Processing checkout.session.completed")
// Parse checkout session from data
// The actual implementation will parse the JSON and create/update subscription
// TODO: Implementation
// 1. Parse checkout session data
// 2. Extract customer_id, subscription_id, user_id (from metadata)
// 3. Create or update subscription record
// 4. Update entitlements
return nil
}
// handleSubscriptionCreated handles new subscription creation
func (h *WebhookHandler) handleSubscriptionCreated(ctx interface{}, data []byte) error {
log.Printf("Webhook: Processing customer.subscription.created")
// TODO: Implementation
// 1. Parse subscription data
// 2. Extract status, plan, trial_end, etc.
// 3. Create subscription record
// 4. Set up initial entitlements
return nil
}
// handleSubscriptionUpdated handles subscription updates
func (h *WebhookHandler) handleSubscriptionUpdated(ctx interface{}, data []byte) error {
log.Printf("Webhook: Processing customer.subscription.updated")
// TODO: Implementation
// 1. Parse subscription data
// 2. Update subscription record (status, plan, cancel_at_period_end, etc.)
// 3. Update entitlements if plan changed
return nil
}
// handleSubscriptionDeleted handles subscription cancellation
func (h *WebhookHandler) handleSubscriptionDeleted(ctx interface{}, data []byte) error {
log.Printf("Webhook: Processing customer.subscription.deleted")
// TODO: Implementation
// 1. Parse subscription data
// 2. Update subscription status to canceled/expired
// 3. Remove or downgrade entitlements
return nil
}
// handleInvoicePaid handles successful invoice payment
func (h *WebhookHandler) handleInvoicePaid(ctx interface{}, data []byte) error {
log.Printf("Webhook: Processing invoice.paid")
// TODO: Implementation
// 1. Parse invoice data
// 2. Update subscription period
// 3. Reset usage counters for new period
// 4. Store invoice record
return nil
}
// handleInvoicePaymentFailed handles failed invoice payment
func (h *WebhookHandler) handleInvoicePaymentFailed(ctx interface{}, data []byte) error {
log.Printf("Webhook: Processing invoice.payment_failed")
// TODO: Implementation
// 1. Parse invoice data
// 2. Update subscription status to past_due
// 3. Send notification to user
// 4. Possibly restrict access
return nil
}