import { randomUUID } from "crypto"; import { requireAuth } from "@/lib/jwt"; import { createCheckoutSession } from "@/lib/areeba"; import { createOrder } from "@/lib/payment-orders"; // Only these return URLs may be handed to the gateway. Anything else // (including open redirects) is rejected and we fall back to the deep link. const isAllowedReturnUrl = (url: string): boolean => { try { const parsed = new URL(url); // The app's own deep link is always allowed. if (parsed.protocol === "waseel:") return true; // The configured server origin (EXPO_PUBLIC_SERVER_URL), if set. const serverUrl = process.env.EXPO_PUBLIC_SERVER_URL; if (serverUrl) { const server = new URL(serverUrl); if (parsed.protocol === server.protocol && parsed.host === server.host) return true; } return false; } catch { return false; } }; // Resolve the fare to integer cents. Accept fare_cents directly, or fare / // amount in dollars (legacy client field) and convert. const resolveAmountCents = (fareCents: unknown, fare: unknown, amount: unknown): number | null => { let cents: number; if (fareCents !== undefined && fareCents !== null) { cents = Math.round(Number(fareCents)); } else if (fare !== undefined && fare !== null) { cents = Math.round(Number(fare) * 100); } else if (amount !== undefined && amount !== null) { cents = Math.round(Number(amount) * 100); } else { return null; } if (!Number.isFinite(cents) || cents <= 0) return null; return cents; }; export async function POST(req: Request) { const auth = requireAuth(req); if ("error" in auth) return auth.error; const body = await req.json().catch(() => ({})); const { name, email, amount, fare_cents, fare, returnUrl, driver_id, origin_address, destination_address, origin_latitude, origin_longitude, destination_latitude, destination_longitude, ride_time, } = body; if (!name || !email) return Response.json( { error: "Missing required payment information." }, { status: 400 }, ); const amountCents = resolveAmountCents(fare_cents, fare, amount); if (amountCents === null) return Response.json({ error: "Invalid fare amount." }, { status: 400 }); const finalReturnUrl = typeof returnUrl === "string" && isAllowedReturnUrl(returnUrl) ? returnUrl : "waseel://book-ride"; try { const orderId = randomUUID(); const session = await createCheckoutSession({ orderId, amount: amountCents / 100, currency: "USD", description: `Waseel ride payment for ${name}`, returnUrl: finalReturnUrl, }); // The successIndicator stays server-side; the client never sees it. if (!session.successIndicator) throw new Error("Areeba did not return a successIndicator."); await createOrder({ order_id: orderId, user_id: auth.userId, amount_cents: amountCents, currency: "USD", driver_id: driver_id ?? null, origin_address: origin_address ?? null, destination_address: destination_address ?? null, origin_latitude: origin_latitude ?? null, origin_longitude: origin_longitude ?? null, destination_latitude: destination_latitude ?? null, destination_longitude: destination_longitude ?? null, ride_time: ride_time ?? null, success_indicator: session.successIndicator, status: "pending", }); return Response.json({ orderId, checkoutUrl: session.checkoutUrl }); } catch (err) { console.log("[AREEBA_PAYMENT_CREATE]: ", err); return Response.json({ error: "Internal Server Error" }, { status: 500 }); } }