forked from Nixius/authelia
1
0
Fork 0
ATLAS/docker/ss-atlas/internal/handlers/webhook.go

149 lines
4.2 KiB
Go

package handlers
import (
"encoding/json"
"io"
"log"
"net/http"
stripego "github.com/stripe/stripe-go/v84"
"github.com/stripe/stripe-go/v84/webhook"
)
const maxWebhookPayload = 65536
func (a *App) handleWebhook(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(io.LimitReader(r.Body, maxWebhookPayload))
if err != nil {
http.Error(w, "read error", http.StatusBadRequest)
return
}
sig := r.Header.Get("Stripe-Signature")
event, err := webhook.ConstructEvent(body, sig, a.stripe.WebhookSecret())
if err != nil {
log.Printf("webhook signature verification failed: %v", err)
http.Error(w, "invalid signature", http.StatusUnauthorized)
return
}
switch event.Type {
case "checkout.session.completed":
a.onCheckoutCompleted(event)
case "customer.subscription.deleted":
a.onSubscriptionDeleted(event)
case "customer.subscription.updated":
a.onSubscriptionUpdated(event)
case "invoice.paid":
a.onInvoicePaid(event)
default:
log.Printf("unhandled webhook event: %s", event.Type)
}
w.WriteHeader(http.StatusOK)
}
// Reconciliation backstop: ensures LLDAP user + Stripe ID are set.
// Does NOT send password reset — that's the success page's responsibility.
func (a *App) onCheckoutCompleted(event stripego.Event) {
var sess stripego.CheckoutSession
if err := json.Unmarshal(event.Data.Raw, &sess); err != nil {
log.Printf("checkout unmarshal error: %v", err)
return
}
email := sess.CustomerDetails.Email
customerID := sess.Customer.ID
username := sanitizeUsername(email)
phone := ""
if sess.Metadata != nil {
phone = sess.Metadata["customer_phone"]
}
log.Printf("webhook: checkout completed email=%s customer=%s", email, customerID)
if a.cfg.MaxSignups > 0 {
count, err := a.ldap.CountCustomers()
if err == nil && count >= a.cfg.MaxSignups {
log.Printf("webhook: signup limit reached (%d), skipping provision for %s", a.cfg.MaxSignups, email)
return
}
}
if err := a.ldap.EnsureUser(username, email, customerID, phone); err != nil {
log.Printf("webhook: ldap ensure user failed: %v", err)
}
if err := a.ldap.AddToGroup(username, "customers"); err != nil {
log.Printf("webhook: ldap add to customers failed for %s: %v", username, err)
}
if sess.Metadata != nil {
if d := sess.Metadata["customer_domain"]; d != "" {
if err := a.ldap.SetCustomerDomain(username, d); err != nil {
log.Printf("webhook: ldap set customer domain failed for %s: %v", username, err)
}
}
}
subID := ""
if sess.Subscription != nil && sess.Subscription.ID != "" {
subID = sess.Subscription.ID
} else {
var raw struct {
Subscription interface{} `json:"subscription"`
}
if json.Unmarshal(event.Data.Raw, &raw) == nil {
if s, ok := raw.Subscription.(string); ok {
subID = s
}
}
}
if subID != "" {
a.maybeScheduleFreeTierCancel(subID)
}
}
func (a *App) onSubscriptionDeleted(event stripego.Event) {
var sub stripego.Subscription
if err := json.Unmarshal(event.Data.Raw, &sub); err != nil {
log.Printf("subscription unmarshal error: %v", err)
return
}
customerID := sub.Customer.ID
log.Printf("subscription deleted for customer %s", customerID)
username, err := a.ldap.FindUserByStripeID(customerID)
if err != nil {
log.Printf("could not find user for customer %s: %v", customerID, err)
return
}
if err := a.ldap.RemoveFromGroup(username, "customers"); err != nil {
log.Printf("ldap group remove failed: %v", err)
}
stackName := "customer-" + username
if err := a.swarm.ArchiveVolumes(stackName, a.cfg.ArchivePath); err != nil {
log.Printf("archive failed for %s: %v", stackName, err)
}
if err := a.swarm.RemoveStack(stackName); err != nil {
log.Printf("stack remove failed for %s: %v", stackName, err)
}
log.Printf("deprovisioned stack for customer %s (%s), volumes archived", customerID, username)
}
func (a *App) onSubscriptionUpdated(event stripego.Event) {
var sub stripego.Subscription
if err := json.Unmarshal(event.Data.Raw, &sub); err != nil {
log.Printf("subscription update unmarshal error: %v", err)
return
}
if sub.Status == stripego.SubscriptionStatusCanceled ||
sub.Status == stripego.SubscriptionStatusUnpaid {
log.Printf("subscription %s status=%s, will be cleaned up on deletion", sub.ID, sub.Status)
}
}