결제 위젯 URL에서 paymentsData에 들어가는 orderName 값이 아래처럼 %를 포함할 때 오류가 발생했다.
맛있는 다라딸기! 59%%%
실제 URL에서는 %가 URL 인코딩되어 아래처럼 보였다.
...paymentsData=%7B...%22orderName%22%3A%22...59%25%25%25%22%7D
이 상태에서 웹 결제 위젯 페이지가 파라미터를 파싱할 때 에러가 났다.
앱 쪽 결제 진입 코드는 아래 흐름이었다.
즉 앱 쪽에서는 paymentsData를 정상적으로 한 번만 인코딩하고 있었다.
문제가 있던 웹 코드는 PaymentsWidgets.tsx (line 52) 의 이 부분이었다.
const parsedPaymentsData = JSON.parse( decodeURIComponent(paymentsDataParam), );
여기서 핵심은:
즉 문제는 URL 자체가 아니라, 웹에서 한 번 더 디코딩한 것이었다.
URL에는 이런 식으로 들어간다.
paymentsData=%7B%22orderId%22%3A%22ORD-20260422-0007%22%2C%22customerKey%22%3A%22USER-20251226-0004%22%2C%22orderName%22%3A%22맛있는+다라딸기!+59%25%25%25%22%7D
하지만 브라우저에서 아래 코드를 실행하면:
const searchParams = new URLSearchParams(window.location.search); const paymentsDataParam = searchParams.get('paymentsData');
paymentsDataParam 값은 이미 이렇게 된다.
'{"orderId":"ORD-20260422-0007","customerKey":"USER-20251226-0004","orderName":"맛있는 다라딸기! 59%%%"}'
따라서 이 값은 바로 JSON.parse(...) 하면 된다.
처음에는 혹시 과거 호환성이나 다른 진입 경로 때문에 이중 인코딩된 값도 들어올 수 있을 가능성을 열어두고, 아래처럼 방어 코드를 잠깐 고려했다.
const parsePaymentsDataParam = (value: string) => { try { return JSON.parse(value); } catch { return JSON.parse(decodeURIComponent(value)); } };
이 방식은 다음 상황을 대비한 것이다.
확인 결과 현재 구조는 다음과 같았다.
이 조건이면 decodeURIComponent fallback을 남겨두는 것보다, 현재 계약에 맞게 단순하게 처리하는 편이 더 맞다.
이유는:
PaymentsWidgets.tsx (line 52) 에서 아래처럼 수정했다.
const parsedPaymentsData = JSON.parse( decodeURIComponent(paymentsDataParam), );
const parsedPaymentsData = JSON.parse(paymentsDataParam) as PaymentsData;
이후 orderName 이 맛있는 다라딸기! 59%%% 여도 정상 처리된다.
as PaymentsData 는 런타임 검증이 아니라 TypeScript 타입 단언이다.
의미는:
즉 현재 수정의 본질은 as PaymentsData 가 아니라, decodeURIComponent 를 제거한 것이다.
현재 웹 결제 위젯 페이지는 아래 규칙으로 동작하면 된다.
const searchParams = new URLSearchParams(window.location.search);
const paymentsDataParam = searchParams.get('paymentsData');
if (!paymentsDataParam) {
// 예외 처리
}
const parsedPaymentsData = JSON.parse(paymentsDataParam);
이 구조면 맛있는 다라딸기! 59%%% 같은 값도 문제없이 처리된다.