+ 404 — Not found +
++ This page doesn't exist. +
++ The link may be outdated or the page was moved. Nothing guessed. +
+diff --git a/app/api/catalog/route.ts b/app/api/catalog/route.ts index cefff7e..fb244af 100644 --- a/app/api/catalog/route.ts +++ b/app/api/catalog/route.ts @@ -2,12 +2,12 @@ import { NextResponse } from "next/server"; // GET /api/catalog (docs/04) — ready-made datasets. Static seed until DB is provisioned. const CATALOG = [ - { id: "nordic-companies", title: "Nordisk bolagsdata", markets: ["SE", "NO", "DK", "FI"], entity_type: "company", count: 4_900_000, updated: "daily", price_ore: 89500, confidence: 0.94 }, - { id: "tech-decisionmakers", title: "Beslutsfattare i tech", markets: ["SE", "NO", "DK", "FI"], entity_type: "contact", count: 182_000, updated: "weekly", price_ore: 149500, confidence: 0.71 }, - { id: "vehicles-se", title: "Fordonsregister & inventory", markets: ["SE"], entity_type: "vehicle", count: 6_100_000, updated: "daily", price_ore: 119500, confidence: 0.9 }, - { id: "financials", title: "Finansiella tidsserier", markets: ["SE", "NO", "DK", "FI"], entity_type: "financials", count: 0, updated: "annual", price_ore: 99500, confidence: 0.88 }, - { id: "intent", title: "Köpsignaler & intent", markets: ["SE", "NO", "DK", "FI"], entity_type: "company", count: 0, updated: "weekly", price_ore: 79500, confidence: 0.62 }, - { id: "sanctions-pep", title: "Sanktioner / PEP", markets: ["EU"], entity_type: "company", count: 0, updated: "daily", price_ore: 59500, confidence: 0.95 }, + { id: "nordic-companies", title: "Nordic company data", markets: ["SE", "NO", "DK", "FI"], entity_type: "company", count: 4_900_000, updated: "daily", price_ore: 89500, confidence: 0.94 }, + { id: "tech-decisionmakers",title: "Decision-makers in tech",markets: ["SE", "NO", "DK", "FI"], entity_type: "contact", count: 182_000, updated: "weekly", price_ore: 149500, confidence: 0.71 }, + { id: "vehicles-fleet", title: "Vehicles & fleet", markets: ["SE"], entity_type: "vehicle", count: 6_100_000, updated: "daily", price_ore: 119500, confidence: 0.9 }, + { id: "financial-series", title: "Financial series", markets: ["SE", "NO", "DK", "FI"], entity_type: "financials", count: 0, updated: "annual", price_ore: 99500, confidence: 0.88 }, + { id: "buying-signals", title: "Buying signals", markets: ["SE", "NO", "DK", "FI"], entity_type: "company", count: 0, updated: "weekly", price_ore: 79500, confidence: 0.62 }, + { id: "sanctions-pep", title: "Sanctions / PEP", markets: ["EU"], entity_type: "company", count: 0, updated: "daily", price_ore: 59500, confidence: 0.95 }, ]; export async function GET() { diff --git a/app/api/payments/[order_id]/deposit/route.ts b/app/api/payments/[order_id]/deposit/route.ts index afc11bf..5d9843e 100644 --- a/app/api/payments/[order_id]/deposit/route.ts +++ b/app/api/payments/[order_id]/deposit/route.ts @@ -1,35 +1,57 @@ -import { NextRequest, NextResponse } from "next/server"; -import { getStripe } from "@/lib/stripe"; -import { getOrder } from "@/lib/orders-store"; +import { NextRequest, NextResponse } from "next/server"; -// POST /api/payments/{order_id}/deposit (docs/04) — Stripe Checkout for the 50% deposit. -export async function POST(req: NextRequest, ctx: { params: Promise<{ order_id: string }> }) { - const { order_id } = await ctx.params; - const order = await getOrder(order_id); - if (!order) return NextResponse.json({ error: { code: "not_found", message: "Order saknas" } }, { status: 404 }); +// POST /api/payments/[order_id]/deposit +// Returns { url } for Stripe Checkout. Returns 501 when STRIPE_SECRET_KEY is absent. +export async function POST( + request: NextRequest, + { params }: { params: Promise<{ order_id: string }> }, +) { + const { order_id } = await params; - const stripe = getStripe(); - if (!stripe) { - return NextResponse.json( - { error: { code: "stripe_not_configured", message: "Stripe ej konfigurerad (saknar STRIPE_SECRET_KEY)" } }, - { status: 501 }, - ); + const secretKey = process.env.STRIPE_SECRET_KEY; + if (!secretKey) { + return NextResponse.json({ error: "payment_not_configured" }, { status: 501 }); } - const origin = req.nextUrl.origin; - const session = await stripe.checkout.sessions.create({ + const origin = + request.headers.get("origin") ?? + process.env.NEXT_PUBLIC_APP_URL ?? + "http://localhost:3000"; + + let depositOre = 44750; + try { + const orderRes = await fetch(`${origin}/api/orders/${order_id}`); + if (orderRes.ok) { + const order = (await orderRes.json()) as { deposit_ore?: number; total_ore?: number }; + depositOre = order.deposit_ore ?? Math.round((order.total_ore ?? 89500) / 2); + } + } catch {} + + const body = new URLSearchParams({ + "payment_method_types[0]": "card", + "line_items[0][price_data][currency]": "sek", + "line_items[0][price_data][product_data][name]": "0pt data deposit", + "line_items[0][price_data][unit_amount]": String(depositOre), + "line_items[0][quantity]": "1", mode: "payment", - line_items: [{ - quantity: 1, - price_data: { - currency: "sek", - unit_amount: order.deposit_ore, - product_data: { name: `0pt order ${order_id} — förskott 50 %` }, - }, - }], - success_url: `${origin}/order/${order_id}`, + success_url: `${origin}/order/${order_id}?deposit=success`, cancel_url: `${origin}/bestall/forskott?order=${order_id}`, - metadata: { order_id, kind: "deposit" }, }); - return NextResponse.json({ checkout_url: session.url }); -} + + const stripeRes = await fetch("https://api.stripe.com/v1/checkout/sessions", { + method: "POST", + headers: { + Authorization: `Bearer ${secretKey}`, + "Content-Type": "application/x-www-form-urlencoded", + }, + body, + }); + + if (!stripeRes.ok) { + const err = (await stripeRes.json()) as { error?: { message?: string } }; + return NextResponse.json({ error: err.error?.message ?? "stripe_error" }, { status: 502 }); + } + + const session = (await stripeRes.json()) as { url: string }; + return NextResponse.json({ url: session.url }); +} \ No newline at end of file diff --git a/app/bestall/forskott/page.tsx b/app/bestall/forskott/page.tsx index b33ac04..9196dba 100644 --- a/app/bestall/forskott/page.tsx +++ b/app/bestall/forskott/page.tsx @@ -99,6 +99,14 @@ export default function DepositPage() { if (paying) return; setPaying(true); if (orderId) { + try { + const r = await fetch(`/api/payments/${orderId}/deposit`, { method: "POST" }); + if (r.ok) { + const body = await r.json() as { url?: string }; + if (body.url) { window.location.href = body.url; return; } + } + } catch {} + // Stripe not configured — advance status directly (demo mode) try { await fetch(`/api/orders/${orderId}`, { method: "PATCH", diff --git a/app/katalog/[id]/page.tsx b/app/katalog/[id]/page.tsx index 5660dfd..fbd92ea 100644 --- a/app/katalog/[id]/page.tsx +++ b/app/katalog/[id]/page.tsx @@ -34,7 +34,7 @@ interface Dataset { const CATALOG: Dataset[] = [ { - id: "nordisk-bolagsdata", + id: "nordic-companies", markets: "SE · NO · DK · FI", marketCodes: ["SE", "NO", "DK", "FI"], title: { en: "Nordic company data", sv: "Nordisk bolagsdata" }, @@ -49,7 +49,7 @@ const CATALOG: Dataset[] = [ artifact: "table", }, { - id: "beslutsfattare-tech", + id: "tech-decisionmakers", markets: "SE · NO · DK · FI", marketCodes: ["SE", "NO", "DK", "FI"], title: { en: "Decision-makers in tech", sv: "Beslutsfattare i tech" }, @@ -64,7 +64,7 @@ const CATALOG: Dataset[] = [ artifact: "schema", }, { - id: "fordon-flotta", + id: "vehicles-fleet", markets: "SE", marketCodes: ["SE"], title: { en: "Vehicles & fleet", sv: "Fordon & flotta" }, @@ -79,7 +79,7 @@ const CATALOG: Dataset[] = [ artifact: "map", }, { - id: "finansiella-serier", + id: "financial-series", markets: "SE · NO · DK · FI", marketCodes: ["SE", "NO", "DK", "FI"], title: { en: "Financial series", sv: "Finansiella serier" }, @@ -93,6 +93,36 @@ const CATALOG: Dataset[] = [ price: 14800, artifact: "chart", }, + { + id: "buying-signals", + markets: "SE · NO · DK · FI", + marketCodes: ["SE", "NO", "DK", "FI"], + title: { en: "Buying signals", sv: "Köpsignaler" }, + blurb: { en: "Ads, news, tech changes. Intent per company.", sv: "Annonser, nyheter, teknikbyten. Intent per bolag." }, + count: { en: "continuous", sv: "löpande" }, + rows: 0, + freshness: { en: "weekly", sv: "veckovis" }, + level: 1, + source: "web", + cat: { en: "Companies", sv: "Bolag" }, + price: 7900, + artifact: "table", + }, + { + id: "sanctions-pep", + markets: "EU", + marketCodes: ["EU"], + title: { en: "Sanctions / PEP", sv: "Sanktioner / PEP" }, + blurb: { en: "Official lists. Automated screening.", sv: "Officiella listor. Automatisk screening." }, + count: { en: "global", sv: "global" }, + rows: 0, + freshness: { en: "daily", sv: "daglig" }, + level: 3, + source: "OpenSanctions", + cat: { en: "Compliance", sv: "Efterlevnad" }, + price: 5900, + artifact: "table", + }, ]; // Stable tab keys — never change, display labels are localized separately diff --git a/app/layout.tsx b/app/layout.tsx index 2393eb7..133d54d 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -2,6 +2,7 @@ import React from "react"; import type { Metadata } from "next"; import { Inter, JetBrains_Mono } from "next/font/google"; import { Analytics } from "@vercel/analytics/next"; +import { Toaster } from "sonner"; import { LocaleProvider } from "@/components/locale-provider"; import "./globals.css"; @@ -20,6 +21,7 @@ export default function RootLayout({ children }: Readonly<{ children: React.Reac
+ 404 — Not found +
++ The link may be outdated or the page was moved. Nothing guessed. +
+