Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Security Guidelines

## Environment Variables

- Never commit `.env.local` files to version control
- Use strong, unique values for all secret keys
- Rotate API keys regularly
- Use different keys for development and production

## Stripe Security

- Always validate webhook signatures (when implementing webhooks)
- Use HTTPS in production
- Implement proper session validation
- Log security events for monitoring

## Input Validation

- Validate all user inputs
- Sanitize data before processing
- Use parameterized queries for database operations
- Implement rate limiting for API endpoints

## Dependencies

Run regular security audits:

```bash
npm audit
npm audit fix
```

## Monitoring

- Monitor failed payment attempts
- Log suspicious activities
- Set up alerts for unusual patterns
- Regular security reviews

## Deployment Security

- Use environment variables for secrets
- Enable HTTPS
- Configure proper CORS policies
- Implement proper authentication
- Use secure cookies when needed
34 changes: 33 additions & 1 deletion next.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,39 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
/* config options here */
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://js.stripe.com",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"connect-src 'self' https://api.stripe.com",
"frame-src https://js.stripe.com",
"font-src 'self'"
].join('; ')
},
{
key: 'X-Frame-Options',
value: 'DENY'
},
{
key: 'X-Content-Type-Options',
value: 'nosniff'
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin'
}
]
}
]
}
};

export default nextConfig;
3 changes: 2 additions & 1 deletion src/action/stripe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { headers } from 'next/headers'
import { stripe } from '../lib/stripe'
import { env } from '@/lib/env'

export async function fetchClientSecret() {
const origin = (await headers()).get('origin')
Expand All @@ -14,7 +15,7 @@ export async function fetchClientSecret() {
// Provide the exact Price ID (for example, price_1234) of
// the product you want to sell
// price: '{{PRICE_ID}}',
price: process.env.PRICE_ID,
price: env.PRICE_ID,
quantity: 1
},
],
Expand Down
62 changes: 44 additions & 18 deletions src/app/return/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,56 @@ import { redirect } from 'next/navigation'
import { stripe } from '../../lib/stripe'

export default async function Return({ searchParams }: { searchParams: Promise<{ session_id: string }> }) {
const { session_id } = await searchParams
try {
const { session_id } = await searchParams

if (!session_id) return
if (!session_id || typeof session_id !== 'string') {
return redirect('/')
}

const session = await stripe.checkout.sessions.retrieve(session_id as string, {
expand: ['line_items', 'payment_intent']
})
const { status, customer_details } = session
const customerEmail = customer_details?.email
const session = await stripe.checkout.sessions.retrieve(session_id, {
expand: ['line_items', 'payment_intent']
})

if (status === 'open') {
return redirect('/')
}
const { status, customer_details } = session
const customerEmail = customer_details?.email

if (status === 'open') {
return redirect('/')
}

if (status === 'complete') {
return (
<section id="success" className="container mx-auto px-4 py-8">
<div className="max-w-md mx-auto text-center bg-green-50 p-6 rounded-lg border border-green-200">
<h2 className="text-2xl font-semibold text-green-800 mb-4">Payment Successful!</h2>
<p className="text-gray-700 mb-4">
We appreciate your business! A confirmation email will be sent to{' '}
<span className="font-semibold">{customerEmail}</span>.
</p>
<p className="text-sm text-gray-600">
If you have any questions, please email{' '}
{/* TODO: Change this email */}
<a href="mailto:support@yourcompany.com" className="text-blue-600 hover:underline">
support@yourcompany.com
</a>
</p>
</div>
</section>
)
}

if (status === 'complete') {
return (
<section id="success">
<p>
We appreciate your business! A confirmation email will be sent to{' '}
{customerEmail}. If you have any questions, please email{' '}
</p>
{/* TODO: Change this email */}
<a href="mailto:orders@example.com">orders@example.com</a>.
<section className="container mx-auto px-4 py-8">
<div className="max-w-md mx-auto text-center bg-yellow-50 p-6 rounded-lg border border-yellow-200">
<h2 className="text-2xl font-semibold text-yellow-800 mb-4">Payment Status Unknown</h2>
<p className="text-gray-700">
Please contact support if you need assistance.
</p>
</div>
</section>
)
} catch {
return redirect('/')
}
}
3 changes: 2 additions & 1 deletion src/components/checkout.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
'use client'

import { fetchClientSecret } from '@/action/stripe'
import { env } from '@/lib/env'
import {
EmbeddedCheckout,
EmbeddedCheckoutProvider
} from '@stripe/react-stripe-js'
import { loadStripe } from '@stripe/stripe-js'


const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY as string)
const stripePromise = loadStripe(env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY as string)

export default function Checkout() {
return (
Expand Down
16 changes: 16 additions & 0 deletions src/lib/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Environment variable validation
export function validateEnv() {
const requiredEnvVars = {
STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY,
PRICE_ID: process.env.PRICE_ID,
}

return {
STRIPE_SECRET_KEY: requiredEnvVars.STRIPE_SECRET_KEY!,
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: requiredEnvVars.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!,
PRICE_ID: requiredEnvVars.PRICE_ID!,
}
}

export const env = validateEnv()
3 changes: 2 additions & 1 deletion src/lib/stripe.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'server-only'

import Stripe from 'stripe'
import { env } from './env'

export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string)
export const stripe = new Stripe(env.STRIPE_SECRET_KEY as string)