Skip to main content

고객 통합 가이드

이 문서는 imprun Mobile SDK를 자신의 Android 앱에 통합하려는 고객 개발자를 위한 가이드입니다.

빠른 시작

1단계: SDK 의존성 추가

// app/build.gradle.kts
dependencies {
    implementation("kr.co.imprun:mobile-sdk:1.0.0")
}
SDK는 내부적으로 다음 라이브러리를 사용합니다. 앱에서 이미 사용 중이라면 버전 충돌에 주의하세요:
  • androidx.webkit:webkit:1.10.0
  • com.squareup.okhttp3:okhttp:4.12.0
  • com.google.code.gson:gson:2.10.1
  • androidx.security:security-crypto:1.1.0-alpha06

2단계: 권한 설정

<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.INTERNET" />

3단계: 엔진 초기화

import kr.co.imprun.sdk.core.AutomationEngine
import kr.co.imprun.sdk.core.SDKConfig

class MyActivity : AppCompatActivity() {

    private lateinit var engine: AutomationEngine

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val config = SDKConfig(
            debug = true,              // 개발 중에만 true
            defaultTimeout = 60_000    // 60초 타임아웃
        )

        engine = AutomationEngine(this, config)

        engine.initialize { success ->
            if (success) {
                Log.i("MyApp", "SDK 초기화 완료")
                // 이제 로그인 자동화를 실행할 수 있음
            } else {
                Log.e("MyApp", "SDK 초기화 실패")
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        engine.cleanup()   // 리소스 정리 필수
    }
}

4단계: 로그인 실행

import kr.co.imprun.sdk.core.LoginCredentials

private fun executeLogin() {
    val credentials = LoginCredentials(
        id = "사용자아이디",
        password = "base64인코딩된비밀번호"  // Base64.encode(rawPassword)
    )

    engine.executeLogin("HMTAX", "ID_PW", credentials) { result ->
        result.onSuccess { session ->
            // 로그인 성공!
            Log.i("MyApp", "사용자: ${session.userName}")
            Log.i("MyApp", "납세자번호: ${session.taxPayerNo}")
            Log.i("MyApp", "쿠키: ${session.cookies}")

            // session 데이터를 서버로 전송하거나 후속 작업 수행
            sendSessionToServer(session)
        }
        .onFailure { error ->
            // 로그인 실패
            Log.e("MyApp", "로그인 실패: ${error.message}")
            handleLoginError(error)
        }
    }
}

SDK 설정

SDKConfig

속성타입기본값설명
debugBooleanfalse디버그 로그 활성화
logLevelLogLevelINFO로그 출력 레벨 (DEBUG, INFO, WARN, ERROR, NONE)
defaultTimeoutLong30000기본 타임아웃 (ms)
useScriptCacheBooleantrue스크립트 캐싱 사용 여부
useSecureStorageBooleantrue암호화된 저장소 사용 여부
val config = SDKConfig(
    debug = false,
    logLevel = LogLevel.WARN,
    defaultTimeout = 60_000
)

지원하는 사이트

홈택스 (HMTAX)

사이트 코드: "HMTAX"
로그인 유형코드설명
ID/PW"ID_PW"아이디 + 비밀번호 로그인
입력 형식:
val credentials = LoginCredentials(
    id = "홈택스아이디",
    password = Base64.encodeToString(
        "평문비밀번호".toByteArray(),
        Base64.NO_WRAP
    )
)
password는 반드시 Base64 인코딩된 값이어야 합니다. 평문 비밀번호를 직접 전달하면 로그인에 실패합니다.
반환 데이터 (SessionInfo):
data class SessionInfo(
    val siteCode: String,         // "HMTAX"
    val timestamp: Long,          // 로그인 시각 (epoch ms)
    val rawResponse: String,      // 원시 JSON 응답
    val cookies: Map<String, String>,  // JSESSIONID 등

    // 홈택스 전용 필드
    val userId: String?,          // "pak2251"
    val userName: String?,        // "박준*" (마스킹)
    val taxPayerNo: String?,      // 납세자식별번호 (마스킹)
    val publicUserNo: String?,    // 공개사용자번호
    val tin: String?,             // TIN
    val loginResultCode: String?, // "01" (성공)
    val loginCertCode: String?    // "03" (ID/PW 인증)
)

에러 처리

에러 유형

에러 코드의미대응
FORM_NOT_FOUND로그인 폼 요소를 찾을 수 없음사이트 UI 변경 가능성 — 관리자에게 문의
TIMEOUT로그인 응답 시간 초과 (30초)네트워크 확인 또는 재시도
SERVER_ERROR서버가 비-JSON 응답 반환사이트 점검 중일 수 있음
TRANSACTION_FAILED서버 트랜잭션 실패재시도
RESULT_ERROR응답 결과 코드 오류에러 메시지 확인
LOGIN_RESULT_02비밀번호 오류비밀번호 확인
LOGIN_RESULT_03존재하지 않는 아이디아이디 확인
LOGIN_RESULT_04계정 잠금사이트에서 직접 해제 필요
LOGIN_RESULT_05탈퇴한 사용자-
PARSE_ERROR응답 파싱 실패관리자에게 문의

에러 처리 예제

engine.executeLogin("HMTAX", "ID_PW", credentials) { result ->
    result.onFailure { error ->
        val message = error.message ?: "알 수 없는 에러"

        when {
            message.contains("LOGIN_RESULT_02") ->
                showToast("비밀번호가 올바르지 않습니다")
            message.contains("LOGIN_RESULT_03") ->
                showToast("존재하지 않는 아이디입니다")
            message.contains("LOGIN_RESULT_04") ->
                showToast("계정이 잠겼습니다. 홈택스에서 해제하세요")
            message.contains("TIMEOUT") ->
                showRetryDialog("응답 시간이 초과되었습니다. 다시 시도하시겠습니까?")
            message.contains("SERVER_ERROR") ->
                showToast("서버 점검 중입니다. 잠시 후 다시 시도하세요")
            else ->
                showToast("로그인 실패: $message")
        }
    }
}

세션 활용

쿠키 기반 후속 요청

로그인 성공 후 SessionInfo.cookies를 사용하여 인증된 API 호출이 가능합니다:
result.onSuccess { session ->
    // OkHttp에 쿠키 설정
    val cookieHeader = session.cookies.entries
        .joinToString("; ") { "${it.key}=${it.value}" }

    val request = Request.Builder()
        .url("https://mob.hometax.go.kr/jsonAction.do?actionId=...")
        .header("Cookie", cookieHeader)
        .build()

    okHttpClient.newCall(request).enqueue(object : Callback {
        override fun onResponse(call: Call, response: Response) {
            val body = response.body?.string()
            // 인증된 데이터 처리
        }
        override fun onFailure(call: Call, e: IOException) { ... }
    })
}

서버로 세션 전송

result.onSuccess { session ->
    val payload = JSONObject().apply {
        put("siteCode", session.siteCode)
        put("userId", session.userId)
        put("userName", session.userName)
        put("taxPayerNo", session.taxPayerNo)
        put("tin", session.tin)
        put("cookies", JSONObject(session.cookies))
        put("timestamp", session.timestamp)
    }

    // 자사 서버로 전송
    apiClient.post("/api/sessions", payload)
}

라이프사이클 관리

올바른 사용 패턴

class LoginActivity : AppCompatActivity() {

