Files
Leon SerfatyandClaude Opus 5 3e9ba07175 feat: Cloudflare Turnstile on auth, CSP fixes, admin/SEO/analytics additions
Turnstile bot protection (sign-in, sign-up, password-reset):
- Register Better Auth's captcha plugin with the cloudflare-turnstile
  provider; endpoints listed explicitly rather than relying on defaults.
  /reset-password is intentionally excluded — it is reached only via a
  single-use emailed token.
- Add an explicit-render Turnstile widget component. Tokens are single-use,
  so each form resets the challenge after a failed submit; submit stays
  disabled until a token is held.
- Read the site key server-side and pass it down as a prop, so rotating it
  does not require a rebuild.
- Fail fast in production when TURNSTILE_SECRET_KEY is missing, and when a
  secret is set without a site key (that combination would demand a token
  no form can produce, locking every user out).
- Pass a throwaway secret during `next build` in the Dockerfile, mirroring
  the existing BETTER_AUTH_SECRET treatment, so image builds don't need it.

CSP fixes in middleware (these blocked Turnstile entirely):
- Add frame-src for challenges.cloudflare.com. Without it the widget's
  iframe fell back to default-src 'self' and was blocked outright.
- Allow 'unsafe-eval' and websockets in DEVELOPMENT only. `next dev`
  compiles with eval(), so the strict policy threw EvalError and killed
  hydration — no client JS ran at all, which also meant form submit
  handlers never fired. Production policy is unchanged and still strict.

Also included (concurrent work in the tree):
- Admin organizations pages and lib/admin/orgs.
- Episode moderation migration, SEO metadata (sitemap, robots, JSON-LD,
  OG/Twitter images, manifest), Umami analytics, not-found page.

Local dev database: docker-compose.dev.yml provisions Postgres 18 on port
5443 (5432-5442 are in use by other local projects).

Note: `npx tsc --noEmit` currently fails in app/(app)/team/page.tsx — an
`invitations` prop the component does not accept. This predates the commit
and will fail `next build` until fixed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 11:10:55 -04:00

79 lines
3.1 KiB
TypeScript

import type { Metadata } from "next";
import { LegalDoc, type LegalSection } from "@/components/marketing/legal-doc";
import { pageMetadata } from "@/lib/seo";
/** Shared by the page metadata and the document's structured data. */
const PATH = "/refunds";
const DESCRIPTION =
"How subscriptions, renewals, cancellations and refunds work at Podcast Distribution AI, including statutory withdrawal rights and billing disputes.";
export const metadata: Metadata = pageMetadata({
title: "Refund & Cancellation Policy",
description: DESCRIPTION,
path: PATH,
});
const UPDATED = "June 7, 2026";
const SECTIONS: LegalSection[] = [
{
heading: "Subscriptions and renewals",
paragraphs: [
"Paid plans are billed in advance and renew automatically each billing period (monthly or yearly) until you cancel. By subscribing you authorize Podcast Distribution AI and our payment processors (Stripe and PayPal) to charge your payment method for each renewal at the then-current price.",
],
},
{
heading: "Cancelling",
paragraphs: [
"You can cancel at any time from the billing page in your account or through the payment provider's portal. Cancellation stops future renewals; your plan stays active until the end of the period you have already paid for, after which the account moves to the Free plan.",
],
},
{
heading: "Refunds",
paragraphs: [
"Except where required by law, payments are non-refundable and we do not pro-rate partial periods. We do not provide refunds for unused generation allowance or for time remaining after a cancellation. If you believe you were charged in error, contact us within 14 days and we will review it in good faith.",
],
},
{
heading: "Free plan",
paragraphs: [
"The Free plan is, and remains, free. It has its own monthly allowances and requires no payment method.",
],
},
{
heading: "Failed payments",
paragraphs: [
"If a renewal payment fails, your subscription may be marked past due and we may retry the charge. If payment cannot be collected, paid features are paused and the account is downgraded to Free until billing is restored.",
],
},
{
heading: "Price changes and taxes",
paragraphs: [
"We may change plan prices; changes take effect at your next renewal and we will give reasonable notice. Prices are exclusive of any taxes, which may be added based on your location.",
],
},
{
heading: "Chargebacks",
paragraphs: [
"Please contact us before initiating a chargeback so we can resolve the issue directly. Accounts with unresolved chargebacks may be suspended.",
],
},
{
heading: "Contact",
paragraphs: ["Billing questions? Email billing@podcastdistributionai.com."],
},
];
export default function RefundsPage() {
return (
<LegalDoc
title="Refund & Cancellation Policy"
updated={UPDATED}
intro="This policy explains how billing, renewals, cancellations, and refunds work for Podcast Distribution AI subscriptions. It forms part of our Terms of Service."
sections={SECTIONS}
path={PATH}
description={DESCRIPTION}
/>
);
}