2026/03/13

오늘의 이야기

인앱결제 코드 이전 이야기


이전 버전에서는 java 코드로 구현된 소스 코드를 공유해 보았습니다.


 


https://billcorea.tistory.com/165



 


안드로이드 앱 만들기 : 구글 인앱 결제 쉽게 따라하기 (정기결제, 소스공유)


이전 포스팅 이전에 작성했던 포스팅을 참고하여 인앱 결제를 구현했던 기억을 되살펴 보겠습니다. https://billcorea.tistory.com/27 안드로이드 앱 만들기 구글 인앱결제 쉽게 따라 하기... 인앱 결제를


billcorea.tistory.com




 


오늘은 이 코드를 그대로 kotlin  코드로 변환을 해 보았습니다. 


Java File to Kotlin File


Android Studio 에서는 java 코드를 kotlin으로 변환해 줍니다. 


메뉴에서 Code 제일 아래에 보면 Convert Java File to Kotlin File 이 보입니다.  물로 이 메뉴는 Java 코드일 때만 보입니다.


android studio 메뉴



 


변환을 시행해 보겠습니다. 변환은 내 앱의 상위 package 이름이 나와 있는 위치에서 오른쪽 마우스를 클릭해서 하는 방법도 있습니다.  개발 java 파일을 선택해서 오른쪽 마우스 클릭해서 하게 되는 경우는 개별 파일만 처리해 주지만, 최상위 package을 선택하고 하는 경우 하위 경로에 있는 모든 파일을 한 번에 변환해 줍니다. 


 


주의 사항  


일괄 변환된 후에 해야할 일들이 생깁니다. java 코드에서는 global 변수로 사용하고자 하는 경우 그냥 변수 이름만 선언해 주면 되었던 부분들이 kotlin을 변환하게 되면 그 값을 정해 주는 것에 대해서 설정을 해 주어야 하는 부분들이 생기며  해당 변수를 일괄적으로 null 대입하는 코드로 변환을 해 주시기 때문에 아래 예시들처럼 수정을 해 주어야 하는 부분들이 생깁니다.


 


변환 전 / 후



위 예시는 kotlin  으로 변환 후에 코드를 정리한 후의 코드이니 변환된 직후의 코드와는 다르다는 것을 염두에 두고 보시길 바랍니다. 


 


조금더 자세한 주의 사항을 보시려면 google  에서 제공하는 codelab 을 살펴 보세요.


https://codelabs.developers.google.com/codelabs/java-to-kotlin?hl=ko#1 



 


Kotlin으로 변환  |  Google Codelabs


이 Codelab에서는 자바 코드를 Kotlin으로 변환하는 방법을 알아봅니다.


codelabs.developers.google.com




 


Gradle 설정 추가


 source code 는 변환을 해 주지만, gradle 파일을 자동 변환을 해 주지 않기 때문에 설정을 일부 추가해 주어야 합니다. 


 


먼저 project 의 gradle 파일에는 아래처럼 2곳에 추가를 해 주었습니다.


buildscript {
ext.kotlin_version = '1.7.20' // kotlin 추가
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.3.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // kotlin 추가
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}

 


module의 gradle 파일은 다음과 같이 추가해 주었습니다.


plugins {
id 'com.android.application'
id 'kotlin-android-extensions' // kotlin 추가
id 'kotlin-android' // kotlin 추가
id 'kotlin-kapt' // kotlin 추가
}

 


이제 빌드를 진행해 봅니다. 


 


앱 실행 화면



 


이런 게 변환된 코드는 정상적으로 실행이 되는 것을 확인했습니다. 


 


구글 인앱 결제 


인앱 정기결제 코드는 어떻게 변환이 되었을까요 ?


 



import android.app.Activity
import android.content.Context
import android.content.SharedPreferences
import android.util.Log
import android.widget.Toast
import com.android.billingclient.api.*
import com.billcoreatech.daycnt415.R
import com.billcoreatech.daycnt415.util.KakaoToast
import java.text.SimpleDateFormat
import java.util.*

class BillingManager(var mActivity: Activity) : PurchasesUpdatedListener, ConsumeResponseListener {
var TAG = "BillingManager"
lateinit var mBillingClient: BillingClient
lateinit var mSkuDetails: List<SkuDetails>

enum class connectStatusTypes {
waiting, connected, fail, disconnected
}

var connectStatus = connectStatusTypes.waiting

/**
* 구글에 설정한 구독 상품 아이디와 일치 하지 않으면 오류를 발생 시킴.
* 21.04.20 이번에는 1회성 구매로 변경 210414_monthly_bill_999, 210420_monthly_bill
*/
var punchName = "220302_bill_1month_999"
var punchNameInapp = "210420_monthly_bill"
var payType = BillingClient.SkuType.SUBS
var option: SharedPreferences
var editor: SharedPreferences.Editor

init {
option = mActivity.getSharedPreferences("option", Context.MODE_PRIVATE)
editor = option.edit()
mBillingClient = BillingClient.newBuilder(mActivity)
.setListener(this)
.enablePendingPurchases()
.build()
mBillingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
Log.e(TAG, "respCode=" + billingResult.responseCode)
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
connectStatus = connectStatusTypes.connected
Log.e(TAG, "connected...")
purchaseAsync()
} else {
connectStatus = connectStatusTypes.fail
Log.i(TAG, "connected... fail ")
}
}

override fun onBillingServiceDisconnected() {
connectStatus = connectStatusTypes.disconnected
Log.i(TAG, "disconnected ")
}
})
}

