- {{ currentDate }}
+ {{ preregistDate }}
- {{ t('Preregist_Agree_News_Complete') }}
+ {{ tm('Preregist_Agree_News_Complete') }}
- {{ t('Preregist_Stove_Download') }}
+ {{ tm('Preregist_Stove_Download') }}
diff --git a/layers/composables/useGds.ts b/layers/composables/useGds.ts
index 1d03d86..5803983 100644
--- a/layers/composables/useGds.ts
+++ b/layers/composables/useGds.ts
@@ -9,109 +9,147 @@ import { DEFAULT_LOCALE_CODE } from '@/i18n.config'
*
* @description https://wiki.smilegate.net/display/SDKAPIDOCU/51-09.+gds
*/
+
+const COUNTRY_GROUPS = {
+ KOREA: ['KR'] as const,
+ NORTH_AMERICA: ['US', 'CA'] as const,
+ TAIWAN_HONG_KONG_MACAU: ['TW', 'HK', 'MO'] as const,
+} as const
+const ERROR_CODE = {
+ UNKNOWN: -99999,
+} as const
+
const useGds = () => {
- const logPrefix = {
- exception: '[Exception] /composables/useGds',
- failure: '[Failure] /composables/useGds',
- }
+ const countryCode = ref('')
+ const isKorea = ref(false)
+ const isTaiwanHongKongMacau = ref(false)
+ const isNorthAmerica = ref(false)
- const countryCode = ref('') // 국가 코드
- const isKorea = ref(false) // 대한민국 여부
- const isTaiwanHongKongMacau = ref(false) // 대홍마 여부
- const isNorthAmerica = ref(false) // 북미 여부
-
- // [Setter] 사전등록 - 국가 코드 세팅
- const setCountryCode = (newCountryCode: string) => {
- countryCode.value = newCountryCode
- }
- // [Setter] 사전등록 - 대한민국 여부 세팅
- const setIsKorea = (newIsKorea: boolean) => {
- isKorea.value = newIsKorea
- }
- // [Setter] 사전등록 - 대홍마 여부 세팅
- const setIsTaiwanHongKongMacau = (newIsTaiwanHongKongMacau: boolean) => {
- isTaiwanHongKongMacau.value = newIsTaiwanHongKongMacau
- }
- // [Setter] 사전등록 - 북미 여부 세팅
- const setIsNorthAmerica = (newIsNorthAmerica: boolean) => {
- isNorthAmerica.value = newIsNorthAmerica
- }
-
- // 사전 등록 - 특정 국가 여부 조회 (IP 기반)
- const checkCountryByIp = async (req: ReqGetGdsClientPolicyTotal) => {
- let res: ResGetGdsClientPolicyTotal
- const arrKorea = ['KR'] // cf. UM: 미국령 군소 제도의 기 미국령 군소 제도, VI: 미국령 버진아일랜드의 기 미국령 버진아일랜드
- const arrNorthAmerica = ['US', 'CA'] // cf. UM: 미국령 군소 제도의 기 미국령 군소 제도, VI: 미국령 버진아일랜드의 기 미국령 버진아일랜드
- const arrTaiwanHongKongMacau = ['TW', 'HK', 'MO'] // 대만, 홍콩, 마카오 국가 코드
- try {
- if (`${req.runType}` !== 'live' && req.qc != null && req.qc !== '') {
- // Live가 아닌 환경에서 qc가 있으면 국가 코드로 설정하여 판별
- setCountryCode(req.qc || '')
- setIsKorea(arrKorea.includes(req.qc))
- setIsTaiwanHongKongMacau(arrTaiwanHongKongMacau.includes(req.qc))
- setIsNorthAmerica(arrNorthAmerica.includes(req.qc))
-
- res = {
- code: 0,
- message: '',
- res_code: 0,
- res_data: {
- is_default: true,
- nation: req.qc,
- regulation: '',
- timezone: '',
- utc_offset: 0,
- lang: req.client_lang || DEFAULT_LOCALE_CODE,
- coverages: [],
- },
- } as ResGetGdsClientPolicyTotal
- } else {
- const runtimeConfig = useRuntimeConfig()
- const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl
-
- const url = `${stoveApiBaseUrl}/gds/v2/client/policy/total`
- const query = {
- policy_grp: req.policy_grp || 'onstove',
- device_nation: req.device_nation || 'KR',
- client_lang: req.client_lang || DEFAULT_LOCALE_CODE,
- include_coverages: req.include_coverages || false,
- }
- res = (await commonFetch('GET', url, {
- query,
- })) as ResGetGdsClientPolicyTotal
-
- if (res != null && res.res_code === 0) {
- // is_default = false : 국가코드를 정상 처리했으나 타임존을 처리하지 못한 경우
- // is_default = true : device_nation 또는 policy_grp 별 디폴트 값으로 처리된 경우
- if (res.res_data != null && res.res_data.nation != null) {
- setCountryCode(res.res_data.nation || '')
- setIsKorea(arrKorea.includes(res.res_data.nation))
- setIsTaiwanHongKongMacau(
- arrTaiwanHongKongMacau.includes(res.res_data.nation)
- )
- setIsNorthAmerica(arrNorthAmerica.includes(res.res_data.nation))
- }
- }
- }
- } catch (e) {
- console.error(`${logPrefix.exception}.checkCountryByIp: `, e)
- res = {
- code: -99999,
- message: `${e}`,
- }
- setCountryCode('')
- setIsKorea(false)
- setIsTaiwanHongKongMacau(false)
- setIsNorthAmerica(false)
+ /**
+ * 국가별 플래그 업데이트
+ */
+ const updateCountryFlags = (nation: string) => {
+ if (!nation) {
+ countryCode.value = ''
+ isKorea.value = false
+ isTaiwanHongKongMacau.value = false
+ isNorthAmerica.value = false
+ return
+ }
+
+ const upperNation = nation.toUpperCase()
+ countryCode.value = upperNation
+ isKorea.value = COUNTRY_GROUPS.KOREA.includes(upperNation as any)
+ isTaiwanHongKongMacau.value =
+ COUNTRY_GROUPS.TAIWAN_HONG_KONG_MACAU.includes(upperNation as any)
+ isNorthAmerica.value = COUNTRY_GROUPS.NORTH_AMERICA.includes(
+ upperNation as any
+ )
+ }
+
+ /**
+ * 에러 응답 생성
+ */
+ const createErrorResponse = (message: string): ResGetGdsClientPolicyTotal => {
+ return {
+ code: ERROR_CODE.UNKNOWN,
+ message,
+ res_code: ERROR_CODE.UNKNOWN,
+ res_data: undefined as any,
+ }
+ }
+
+ /**
+ * Mock 응답 생성 (개발/QA 환경)
+ */
+ const createMockResponse = (
+ qc: string,
+ clientLang: string
+ ): ResGetGdsClientPolicyTotal => {
+ return {
+ code: 0,
+ message: '',
+ res_code: 0,
+ res_data: {
+ is_default: true,
+ nation: qc.toUpperCase(),
+ regulation: '',
+ timezone: '',
+ utc_offset: 0,
+ lang: clientLang || DEFAULT_LOCALE_CODE,
+ coverages: [],
+ },
+ }
+ }
+
+ /**
+ * 사전 등록 - 특정 국가 여부 조회 (IP 기반)
+ */
+ const checkCountryByIp = async (
+ req: ReqGetGdsClientPolicyTotal
+ ): Promise => {
+ try {
+ // runType 우선순위: req.runType > runtimeConfig.runType
+ const runtimeConfig = useRuntimeConfig()
+ const runType = req.runType || runtimeConfig.public.runType
+ const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl
+
+ // Mock 모드 (개발/QA 환경)
+ const isMockMode = runType !== 'live' && req.qc && req.qc !== ''
+
+ if (isMockMode) {
+ const mockQc = req.qc!.toUpperCase()
+ updateCountryFlags(mockQc)
+ return createMockResponse(mockQc, req.client_lang || '')
+ }
+
+ // 실제 API 호출
+ const url = `${stoveApiBaseUrl}/gds/v2/client/policy/total`
+ const query = {
+ policy_grp: req.policy_grp || 'onstove',
+ device_nation: req.device_nation || 'KR',
+ client_lang: req.client_lang || DEFAULT_LOCALE_CODE,
+ include_coverages: req.include_coverages ?? false,
+ }
+
+ const res = (await commonFetch('GET', url, {
+ query,
+ })) as ResGetGdsClientPolicyTotal
+
+ // 성공 응답 처리
+ if (res.res_code === 0 && res.res_data?.nation) {
+ updateCountryFlags(res.res_data.nation)
+ return res
+ }
+
+ // 실패 응답 처리
+ // eslint-disable-next-line no-console
+ console.error('[useGds].checkCountryByIp: Invalid response', res)
+ updateCountryFlags('')
+ return res
+ } catch (error) {
+ // 에러 로깅
+ // eslint-disable-next-line no-console
+ console.error('[useGds].checkCountryByIp: Exception', error)
+
+ // 상태 초기화
+ updateCountryFlags('')
+
+ // 에러 응답 반환
+ return createErrorResponse(
+ error instanceof Error ? error.message : String(error)
+ )
}
- return res
}
return {
- isKorea,
- isTaiwanHongKongMacau,
- isNorthAmerica,
- countryCode,
+ // Reactive state
+ isKorea: readonly(isKorea),
+ isTaiwanHongKongMacau: readonly(isTaiwanHongKongMacau),
+ isNorthAmerica: readonly(isNorthAmerica),
+ countryCode: readonly(countryCode),
+
+ // Methods
checkCountryByIp,
}
}
diff --git a/layers/composables/usePreregist.ts b/layers/composables/usePreregist.ts
index 53d614d..cc84690 100644
--- a/layers/composables/usePreregist.ts
+++ b/layers/composables/usePreregist.ts
@@ -11,20 +11,24 @@ import { countryDialingCodes } from '#layers/assets/data/countryData'
/**
* 프로모션 - 사전등록
*/
+
+const PREREGIST_ERROR_CODE = {
+ SUCCESS: 0,
+ NO_DATA: -1, // 조회된 데이터가 없습니다 (최초)
+ NOT_PERIOD: -90002, // 사전 등록 기간이 아닙니다
+ REQUIRED_TERMS: -90000, // 필수 약관을 모두 선택해 주세요
+ // BIRTH_DATE_REQUIRED: -90018, // 생년 월일을 입력해 주세요
+ AGE_RESTRICTION: -90022, // 사전 등록 가능한 연령이 아닙니다
+ ALREADY_REGISTERED: -90023, // 이미 사전 등록이 완료된 계정입니다
+ LOGIN_REQUIRED: -90028, // 로그인 후 이용하실 수 있습니다
+ // MAINTENANCE: -90003, // 점검 진행 중
+ UNKNOWN: -99999, // 알 수 없는 오류
+} as const
+
const usePreregist = () => {
- const preregistDate = ref(Date.now()) // 사전 등록일
+ const preregistDate = ref(Date.now())
- // [Setter] 사전등록 - 사전 등록일 세팅
- const setPreregistDate = (newPreregistDate: number) => {
- preregistDate.value = newPreregistDate
- }
-
- // 국가 번호 조회
- const countryDialingCode = computed(() => {
- return countryDialingCodes[countryCode.value.toUpperCase()]
- })
-
- // 사전 등록 - 특정 국가 여부 조회 (IP 기반)
+ // GDS composable
const {
isKorea,
isTaiwanHongKongMacau,
@@ -33,14 +37,54 @@ const usePreregist = () => {
checkCountryByIp,
} = useGds()
- // 사전 등록 - (등록 여부) 조회
- const getPreregist = async (req: ReqPreorderSelectEvent) => {
- let res: ResPreorderSelectEvent = {} as ResPreorderSelectEvent
+ // 국가 번호 조회
+ const countryDialingCode = computed(() => {
+ const code = countryCode.value?.toUpperCase()
+ return code ? countryDialingCodes[code] : undefined
+ })
+
+ /**
+ * 사전 등록일 세팅 (숫자 검증)
+ */
+ const setPreregistDate = (dateValue: number | string | undefined) => {
+ if (dateValue && isNumeric(String(dateValue))) {
+ preregistDate.value = Number(dateValue)
+ } else {
+ preregistDate.value = Date.now()
+ }
+ }
+
+ /**
+ * 에러 응답 생성
+ */
+ const createErrorResponse = (
+ code: number,
+ message: string = ''
+ ): T => {
+ return { code, message } as T
+ }
+
+ /**
+ * 401 에러를 LOGIN_REQUIRED로 정규화
+ */
+ const normalizeAuthError = (code: number): number => {
+ return String(code).startsWith('401')
+ ? PREREGIST_ERROR_CODE.LOGIN_REQUIRED
+ : code
+ }
+
+ /**
+ * 사전 등록 - 조회 (등록 여부)
+ */
+ const getPreregist = async (
+ req: ReqPreorderSelectEvent
+ ): Promise => {
try {
const runtimeConfig = useRuntimeConfig()
- const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl
+ const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl
const url = `${stoveApiBaseUrl}/pub-comm/v1.0/Preorder/SelectEvent`
+
const headers = {
Authorization: `Bearer ${req.accessToken}`,
}
@@ -49,59 +93,69 @@ const usePreregist = () => {
lang: req.lang || DEFAULT_LOCALE_CODE,
terms_type: req.terms_type,
}
- res = (await commonFetch('POST', url, {
+
+ const res = (await commonFetch('POST', url, {
headers,
body,
})) as ResPreorderSelectEvent
- if (res != null) {
- if (res.code === 0) {
- setPreregistDate(res.value?.terms_time_long || Date.now())
- } else if (res.code === -1) {
- //= 조회 된 데이터가 없습니다. -> 최초
- } else if (res.code === -90002) {
- //= 사전 등록 기간이 아닙니다.
- } else if (`${res.code}`.startsWith('401')) {
- //= [01] 로그인 후 이용하실 수 있습니다.
- res = { code: -90028, message: res.message || '' } // 401은 -90028로 처리
- } else if (res.code === -90028) {
- //= [01] 로그인 후 이용하실 수 있습니다.
- } else {
- // [-90003] 점검 진행 중
- res = { code: res.code, message: res.message || '' }
- console.log(
- '[Failure] /composables/promotion/usePreregist.getPreregist: ',
- res
- )
- res.code = -99999 // else 알럿 띄우기 용 세팅
- }
- } else {
- res = { code: -99999, message: '' }
- console.log(
- '[Failure] /composables/promotion/usePreregist.getPreregist: ',
- res
- )
- res.code = -99999 // else 알럿 띄우기 용 세팅
+
+ // 응답 검증
+ if (!res) {
+ // eslint-disable-next-line no-console
+ console.error('[usePreregist].getPreregist: Empty response')
+ return createErrorResponse(PREREGIST_ERROR_CODE.UNKNOWN)
}
- } catch (e) {
- console.error(
- '[Exception] /composables/promotion/usePreregist.getPreregist: ',
- e
+
+ // 정규화된 에러 코드
+ const normalizedCode = normalizeAuthError(res.code)
+
+ // 성공 케이스
+ if (normalizedCode === PREREGIST_ERROR_CODE.SUCCESS) {
+ setPreregistDate(res.value?.terms_time_long ?? Date.now())
+ return res
+ }
+
+ // 예상된 에러 케이스 (NO_DATA, NOT_PERIOD, LOGIN_REQUIRED)
+ const expectedErrors: number[] = [
+ PREREGIST_ERROR_CODE.NO_DATA,
+ PREREGIST_ERROR_CODE.NOT_PERIOD,
+ PREREGIST_ERROR_CODE.LOGIN_REQUIRED,
+ ]
+
+ if (expectedErrors.includes(normalizedCode)) {
+ return createErrorResponse(normalizedCode, res.message)
+ }
+
+ // 예상치 못한 에러
+ if (import.meta.dev) {
+ // eslint-disable-next-line no-console
+ console.error('[usePreregist].getPreregist: Unexpected error', res)
+ }
+ return createErrorResponse(PREREGIST_ERROR_CODE.UNKNOWN, res.message)
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error('[usePreregist].getPreregist: Exception', error)
+ return createErrorResponse(
+ PREREGIST_ERROR_CODE.UNKNOWN,
+ error instanceof Error ? error.message : String(error)
)
- res = {
- code: -99999,
- message: `${e}`,
- }
}
- return res
}
- // 사전 등록 - 저장
- const setPreregist = async (req: ReqPreorderReserveDataUpdate) => {
- let res: ResPreorderReserveDataUpdate = {} as ResPreorderReserveDataUpdate
+ /**
+ * 사전 등록 - 저장
+ */
+ const setPreregist = async (
+ req: ReqPreorderReserveDataUpdate
+ ): Promise => {
try {
const runtimeConfig = useRuntimeConfig()
const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl
+ if (!stoveApiBaseUrl) {
+ throw new Error('stoveApiUrl is not configured')
+ }
+
const url = `${stoveApiBaseUrl}/pub-comm/v1.0/Preorder/ReserveDataUpdate`
const headers = {
Authorization: `Bearer ${req.accessToken}`,
@@ -116,104 +170,93 @@ const usePreregist = () => {
lang_code: req.lang_code,
hp: req.hp,
email: req.email,
- metric_seq: req.metric_seq, //= mcode
+ metric_seq: req.metric_seq,
g_server: req.g_server,
world_id: req.world_id,
game_unique_num: req.game_unique_num,
- event_info1: req.event_info1, // 각 이벤트 필요할때 사용
- event_info2: req.event_info2, // 각 이벤트 필요할때 사용
- event_info3: req.event_info3, // 각 이벤트 필요할때 사용
- event_info4: req.event_info4, // 각 이벤트 필요할때 사용
- under14_terms: req.under14_terms, // 14세미만 진행 하는 구분값
- device_type: req.device_type, // 접속 기기 pc / mobile
+ event_info1: req.event_info1,
+ event_info2: req.event_info2,
+ event_info3: req.event_info3,
+ event_info4: req.event_info4,
+ under14_terms: req.under14_terms,
+ device_type: req.device_type,
country_code: req.country_code,
- country_dialing_code: req.country_dialing_code, // 국가 번호
- birth_date: req.birth_date, // 생년월일 - 북미(미국, 캐나다)의 경우
+ country_dialing_code: req.country_dialing_code,
+ birth_date: req.birth_date,
}
- res = (await commonFetch('POST', url, {
+ const res = (await commonFetch('POST', url, {
headers,
body,
})) as ResPreorderReserveDataUpdate
- if (res != null) {
- if (res.code === 0) {
- //= [08] [사전 등록 완료 레이어] 노출
- if (
- res.message != null &&
- res.message !== '' &&
- isNumeric(res.message)
- ) {
- // 사전 등록일이 숫자면 날짜로 변환
- setPreregistDate(Number(res.message))
- } else {
- setPreregistDate(Date.now())
- }
- } else if (`${res.code}`.startsWith('401')) {
- //= [01] 로그인 후 이용하실 수 있습니다.
- res = { code: -90028, message: res.message || '' } // 401은 -90028로 처리
- } else if (res.code === -90028) {
- //= [01] 로그인 후 이용하실 수 있습니다.
- } else if (res.code === -90002) {
- //= [02] 사전 등록 기간이 아닙니다.
- } else if (res.code === -90018) {
- //= [04] 생년 월일을 입력해 주세요.
- } else if (res.code === -90022) {
- //= [05] 사전 등록 가능한 연령이 아닙니다.
- } else if (res.code === -90000) {
- //= [06] 필수 약관을 모두 선택해 주세요.
- } else if (res.code === -90023) {
- //= [07] 이미 사전 등록이 완료된 계정입니다.
- if (
- res.message != null &&
- res.message !== '' &&
- isNumeric(res.message)
- ) {
- // 사전 등록일이 숫자면 날짜로 변환
- setPreregistDate(Number(res.message))
- } else {
- setPreregistDate(Date.now())
- }
- } else {
- //= [03] 오류가 발생했습니다.\n계속 오류가 발생하면 고객센터로 문의해 주세요.
- res = { code: res.code, message: res.message || '' }
- console.log(
- '[Failure] /composables/promotion/usePreregist.setPreregist: ',
- res
- )
- res.code = -99999 // else 알럿 띄우기 용 세팅
- }
- } else {
- res = { code: -99999, message: '' }
- console.log(
- '[Failure] /composables/promotion/usePreregist.setPreregist: ',
- res
- )
+ // 응답 검증
+ if (!res) {
+ // eslint-disable-next-line no-console
+ console.error('[usePreregist].setPreregist: Empty response')
+ return createErrorResponse(PREREGIST_ERROR_CODE.UNKNOWN)
}
- } catch (e) {
- console.error(
- '[Exception] /composables/promotion/usePreregist.setPreregist: ',
- e
+
+ // 정규화된 에러 코드
+ const normalizedCode = normalizeAuthError(res.code)
+
+ // 성공 케이스
+ if (normalizedCode === PREREGIST_ERROR_CODE.SUCCESS) {
+ setPreregistDate(res.message ? Number(res.message) : Date.now())
+ return res
+ }
+
+ // 이미 등록된 경우 (날짜 업데이트)
+ if (normalizedCode === PREREGIST_ERROR_CODE.ALREADY_REGISTERED) {
+ setPreregistDate(res.message ? Number(res.message) : Date.now())
+ return createErrorResponse(normalizedCode, res.message)
+ }
+
+ // 예상된 에러 케이스
+ const expectedErrors: number[] = [
+ PREREGIST_ERROR_CODE.LOGIN_REQUIRED,
+ PREREGIST_ERROR_CODE.NOT_PERIOD,
+ // PREREGIST_ERROR_CODE.BIRTH_DATE_REQUIRED,
+ PREREGIST_ERROR_CODE.AGE_RESTRICTION,
+ PREREGIST_ERROR_CODE.REQUIRED_TERMS,
+ ]
+
+ if (expectedErrors.includes(normalizedCode)) {
+ return createErrorResponse(normalizedCode, res.message)
+ }
+
+ // 예상치 못한 에러
+ if (import.meta.dev) {
+ // eslint-disable-next-line no-console
+ console.error('[usePreregist].setPreregist: Unexpected error', res)
+ }
+ return createErrorResponse(PREREGIST_ERROR_CODE.UNKNOWN, res.message)
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error('[usePreregist].setPreregist: Exception', error)
+ return createErrorResponse(
+ PREREGIST_ERROR_CODE.UNKNOWN,
+ error instanceof Error ? error.message : String(error)
)
- res = {
- code: -99999,
- message: `${e}`,
- }
}
- return res
}
return {
+ // GDS state & methods
isKorea,
isTaiwanHongKongMacau,
isNorthAmerica,
countryCode,
- countryDialingCode,
- preregistDate,
checkCountryByIp,
+
+ // Preregist state & computed
+ countryDialingCode,
+ preregistDate: readonly(preregistDate),
+
+ // Preregist methods
getPreregist,
setPreregist,
}
}
-export { usePreregist }
+export { usePreregist, PREREGIST_ERROR_CODE }
diff --git a/layers/templates/FxPreregist01/index.vue b/layers/templates/FxPreregist01/index.vue
index 5e19711..5eac0b2 100644
--- a/layers/templates/FxPreregist01/index.vue
+++ b/layers/templates/FxPreregist01/index.vue
@@ -39,9 +39,9 @@ const OS_TYPE_MAP: Record = {
'3': ['google_play', 'app_store'],
}
-const preregistModalRef = ref<{ handlePreregist: () => Promise } | null>(
- null
-)
+const preregistModalRef = ref<{
+ handleOpenPreregist: () => Promise
+} | null>(null)
// Preregist Section
const preregistCode = computed(
@@ -209,7 +209,7 @@ const splideOptions = computed(() => {
// Handler
const handlePreregistClick = () => {
- preregistModalRef.value?.handlePreregist()
+ preregistModalRef.value?.handleOpenPreregist()
}