diff --git a/docker/ss-atlas/internal/handlers/subscription.go b/docker/ss-atlas/internal/handlers/subscription.go index 14e50dc..f8255fe 100644 --- a/docker/ss-atlas/internal/handlers/subscription.go +++ b/docker/ss-atlas/internal/handlers/subscription.go @@ -72,50 +72,41 @@ func (a *App) handleSuccess(w http.ResponseWriter, r *http.Request) { return } - if result.IsNew { - // New user: send password setup email, show onboarding page. - // Group membership and stack deploy happen on /activate after they set a password. - if err := a.triggerPasswordReset(result.Username); err != nil { - log.Printf("authelia reset trigger failed for %s: %v", username, err) - } - data := map[string]any{ - "Username": result.Username, - "IsNew": true, - "Email": email, - "LoginURL": a.cfg.AutheliaURL, - "ResetURL": a.cfg.AutheliaURL + "/#/reset-password/step1", - "ActivateURL": a.cfg.AppURL + "/activate", - "DashboardURL": a.cfg.AppURL + "/dashboard", - "InstanceURL": "https://" + result.Username + "." + a.cfg.CustomerDomain, - } - if err := a.tmpl.ExecuteTemplate(w, "welcome.html", data); err != nil { - log.Printf("template error: %v", err) - http.Error(w, "internal error", http.StatusInternalServerError) + // Check if user is already an active customer (resubscribe case) + inGroup, _ := a.ldap.IsInGroup(result.Username, "customers") + if inGroup { + // Returning customer: ensure stack exists, go to dashboard + stackName := fmt.Sprintf("customer-%s", result.Username) + exists, _ := a.swarm.StackExists(stackName) + if !exists { + if err := a.swarm.DeployStack(stackName, result.Username, a.cfg.TraefikDomain); err != nil { + log.Printf("resubscribe: stack deploy failed for %s: %v", result.Username, err) + } } + log.Printf("resubscribe: %s payment verified, redirecting to dashboard", result.Username) + http.Redirect(w, r, a.cfg.AppURL+"/dashboard", http.StatusSeeOther) return } - // Existing user resubscribing: re-add to customers group if needed and - // ensure their stack is running, then send straight to dashboard. - inGroup, _ := a.ldap.IsInGroup(result.Username, "customers") - if !inGroup { - if err := a.ldap.AddToGroup(result.Username, "customers"); err != nil { - log.Printf("resubscribe: add to group failed for %s: %v", result.Username, err) - } else { - log.Printf("resubscribe: re-added %s to customers group", result.Username) - } + // New or lapsed customer: send password setup email, show onboarding. + // Group membership and stack deploy happen on /activate after they set a password. + if err := a.triggerPasswordReset(result.Username); err != nil { + log.Printf("authelia reset trigger failed for %s: %v", username, err) } - - stackName := fmt.Sprintf("customer-%s", result.Username) - exists, _ := a.swarm.StackExists(stackName) - if !exists { - if err := a.swarm.DeployStack(stackName, result.Username, a.cfg.TraefikDomain); err != nil { - log.Printf("resubscribe: stack deploy failed for %s: %v", result.Username, err) - } + data := map[string]any{ + "Username": result.Username, + "IsNew": true, + "Email": email, + "LoginURL": a.cfg.AutheliaURL, + "ResetURL": a.cfg.AutheliaURL + "/#/reset-password/step1", + "ActivateURL": a.cfg.AppURL + "/activate", + "DashboardURL": a.cfg.AppURL + "/dashboard", + "InstanceURL": "https://" + result.Username + "." + a.cfg.CustomerDomain, + } + if err := a.tmpl.ExecuteTemplate(w, "welcome.html", data); err != nil { + log.Printf("template error: %v", err) + http.Error(w, "internal error", http.StatusInternalServerError) } - - log.Printf("resubscribe: %s payment verified, redirecting to dashboard", result.Username) - http.Redirect(w, r, a.cfg.AppURL+"/dashboard", http.StatusSeeOther) } func (a *App) handlePortal(w http.ResponseWriter, r *http.Request) { diff --git a/docker/ss-atlas/internal/handlers/webhook.go b/docker/ss-atlas/internal/handlers/webhook.go index c7e664e..cf493a1 100644 --- a/docker/ss-atlas/internal/handlers/webhook.go +++ b/docker/ss-atlas/internal/handlers/webhook.go @@ -41,7 +41,9 @@ func (a *App) handleWebhook(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } -// Reconciliation: ensures LLDAP user exists. Group + stack are handled by /activate. +// Reconciliation backstop: ensures LLDAP user + Stripe ID are set. +// Does NOT send password reset — that's the success page's responsibility +// so it can reliably show the welcome/onboarding page. func (a *App) onCheckoutCompleted(event stripego.Event) { var sess stripego.CheckoutSession if err := json.Unmarshal(event.Data.Raw, &sess); err != nil { @@ -55,18 +57,8 @@ func (a *App) onCheckoutCompleted(event stripego.Event) { log.Printf("webhook: checkout completed email=%s customer=%s", email, customerID) - result, err := a.ldap.ProvisionUser(username, email, customerID) - if err != nil { - log.Printf("webhook: ldap provision user failed: %v", err) - return - } - - if result.IsNew { - if err := a.triggerPasswordReset(username); err != nil { - log.Printf("webhook: password reset email failed for %s: %v", username, err) - } else { - log.Printf("webhook: password reset email sent to %s (%s)", username, email) - } + if err := a.ldap.EnsureUser(username, email, customerID); err != nil { + log.Printf("webhook: ldap ensure user failed: %v", err) } }