diff --git a/CLOUDFLARE_DEPLOYMENT.md b/CLOUDFLARE_DEPLOYMENT.md index 76090dc..d811374 100644 --- a/CLOUDFLARE_DEPLOYMENT.md +++ b/CLOUDFLARE_DEPLOYMENT.md @@ -56,6 +56,7 @@ Add these environment variables in Cloudflare Pages dashboard: - `STRIPE_PUBLISHABLE_KEY` - `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` - `STRIPE_WEBHOOK_SECRET` +- `SENDGRID_API_KEY` - For sending notification emails (thank you emails after purchases) ## Important Notes diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c728cf0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,43 @@ +FROM node:20-alpine AS base + +# Install dependencies only when needed +FROM base AS deps +RUN apk add --no-cache libc6-compat +WORKDIR /app + +COPY package.json package-lock.json* ./ +RUN npm ci + +# Rebuild the source code only when needed +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +ENV NEXT_TELEMETRY_DISABLED=1 +ENV NODE_ENV=production + +RUN npm run build + +# Production image, copy all the files and run next +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +CMD ["node", "server.js"] diff --git a/app/api/create-checkout-session/route.ts b/app/api/create-checkout-session/route.ts index 4f0a38b..21094e8 100644 --- a/app/api/create-checkout-session/route.ts +++ b/app/api/create-checkout-session/route.ts @@ -3,8 +3,22 @@ import Stripe from "stripe" export const runtime = "edge" +function getBaseUrl(request: NextRequest): string { + // Use environment variable if set, otherwise construct from headers + if (process.env.NEXT_PUBLIC_BASE_URL) { + return process.env.NEXT_PUBLIC_BASE_URL + } + const host = request.headers.get("host") || request.headers.get("x-forwarded-host") + const protocol = request.headers.get("x-forwarded-proto") || "https" + if (host) { + return `${protocol}://${host}` + } + return "https://alertbaytrumpeter.com" +} + export async function POST(request: NextRequest) { try { + const baseUrl = getBaseUrl(request) const stripeSecretKey = process.env.STRIPE_SECRET_KEY if (!stripeSecretKey) { @@ -60,8 +74,8 @@ export async function POST(request: NextRequest) { quantity: 1, }, ], - success_url: `${request.nextUrl.origin}/success?session_id={CHECKOUT_SESSION_ID}`, - cancel_url: `${request.nextUrl.origin}/cancel`, + success_url: `${baseUrl}/success?session_id={CHECKOUT_SESSION_ID}`, + cancel_url: `${baseUrl}/cancel`, }) console.log("[v0] Subscription checkout session created successfully:", session.id) @@ -89,8 +103,8 @@ export async function POST(request: NextRequest) { quantity: 1, }, ], - success_url: `${request.nextUrl.origin}/success?session_id={CHECKOUT_SESSION_ID}`, - cancel_url: `${request.nextUrl.origin}/cancel`, + success_url: `${baseUrl}/success?session_id={CHECKOUT_SESSION_ID}`, + cancel_url: `${baseUrl}/cancel`, }) console.log("[v0] Monthly subscription checkout session created successfully:", session.id) @@ -140,8 +154,8 @@ export async function POST(request: NextRequest) { quantity: 1, }, ], - success_url: `${request.nextUrl.origin}/success?session_id={CHECKOUT_SESSION_ID}`, - cancel_url: `${request.nextUrl.origin}/cancel`, + success_url: `${baseUrl}/success?session_id={CHECKOUT_SESSION_ID}`, + cancel_url: `${baseUrl}/cancel`, }) console.log("[v0] Checkout session created successfully:", session.id) diff --git a/app/api/create-portal-session/route.ts b/app/api/create-portal-session/route.ts index bc9007b..9276654 100644 --- a/app/api/create-portal-session/route.ts +++ b/app/api/create-portal-session/route.ts @@ -3,8 +3,21 @@ import Stripe from "stripe" export const runtime = "edge" +function getBaseUrl(request: NextRequest): string { + if (process.env.NEXT_PUBLIC_BASE_URL) { + return process.env.NEXT_PUBLIC_BASE_URL + } + const host = request.headers.get("host") || request.headers.get("x-forwarded-host") + const protocol = request.headers.get("x-forwarded-proto") || "https" + if (host) { + return `${protocol}://${host}` + } + return "https://alertbaytrumpeter.com" +} + export async function POST(request: NextRequest) { try { + const baseUrl = getBaseUrl(request) const stripeSecretKey = process.env.STRIPE_SECRET_KEY?.trim() if (!stripeSecretKey) { @@ -25,7 +38,7 @@ export async function POST(request: NextRequest) { const portalSession = await stripe.billingPortal.sessions.create({ customer: checkoutSession.customer as string, - return_url: `${request.nextUrl.origin}`, + return_url: baseUrl, }) return NextResponse.json({ url: portalSession.url }) diff --git a/app/api/webhook/route.ts b/app/api/webhook/route.ts index 354069b..fc469bd 100644 --- a/app/api/webhook/route.ts +++ b/app/api/webhook/route.ts @@ -3,6 +3,86 @@ import Stripe from "stripe" export const runtime = "edge" +async function sendThankYouEmail( + customerEmail: string, + customerName: string | null, + amount: number, + currency: string, + productName: string +) { + const sendgridApiKey = process.env.SENDGRID_API_KEY?.trim() + + if (!sendgridApiKey) { + console.log("⚠️ SENDGRID_API_KEY not configured, skipping email") + return + } + + const formattedAmount = new Intl.NumberFormat("en-CA", { + style: "currency", + currency: currency.toUpperCase(), + }).format(amount / 100) + + const name = customerName || "Valued Supporter" + + try { + const response = await fetch("https://api.sendgrid.com/v3/mail/send", { + method: "POST", + headers: { + "Authorization": `Bearer ${sendgridApiKey}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + personalizations: [ + { + to: [{ email: customerEmail, name: name }], + }, + ], + from: { + email: "noreply@alertbaytrumpeter.com", + name: "Alert Bay Trumpeter", + }, + subject: "Thank You for Supporting the Alert Bay Trumpeter!", + content: [ + { + type: "text/html", + value: ` +
Your generous support means the world to Jerry Higginson, the Alert Bay Trumpeter.
+Amount: ${formattedAmount}/month
+Plan: ${productName}
+Your monthly contribution helps Jerry continue spreading smiles at sea by serenading cruise ship passengers with his trumpet.
+With over 1,000 nautical serenades since 1996, your support keeps the music playing!
+
+ With gratitude,
+ Jerry Higginson
+ The Alert Bay Trumpeter
+
+ If you have any questions, contact us at alertbaytrumpeter@icloud.com +
+