/**
* 정기 결재 소모 여부를 수신 : 21.04.20 1회성 구매의 경우는 결재하면 끝임.
* @param billingResult
* @param purchaseToken
*/
override fun onConsumeResponse(billingResult: BillingResult, purchaseToken: String) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
Log.i(TAG, "사용끝 + $purchaseToken")
return
} else {
Log.i(TAG, "소모에 실패 " + billingResult.responseCode + " 대상 상품 " + purchaseToken)
return
}
}

fun purchase(skuDetails: SkuDetails?): Int {
val flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails!!)
.build()
return mBillingClient.launchBillingFlow(mActivity, flowParams).responseCode
}

fun purchaseAsync() {
Log.e(TAG, "--------------------------------------------------------------")
mBillingClient.queryPurchasesAsync(payType) { billingResult, list ->
Log.e(TAG, "onQueryPurchasesResponse=" + billingResult.responseCode)
val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
if (list.size < 1) {
editor = option.edit()
editor.putBoolean("isBill", false)
editor.commit()
Log.e(TAG, "getData=" + list.size)
} else {
for (purchase in list) {
Log.e(TAG, "getPurchaseToken=" + purchase.purchaseToken)
for (str in purchase.skus) {
Log.e(TAG, "getSkus=$str")
}
val now = Date()
now.time = purchase.purchaseTime
Log.e(TAG, "getPurchaseTime=" + sdf.format(now))
Log.e(TAG, "getQuantity=" + purchase.quantity)
Log.e(TAG, "getSignature=" + purchase.signature)
Log.e(TAG, "isAutoRenewing=" + purchase.isAutoRenewing)
Log.e(TAG, "getPurchaseState=" + purchase.purchaseState)
editor = option.edit()
editor.putBoolean("isBill", purchase.isAutoRenewing)
editor.commit()
}
}
Log.e(TAG, "--------------------------------------------------------------")
}
}

val skuDetailList: Unit
get() {
val skuIdList: MutableList<String> = ArrayList()
skuIdList.add(punchName)
val params = SkuDetailsParams.newBuilder()
params.setSkusList(skuIdList).setType(payType)
mBillingClient.querySkuDetailsAsync(
params.build(),
SkuDetailsResponseListener { billingResult, skuDetailsList ->
if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
Log.i(TAG, "detail respCode=" + billingResult.responseCode)
return@SkuDetailsResponseListener
}
if (skuDetailsList == null) {
KakaoToast.makeToast(
mActivity,
mActivity.getString(R.string.msgNotInfo),
Toast.LENGTH_LONG
).show()
return@SkuDetailsResponseListener
}
Log.i(TAG, "listCount=" + skuDetailsList.size)
for (skuDetails in skuDetailsList) {
Log.i(TAG, """
${skuDetails.sku}
${skuDetails.title}
${skuDetails.price}
${skuDetails.description}
${skuDetails.freeTrialPeriod}
${skuDetails.iconUrl}
${skuDetails.introductoryPrice}
${skuDetails.introductoryPriceAmountMicros}
${skuDetails.originalPrice}
${skuDetails.priceCurrencyCode}
""".trimIndent()
)
}
purchase(skuDetailsList[0])
})
}

/**
* @param billingResult
* @param purchases
*/
override fun onPurchasesUpdated(billingResult: BillingResult, purchases: List<Purchase>?) {
if (billingResult == null) {
Log.wtf(TAG, "onPurchasesUpdated: null BillingResult")
return
}
val responseCode = billingResult.responseCode
val debugMessage = billingResult.debugMessage
Log.d(TAG, "onPurchasesUpdated: ${responseCode} ${debugMessage}")
when (responseCode) {
BillingClient.BillingResponseCode.OK -> if (purchases == null) {
Log.d(TAG, "onPurchasesUpdated: null purchase list")
processPurchases(null)
} else {
processPurchases(purchases)
}
BillingClient.BillingResponseCode.USER_CANCELED -> Log.i(
TAG,
"onPurchasesUpdated: User canceled the purchase"
)
BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED -> Log.i(
TAG,
"onPurchasesUpdated: The user already owns this item"
)
BillingClient.BillingResponseCode.DEVELOPER_ERROR -> Log.e(
TAG, "onPurchasesUpdated: Developer error means that Google Play " +
"does not recognize the configuration. If you are just getting started, " +
"make sure you have configured the application correctly in the " +
"Google Play Console. The SKU product ID must match and the APK you " +
"are using must be signed with release keys."
)
}
}

private fun processPurchases(purchasesList: List<Purchase>?) {
if (purchasesList != null) {
Log.d(TAG, "processPurchases: " + purchasesList.size + " purchase(s)")
} else {
Log.d(TAG, "processPurchases: with no purchases")
}
if (isUnchangedPurchaseList(purchasesList)) {
Log.d(TAG, "processPurchases: Purchase list has not changed")
return
}
}

/**
* subs 의 경우는 아래와 같이 구매확인을 해 주어야 됨.
* @param purchase
*/
fun confirmPerchase(purchase: Purchase) {
//PURCHASED
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
if (!purchase.isAcknowledged) {
val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build()
mBillingClient.acknowledgePurchase(acknowledgePurchaseParams) { billingResult ->
Log.e(TAG, "getResponseCode=" + billingResult.responseCode)
editor.putBoolean("isBill", true)
editor.commit()
}
}
} else if (purchase.purchaseState == Purchase.PurchaseState.PENDING) {
//구매 유예
Log.e(TAG, "//구매 유예")
} else {
//구매확정 취소됨(기타 다양한 사유...)
Log.e(TAG, "//구매확정 취소됨(기타 다양한 사유...)")
}
}

private fun isUnchangedPurchaseList(purchasesList: List<Purchase>?): Boolean {
for (purchase in purchasesList!!) {
confirmPerchase(purchase)
}
return false
}
}

이전에 포스팅했던 java 코드와 비교를 해 보면 코드가 많이 간소화되었다는 것을 알 수 있습니다.  호출해서 사용하는 코드는 github의 코드를 참고해 보세요. 


 


