Audit fixes: VIP self-grant, draft leak, onboarding flag, hero personalization, gated downloads
Deploy GitHub Pages / deploy (push) Canceled after 0s
Deploy GitHub Pages / deploy (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
import Topnav from '@/components/topnav'
|
||||
import Footer from '@/components/footer'
|
||||
import Stripe from 'stripe'
|
||||
import { getPayload } from 'payload'
|
||||
import { headers } from 'next/headers'
|
||||
import { redirect } from 'next/navigation'
|
||||
import configPromise from '@payload-config'
|
||||
import Link from 'next/link'
|
||||
import '../styles/payment-success.css'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
export const metadata = { title: 'تم تفعيل العضوية' }
|
||||
|
||||
type Props = { searchParams: Promise<{ session_id?: string }> }
|
||||
|
||||
/* Payment success — design from payment-success.html.
|
||||
- real Stripe: verifies the checkout session_id with Stripe before
|
||||
activating (paid + the checkout email matches the signed-in account)
|
||||
- no session: nobody is activated here — /api/stripe/webhook is the primary
|
||||
activator; this page only re-verifies a session it can prove */
|
||||
export default async function PaymentSuccessPage({ searchParams }: Props) {
|
||||
const { session_id: sessionId } = await searchParams
|
||||
const stripeKey = process.env.STRIPE_SECRET_KEY
|
||||
|
||||
const payload = await getPayload({ config: configPromise })
|
||||
const { user } = await payload.auth({ headers: await headers() })
|
||||
if (!user) redirect('/login?next=/payment-success')
|
||||
|
||||
let activate = false
|
||||
if (sessionId && stripeKey) {
|
||||
try {
|
||||
const stripe = new Stripe(stripeKey)
|
||||
const session = await stripe.checkout.sessions.retrieve(sessionId)
|
||||
activate =
|
||||
session.payment_status === 'paid' &&
|
||||
(session.customer_details?.email ?? '').toLowerCase() === user.email.toLowerCase()
|
||||
} catch {
|
||||
// Any Stripe failure (bad key, unknown session, network) → no activation.
|
||||
activate = false
|
||||
}
|
||||
}
|
||||
|
||||
if (activate && user.role !== 'vip' && user.role !== 'admin') {
|
||||
await payload.update({
|
||||
collection: 'users',
|
||||
id: user.id,
|
||||
data: { role: 'vip' },
|
||||
overrideAccess: true,
|
||||
context: { systemActivation: true },
|
||||
})
|
||||
}
|
||||
|
||||
const vipNow = user.role === 'vip' || user.role === 'admin' || activate
|
||||
|
||||
return (
|
||||
<div id="site" className="site-page page-payment-success">
|
||||
<Topnav viewer={user} />
|
||||
<main id="content">
|
||||
<section className="section" data-od-id="payment-success">
|
||||
<div className="container">
|
||||
<article className="state-card">
|
||||
<div className="status-mark" aria-hidden="true"><svg viewBox="0 0 32 32" fill="none" stroke="currentColor" strokeWidth={2}><path d="m8 17 5 5 11-13" /></svg></div>
|
||||
{vipNow ? (
|
||||
<>
|
||||
<p className="eyebrow">تم الدفع · العضوية فعّالة</p>
|
||||
<h1 data-od-id="success-title">مرحباً بك في عضوية أنور بآي.</h1>
|
||||
<p className="lead">تم تفعيل وصولك إلى الأدلة الحصرية وملفات العمل. بقيت خطوة قصيرة لاختيار نقطة البداية المناسبة لك.</p>
|
||||
<dl className="summary" data-od-id="payment-summary">
|
||||
<div className="summary-row"><dt>الحالة</dt><dd>عضوية فعّالة</dd></div>
|
||||
<div className="summary-row"><dt>الخطة</dt><dd>عضوية فردية · شهرياً · <bdi dir="ltr">$10</bdi></dd></div>
|
||||
<div className="summary-row"><dt>الوصول</dt><dd>كل محتوى العضوية وملفات الأعضاء</dd></div>
|
||||
</dl>
|
||||
<div className="next-step" data-od-id="next-step">
|
||||
<h2 data-od-id="next-step-title">جهّز مسارك الأول</h2>
|
||||
<p>حدد هدفك ومستواك في أقل من خمس دقائق، ثم ابدأ بدليل واحد يناسبك.</p>
|
||||
<div className="actions">
|
||||
<Link className="btn btn-primary" href="/vip-onboarding" data-od-id="start-onboarding-cta">ابدأ إعداد العضوية</Link>
|
||||
<Link className="btn btn-secondary" href="/posts/agent-loop-stop-condition" data-od-id="open-member-guide-cta">افتح دليل الأعضاء</Link>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="eyebrow">لم تكتمل عملية الدفع</p>
|
||||
<h1 data-od-id="payment-incomplete-title">لم يتم تأكيد الدفع بعد</h1>
|
||||
<p className="lead">لم نتمكن من تأكيد عملية دفع مرتبطة بحسابك. إن أتممت الدفع للتو فقد يحتاج التأكيد بضع دقائق، ويمكنك إعادة المحاولة من صفحة الاشتراك.</p>
|
||||
<div className="actions">
|
||||
<Link className="btn btn-primary" href="/subscription" data-od-id="retry-subscription-cta">العودة إلى صفحة الاشتراك</Link>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user