From 5bef4efbf14beb8a77844788fda50d2ce41f9807 Mon Sep 17 00:00:00 2001 From: "dev.daminik00" Date: Wed, 24 Dec 2025 22:03:08 +0300 Subject: [PATCH] fix utm unicode --- src/app/[locale]/(payment)/payment/route.ts | 14 +++++++++++++- src/shared/utils/url.ts | 19 ++++++++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/app/[locale]/(payment)/payment/route.ts b/src/app/[locale]/(payment)/payment/route.ts index fe7a8c9..d4a1fea 100644 --- a/src/app/[locale]/(payment)/payment/route.ts +++ b/src/app/[locale]/(payment)/payment/route.ts @@ -3,8 +3,18 @@ import { NextRequest, NextResponse } from "next/server"; import { createPaymentCheckout } from "@/entities/payment/api"; import { ROUTES } from "@/shared/constants/client-routes"; +/** + * Convert base64 string to bytes (handles UTF-8 properly) + * MDN recommended approach: https://developer.mozilla.org/en-US/docs/Web/API/Window/btoa#unicode_strings + */ +function base64ToBytes(base64: string): Uint8Array { + const binString = atob(base64); + return Uint8Array.from(binString, (m) => m.codePointAt(0) ?? 0); +} + /** * Decode URL-safe base64 state parameter to UTM object + * Supports UTF-8 encoded content (e.g., utm_campaign=夏セール) */ function decodeStateParam(state: string): Record | undefined { if (!state) return undefined; @@ -16,7 +26,9 @@ function decodeStateParam(state: string): Record | undefined { while (base64.length % 4) { base64 += "="; } - const json = atob(base64); + // Decode base64 to bytes, then decode UTF-8 + const bytes = base64ToBytes(base64); + const json = new TextDecoder().decode(bytes); return JSON.parse(json); } catch { return undefined; diff --git a/src/shared/utils/url.ts b/src/shared/utils/url.ts index 34155ba..d069716 100644 --- a/src/shared/utils/url.ts +++ b/src/shared/utils/url.ts @@ -61,17 +61,30 @@ export const getCurrentQueryParams = (): Record => { return utmParams; }; +/** + * Convert bytes to base64 string (handles UTF-8 properly) + * MDN recommended approach: https://developer.mozilla.org/en-US/docs/Web/API/Window/btoa#unicode_strings + */ +const bytesToBase64 = (bytes: Uint8Array): string => { + const binString = Array.from(bytes, (byte) => + String.fromCodePoint(byte) + ).join(""); + return btoa(binString); +}; + /** * Encode params as base64 JSON for state parameter - * Uses URL-safe base64 encoding + * Uses URL-safe base64 encoding with UTF-8 support + * Handles Unicode characters (e.g., utm_campaign=夏セール) */ export const encodeStateParam = (params: Record): string => { if (Object.keys(params).length === 0) return ""; try { const json = JSON.stringify(params); - // Use btoa for base64, replace unsafe chars for URL - const base64 = btoa(json) + // Encode string as UTF-8 bytes, then convert to base64 + const bytes = new TextEncoder().encode(json); + const base64 = bytesToBase64(bytes) .replace(/\+/g, "-") .replace(/\//g, "_") .replace(/=+$/, "");