145 lines
4.8 KiB
TypeScript
145 lines
4.8 KiB
TypeScript
import { type NextRequest, NextResponse } from "next/server"
|
|
import Stripe from "stripe"
|
|
|
|
// Lazy initialization to avoid build-time errors
|
|
let stripe: Stripe | null = null
|
|
|
|
function getStripe() {
|
|
if (!stripe) {
|
|
stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
|
apiVersion: "2024-12-18.acacia",
|
|
})
|
|
}
|
|
return stripe
|
|
}
|
|
|
|
// Dynamic pricing configuration (in EUR cents)
|
|
const TICKET_PRICE_CENTS = 8000 // €80 early bird
|
|
|
|
// Public base URL (needed because request.nextUrl.origin returns internal Docker address)
|
|
const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || "https://cryptocommonsgather.ing"
|
|
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
const formData = await request.formData()
|
|
const paymentMethod = formData.get("paymentMethod") as string
|
|
const registrationDataStr = formData.get("registrationData") as string
|
|
const includeAccommodation = formData.get("includeAccommodation") === "true"
|
|
const accommodationType = formData.get("accommodationType") as string || "dorm"
|
|
const includeFood = formData.get("includeFood") === "true"
|
|
|
|
const registrationData = registrationDataStr ? JSON.parse(registrationDataStr) : null
|
|
|
|
// Accommodation pricing (in EUR cents)
|
|
const DORM_PRICE_CENTS = 23520 // €235.20 (€39.20/night x 6)
|
|
const DOUBLE_PRICE_CENTS = 30120 // €301.20 (€50.20/night x 6)
|
|
const FOOD_PRICE_CENTS = 13500 // €135 (6 days)
|
|
|
|
// Build line items dynamically
|
|
const lineItems: Stripe.Checkout.SessionCreateParams.LineItem[] = [
|
|
{
|
|
price_data: {
|
|
currency: "eur",
|
|
product_data: {
|
|
name: "CCG 2026 Ticket",
|
|
description: "Crypto Commons Gathering 2026 - August 16-22, Austria",
|
|
},
|
|
unit_amount: TICKET_PRICE_CENTS,
|
|
},
|
|
quantity: 1,
|
|
},
|
|
]
|
|
|
|
if (includeAccommodation) {
|
|
const isDorm = accommodationType === "dorm"
|
|
lineItems.push({
|
|
price_data: {
|
|
currency: "eur",
|
|
product_data: {
|
|
name: isDorm ? "Accommodation - Dorm (6 nights)" : "Accommodation - Double Room (6 nights)",
|
|
description: `Commons Hub, ${isDorm ? "dorm bed" : "double room"} — ${isDorm ? "€39.20" : "€50.20"}/night`,
|
|
},
|
|
unit_amount: isDorm ? DORM_PRICE_CENTS : DOUBLE_PRICE_CENTS,
|
|
},
|
|
quantity: 1,
|
|
})
|
|
}
|
|
|
|
if (includeFood) {
|
|
lineItems.push({
|
|
price_data: {
|
|
currency: "eur",
|
|
product_data: {
|
|
name: "Food Package (6 days)",
|
|
description: "Breakfast, catered lunches & dinners, coffee & tea",
|
|
},
|
|
unit_amount: FOOD_PRICE_CENTS,
|
|
},
|
|
quantity: 1,
|
|
})
|
|
}
|
|
|
|
let paymentMethodTypes: Stripe.Checkout.SessionCreateParams.PaymentMethodType[] = ["card"]
|
|
|
|
if (paymentMethod === "sepa_debit") {
|
|
paymentMethodTypes = ["sepa_debit"]
|
|
} else if (paymentMethod === "crypto") {
|
|
paymentMethodTypes = ["customer_balance"]
|
|
}
|
|
|
|
const session = await getStripe().checkout.sessions.create({
|
|
payment_method_types: paymentMethodTypes,
|
|
line_items: lineItems,
|
|
mode: "payment",
|
|
success_url: `${BASE_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
|
|
cancel_url: `${BASE_URL}/register`,
|
|
metadata: registrationData
|
|
? {
|
|
name: registrationData.name,
|
|
contact: registrationData.contact,
|
|
contributions: registrationData.contributions.substring(0, 500),
|
|
expectations: registrationData.expectations.substring(0, 500),
|
|
howHeard: registrationData.howHeard || "",
|
|
dietary:
|
|
registrationData.dietary.join(", ") +
|
|
(registrationData.dietaryOther ? `, ${registrationData.dietaryOther}` : ""),
|
|
crewConsent: registrationData.crewConsent,
|
|
accommodation: includeAccommodation ? accommodationType : "none",
|
|
food: includeFood ? "yes" : "no",
|
|
}
|
|
: {},
|
|
...(paymentMethod === "crypto" && {
|
|
payment_method_options: {
|
|
customer_balance: {
|
|
funding_type: "bank_transfer",
|
|
bank_transfer: {
|
|
type: "us_bank_account",
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
allow_promotion_codes: true,
|
|
billing_address_collection: "required",
|
|
phone_number_collection: {
|
|
enabled: true,
|
|
},
|
|
})
|
|
|
|
// Use 303 redirect for POST requests (tells browser to follow with GET)
|
|
return new Response(null, {
|
|
status: 303,
|
|
headers: { Location: session.url! },
|
|
})
|
|
} catch (err) {
|
|
console.error("Error creating checkout session:", err)
|
|
return NextResponse.json({ error: "Error creating checkout session" }, { status: 500 })
|
|
}
|
|
}
|
|
|
|
export async function GET() {
|
|
return NextResponse.json(
|
|
{ message: "This is an API endpoint. Use POST to create a checkout session." },
|
|
{ status: 405 },
|
|
)
|
|
}
|