결제 테스트


결제 진행에 대한 테스트는 꼭 playstore에 게시한 이후에 진행하여야 합니다.  저는 내부 테스트로 게시한 이후 진행 하고 있습니다. 그리고  정기결제 항목을 처음 등록한 경우에는 해당 결제 항목이 사용이 될 수 있으려면 24시간 이상 걸리는 경우가 있으므로 미리 정기결제 항목을 등록해 두고 앱을 만들어 가는 것이 시간 활용에 도움이 됩니다. 


 


결제 테스트



 


이상으로 Java 코드의 소스를 Kotlin으로 변환을 해 보았습니다.


 


전체 코드 보기


전체소스코드는 아래 링크를 참고하세요.


https://github.com/nari4169/daycnt415_kotlin



 


GitHub - nari4169/daycnt415_kotlin


Contribute to nari4169/daycnt415_kotlin development by creating an account on GitHub.


github.com




 





오늘의 이야기

정기결제


앱에 결제 기능을 다는 이야기는 이전 포스팅에 있습니다. 이번에는 혜택을 주는 방법에 대한 이야기를 적어 봅니다. 


먼저 이전에 등록해서 운영하던 경우를 기준으로 작성하고 있음을 밝혀 둡니다.  이전에 만들었던 앱에 매월 정기 결제를 통해 광고를 제거하는 옵션을 달았던 적이 있습니다. 


 


정기 결제(구독)이 등록된 정보



 


그중에서 현재 운영 중인 구독 보기를 선택합니다.


 


혜택 추가 하기



 


혜택 추가


이제 혜택 추가를 해 보겠습니다.


 


혜택 추가



혜택 추가 하기에는 신규 고객을 선택하는 경우와 이전 사용자를 선택 하는 경우, 그 외 개발자의 임의 지정을 선택할 수 있을 듯합니다.  기존 고객을 위한 프로모션을 하는 경우도 있겠지만, 제가 배포한 앱은 아직 사용자가 없기 때문에 신규 고객을 대상으로 한 혜택 추가를 해 보겠습니다.


자격기준



저 선택 사항 아래 탱크를 달도록 되어 있는 데, 일단은 무시해 보겠습니다.


저장해 보기



 


그냥 저장 버튼을 눌렀더니 아래와 같이 단계를 추가하도록 가이드를 하고 있습니다.  단계는 2개까지 등록이 될 것 같습니다.


 


단계 추가



 


신규 고객에게 혜택을 등록하는 것으로 정했으니, 단계 추가에서는 무료 체험판이라고 선택을 하는 것이 맞을 듯합니다. 


 



  • 무료체험판  : 지정하는 기간 동안 무료 체험을 제공합니다.

  • 1회 결제      : 지정하는 기간 동안 1회 결제에 한하여 정액, 할인율, 일정금액 등으로 가격을 조정해 줄 수 있습니다. 

  • 할인된 반복 결제 : 지정하는 결제 기간 동안 정액, 할인율, 일정금액 등으로 가격을 조정해 줄 수 있습니다.


단계 옵션



저는 신규 고객에게 3개월 동안 무료 체험을 할 수 있도록 하고자 합니다. 그랬더니 판매가 되는 국가별로 가격표가 노출이 됩니다. 그리고 적용을 눌러보겠습니다.


무료체험 단계



적용하고 활성화를 시켜 봅니다. 


 


혜택 활성화 예시



결제 진행


내부 테스트 계정으로 앱을 설치하고 테스트를 진행해 봅니다. 


 


인앱 결제 혜택 추가 후 결제 처리 화면



 


이상으로 정기 구독자를 위한 혜택 추가 하는 방법에 대한 이야기를 추가해 봅니다.


 





오늘의 이야기



#스치니1000프로젝트 #재미 #행운기원 #Compose #Firebase

🎯 야 너 토요일마다 로또 확인하냐?
나도 맨날 "혹시나~" 하면서 봤거든 ㅋㅋ

근데 이제는 그냥 안 해
AI한테 맡겼어 🤖✨

그것도 구글 Gemini로다가!

그래서 앱 하나 만들었지
👉 "로또 예상번호 by Gemini" 🎱

AI가 분석해서 번호 딱! 뽑아줌
그냥 보고 참고만 하면 됨

재미로 해도 좋고…
혹시 모르는 거잖아? 😏


https://play.google.com/store/apps/details?id=com.billcorea.gptlotto1127




오늘의 이야기

AVD


Android Virtual Device는 Android Studio에서 앱을 개발하고 테스트하는 동안 실물 기기를 대신해서 테스트해 볼 수 있도록 지원하는 장치(?)입니다. 가상의 휴대폰이 되는 것입니다.

https://developer.android.com/studio/run/managing-avds?hl=ko







가상 기기 만들기 및 관리하기  |  Android 개발자  |  Android Developers


Android 스튜디오에서 가상 기기를 만들고 관리하는 방법에 관해 알아보세요.


developer.android.com





앱을 개발 하다 보면 카메라 촬영을 통해서 이미지를 사용하는 앱들도 구현하게 됩니다. 이런 경우 AVD에서 직접 촬영한 이미지를 볼 수 있도록 하면 좋을 것 같습니다. (이미 알고 계시는 경우도 있기는 하겠지만...)


설정해 보기


Android Studio 을 실행하고 AVD을 하나 실행해 보겠습니다. Android Studio Dolphin | 2021.3.1 Patch 1을 기준으로 설명해 드립니다. 이전 버전에서도 지원이 되기는 하니 참고하시면 될 것 같습니다.

먼저 오른쪽 바에서 Device Manager 을 열어서 이미 만들어 놓은 가상 머신 하나를 선택하고 연필 모양의 수정 버튼을 크릭 합니다.