    private var engine: AutomationEngine? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        engine = AutomationEngine(this, SDKConfig(debug = BuildConfig.DEBUG))
    }

    fun startLogin() {
        engine?.initialize { success ->
            if (success) {
                performLogin()
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        engine?.cleanup()    // WebView, 코루틴, 캐시 정리
        engine = null
    }
}
  • cleanup()을 호출하지 않으면 WebView 메모리 누수가 발생합니다
  • AutomationEngine은 Activity/Fragment의 Context를 필요로 합니다 — Application Context를 사용하지 마세요
  • 한 번에 하나의 AutomationEngine 인스턴스만 사용하세요

보안 참고사항

  • 비밀번호 저장 금지: 앱에서 비밀번호를 영구 저장하지 마세요. 사용 즉시 메모리에서 삭제하세요.
  • WebView 격리: SDK의 WebView는 앱의 다른 WebView와 독립적입니다.
  • 세션 유효기간: 홈택스 세션(JSESSIONID)은 약 30분 유효합니다.
  • HTTPS 전용: SDK는 HTTPS URL만 허용합니다.
  • 데이터 암호화: SecureStorage는 AES-256-GCM으로 암호화됩니다.

자주 묻는 질문

SDK는 @JavascriptInterface 애노테이션을 사용하므로, ProGuard에서 NativeBridge 클래스가 난독화되지 않도록 해야 합니다. SDK의 consumer rules가 자동으로 적용되지만, 문제가 생기면:
-keepclassmembers class kr.co.imprun.sdk.bridge.NativeBridge {
    @android.webkit.JavascriptInterface <methods>;
}
API 24 (Android 7.0)입니다. atDocumentStart 주입은 AndroidX WebKit 1.5.0+가 필요하며, 미지원 기기에서는 자동으로 onPageFinished 폴백을 사용합니다.
네. Google Play가 포함된 에뮬레이터(API 28+)에서 정상 동작합니다.
가능합니다. 단, 각 로그인 사이의 쿠키 격리를 위해 engine.cleanup()engine.initialize() 사이클을 권장합니다.