Skip to content

feat(website): add payment success and failed pages#11855

Open
christian-byrne wants to merge 6 commits intomainfrom
glary/payment-status-pages
Open

feat(website): add payment success and failed pages#11855
christian-byrne wants to merge 6 commits intomainfrom
glary/payment-status-pages

Conversation

@christian-byrne
Copy link
Copy Markdown
Contributor

@christian-byrne christian-byrne commented May 3, 2026

PR Created by the Glary-Bot Agent


Summary

Recreate the two payment status pages that were never migrated from Framer (they weren't in the sitemap, so were missed). The Stripe checkout flow in comfy-api redirects to https://www.comfy.org/payment/{success,failed} after a checkout session, so users currently hit a 404 on completion or cancel.

Changes

  • What: New static Astro pages at /payment/success, /payment/failed and their /zh-CN/ variants, sharing a PaymentStatusSection.vue component built from the existing BrandButton / SectionLabel primitives. Translation keys live in src/i18n/translations.ts for both locales. Pages are noindex and explicitly excluded from the generated sitemap. Adds a Playwright e2e spec covering both pages in both locales.
  • Dependencies: none

Review Focus

  • URL slug: matches production Stripe config (STRIPE_CANCEL_URL=…/payment/failed in comfy-api/run-service-{prod,staging}.yaml), not /payment/failure.
  • Primary CTA target: links to externalLinks.apiKeys (platform.comfy.org/profile/api-keys) — the platform root is just a redirect, so this avoids a hop.
  • Locale-aware secondary CTA: derived from getRoutes(locale) so future i18n/route changes flow through the existing helper.
  • Stale fallback (out of scope): comfy-api/gateways/stripe/stripe.go:159 has an unrelated stale fallback pointing at /payments/ (plural) that's overridden by the env config in production. Worth fixing in a follow-up.

Screenshots

EN success / failed and zh-CN success / failed at desktop widths. Mobile viewport stacks the CTA buttons vertically (verified locally).

Screenshots

Payment success page (EN, desktop)

Payment failed page (EN, desktop)

Payment success page (zh-CN, desktop)

Payment failed page (zh-CN, desktop)

┆Issue is synchronized with this Notion page by Unito

Glary-Bot added 3 commits May 2, 2026 20:11
The Stripe checkout flow in comfy-api redirects to
https://www.comfy.org/payment/{success,failed} after a checkout session,
but those pages were never migrated from Framer (they weren't listed in
the sitemap). Users currently hit a 404 after completing or canceling a
Stripe checkout.

Recreate both pages on the Astro marketing site using the new design
system: a centered hero-style status panel with brand iconography,
SectionLabel, and BrandButton CTAs. The success page links to the
platform dashboard and back to the marketing home; the failed page
links to the platform dashboard (to retry) and to /contact.

URL path note: matches the production Stripe config in
comfy-api/run-service-{prod,staging}.yaml (/payment/failed, not
/payment/failure).
- Point primary CTA at platform.comfy.org/profile/api-keys via
  externalLinks.apiKeys (the platform root is just a redirect; this
  is the actual authenticated landing page)
- Exclude /payment/{success,failed} from the generated sitemap, since
  these are post-checkout redirect targets and already noindex
- Drop client:load on the static PaymentStatusSection \u2014 no client JS
  needed
- Even out e2e coverage: assert noindex on every payment page and
  verify the zh-CN failed primary CTA
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 3, 2026

📝 Walkthrough

Walkthrough

Adds localized payment success and failure pages (EN and zh-CN), a client-side PaymentStatusSection Vue component, translation keys, sitemap exclusions and robots rule for payment routes, and Playwright smoke tests asserting UI, CTAs, and noindex behavior.

Changes

Payment status pages, component, translations, sitemap, and tests

Layer / File(s) Summary
Translation Data
apps/website/src/i18n/translations.ts
Adds payment.success.* and payment.failed.* localized keys and fixes a nearby object closure.
Config / Constants
apps/website/src/config/routes.ts
Adds externalLinks.app (https://app.comfy.org) to externalLinks.
Sitemap Filter & i18n wiring
apps/website/astro.config.ts
Introduces LOCALES, DEFAULT_LOCALE, PAYMENT_STATUSES, LOCALE_PREFIXES, builds SITEMAP_EXCLUDED_PATHNAMES, adds isExcludedFromSitemap(page) that normalizes page pathname, and wires sitemap({ filter }) to exclude /payment/{status} routes (including locale-prefixed variants). Also sources i18n.locales and i18n.defaultLocale from the new constants.
Shared Component
apps/website/src/components/payment/PaymentStatusSection.vue
New Vue SFC with `status: 'success'
Pages / Routes
apps/website/src/pages/payment/*.astro, apps/website/src/pages/zh-CN/payment/*.astro
Adds four Astro routes mounting PaymentStatusSection with client:load, sets page title/description, and marks pages noindex.
Robots
apps/website/public/robots.txt
Adds Disallow: /payment/.
End-to-end Tests
apps/website/e2e/payment.spec.ts
Adds Playwright smoke tests verifying page titles, localized headings/subtitles, meta[name="robots"]=noindex, nofollow, CTA href targets (including locale-specific contact path), and presence of failure tertiary CTA (CLOSE TAB).
sequenceDiagram
    participant User
    participant Browser
    participant AstroPage as Astro Page
    participant Component as PaymentStatusSection
    participant Translations as i18n/translations
    participant Sitemap as Astro sitemap filter
    User->>Browser: Navigate to /payment/success
    Browser->>AstroPage: Request page
    AstroPage->>Component: Mount with props (status, locale)
    Component->>Translations: request localized strings
    Translations-->>Component: return strings
    Component->>Browser: render icons, headings, CTAs
    Browser->>User: display page
    Note right of Sitemap: Sitemap filter excludes payment paths during build
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 I hopped to pages, near and far,
I stitched some strings and trimmed a star,
Two routes that show success or fail,
Hidden from maps, tested without fail,
Buttons, titles, and a tiny trail ✨


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore (reviewers only)

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
End-To-End Regression Coverage For Fixes ❌ Error PR contains bug-fix language in commit subject but does not meet the alternative criteria (no browser_tests/ files changed, no explanation for lack of e2e tests in browser_tests/). Add tests under browser_tests/ directory or explain in PR description why e2e/ location is acceptable for this fix.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main changeset: adding payment success and failed pages to the website.
Description check ✅ Passed The PR description comprehensively follows the template structure with Summary, Changes, and Review Focus sections, detailing objectives, implementation approach, critical decisions, and dependencies.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Adr Compliance For Entity/Litegraph Changes ✅ Passed PR modifies only website files (Astro config, Vue components, pages, translations, routes) and does not touch src/lib/litegraph/, src/ecs/, or graph entity-related code.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch glary/payment-status-pages

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 3, 2026

🌐 Website E2E

Tip

All tests passed.

Status ✅ Passed
Report View Report

🔗 Website Preview

Website Preview: https://comfy-website-preview-pr-11855.vercel.app

This commit: https://website-frontend-2fyoi2l4x-comfyui.vercel.app

Last updated: 2026-05-04T07:17:15Z for 0e07753

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
apps/website/e2e/payment.spec.ts (1)

15-110: ⚡ Quick win

Keep the smoke suite focused on rendering, not route targets.

These @smoke tests pin exact href destinations for both CTAs across locales, which makes the suite brittle and couples it to routing details. Move the destination checks to a non-smoke integration/component test and keep smoke coverage limited to title, noindex, and visible copy.

✂️ Suggested split
   test('primary CTA links to platform dashboard', async ({ page }) => {
     const cta = page.getByRole('link', { name: /GO TO DASHBOARD/i })
     await expect(cta).toBeVisible()
-    await expect(cta).toHaveAttribute('href', PLATFORM_DASHBOARD_URL)
   })
@@
   test('secondary CTA links back to home', async ({ page }) => {
     const cta = page.getByRole('link', { name: /BACK TO HOME/i })
     await expect(cta).toBeVisible()
-    await expect(cta).toHaveAttribute('href', '/')
   })

[review_comment_end -->

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/e2e/payment.spec.ts` around lines 15 - 110, The smoke tests in
payment.spec.ts are asserting exact CTA hrefs which couples rendering smoke
tests to routing; remove href assertions from the `@smoke` suites and keep only
title, noindex, and visible copy checks. Specifically, in the "Payment success
page `@smoke`", "Payment failed page `@smoke`", and "Payment pages zh-CN `@smoke`"
tests drop the await expect(...).toHaveAttribute('href', ...) lines for the CTAs
(the tests that reference PLATFORM_DASHBOARD_URL, '/', '/contact', '/zh-CN/',
and '/zh-CN/contact') but keep the getByRole(...).toBeVisible() checks; then add
separate non-@smoke integration tests (e.g., new tests named like "Payment CTA
destinations" or similar) that assert the exact href targets for those links so
routing assertions live outside the smoke suite.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/website/e2e/payment.spec.ts`:
- Around line 15-110: The smoke tests in payment.spec.ts are asserting exact CTA
hrefs which couples rendering smoke tests to routing; remove href assertions
from the `@smoke` suites and keep only title, noindex, and visible copy checks.
Specifically, in the "Payment success page `@smoke`", "Payment failed page
`@smoke`", and "Payment pages zh-CN `@smoke`" tests drop the await
expect(...).toHaveAttribute('href', ...) lines for the CTAs (the tests that
reference PLATFORM_DASHBOARD_URL, '/', '/contact', '/zh-CN/', and
'/zh-CN/contact') but keep the getByRole(...).toBeVisible() checks; then add
separate non-@smoke integration tests (e.g., new tests named like "Payment CTA
destinations" or similar) that assert the exact href targets for those links so
routing assertions live outside the smoke suite.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 51fd7f66-558d-45a6-af00-246cf241c401

📥 Commits

Reviewing files that changed from the base of the PR and between 7abd9d1 and b89d293.

📒 Files selected for processing (8)
  • apps/website/astro.config.ts
  • apps/website/e2e/payment.spec.ts
  • apps/website/src/components/payment/PaymentStatusSection.vue
  • apps/website/src/i18n/translations.ts
  • apps/website/src/pages/payment/failed.astro
  • apps/website/src/pages/payment/success.astro
  • apps/website/src/pages/zh-CN/payment/failed.astro
  • apps/website/src/pages/zh-CN/payment/success.astro

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 3, 2026
@glary-bot
Copy link
Copy Markdown

glary-bot Bot commented May 3, 2026

Pushing back on this one. The existing @smoke suites in this repo (download, cloud, homepage, navigation) all assert toHaveAttribute('href', …) on key CTAs alongside title / visible-copy checks — see for example:

  • download.spec.ts:8,20Download page @smoke pins the cloud link href
  • cloud.spec.ts:5,79Cloud page @smoke pins the pricing CTA href
  • homepage.spec.ts:150-178Product cards links @smoke and Get started section links @smoke pin the download/cloud hrefs
  • navigation.spec.ts:5,13 — logo href pinned in the desktop navigation smoke

For these payment status pages the CTA destination is the whole point — sending a user to platform.comfy.org/profile/api-keys (vs. /) and /contact (vs. /) is the page's primary contract, and silently regressing those would defeat the purpose of having the pages at all. Splitting them out would make the spec the odd one out without real upside, so I'd rather match the existing convention. Happy to revisit if the team wants to standardise the smoke layer differently across the whole suite.

Payment doesn't actually happen on platform.comfy.org \u2014 that's just
the usage / API key dashboard. The real flow is:

  ComfyUI workspace (app.comfy.org) \u2192 Buy Credits dialog \u2192 Stripe
  (new tab) \u2192 www.comfy.org/payment/{success,failed}

Update CTAs to match:

- Success: "Continue in ComfyUI" (origin-aware via document.referrer
  with allowlist for app.comfy.org / stagingapp.comfy.org, fallback to
  app.comfy.org) + "View balance & usage" \u2192 platform.comfy.org.
- Failed: "Contact support" \u2192 /contact, "Read billing docs" \u2192
  docs.comfy.org/api-reference/cloud, "Close tab" \u2192 window.close().

Add `app` to externalLinks and a small `resolveAppOrigin` helper in
config/routes.ts. Pages re-hydrate with client:load for the referrer
detection. Tests + translations updated for both locales.
@glary-bot
Copy link
Copy Markdown

glary-bot Bot commented May 3, 2026

Reworked the CTAs after a clarification: payment doesn't actually happen on platform.comfy.org — that's just the usage / API-key dashboard. The real flow is ComfyUI workspace (app.comfy.org) → Buy Credits dialog → Stripe (new tab) → www.comfy.org/payment/{success,failed}.

Success page

  • Primary: Continue in ComfyUIhttps://app.comfy.org (origin-aware via document.referrer with allowlist for app.comfy.org / stagingapp.comfy.org, falls back to app.comfy.org)
  • Secondary: View balance & usagehttps://platform.comfy.org

Failed page

  • Primary: Contact support/contact
  • Secondary: Read billing docsdocs.comfy.org/api-reference/cloud
  • Tertiary: Close tabwindow.close()

Implementation notes

  • New resolveAppOrigin(referrer) helper + externalLinks.app in config/routes.ts. Allowlist is explicit; anything else falls through to the default.
  • Pages re-hydrate with client:load (now needed for the referrer check). The page is still SSR'd with the default href, so the link is correct even if JS fails — hydration only swaps it for the matched origin.
  • Tests cover all CTAs in EN + zh-CN, plus a non-allowlisted referrer falling back to default.

Screenshots

Payment success page (EN, desktop) with Continue in ComfyUI and View balance & usage CTAs

Payment failed page (EN, desktop) with Contact support, Read billing docs and Close tab CTAs

Payment success page (zh-CN, desktop)

Payment failed page (zh-CN, desktop)

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 3, 2026
@christian-byrne christian-byrne marked this pull request as ready for review May 3, 2026 06:36
@christian-byrne christian-byrne requested a review from a team May 3, 2026 06:36
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label May 3, 2026
@christian-byrne
Copy link
Copy Markdown
Contributor Author

@Comfy-Org/comfy_frontend_devs @DrJKL ready for review — CI green, CodeRabbit approved. Recreates Framer-era /payment/{success,failed} pages so the Stripe checkout redirect from comfy-api stops 404'ing.

@christian-byrne christian-byrne requested a review from DrJKL May 3, 2026 06:42
Copy link
Copy Markdown
Contributor

@DrJKL DrJKL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid, focused PR. A few inline notes — main concern is that the document.referrer allowlist in resolveAppOrigin likely never matches in the real Stripe Checkout redirect flow (referrer becomes checkout.stripe.com or empty under modern Referrer-Policy), making the logic effectively dead. Worth either dropping it or routing the source via a query param / Stripe session metadata. Also a few low-risk nits inline.

Open questions:

  • ❓ Is Stripe session_id verification handled elsewhere (webhooks)? If so, a one-line comment on the page explaining that this is a thank-you page only would help future readers.
  • noindex meta is set and sitemap excludes these paths — should they also be in robots.txt? Direct/accidental links remain crawlable until Googlebot hits them.

const appOrigin = ref(externalLinks.app)
onMounted(() => {
if (status === 'success' && typeof document !== 'undefined') {
appOrigin.value = resolveAppOrigin(document.referrer)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 risk: document.referrer after a Stripe Checkout redirect is https://checkout.stripe.com/ (or empty under Referrer-Policy: strict-origin-when-cross-origin), never app.comfy.org. With the allowlist in resolveAppOrigin, the success branch falls through to externalLinks.app in practice — making this whole ref/onMounted dance effectively dead code in the real flow.

Consider one of:

  • Pass the source via query param: ?from=app / ?from=stagingapp and resolve from that.
  • Stash the origin in sessionStorage before the redirect to Stripe and read it here.
  • Drop the logic and always link to externalLinks.app.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — you're right. After a Stripe Checkout redirect, document.referrer is https://checkout.stripe.com/ (or empty under Referrer-Policy: strict-origin-when-cross-origin), so the allowlist always misses and the whole onMounted dance is dead code in the real flow. I'd added it speculatively without testing the actual redirect.

Dropped it. Primary success CTA is now a static link to externalLinks.app, and the success pages are back to being fully static (no client:load). The query-param / sessionStorage approaches are real options if we ever need per-origin routing back, but for the current single-origin flow (app.comfy.org → Stripe → here) the static link is the right answer.

})

function handleCloseTab() {
if (typeof window !== 'undefined') window.close()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 risk: window.close() silently no-ops unless the tab was opened by window.open() from script — which is not how users typically arrive after a Stripe redirect. Most users will click CLOSE TAB and see nothing happen.

Either remove the tertiary CTA or add a fallback navigation, e.g.:

function handleCloseTab() {
  if (typeof window === 'undefined') return
  window.close()
  // window.close() is a no-op for tabs not script-opened; navigate home as a fallback.
  setTimeout(() => { if (!window.closed) window.location.href = '/' }, 100)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied the suggested fallback exactly as written, with a brief comment explaining why. Users who land on this page after Stripe redirects them through the popup chain should be able to window.close() (the original window.open(checkout_url, '_blank') is in useAuthActions.ts:115), but the opener relationship through Stripe isn't reliable, so the setTimeout fallback to / covers the case where it no-ops.


const routes = getRoutes(locale)

const appOrigin = ref(externalLinks.app)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 nit: appOrigin is only meaningful on the success branch and is used as the href of the primary CTA. successCtaHref would describe intent better and avoid the implication that it's a generic origin string.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved by removing the ref entirely along with the referrer logic — the success primary CTA is now a static externalLinks.app link in the template.

Comment thread apps/website/astro.config.ts Outdated
Comment on lines +6 to +11
const SITEMAP_EXCLUDED_PATHNAMES = new Set([
'/payment/success',
'/payment/failed',
'/zh-CN/payment/success',
'/zh-CN/payment/failed'
])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 nit: hardcoding both /payment/... and /zh-CN/payment/... means future locales won't get auto-excluded. Derive instead:

Suggested change
const SITEMAP_EXCLUDED_PATHNAMES = new Set([
'/payment/success',
'/payment/failed',
'/zh-CN/payment/success',
'/zh-CN/payment/failed'
])
const PAYMENT_STATUSES = ['success', 'failed'] as const
const LOCALE_PREFIXES = ['', '/zh-CN'] as const
const SITEMAP_EXCLUDED_PATHNAMES = new Set(
LOCALE_PREFIXES.flatMap((prefix) =>
PAYMENT_STATUSES.map((status) => `${prefix}/payment/${status}`)
)
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied the suggestion verbatim. Future locales will now auto-exclude as long as they're added to LOCALE_PREFIXES.

Comment thread apps/website/src/config/routes.ts Outdated
'https://stagingapp.comfy.org'
])

export function resolveAppOrigin(referrer: string | undefined): string {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 nit: document.referrer is always string ("" when absent), never undefined. The string | undefined type adds noise.

Suggested change
export function resolveAppOrigin(referrer: string | undefined): string {
export function resolveAppOrigin(referrer: string): string {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved by deleting resolveAppOrigin and the allowlist entirely (dead code per the referrer-policy issue above).

Comment thread apps/website/e2e/payment.spec.ts Outdated
Comment on lines +6 to +8
const APP_URL = 'https://app.comfy.org'
const PLATFORM_URL = 'https://platform.comfy.org'
const BILLING_DOCS_URL = 'https://docs.comfy.org/api-reference/cloud'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 nit: re-declaring URLs that already live in externalLinks lets test and prod drift. Import the source of truth:

Suggested change
const APP_URL = 'https://app.comfy.org'
const PLATFORM_URL = 'https://platform.comfy.org'
const BILLING_DOCS_URL = 'https://docs.comfy.org/api-reference/cloud'
import { externalLinks } from '../src/config/routes'
const APP_URL = externalLinks.app
const PLATFORM_URL = externalLinks.platform
const BILLING_DOCS_URL = externalLinks.docsApi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — tests now import externalLinks and assign APP_URL / PLATFORM_URL / BILLING_DOCS_URL from it. No more drift risk.

Comment thread apps/website/e2e/payment.spec.ts Outdated
})
const cta = page.getByRole('link', { name: /CONTINUE IN COMFYUI/i })
await expect(cta).toHaveAttribute('href', APP_URL)
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 nit: the @interaction block only covers the negative (non-allowlisted) path. Add a positive case so allowlist behavior is locked in:

test('uses referrer origin for allowlisted referrer', async ({ page }) => {
  await page.goto('/payment/success', { referer: 'https://app.comfy.org/checkout/' })
  const cta = page.getByRole('link', { name: /CONTINUE IN COMFYUI/i })
  await expect(cta).toHaveAttribute('href', 'https://app.comfy.org')
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved by removing the @interaction block entirely. Once the referrer logic was dropped, both branches of the test asserted the same static fallback — the test had no remaining behaviour to lock in.

DrJKL
DrJKL previously approved these changes May 4, 2026
- Drop document.referrer / resolveAppOrigin logic. After Stripe Checkout,
  document.referrer is checkout.stripe.com (or empty under modern
  Referrer-Policy), never app.comfy.org \u2014 the allowlist always missed,
  making the onMounted dance dead code. Primary success CTA now links
  statically to externalLinks.app.
- Add fallback navigation to handleCloseTab so users aren't stuck if
  window.close() no-ops on tabs not script-opened.
- Derive sitemap exclusions from PAYMENT_STATUSES x LOCALE_PREFIXES so
  future locales auto-exclude.
- E2E tests import externalLinks instead of duplicating URL literals;
  drop the now-meaningless allowlist negative test.
- Drop client:load on success pages (now fully static); failed pages
  keep it for the Close-tab handler.
- Add Disallow: /payment/ to robots.txt as defense in depth alongside
  the per-page noindex meta and sitemap exclusion.
- Document that these pages are display-only \u2014 payment state is
  verified server-side via Stripe webhooks.
@glary-bot glary-bot Bot dismissed stale reviews from DrJKL and coderabbitai[bot] via 535a034 May 4, 2026 07:06
@glary-bot
Copy link
Copy Markdown

glary-bot Bot commented May 4, 2026

Thanks @DrJKL — addressed all 7 inline comments in 535a0341e. Quick summary:

Risks (both fixed):

  • Dropped the document.referrer / resolveAppOrigin logic entirely — you're right, after Stripe Checkout the referrer is checkout.stripe.com (or empty), so the allowlist always missed and the onMounted dance was dead code. Primary success CTA is now a static link to externalLinks.app.
  • Added the window.close() fallback navigation to / so users aren't stuck if the tab wasn't script-opened (or the opener relationship through Stripe isn't preserved).

Nits (all applied): sitemap exclusions derived from PAYMENT_STATUSES × LOCALE_PREFIXES; e2e tests import externalLinks instead of duplicating URLs; dropped appOrigin ref and the now-meaningless allowlist negative test.

Open questions:

  • Stripe session_id verification — yes, handled server-side via Stripe webhooks in comfy-api; these pages are display-only redirect targets. Added a brief comment at the top of PaymentStatusSection.vue documenting that.
  • robots.txt — good call, added Disallow: /payment/ to apps/website/public/robots.txt as defense in depth alongside the per-page noindex and the sitemap exclusion.

Also dropped client:load on the success pages (now fully static); failed pages keep it for the close-tab handler.

DrJKL
DrJKL previously approved these changes May 4, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
apps/website/e2e/payment.spec.ts (1)

70-74: ⚡ Quick win

Use getRoutes() for the contact-link expectations too.

These assertions still duplicate /contact and /zh-CN/contact even though the component reads them from route config. Pulling the expected values from getRoutes(locale) keeps the test focused on behavior instead of string drift.

♻️ Proposed refactor
-import { externalLinks } from '../src/config/routes'
+import { externalLinks, getRoutes } from '../src/config/routes'
 import { test } from './fixtures/blockExternalMedia'
 
 const APP_URL = externalLinks.app
 const PLATFORM_URL = externalLinks.platform
 const BILLING_DOCS_URL = externalLinks.docsApi
+const EN_ROUTES = getRoutes('en')
+const ZH_CN_ROUTES = getRoutes('zh-CN')
@@
   test('primary CTA links to contact page', async ({ page }) => {
     const cta = page.getByRole('link', { name: /CONTACT SUPPORT/i })
     await expect(cta).toBeVisible()
-    await expect(cta).toHaveAttribute('href', '/contact')
+    await expect(cta).toHaveAttribute('href', EN_ROUTES.contact)
   })
@@
     await expect(page.getByRole('link', { name: '联系支持' })).toHaveAttribute(
       'href',
-      '/zh-CN/contact'
+      ZH_CN_ROUTES.contact
     )

Also applies to: 111-114

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/e2e/payment.spec.ts` around lines 70 - 74, The test "primary CTA
links to contact page" duplicates the contact URL string; update it to use the
route config instead by calling getRoutes(locale) and asserting the CTA's href
equals the contact route from that object. Locate the test function
(test('primary CTA links to contact page', ...)) and replace the hard-coded
'/contact' check with the value returned from getRoutes(locale). Do the same for
the parallel test at lines 111-114 so both tests derive expected hrefs from
getRoutes(locale) rather than literal strings.
apps/website/astro.config.ts (1)

6-12: ⚡ Quick win

Derive sitemap locale prefixes from the actual locale list.

LOCALE_PREFIXES now duplicates the i18n.locales source later in this file. If another locale is added and this array is missed, its /payment/* pages will leak back into the sitemap.

♻️ Proposed refactor
-const PAYMENT_STATUSES = ['success', 'failed'] as const
-const LOCALE_PREFIXES = ['', '/zh-CN'] as const
+const LOCALES = ['en', 'zh-CN'] as const
+const PAYMENT_STATUSES = ['success', 'failed'] as const
+const LOCALE_PREFIXES = LOCALES.map((locale) =>
+  locale === 'en' ? '' : `/${locale}`
+)
 const SITEMAP_EXCLUDED_PATHNAMES = new Set(
   LOCALE_PREFIXES.flatMap((prefix) =>
     PAYMENT_STATUSES.map((status) => `${prefix}/payment/${status}`)
   )
 )
@@
   i18n: {
-    locales: ['en', 'zh-CN'],
+    locales: LOCALES,
     defaultLocale: 'en',
     routing: {
       prefixDefaultLocale: false
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/astro.config.ts` around lines 6 - 12, LOCALE_PREFIXES is being
hard-coded and duplicates the source of truth (i18n.locales), causing new
locales to be missed; change the code so LOCALE_PREFIXES is derived from the
existing i18n.locales array instead of a manual array. Specifically, use the
i18n.locales value (or move the i18n definition earlier) and produce prefixes by
mapping locales to '' for the default and '/<locale>' for others, then use that
derived LOCALE_PREFIXES when building SITEMAP_EXCLUDED_PATHNAMES alongside
PAYMENT_STATUSES so any added locale is automatically included; update the
declarations around LOCALE_PREFIXES, SITEMAP_EXCLUDED_PATHNAMES and i18n to
ensure i18n is available when computing the prefixes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/website/src/components/payment/PaymentStatusSection.vue`:
- Around line 23-31: The failed-state recovery text promises a "return-to-start"
flow but handleCloseTab only calls window.close() and falls back to routes.home,
losing billing context; either persist a return URL through checkout and use it
in handleCloseTab (read returnUrl from the query string or localStorage and
navigate to it instead of routes.home when window.close() fails) or update the
component copy and CTA in PaymentStatusSection.vue (subtitle and retry button)
to remove the return-to-start promise and present explicit options like "Contact
support" and "Start over" that navigate to docs/contact or a billing retry
route; adjust handleCloseTab and any CTA click handlers accordingly so behavior
matches the updated copy.

---

Nitpick comments:
In `@apps/website/astro.config.ts`:
- Around line 6-12: LOCALE_PREFIXES is being hard-coded and duplicates the
source of truth (i18n.locales), causing new locales to be missed; change the
code so LOCALE_PREFIXES is derived from the existing i18n.locales array instead
of a manual array. Specifically, use the i18n.locales value (or move the i18n
definition earlier) and produce prefixes by mapping locales to '' for the
default and '/<locale>' for others, then use that derived LOCALE_PREFIXES when
building SITEMAP_EXCLUDED_PATHNAMES alongside PAYMENT_STATUSES so any added
locale is automatically included; update the declarations around
LOCALE_PREFIXES, SITEMAP_EXCLUDED_PATHNAMES and i18n to ensure i18n is available
when computing the prefixes.

In `@apps/website/e2e/payment.spec.ts`:
- Around line 70-74: The test "primary CTA links to contact page" duplicates the
contact URL string; update it to use the route config instead by calling
getRoutes(locale) and asserting the CTA's href equals the contact route from
that object. Locate the test function (test('primary CTA links to contact page',
...)) and replace the hard-coded '/contact' check with the value returned from
getRoutes(locale). Do the same for the parallel test at lines 111-114 so both
tests derive expected hrefs from getRoutes(locale) rather than literal strings.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 104f0442-0265-4f28-b180-4073bb865c7d

📥 Commits

Reviewing files that changed from the base of the PR and between 0ab0fe5 and 535a034.

📒 Files selected for processing (7)
  • apps/website/astro.config.ts
  • apps/website/e2e/payment.spec.ts
  • apps/website/public/robots.txt
  • apps/website/src/components/payment/PaymentStatusSection.vue
  • apps/website/src/config/routes.ts
  • apps/website/src/pages/payment/success.astro
  • apps/website/src/pages/zh-CN/payment/success.astro
✅ Files skipped from review due to trivial changes (3)
  • apps/website/public/robots.txt
  • apps/website/src/pages/payment/success.astro
  • apps/website/src/pages/zh-CN/payment/success.astro

Comment on lines +23 to +31
function handleCloseTab() {
if (typeof window === 'undefined') return
window.close()
// window.close() only succeeds on tabs that were script-opened. After a
// Stripe Checkout redirect the opener relationship is unreliable, so fall
// back to navigating home if the tab is still open shortly after.
setTimeout(() => {
if (!window.closed) window.location.href = routes.home
}, 100)
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot May 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Failed-state recovery doesn't match what the page promises.

The failed subtitle says users can retry from where they started, but this component only offers contact/docs and a window.close() fallback to home. In the common case where close is ignored after the Stripe redirect, users lose their billing context instead of getting a retry path.

Please either carry a return URL through checkout and use it here, or change the copy/CTA so the page stops promising a return-to-start flow.

Also applies to: 110-123

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/src/components/payment/PaymentStatusSection.vue` around lines 23
- 31, The failed-state recovery text promises a "return-to-start" flow but
handleCloseTab only calls window.close() and falls back to routes.home, losing
billing context; either persist a return URL through checkout and use it in
handleCloseTab (read returnUrl from the query string or localStorage and
navigate to it instead of routes.home when window.close() fails) or update the
component copy and CTA in PaymentStatusSection.vue (subtitle and retry button)
to remove the return-to-start promise and present explicit options like "Contact
support" and "Start over" that navigate to docs/contact or a billing retry
route; adjust handleCloseTab and any CTA click handlers accordingly so behavior
matches the updated copy.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — the copy was overpromising. Carrying a return URL through Stripe Checkout is the right long-term fix but it needs a coordinated comfy-api change (Stripe success_url / cancel_url augmentation + handling), which is out of scope for this PR.

Going with the copy fix here so the page stops promising what it can't deliver. Updated payment.failed.subtitle (en + zh-CN) to drop the "retry from where you started" phrasing and direct users to support / billing docs, which matches the actual CTAs:

Your payment didn't go through and you have not been charged. Reach out to support or check the billing docs if you need help.

The three CTAs (Contact support / Read billing docs / Close tab) now match what the copy promises. I'll file a follow-up issue for the proper return-URL plumbing once we want to do the backend change.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipped: comment is from another GitHub bot.

- Failed-page subtitle no longer promises "retry from where you
  started" since this page can't reliably do that. Soften to direct
  users to support / billing docs, which matches what the CTAs
  actually do.
- Derive sitemap LOCALE_PREFIXES from the same LOCALES constant used
  by Astro's i18n config so future locales auto-exclude.
- E2E tests assert against getRoutes(locale).contact instead of
  duplicating literal /contact and /zh-CN/contact strings.
@glary-bot
Copy link
Copy Markdown

glary-bot Bot commented May 4, 2026

Addressed the new round of CodeRabbit feedback in 0e0775308:

  • Failed-page copy fix (the major one) — replied inline. Subtitle no longer promises "retry from where you started"; it now directs to support / billing docs, matching the actual CTAs. Proper return-URL plumbing through Stripe Checkout is a follow-up that needs comfy-api changes.
  • astro.config.ts nitLOCALE_PREFIXES is now derived from a LOCALES constant shared with i18n.locales (single source of truth). Add a locale → it auto-excludes from the sitemap.
  • payment.spec.ts nit — Tests assert against getRoutes('en').contact / getRoutes('zh-CN').contact instead of duplicating literal paths.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants