fix( dmoney ): make D-Money /go redirect

This commit is contained in:
Abubeker Yasin
2026-07-07 15:12:16 +03:00
parent 5763a3946d
commit 2398e24c1a

View File

@@ -2,9 +2,25 @@
import { Suspense, useEffect, useMemo } from "react"; import { Suspense, useEffect, useMemo } from "react";
import { useSearchParams } from "next/navigation"; import { useSearchParams } from "next/navigation";
import { Loader2, ShieldAlert, ExternalLink } from "lucide-react";
/**
* /go — payment redirect bounce page for D-Money web checkout.
*
* The redirect is done CLIENT-SIDE on purpose: the navigation must originate
* from the loaded https://edrpassenger.triaplc.com/go document so the browser
* sends `Referer: https://edrpassenger.triaplc.com` to D-Money. D-Money only
* whitelists that origin, so a server-side 307 (whose referrer on the redirect
* hop is browser-dependent and can be stripped) must NOT be used here.
*
* It fires immediately (no delay) and paints a bare white full-screen cover
* above the sticky header (z-[60]) — no portal chrome, no text on the happy
* path. A short message shows only when the link is missing/untrusted.
*
* `?url=` MUST be percent-encoded by the caller (Uri.encodeComponent() in the
* Flutter app / encodeURIComponent() on web); otherwise the query parser
* truncates the D-Money URL at its first `&` and merch_code/sign are lost.
* See scripts/test-go-redirect.mjs.
*/
const ALLOWED_HOSTS = ( const ALLOWED_HOSTS = (
process.env.NEXT_PUBLIC_DMONEY_ALLOWED_HOSTS ?? "d-money.dj" process.env.NEXT_PUBLIC_DMONEY_ALLOWED_HOSTS ?? "d-money.dj"
@@ -29,8 +45,6 @@ function isTrustedDMoneyUrl(raw: string | null): raw is string {
); );
} }
const REDIRECT_DELAY_MS = 1000;
function RedirectView() { function RedirectView() {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const raw = searchParams.get("url"); const raw = searchParams.get("url");
@@ -38,63 +52,27 @@ function RedirectView() {
useEffect(() => { useEffect(() => {
if (!target) return; if (!target) return;
const timer = setTimeout(() => { // Navigate from this document so the D-Money request carries
// Referer: https://edrpassenger.triaplc.com (the origin D-Money whitelists).
window.location.replace(target); window.location.replace(target);
}, REDIRECT_DELAY_MS);
return () => clearTimeout(timer);
}, [target]); }, [target]);
if (!target) {
return ( return (
<div className="w-full max-w-sm text-center"> <div className="fixed inset-0 z-[100] flex items-center justify-center bg-white px-6 text-center">
<div className="mx-auto mb-5 flex h-16 w-16 items-center justify-center rounded-full bg-red-100"> {!target && (
<ShieldAlert className="h-8 w-8 text-red-600" />
</div>
<h1 className="mb-2 text-xl font-bold text-gray-900">
Can&apos;t continue
</h1>
<p className="text-sm text-gray-500"> <p className="text-sm text-gray-500">
This link is missing a valid D-Money checkout address or points to an This payment link is invalid or has expired. Please start the payment
untrusted destination. Please start the payment again from the app. again from the app.
</p> </p>
</div> )}
);
}
return (
<div className="w-full max-w-sm text-center">
<div className="mx-auto mb-5 flex h-16 w-16 items-center justify-center rounded-full bg-primary/10">
<Loader2 className="h-8 w-8 animate-spin text-primary" />
</div>
<h1 className="mb-2 text-xl font-bold text-gray-900">
Redirecting to D-Money
</h1>
<p className="text-sm text-gray-500">
Taking you to the secure D-Money checkout to complete your payment
</p>
<a
href={target}
className="mt-6 inline-flex items-center justify-center gap-2 text-sm font-medium text-primary hover:underline"
>
Continue to D-Money
<ExternalLink className="h-4 w-4" />
</a>
</div> </div>
); );
} }
export default function GoPage() { export default function GoPage() {
return ( return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-white px-4"> <Suspense fallback={<div className="fixed inset-0 z-[100] bg-white" />}>
<Suspense
fallback={
<Loader2 className="h-8 w-8 animate-spin text-primary" aria-label="Loading" />
}
>
<RedirectView /> <RedirectView />
</Suspense> </Suspense>
</div>
); );
} }