2017년 10월 31일 화요일

[Links] OKR

이전에 좀 보긴했지만
잘정리된 사이트라 보관 차원에서 링크 저장

번역하신분께서 적어두셨듯 (http://infuture.kr/m/1629)
영어로 인해 제대로 보지 못했었는데
아래 링크에서 잘 정리해 주신듯 함.

http://infuture.kr/m/1630

연재니 계속 읽어보고 정리해야겠음.

Android IAP 대충 정리

안드로이드 IAP가 궁금해서 대충 요약함.

https://developer.android.com/google/play/billing/billing_subscriptions.html

[인앱구독]
인앱 결재를 사용하여 구독 서비스 판매 가능.

- 다양한 주기의 구독 결재 판매 가능
- 월간, 연간 구독을 위해 체험 기간 구성 가능
- Google Play Developer API나 Developer Console을 사용하여 구독 관리
- 사용자는 앱내에서 구독 구매
- 구독 갱신, 업그레이드, 다운그레이드 가능
- 구독 결재 연기 가능

구독은 개발자가 지정한 간격으로 자동 반복되는 결재
인앱 상품과 유사한 절차로 구독을 구성하고 게시함. 단 체험기간과 결재 주기 선택이 다름

[구독 아이템 구성]

  • 구매 유형: 항상 Subscription으로 설정
  • 구독 ID: 구독 식별자
  • 게시 상태: 게시 안 됨/게시됨
  • 언어: 구독을 표시하기 위한 기본 언어
  • 제목: 구독 상품의 제목
  • 설명: 사용자에게 구독에 대해 설명하는 세부정보
  • 가격: 반복 구매 시마다 적용되는 기본 구독 가격
  • 반복 구매: 반복 구매 결제 주기
  • 추가적인 통화 가격(자동 채우기 가능)

[구독 가격]
- 사용 가능 통화로 구독 가격 설정.
- 구독 가격은 0원 초과
- 구독 가격을 다르게 설정 가능(ex 월간 요금 vs 할인된 연간 요금)

[사용자 결제]
- 주간, 월간, 3개월 6개월, 연간, 시즌

[수동 갱신]
- 구독 중 추가 구독 시 만료일이 추가 구독 기간을 포함하여 연장 됨.

[구독 업그레이드/다운그레이드]
- 구독을 변경할 경우 기존 구독이 취소되고 새 구독이 생성됨.

[결제 유예]
- 유예기간 동안 컨텐트 구독 및 사용 권한을 가지지만 요금이 처구되지 않음.
- 단 유예할 수 있는 최장기간은 API 호출 당 1년 이고 1년 중 다시 호출하여 유예기간 연장 가능

[무료 평가판]
- 무료 체험 기간을 설정할 수 있음.
- 개발자가 지정한 기간 동안만 체험이 가능하고 기간이 지나면 구독 결재 주기와 가격에 따라 관리되는 구독으로 자동 전환됨.
- 일반적인 구매 절차를 따라 구매하지만 무료 체험기간 동안은 요금이 0.00 임.

[구독 취소]
- App이 아닌 Play Store의 앱의 My Apps 화면에서 구독에 대한 상태 확인, 취소 가능.
- 구독 취소 시 Google Play는 결재 주기에 대해서는 환불하지 않고 결제 주기 종료까지 구독에 액세스 할 수 있음.

[앱 제거]
- 사용자가 구매한 구독을 포함한 앱 제거시 구독 정보를 알려줌.
- App을 제거하여도 구독은 제거되지 않음.

[결제 처리 및 정책]
- Google Payments를 통해서만 판매 가능
- 구독 상품 구매 시 Transaction 요금은 구매와 같이 30%임.
- 매 구독 마다 구매 정보(판매자 주문 번호와 각각 반복 transaction 정보를 제공함)

[Google Play Developer API]
  • 언제든지 특정 구독의 유효성을 원격으로 쿼리
  • 구독 취소
  • 구독의 다음 결제일 유예
  • 구독을 취소하지 않고 구독 결제 대금 환불
  • 구독 환불 및 취소

https://developer.android.com/google/play/billing/billing_integrate.html#Subs

구독 구현

구독에 대한 구매 흐름을 시작하는 것은 상품에 대한 구매 흐름을 시작하는 것과 비슷하지만, 상품 유형을 "subs"로 설정해야 한다는 점이 다릅니다. 인앱 상품의 경우에서와 정확히 똑같이, 액티비티의 onActivityResult 메서드로 구매 결과가 전달됩니다.
Bundle bundle = mService.getBuyIntent(3, "com.example.myapp",
   MY_SKU, "subs", developerPayload);
PendingIntent pendingIntent = bundle.getParcelable(RESPONSE_BUY_INTENT);
if (bundle.getInt(RESPONSE_CODE) == BILLING_RESPONSE_RESULT_OK) {
   // Start purchase flow (this brings up the Google Play UI).
   // Result will be delivered through onActivityResult().
   startIntentSenderForResult(pendingIntent, RC_BUY, new Intent(),
       Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0));
}
활성 구독을 쿼리하려면 이때도 상품 유형 매개변수를 "subs"로 설정하여 getPurchases 메서드를 사용하세요.
Bundle activeSubs = mService.getPurchases(3, "com.example.myapp",
                   "subs", continueToken);