device manager



이제 설정 화면이 나오면 왼쪽 하단에 있는 show Advanced Settings을 클릭해서 열어 봅니다.

설정 화면


Camera 설정을 보면 Front 카메라에는 없지만, Back 카메라에는 VirtualScene 라는 옵션이 있습니다. 이 옵션이 선택되도록 수정하고 저장을 클릭합니다.

선택 사항은 다음과 같습니다.



  • None : 카메라가 없는 선택

  • VirtualScene : 시뮬레이션 환경에서 가상 카메라 사용

  • Emulated : 시뮬레이션 카메라 사용

  • Device : 호스트 컴퓨터 웹캠 또는 내장 카메라 사용



가상 카메라는 마치 카메라가 있는 것 처럼 가상공간을 촬영하는 시뮬레이션을 해 준다는 의미입니다. 이제 앱을 실행해서 어떤 동작을 하는지 살펴보겠습니다.


이미지 적용 방법



  • avd 을 실행합니다. 다음 오른쪽 제일 하단에 있는 햄버거 메뉴를 클릭하여 설정 화면으로 들어갑니다. camera 메뉴를 클릭합니다.

  • Virtual Sence Images 에는 wall과 table 두 개가 있는데 wall 은 벽면에 표시되는 이미지 이고 table 은 테이블 위에 표시 되는 이미지 을 설정 하는 부분입니다. 이미지 열기를 선택하여 보여 주고 싶은 이미지를 선택 하여 적용합니다.

  • 주의할 것은 이미지 촬영 시 휴대폰이 세로가 되도록 해서 촬영된 이미지를 적용해 주세요. 그래야 아래 예시처럼 벽면에 정상적으로 된 이미지를 보여 줄 수 있습니다.

이미지 적용 순서




  • 적용된 이미지는 가상공간에서 주방을 찾아가면 벽면과 테이블에 각각 선택한 이미지가 보입니다.





카메라 영상 샘플


이제 AVD에서 카메라를 열어 선택한 이미지가 나오는 곳을 찾아보도록 하겠습니다.


가상공간 탐색


AVD의 카메라에 보이는 가상공간을 탐색해 보겠습니다. AVD의 하단에 보면 alt 키를 이용하여 카메라를 이동할 수 있다는 표시가 나옵니다.

alt 키와 w (전진), s (후진), a (왼쪽으로), d (오른쪽으로) 등의 방향키를 이용하여 카메라의 시선을 옮겨 봅니다. 잘 찾아보면 거실에 있는 TV을 비추고 있던 카메라 안에 강아지 모양도 보이고, 주방으로 가는 공간도 보일 겁니다. 위 동영상 예시와 같이 말입니다.

alt 키를 누른 상태에서 마우스를 드래그해 보면 카메라의 앵글이 움직이는 것도 보실 수 있습니다.

AVD 카메라 모습



이와 같이 해서 주방 뒤에 있는 다이닝 룸(?)을 찾아가셨다면 벽면에 비치는 사진과 테이블 위에 비치는 사진을 보실 수 있습니다. 이렇게 해서 AVD에서 이미지 보여 주기 방법에 대하여 알아보았습니다.





오늘의 이야기


#스하리1000명프로젝트

스치니들!
내가 만든 이 앱은, 내 폰에 오는 알림 중에서 중요한 키워드가 있는 경우
등록해둔 친구에게 자동으로 전달해주는 앱이야 📲

예를 들어, 카드 결제 알림을 와이프나 자녀에게 보내주거나
이번 달 지출을 달력처럼 확인할 수도 있어!

앱을 함께 쓰려면 친구도 설치 & 로그인해줘야 해.
그래야 친구 목록에서 서로 선택할 수 있으니까~
서로 써보고 불편한 점 있으면 알려줘 🙏

👉 https://play.google.com/store/apps/details?id=com.nari.notify2kakao





오늘의 이야기

<주문이요 앱 >은(는) 「개인정보 보호법」 제30조에 따라 정보주체의 개인정보를 보호하고 이와 관련한 고충을 신속하고 원활하게 처리할 수 있도록 하기 위하여 다음과 같이 개인정보 처리방침을 수립·공개합니다.
 
○ 이 개인정보처리방침은 2022년 12월 21부터 적용됩니다.
 
제1조(개인정보의 처리 목적)

<주문이요 앱 >은(는) 다음의 목적을 위하여 개인정보를 처리합니다. 처리하고 있는 개인정보는 다음의 목적 이외의 용도로는 이용되지 않으며 이용 목적이 변경되는 경우에는 「개인정보 보호법」 제18조에 따라 별도의 동의를 받는 등 필요한 조치를 이행할 예정입니다.

 
○ 제공되는 앱의 위치 정보를 항상 확인하고 사용자가 지정한 주된 휴대장치로 위치 정보를 전달하여, 앱이 설치된 보조 휴대 장치의 분실을 방지하기 위한 용도로만 사용됩니다.
○ 앱의 정당한 사용자 여부를 확인하기 위한 이메일 주소 정보를 확인하고 있으며, 해당 용도 이외의 정보 사용은 하지 않습니다.
 
제2조(개인정보의 처리 및 보유 기간)

① <주문이요 앱 >은(는) 법령에 따른 개인정보 보유·이용기간 또는 정보주체로부터 개인정보를 수집 시에 동의받은 개인정보 보유·이용기간 내에서 개인정보를 처리·보유합니다.

