fix(transak): derive referrerDomain from request hostname instead of hardcoding

Resolves T-INF-101 Access Denied when accessing payment links from
subdomains like demo.rspace.online. Adds extractRootDomain() helper
to shared/transak.ts, used by both rcart and rflows onramp adapters.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-12 10:49:29 -07:00
parent 87d5e8b1cb
commit c668d5700c
5 changed files with 14 additions and 4 deletions

View File

@ -30,7 +30,7 @@ import {
type CartItem, type CartStatus,
} from './schemas';
import { extractProductFromUrl } from './extract';
import { createTransakWidgetUrl } from '../../shared/transak';
import { createTransakWidgetUrl, extractRootDomain } from '../../shared/transak';
import QRCode from 'qrcode';
let _syncServer: SyncServer | null = null;
@ -1368,9 +1368,11 @@ routes.post("/api/payments/:id/transak-session", async (c) => {
const networkMap: Record<number, string> = { 8453: 'base', 84532: 'base', 1: 'ethereum' };
const host = new URL(c.req.url).hostname;
const widgetUrl = createTransakWidgetUrl({
apiKey: transakApiKey,
referrerDomain: 'rspace.online',
referrerDomain: extractRootDomain(host),
cryptoCurrencyCode: p.token,
network: networkMap[p.chainId] || 'base',
defaultCryptoCurrency: p.token,

View File

@ -14,6 +14,7 @@ export interface OnrampSessionRequest {
fiatCurrency: string;
sessionId: string;
returnUrl?: string;
hostname?: string;
}
export interface OnrampSessionResult {

View File

@ -4,7 +4,7 @@
*/
import type { OnrampProvider, OnrampSessionRequest, OnrampSessionResult } from './onramp-provider';
import { createTransakWidgetUrl } from '../../../shared/transak';
import { createTransakWidgetUrl, extractRootDomain } from '../../../shared/transak';
export class TransakOnrampAdapter implements OnrampProvider {
id = 'transak' as const;
@ -20,7 +20,7 @@ export class TransakOnrampAdapter implements OnrampProvider {
const widgetParams: Record<string, string> = {
apiKey,
referrerDomain: 'rspace.online',
referrerDomain: req.hostname ? extractRootDomain(req.hostname) : 'rspace.online',
cryptoCurrencyCode: 'USDC',
network: 'base',
defaultCryptoCurrency: 'USDC',

View File

@ -233,6 +233,7 @@ routes.post("/api/flows/user-onramp", async (c) => {
fiatCurrency,
sessionId,
returnUrl,
hostname: new URL(c.req.url).hostname,
});
console.log(`[rflows] On-ramp session created: provider=${provider} session=${sessionId} wallet=${wallet.address}`);

View File

@ -9,6 +9,12 @@
const TRANSAK_WIDGET_BASE = 'https://global.transak.com';
const TRANSAK_WIDGET_BASE_STG = 'https://global-stg.transak.com';
/** Extract root domain from a hostname (e.g. "demo.rspace.online" → "rspace.online") */
export function extractRootDomain(host: string): string {
const parts = host.replace(/:\d+$/, '').split('.');
return parts.length > 2 ? parts.slice(-2).join('.') : parts.join('.');
}
export function createTransakWidgetUrl(params: Record<string, string>): string {
const env = process.env.TRANSAK_ENV || 'PRODUCTION';
const base = env === 'PRODUCTION' ? TRANSAK_WIDGET_BASE : TRANSAK_WIDGET_BASE_STG;