이렇게 호출하면 사용자가 소유한 모든 활성 구독을 포함한 Bundle이 반환됩니다. 구독이 만료되고 갱신하지 않으면 반환되는 Bundle에 더 이상 구독이 표시되지 않습니다.

[인앱 프로모션]
https://developer.android.com/google/play/billing/billing_promotions.html?hl=ko

- 모든 프로모션 코드는 특정한 상품ID(SKU)와 연결됨.
- 프로모션 코드를 입력하면 특정 아이템의 가격을 완불한 것과 같이 처리됨.

[프로모션 코드 생성 및 사용]
- 프로모션 코드는 Google Play Developer console에서 생성
- 각 프로모션 코드는 Developer console의 단일 상품과 연결
- 프로모션 코드는 일반 인앱 결제와 동일한 절차를 거치되 단 돈대신 프로모션 코드를 사용한다는 점이 다름.
- Google Play 스토어 앱에서 코드를 사용할 수 있음.


[앱에서 프로모션 코드 지원]
- 앱이 다시 시작(onResume())될 때마다 getPurchases() 메서드 호출 후 구매내역 확인
- 앱이 IAP를 지원한다면 구매와 유사하게 진행 되므로 별도 처리 필요 없음.
- 앱 실행 중에 Play 스토어 앱에서 프로모션 코드를 사용하는 경우 처리가 필요함.
: com.android.vending.billing.PURCHASES_UPDATED intent 수신을 위한 처리 필요.

(등록)
IntentFilter promoFilter =
    new IntentFilter("com.android.vending.billing.PURCHASES_UPDATED");
registerReceiver(myPromoReceiver, promoFilter);

(취소)
unRegisterReceiver(myPromoReceiver);

[보안 및 디자인]
https://developer.android.com/google/play/billing/billing_best_practices.html?hl=ko

- 잠금 해제된 콘텐츠를 apk 파일에 포함하여 재배포 할 수 있게 하지 말라.
- 코드 난독 처리 필요, proguard 실행, 아래 방법도 사용가능
 . 메서드를 다른 메서드로 인라인 처리합니다.
 . 문자열을 상수로 정의하는 대신 즉석에서 생성합니다.
 . Java 리플렉션(reflection)을 사용하여 메서드를 호출합니다.

- 보안 랜덤 Nonce를 사용하라.
- 잠금 해제된 콘텐츠를 사용할 때 마다 취소 여부를 확인하라.
- Google Play 공개키를 안전하게 보관하라.
 . 문자열로 삽입하지 말고 런타임에 문자열 생성이나 비트조작을 사용하여 숨겨라.

2017년 10월 30일 월요일

Android IAP references, 개요, 아이템 구매

Android IAP가 궁금해서 대충 요약 해 봄.
: https://developer.android.com/google/play/billing/index.html?hl=ko

대상 : 표준 인앱 상품(일회성 결제), 구독(반복 자동 결제)
- IAP를 사용하여 아이템 판매 시 Google Play가 모든 결제 정보를 처리하여 App은 결재 transaction 관련 처리가 필요 없음.
- Google Play에서 제공하는 결재 서비스를 사용하므로 모든 app들에 대해 일관적인 경험 제공
- Google Play를 통해 세기되는 App 대상



(개요)

[In-app Billing API]
[Google Play Developer Console]

: https://developer.android.com/google/play/billing/billing_overview.html?hl=ko#api

- Google Play 앱에 의해 노출 되는 API를 통해 결제 서비스 사용

- Google Play 앱이 App<->Google Play server 사이 결제 요청, 응답 전달
- App<->Google Play 앱은 IPC를 사용하여 결제 요청, 응답을 받음.
- 결재는 Google Play 서버를 통해서 진행하므로 Google Play app이 서버와 통신할 수 있어야 함.
- Android In-app billing version은 현재 v3가 최신 버전임.
 . App은 google Play의 API를 통해 상품 세부 정보 요청, 상품 주문, 소유 상품 복원 가능.
 . API는 구매 완료 시 주문 정보를 동기적으로 기기로 전파.
 . 모든 구매가 Google Play를 통해 관리됨. 
 . 구매한 상품은 소비 가능하며 소비 시 소유하지 않은 상태로 변경됨.
 . 구독 서비스를 지원함.