② 각각의 개인정보 처리 및 보유 기간은 다음과 같습니다.

  • 휴대장치의 위치 정보 : 앱 설치된 휴대장치 분실을 방지하기 위한 위치 확인 
  • 이메일 주소 / 비밀번호 : 앱 사용자 인증 목적
  • <앱 사용 단말의 위치 정보 확인>과 관련한 개인정보는 수집. 이용에 관한 동의일로부터 <이 앱의 사용기간 동안>까지 위 이용목적을 위하여 보유. 이용됩니다.
  • 보유근거 : 이 앱의 사용자의 동의에 의거하여

제3조(개인정보의 제삼자 제공)

① <주문이요 앱 >은(는) 개인정보를 제1조(개인정보의 처리 목적)에서 명시한 범위 내에서만 처리하며, 정보주체의 동의, 법률의 특별한 규정 등 「개인정보 보호법」 제17조 및 제18조에 해당하는 경우에만 개인정보를 제삼자에게 제공합니다.
② < 주문이요 앱 >은(는) 개인정보를 제3자에게 제공하지 않습니다.
 
제4조(개인정보처리 위탁)

① <주문이요 앱 >은(는) 원활한 개인정보 업무처리를 위하여 다음과 같이 개인정보 처리업무를 위탁하지 않습니다.
②  위탁업무의 내용이나 수탁자가 변경될 경우에는 지체 없이 본 개인정보 처리방침을 통하여 공개하도록 하겠습니다.
 
제5조(정보주체와 법정대리인의 권리·의무 및 그 행사방법)

① 정보주체는 주문이요 앱에 대해 언제든지 개인정보 열람·정정·삭제·처리정지 요구 등의 권리를 행사할 수 있습니다.
② 제1항에 따른 권리 행사는 주문이요 앱에 대해 「개인정보 보호법」 시행령 제41조 제1항에 따라 서면, 전자우편, 모사전송(FAX) 등을 통하여하실 수 있으며 주문이요 앱은(는) 이에 대해 지체 없이 조치하겠습니다.
③ 제1항에 따른 권리 행사는 정보주체의 법정대리인이나 위임을 받은 자 등 대리인을 통하여 하실 수 있습니다. 이 경우 "개인정보 처리 방법에 관한 고시(제2020-7호)" 별지 제11호 서식에 따른 위임장을 제출하셔야 합니다.
④ 개인정보 열람 및 처리정지 요구는 「개인정보 보호법」 제35조 제4항, 제37조 제2항에 의하여 정보주체의 권리가 제한될 수 있습니다.
⑤ 개인정보의 정정 및 삭제 요구는 다른 법령에서 그 개인정보가 수집 대상으로 명시되어 있는 경우에는 그 삭제를 요구할 수 없습니다.
⑥ 주문이요 앱은(는) 정보주체 권리에 따른 열람의 요구, 정정·삭제의 요구, 처리정지의 요구 시 열람 등 요구를 한 자가 본인이거나 정당한 대리인인지를 확인합니다.
 
제6조(처리하는 개인정보의 항목 작성)

①<주문이요 앱 >은(는) 다음의 개인정보 항목을 처리하고 있습니다.

  • 휴대장치의 위치 정보 : 앱 설치된 휴대장치 분실을 방지하기 위한 위치 확인 
  • 이메일 주소 / 비밀번호 : 앱 사용자 인증 목적

제7조(개인정보의 파기)

① <주문이요 앱 > 은(는) 개인정보 보유기간의 경과, 처리목적 달성 등 개인정보가 불필요하게 되었을 때에는 지체 없이 해당 개인정보를 파기합니다.

② 정보주체로부터 동의받은 개인정보 보유기간이 경과하거나 처리목적이 달성되었음에도 불구하고 다른 법령에 따라 개인정보를 계속 보존하여야 하는 경우에는, 해당 개인정보를 별도의 데이터베이스(DB)로 옮기거나 보관장소를 달리하여 보존합니다.
1. 법령 근거 :
2. 보존하는 개인정보 항목 : 없음

③ 개인정보 파기의 절차 및 방법은 다음과 같습니다.
1. 파기절차
<주문이요 앱 > 은(는) 파기 사유가 발생한 개인정보를 선정하고, < 주문이요 앱 >의 개인정보 보호책임자의 승인을 받아 개인정보를 파기합니다.



제8조(개인정보의 안전성 확보 조치)

<주문이요 앱 >은(는) 개인정보의 안전성 확보를 위해 다음과 같은 조치를 취하고 있습니다.

1. 내부관리계획의 수립 및 시행
개인정보의 안전한 처리를 위하여 내부관리계획을 수립하고 시행하고 있습니다.

2. 개인정보에 대한 접근 제한
개인정보를 처리하는 데이터베이스시스템에 대한 접근권한의 부여, 변경, 말소를 통하여 개인정보에 대한 접근통제를 위하여 필요한 조치를 하고 있으며 침입차단시스템을 이용하여 외부로부터의 무단 접근을 통제하고 있습니다.

3. 비인가자에 대한 출입 통제
개인정보를 보관하고 있는 물리적 보관 장소를 별도로 두고 이에 대해 출입통제 절차를 수립, 운영하고 있습니다.

제9조(개인정보 자동 수집 장치의 설치•운영 및 거부에 관한 사항)
<주문이요 앱 > 은(는) 정보주체의 이용정보를 저장하고 수시로 불러오는 '쿠키(cookie)'를 사용하지 않습니다.
 
제10조 (개인정보 보호책임자)
① <주문이요 앱 > 은(는) 개인정보 처리에 관한 업무를 총괄해서 책임지고, 개인정보 처리와 관련한 정보주체의 불만처리 및 피해구제 등을 위하여 아래와 같이 개인정보 보호책임자를 지정하고 있습니다.

  • ▶ 개인정보 보호책임자
  • 성명 :Nari Kang
  • 직책 :manager
  • 직급 :manager
  • 연락처 : help@billcorea.com

