diff --git a/docs/release-control/v6/internal/subsystems/cloud-paid.md b/docs/release-control/v6/internal/subsystems/cloud-paid.md index 2bef44d81..7367be69b 100644 --- a/docs/release-control/v6/internal/subsystems/cloud-paid.md +++ b/docs/release-control/v6/internal/subsystems/cloud-paid.md @@ -266,7 +266,8 @@ instance, not as a generic purchase funnel. Recovery-contact fields such as work email and optional company name must remain clearly secondary to the instance-bound entitlement handoff. Hosted form-stage issuance conflicts must also preserve the canonical reason shape: duplicate recovery-email usage must -not be flattened into an organization-level message. +not be flattened into an organization-level message, and terminal conflicts +must render as owned hosted outcome UX rather than editable inline form state. That same hosted owner also applies after Stripe returns to `/trial-signup/complete`: customer-facing completion failures must stay inside owned trial UX rather than dropping raw control-plane error strings, and they diff --git a/internal/cloudcp/trial_signup_handlers.go b/internal/cloudcp/trial_signup_handlers.go index fe2f5fa72..f68fac1c7 100644 --- a/internal/cloudcp/trial_signup_handlers.go +++ b/internal/cloudcp/trial_signup_handlers.go @@ -664,8 +664,12 @@ func (h *TrialSignupHandlers) HandleRequestVerification(w http.ResponseWriter, r return } if conflict != nil { - data.ErrorMessage = trialSignupIssuanceConflictMessage(conflict) - h.renderTrialSignupPage(w, r, http.StatusConflict, data) + h.renderTrialSignupFailurePage(w, r, http.StatusConflict, trialSignupFailureDataForPage( + h.cfg, + data, + trialSignupFailureConflict, + trialSignupIssuanceConflictMessage(conflict), + )) return } } @@ -842,8 +846,12 @@ func (h *TrialSignupHandlers) HandleCheckout(w http.ResponseWriter, r *http.Requ return } if conflict != nil { - data.ErrorMessage = trialSignupIssuanceConflictMessage(conflict) - h.renderTrialSignupPage(w, r, http.StatusConflict, data) + h.renderTrialSignupFailurePage(w, r, http.StatusConflict, trialSignupFailureDataForPage( + h.cfg, + data, + trialSignupFailureConflict, + trialSignupIssuanceConflictMessage(conflict), + )) return } record = &TrialSignupRecord{ diff --git a/internal/cloudcp/trial_signup_handlers_test.go b/internal/cloudcp/trial_signup_handlers_test.go index 31a6feece..c36525462 100644 --- a/internal/cloudcp/trial_signup_handlers_test.go +++ b/internal/cloudcp/trial_signup_handlers_test.go @@ -186,9 +186,13 @@ func TestTrialSignupHandleRequestVerificationRejectsEmailThatAlreadyUsedTrial(t if rec.Code != http.StatusConflict { t.Fatalf("status=%d, want %d body=%q", rec.Code, http.StatusConflict, rec.Body.String()) } - if !strings.Contains(rec.Body.String(), "recovery email has already used a Pulse Pro trial") { - t.Fatalf("expected duplicate trial message, got %q", rec.Body.String()) - } + assertTrialSignupFailurePageContains(t, rec.Body.String(), + "Trial already used", + "This recovery email has already used a Pulse Pro trial.", + "pulse.example.com", + "This trial request cannot be restarted for the same recovery contact or organization.", + ) + assertTrialSignupFailurePageOmits(t, rec.Body.String(), "Continue To Secure Trial Setup", "