- IAP 사용 App을 게시하고 IAP로 판매할 상품을 관리(구매/구독, 상품 목록 생성)함.

- 제품에 대한 다음 정보들을 정의함.
 . 상품 ID(SKU), 상품 유형, 가격, 설명, 구매에 대한 처리 및 추적?

https://developer.android.com/google/play/billing/billing_reference.html?hl=ko#getSkuDetails

표 2. getSkuDetails 요청에서 반환되는 상품 아이템 세부정보를 포함한 JSON 필드에 대한 설명.
설명
productId상품의 ID입니다.
type이 키의 값은 인앱 상품의 경우 “inapp”, 구독의 경우 "subs"여야 합니다.
price아이템의 형식 지정된 가격(통화 기호 포함)으로, 세금을 제외한 가격입니다.
price_amount_micros마이크로 단위의 가격으로, 1,000,000 마이크로 단위가 1 통화 단위와 같습니다. 예를 들어, price가 "€7.99"이면 price_amount_micros는 "7990000"입니다. 이 값은 특정 통화에 대해 현지화된 반올림 가격을 나타냅니다.
price_currency_codeprice에 대한 ISO 4217 통화 코드입니다. 예를 들어, price가 영국 파운드 단위로 지정되어 있는 경우 price_currency_code는 "GBP"입니다.
title상품의 제목입니다.
description상품에 대한 설명입니다.

[Google Play 구매 흐름]

1. App >- IAP 상품 결재 요청 -> Google Play
2. Google Play 결재 양식 요청, 유효성 검사, 구매 transaction 처리
3. Google Play >- 구매 세부 정보(주문 번호, 날짜, 시간, 가격 등) -> Google Play


(In-app Billing API)

https://developer.android.com/google/play/billing/api.html?hl=ko#producttypes
: https://developer.android.com/google/play/billing/billing_integrate.html?hl=ko
  * 구현방법이 좀 다름(확인 필요). https://developer.android.com/training/in-app-billing/preparing-iab-app.html?hl=ko

[아이템 구매]

구매 물품 확인
Bundle ownedItems = mService.getPurchases(3, getPackageName(), "inapp", null);
int response = ownedItems.getInt("RESPONSE_CODE");
if (response == 0) {
   ArrayList<String> ownedSkus =
      ownedItems.getStringArrayList("INAPP_PURCHASE_ITEM_LIST");
   ArrayList<String>  purchaseDataList =
      ownedItems.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
   ArrayList<String>  signatureList =
      ownedItems.getStringArrayList("INAPP_DATA_SIGNATURE_LIST");
   String continuationToken =
      ownedItems.getString("INAPP_CONTINUATION_TOKEN");

   for (int i = 0; i < purchaseDataList.size(); ++i) {
      String purchaseData = purchaseDataList.get(i);
      String signature = signatureList.get(i);
      String sku = ownedSkus.get(i);

      // do something with this purchase information
      // e.g. display the updated list of products owned by user
   }

   // if continuationToken != null, call getPurchases again
   // and pass in the token to retrieve more items
}

인앱 상품 세부정보 확인
Bundle skuDetails = mService.getSkuDetails(3,
   getPackageName(), "inapp", querySkus);
int response = skuDetails.getInt("RESPONSE_CODE");
if (response == 0) {
   ArrayList<String> responseList
      = skuDetails.getStringArrayList("DETAILS_LIST");

   for (String thisResponse : responseList) {
      JSONObject object = new JSONObject(thisResponse);
      String sku = object.getString("productId");
      String price = object.getString("price");
      if (sku.equals("premiumUpgrade")) mPremiumUpgradePrice = price;
      else if (sku.equals("gas")) mGasPrice = price;
   }
}

인앱 상품 구매
(요청)
Bundle buyIntentBundle = mService.getBuyIntent(3, getPackageName(),
   sku, "inapp", "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ");
PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT");
startIntentSenderForResult(pendingIntent.getIntentSender(),
   1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0),
   Integer.valueOf(0));