※ 개인정보 보호 담당부서로 연결됩니다.

  • ▶ 개인정보 보호 담당부서
  • 부서명 :manager
  • 담당자 :Nari Kang
  • 연락처 : help@billcorea.com

② 정보주체께서는 주문이요 앱의 서비스(또는 사업)를 이용하시면서 발생한 모든 개인정보 보호 관련 문의, 불만처리, 피해구제 등에 관한 사항을 개인정보 보호책임자 및 담당부서로 문의하실 수 있습니다. 주문이요 앱 은(는) 정보주체의 문의에 대해 지체 없이 답변 및 처리해드릴 것입니다.
 
제11조(개인정보 열람청구)
정보주체는 <개인정보 보호법> 제35조에 따른 개인정보의 열람 청구를 아래의 부서에 할 수 있습니다.
<주문이요 앱 >은(는) 정보주체의 개인정보 열람청구가 신속하게 처리되도록 노력하겠습니다.

  • ▶ 개인정보 열람청구 접수·처리 부서
  • 부서명 : manager
  • 담당자 : Nari Kang
  • 연락처 : help@billcorea.com

제12조(권익침해 구제방법)

정보주체는 개인정보침해로 인한 구제를 받기 위하여 개인정보분쟁조정위원회, 한국인터넷진흥원 개인정보침해신고센터 등에 분쟁해결이나 상담 등을 신청할 수 있습니다. 이 밖에 기타 개인정보침해의 신고, 상담에 대하여는 아래의 기관에 문의하시기 바랍니다.

1. 개인정보분쟁조정위원회 : (국번 없이) 1833-6972 (www.kopico.go.kr)
2. 개인정보침해신고센터 : (국번없이) 118 (privacy.kisa.or.kr)
3. 대검찰청 : (국번없이) 1301 (www.spo.go.kr)
4. 경찰청 : (국번없이) 182 (ecrm.cyber.go.kr)

「개인정보보호법」제35조(개인정보의 열람), 제36조(개인정보의 정정·삭제), 제37조(개인정보의 처리정지 등)의 규정에 의한 요구에 대하여 공공기관의 장이 행한 처분 또는 부작위로 인하여 권리 또는 이익의 침해를 받은 자는 행정심판법이 정하는 바에 따라 행정심판을 청구할 수 있습니다.

※ 행정심판에 대해 자세한 사항은 중앙행정심판위원회(www.simpan.go.kr) 홈페이지를 참고하시기 바랍니다.

제13조(개인정보 처리방침 변경)
 
① 이 개인정보처리방침은 2022년  12월 21부터 적용됩니다.





오늘의 이야기

SafetyNet Attenstation API


https://developer.android.com/training/safetynet/attestation



 


SafetyNet Attestation API  |  Android Developers


SafetyNet Attestation API는 앱을 실행하는 기기가 Android 호환성 테스트를 충족하는지 확인하는 서비스를 제공합니다.


developer.android.com




SafetyNet Attestation API는 앱 개발자가 앱을 실행하는 Android 기기를 평가할 수 있는 악용 방지 API입니다. 이 API는 서버가 정품 Android 기기에서 실행 중인 정품 앱과 상호작용하고 있는지 여부를 확인하는 악용 감지 시스템의 일부로 사용해야 합니다.


 


playstore에 배포되고 있는 앱의 정당성을 증명하는 기능이라고 볼 수 있습니다. 이걸 하는 이유는 앱의 안전성을 확보하여 Firebase 등의 사용 시에 불량 사용자가 접근하여 데이터를 가공하는 일을 방지할 수 있다고 볼 수 있습니다.


 


그런데, 언제 부터 인가 이 것을 Play Integrity API로 이전해야 한다는 메일을 받았습니다.  가이드의 내용으로는 2023년 6월 30일까지 이전을 해야 한다고 되어 있습니다.  아직 유예기간이 6개월가량 남아 있기는 하지만 언제까지 그대로 유지할 수는 없는 일입니다.  


 


API 이전 안내 메일



Play Integrity API


https://developer.android.com/google/play/integrity



 


Play Integrity API  |  Google Play  |  Android Developers


Play Integrity API를 사용하면 잠재적으로 위험하고 허위일 가능성이 있는 상호작용으로부터 앱과 게임을 보호하고, 적절한 조치를 취해 사기, 속임수, 무단 액세스와 같은 공격 및 악용을 줄일 수


developer.android.com




기능은 같은 내용을 말하고 있습니다.   


 


이전 방법


앱 gradle 파일을 열어서 보면 어느 순간 부터 아래 그림과 같이 빨간 줄이 나타납니다. 이제 더 사용할 수 없다는 것을 알려 주기 위해서 표시가 되는 것입니다.


앱 gradle



 


이전과 관련된 개발자 문서는 아래 링크를 참고 했습니다.


https://developer.android.com/google/play/integrity/migrate?hl=ko 



 


SafetyNet Attestation API에서 이전  |  Google Play  |  Android Developers


SafetyNet Attestation API에서 이전 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 이미 신뢰할 수 있는 서버를 사용하여 응답을 확인하고 있다면 SafetyNet Attestation


developer.android.com




 


이제 이전을 시작해 보겠습니다.  먼저 playstore에 이미 등록된 프로젝트를 구현해 보겠습니다.  safetynet 을 사용하고 있다면 이미 playstore 에 게시된 앱이라고 생각하고 시작합니다.  


 


1. playstore console에서 설정의 앱 무결성을 선택해 들어갑니다. 


2. Google Cloud Project와 연결해 줍니다. 이전에 사용했던 프로젝트는 존재한다고 가정합니다. 


앱 무결성 설정



3. 다음은 앱의 gradle 파일을 수정해 보겠습니다.


이전에 사용하던 safetynet을 사용중지 하고 playintegrity을 추가합니다.


//    implementation 'com.google.firebase:firebase-appcheck-safetynet:16.1.0'
// Add the dependency for the App Check library
implementation 'com.google.firebase:firebase-appcheck-playintegrity'

mainActivity에서 앱 체크를 위해서 넣었던 부분을 수정해 봅니다.


        FirebaseApp.initializeApp(/*context=*/this)
val firebaseAppCheck = FirebaseAppCheck.getInstance()
firebaseAppCheck.installAppCheckProviderFactory(
if (BuildConfig.DEBUG_MODE) {
Log.e(TAG, "DebugAppCheckProviderFactory ...")
DebugAppCheckProviderFactory.getInstance() // AVD 에서 적용시
//SafetyNetAppCheckProviderFactory.getInstance() // 폰에서 적용시
} else {
Log.e(TAG, "SafetyNetAppCheckProviderFactory ...")
// SafetyNetAppCheckProviderFactory.getInstance() // 폰에서 적용시
PlayIntegrityAppCheckProviderFactory.getInstance()
}
)

 


그다음은 firebase의 콘솔에서 앱 check 에 Play Integrity을 추가해 줍니다.


주의할 부분은 play integrity 등록 시 앱의 인증키 을 확인할 필요가 있습니다. 아시겠지만, play store에 등록된 sha256 키는 google에서 관리되기 때문에 playstore console의 앱 인증키를 찾아서 등록해 주어야 합니다. 


 


인증키 등록해 주기



이제 작업은 마무리되었습니다.  


 


이제 playstore에 수정된 앱을 등록해 보겠습니다.


 





오늘의 이야기


#스하리1000명프로젝트,
Às vezes é difícil conversar com trabalhadores estrangeiros, certo?
Fiz um aplicativo simples que ajuda! Você escreve na sua língua e os outros veem na deles.
Ele é traduzido automaticamente com base nas configurações.
Muito útil para bate-papos fáceis. Dê uma olhada quando tiver uma chance!
https://play.google.com/store/apps/details?id=com.billcoreatech.multichat416




오늘의 이야기

3일 차


3번째 날 다녔던 곳들에 대한 이야기입니다.  남는 게 시간이었던 날들이라... 3일 차가 되었습니다.  군데군데 가보지 못했던 곳을 가 보기로 했습니다. 먼저 생각난 것은 모노레일인데요. 울릉도에서 탈 수 있는 모노레일 2곳 중 하나가 있는 남서 모노레일을 찾아보기로 했습니다.  


 


저동에서 버스를 타고 남양에서 내렸습니다. 길을 걷다 보니 우산국 박물관 표지가 보입니다.  예전 울릉도에 있었던 우산국이라는 나라를 아시나요? 그곳에 대한 이야기들이 있는 박물관입니다.  새롭기는 합니다. 역사책에서나 보았던 것들을 이곳에서 보게 되니 말입니다.  그리고 그 옆에서 모노레일을 타고 일몰 전망대에 오릅니다.  추운 날이라 입장객이 없네요. 혼자 타고 올랐습니다.  박물관 2층에 가면 무료로 탈 수 있습니다.


남서 일몰 전망대 풍경



 


이곳의 풍광은 일몰때 다시 와서 봐야 할 까 봅니다. 그럴 수 있는 날이 있을지는 모르겠지만요. 아무튼 저기 바위가 하나 보입니다. 잘 보시면 뭔 가 보입니다.


남근이 보이시나요?



 


다음 버스까지 1시간가량 간격이 생겨서 남서 고분을 찾아보기로 했습니다. 무작정이요... 2km가량 산 쪽으로 갔는 데, 찾을 수 없었습니다. 정확한 표지판도 없었고, 그래서 초록창 검색을 해 보니 민가를 지나서 가야 한다는 말이 있고 비추하는 리뷰가 있어서 그곳 근처가 고분이라는 것만 알고 왔습니다. 


남서고분을 찾아 헤매다가



 


내려서 다음 목적지를 찾아 버스를 탔습니다. 이 때까지만 해도 원로 가수가 사신다는 현포에서 무언가를 찾아볼 요량이었습니다.  현포에서 내려 점심을 먹고 앞에 있는 카페에서 차를 마시며 다음 행선지를 찾아보았는 데, 이때 울릉군청 홈페이지에서 관음도 방문이 가능하다는 것을 알게 되었습니다.


울릉도의 카페라떼 는 다를까요?



 


맛난 라떼를 주신 카페 사진입니다. 시골 스러운 울릉도에서는 특별한 카페였습니다.  다른 풍광이 좋은 카페도 있기는 하겠지만. 현포에 가신다면 한번 들러 보세요. 바다를 보면 커피 한잔을 할 수 있습니다.


현포리 카페



 


관음도에 왔습니다. 이제 저 다리를 건너 관음도를 갈 겁니다.  첫날 왔을 때는 풍랑 때문에 갈 수 없었던 곳입니다. 이날은 다행히도 날씨가 좋아 통제를 하지 않네요.  이런 정보는 울릉군청 홈페이지에 실시간으로 나오니 꼭 참고하세요.


관음도 다리



 


다리에 오르기 전에 관음도에는 무엇이 있을까요?  코스 정보를 미리 봅니다. 


관음도 안내문



 


다리를 건너다가 멀리 죽도가 보이네요. 저 섬에는 가 볼 생각을 미쳐 하지 못했네요. 다음에 가게 되면 꼭 한번 가 보고 싶습니다.  다만, 겨울에는 배편이 없을 것 같기도 합니다.


다리에 가운데서 보는 죽도



 


다리 가운데 서서 주변 풍광을 둘러보았습니다.


다리위에서 보는 풍경



 