(응답)
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
   if (requestCode == 1001) {
      int responseCode = data.getIntExtra("RESPONSE_CODE", 0);
      String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");
      String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");

      if (resultCode == RESULT_OK) {
         try {
            JSONObject jo = new JSONObject(purchaseData);
            String sku = jo.getString("productId");
            alert("You have bought the " + sku + ". Excellent choice,
               adventurer!");
          }
          catch (JSONException e) {
             alert("Failed to parse purchase data.");
             e.printStackTrace();
          }
      }
   }
}
(구매 정보)
'{
   "orderId":"GPA.1234-5678-9012-34567",
   "packageName":"com.example.app",
   "productId":"exampleSku",
   "purchaseTime":1345678900000,
   "purchaseState":0,
   "developerPayload":"bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ",
   "purchaseToken":"opaque-token-up-to-1000-characters"
 }'

* 구독에 대한 구매 흐름은 유사하지만 상품 유형이 subs로 설정해야 함.
(요청)
Bundle bundle = mService.getBuyIntent(3, "com.example.myapp",
   MY_SKU, "subs", developerPayload);
PendingIntent pendingIntent = bundle.getParcelable(RESPONSE_BUY_INTENT);
if (bundle.getInt(RESPONSE_CODE) == BILLING_RESPONSE_RESULT_OK) {
   // Start purchase flow (this brings up the Google Play UI).
   // Result will be delivered through onActivityResult().
   startIntentSenderForResult(pendingIntent, RC_BUY, new Intent(),
       Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0));
}
(구매 확인)
Bundle activeSubs = mService.getPurchases(3, "com.example.myapp",
                   "subs", continueToken);

* Transaction 무결성을 보장하기 위해 Google Play는 구매 응답 데이터를 포함한 JSON 문자열에 서명함. 


[인앱 상품 소비]

구매한 상품은 소유된 상태로 간주되고 소유된 상태는 Google Play에서 다시 구매할 수 없음. 하지만 소유한 상품을 소비 하면 구매 데이터가 삭제 되고 소유되지 않는 상태로 변경됨.


int response = mService.consumePurchase(3, getPackageName(), token);


[구독]

[홍보]

[보안 및 디자인]

[테스트]

[관리]

2017년 10월 23일 월요일

DEVIEW 2017 key note

Deview (Day1) 참석해서 대충 요약




매년 Naver에서 주최하는 행사로 국내 IT 관련 컨퍼런스 중에서는 손에 꼽을만한 행사이고
Deview를 통해 선물 보따리 풀 듯 1년 동안 준비했던 서비스, 기술 등을 발표하는 자리라 어렴풋이 Naver의 나아갈 방향을 확인할 수 있습니다.

이번 Keynote를 보면서 몇 가지 인상 깊었던 것은 간략히 적어 보자면






Ambient intelligence라는 키워드를 언급하면서 

Naver의 서비스들(Disco, Clova, 파파고 등)에 AI 기반의 기술들을 적용되어 

사용자의 의도를 이해하고 그에 대한 답을 주고 상황에 맞는 자연스러운 GUI를 제공하는 것이 였었습니다.




Clova 관련해서는 
Clova Interface Connect를 통해서 human interface에 대한 API를 제공하여 

3rd party 제조사에서도 AI 기반 스피커, 등의 제품을 만들어 Clova Platform을 사용할 수 있게 하고 
Clova Extension Kit을 사용하여 Service content provider들이 Clova Platform과 연동하여 

자신의 content를 서비스 할 수 있도록 하여 다른 제조사와 content provider의 참여를 염두 한 점이 인상 깊었습니다.




또한 그 동안 Deview를 통해서 발표, 논의한 기술들이 어떻게 Naver 서비스에 적용되었는지 잠깐 언급 했었는데요. 

큰 그림이나 직접적인 의도를 가지고 이런 roadmap을 유지한 것인지는 모르겠으나 

서비스 기업이라 그런지 모르겠지만 장시간에 걸쳐서 개발한 기술과 역량을 버리지 않고 서비스에 적용하는 모습이 좀 부러웠었습니다.








마지막으로 많이 기사화 되었던 것 같은데 

작년에 Naver Labs가 Naver에서 분사하여 Location과 Mobility에 집중하게 되었다는 것과 함께 여러 제품, 로봇들을 발표하였습니다. 

발표하는 제품이나 로봇들을 보다 보면 S/W 기반 서비스를 넘어서서 다양한 시도를 하고 재미있는 제품들이 나오지 않을까 생각을 했었습니다. 


세션을 들으면서 느꼈던 점은 점점 세션 구성이 regacy한 기술들은 거의 없어지고 

딥러닝, AI쪽이 많아진 것이었고 이게 지금의 흐름을 반영하는 것이 아닌가 생각하게 되었습니다.


[DEVIEW 2017] DAY 1 세션 발표자료


[DEVIEW 2017] DAY 2 세션 발표자료