동백나무가 많은 숲을 지나 오르니 이런 의자가 있네요, 풀꽃 형상을 하고 있어서 사진을 찍어 보기에는 좋을 듯합니다. 둘이서 말이에요.


풀꽃 밴치



 


관음도 코스 중 하나에서 주변을 둘러보았습니다. 겨울 하늘과 바다가 마치 하나가 된 것 같이 파랐게 보입니다. 햇살이 좋아 하늘이 더 예쁘게 보입니다.


관음도 A 코스 풍경



 


저기에 삼선암이 보입니다.  사진으로는 구분이 잘 안 되기는 하는데,  위치를 보니 맞는 것 같습니다.


관음도 에서 바라 보는 풍광



 


어선 2척이 길을 나섭니다. 이 추운 겨울날에도 출어를 하는 것일까요?


출어를 하는 걸까요?



 


이번에는 관음도 오른쪽에서 바라보는 풍경입니다. 저기 죽도도 보이고요. 파란 하늘이 예쁘게 비칩니다.


관음도 B 코스 풍경



 


 


가까이에서 보이는 죽도입니다.  zoom으로 당겨 보면 해변 동굴도 보이기는 합니다.  날 좋은 여름이었다면 유람선을 타고 둘러보면 그것도 나름 좋을 것 같습니다. 하지만 이 때는 그 생각을 하지 못 했습니다.  정리를 하다 보니 그런 생각이 듭니다.


죽도



 


이제 다음 버스 시간에 맞게 내려가 볼 생각입니다.  50분 동안에 둘러보기에는 여유가 없습니다.  풍광을 즐길 생각이라면 그다음 버스 시간에 맞게 여유롭게 둘러보는 걸 추천드립니다.  차편이 있다면 시간에 구애는 없을 것 같습니다. 


저기 움막에서 잠시 쉬어 가라네요



 


이제 저기 보이는 저동 숙소를 향해 가 보겠습니다. 


저기 멀리 저동이 보입니다



 


 


조금 있으면 저기 보이는 일주도로를 따라 버스가 올 겁니다. 그 버스가 오기 전에 내려가 보아야 합니다.  가는 길에 동백나무 숲에서 사진도 한 장 찍어야 할 것 같고요. 올라오는 길에 찍어 보고 싶었던 다리 위에서의 사진도 한 장 남겨야 할 듯합니다. 


이제 나가 볼까요



 


내려가는 계단에 올라올 때와 다르게 느껴집니다. 내려가는 길은 조심하게 내려가세요. 저기 동백나무 옆에서 사진도 한장 찍고 내려가세요.


집으로 가는 계단 이에요



 


어느덧 저녁을 먹고 나니 보름달이 휘영청 하게 떠 올랐습니다. (마침 이날이 보름이네요.)  저기 저동항 촛대 바위가 있고 포구에 켜져 있는 불빛 들과 잘 어우러집니다. 


저동 촛대 바위와 보름달



 


 


이제 3일간의 울릉도 여행을 마치고 집으로 가야 합니다.  며칠 동안의 휴식에서 얻은 힘으로 다시 힘을 내어 삶을 걸어 봅니다.


 


3일간의 여행에서 느낀 건 여행은 사전 준비를 해야 합니다.  주변 관광지 정보, 교통편, 날씨의 변화에 따른 일정 정보 등등


 


이 여행은 무작정 가보고 싶다는 의지 하나로 출발하는 여행이었고,  사전 정보는 하나도 없이 출발한 여행입니다. 그래서 두서없는 여행이 되기도 했지만, 나름 얻어가는 것도 있는 여행이 된 것 같습니다. 


 


겨울철 울릉도를 가신다면 


 


1. 가급적 큰 배편을 이용하세요  저처럼... 그래야 멀미를 덜 합니다.  쾌속선을 타는 건 날씨 좋은 날에만 추천해 드립니다. 풍랑에 출렁거리니까요. 


 


2. 대형 페리는 포항에서만 운행을 하는 듯 하니, 가실 때 코레일 연계 편을 찾아보시면 조금 저렴할 수 도 있습니다.  그리고 23시 50분에 출항하는 배 시간에 맞게 포항역에 내리면 셔틀이 다닙니다. 그럼 시간을 허비하지 않을 수 있습니다.


 


3. 울릉도에서는 대중교통을 이용하는 건 여유로울 때만 추천해 드립니다. 배차간격이 길어요.  버스들은 일주도로를 타고 다니기 때문에 한쪽 방향으로만 일정을 계획하세요.  그리고 중간에 있는 지선들은 봉래폭포나 나리분지를 다녀올 때, 그리고 석포를 갈 때만 중간 기착지를 찾아서 이용하시면 됩니다.


 


4. 렌터카를 이용하신다면 고바위(?) 운전에 자신이 있을 때만 추천드립니다.  아직도 일주도로에 공사 구간이 많고 주요 도로는 고도의 차이가 심하며 편도 1차로 도로 이기 때문에 승용차로 다니는 건 부담이 될 듯합니다. 그래도 편하기는 하겠지만요.  (일주도로 공사는 겨울철에만 편중되어 있을 수 도 있습니다.)


 


5. 숙소는 예약 앱에서 예약을 하셔도 그렇게 나쁠 것 같지는 않습니다. 편차는 있을 수 있지만요. 제가 묵었던 울릉 위드 U는 그렇게 나쁘지 않았습니다. 도심에 있는 모텔들과 비교해서는...(다음에 1인실도 한번 가 보면 어떨지???)


 


 





오늘의 이야기

인앱결제 코드 이전 이야기 이전 버전에서는 java 코드로 구현된 소스 코드를 공유해 보았습니다.   https://billcorea.tistory.com/165   안드로이드 앱 만들기 : 구글 인앱 결제 쉽게 따라하기 (정기결제, 소스공유) 이전 ...