UNITY Firebase push

유니티 2019. 4. 27. 02:28

예전에 게임에 푸쉬를 추가하려면, android iOS 따로 따로 만들어 푸쉬를 추가해야 했습니다. 힘들기도 하지만, 트러블 슈팅에 많은 시간을 소요해야만 했습니다. 하지만, 이제는 Firebase를 통해 추가하면, 한 번에 추가하고 관리할 수 있습니다. 물론, 문제는 여전히 산재해 있습니다. 하지만, 최소한 예전보다는 좋아졌습니다.

 

[선행작업]

프로비저닝 프로파일 만들기 (푸쉬 인증서 설명도 같이 있음) : https://nicgoon.tistory.com/202 .

 

[공식문서]

Unity 프로젝트에 Firebase 추가 : https://firebase.google.com/docs/unity/setup .
GitHub의 Unity용 Firebase 빠른 시작 샘플 : https://github.com/firebase/quickstart-unity .
Unity로 Firebase 클라우드 메시징 클라이언트 앱 설정 : https://firebase.google.com/docs/cloud-messaging/unity/client .

 

[주요링크]

파이어 베이스 : https://firebase.google.com .

 

 

1. 유니티 프로젝트 생성.

유티 프로젝트 생성은 크게 힘들 일이 아닐 것 입니다. 적당한 이름으로 프로젝트를 생성해 주세요. 그리고, 안드로이드를 위한 빌드 셋팅을 해 주도록 합니다.

상단 메뉴에서 File > Bild Settings 를 선택해 주도록 합니다. 그럼 빌드 셋팅 창이 뜨는데, Android를 선택하고 Switch Platform 버튼을 눌러 플랫폼을 변경하도록 합니다.

플랫폼이 변경되면, Player Settings 버튼을 눌러서, Player Settings 창을 호출하도록 합니다.

창이 뜨면, 안드로이드 > Other Settings 버튼을 눌러 Package Name 을 적당한 값으로 변경해 줍니다.

그리고, 다시 Build Settings 창에서, Build And Run 버튼을 눌러 한 번 빌드를해 잘 동작하는 지 확인합니다.

 

2. 파이어 베이스 프로젝트 추가.

첫 화면에서 콘솔로 이동합니다.

콘솔 첫 화면에서 프로젝트 추가 버튼을 눌러 주도록 합니다.

아래와 같이 프로젝트 추가 창이 뜨면, 적당한 값들을 입력하고, 프로젝트 만들기 버튼을 눌러 주도록 합니다.

 

3. 유니티 앱 추가 하기.

프로젝트 창이 뜨면, 유니티 앱 추가 버튼을 눌러 앱을 추가하도록 합니다.

추가 페이지의 앱등록 화면이 뜨면, 아래와 같이 적당한 값들을 넣고, 앱 등록 버튼을 눌러 주도록 합니다.

구성 파일 다운로드 항목에서는 시키는 대로 android, iOS 를 위한 설정 파일을 다운로드 받아 적당한 위치에 배치 합니다. 그리고 다음 버튼을 눌러 주도록 합니다.

다음 항목에서 파이어 베이스 sdk를 다운로드 받습니다. 다운로드 후 폴더를 열어 보면, dotnet3, dotnet4 폴더가 각각 있을 것 입니다. 그 중 dotnet4 속의 FirebaseAnalytics.unitypackage 를 가지고 오도록 합니다. 여기 까지 했다면 다음 버튼을 눌러 주도록 합니다.

여기서, 유니티 Preference > External Tools > Android SDK Tools Installed with Unity 체크 되어 있다면, 오류를 만날 수 있습니다. 잠시, 해제를 해 주면, 오류가 해결되고, 파이어 베이스가 종속성이 있는 모듈을 확인할 수 있도록 해 줍니다. 끝나면, 다시 체크 해 줍니다.

다음 창이 나오면, 문서와 예제 링크가 나옵니다. Unity 문서를 눌러, Unity 프로젝트에 Firebase 추가 페이지를 불러 옵니다.

이 문서를 확인해 보면, 우리는 5단계까지 완료했고, 마지막, 6단계를 하지 않았습니다. 파이어 베이스 기능을 사용하기 위한 앱을 호출 하는 것 인데, 저는 프로젝트에 게임오브젝트를 만들고, Start 메소드에 이 코드를 추가했습니다. 코드는 다음과 같습니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using Firebase;

public class PushTestScene : MonoBehaviour
{
    
    public FirebaseApp app = null;

    void Start()
    {

        Firebase.FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task => {

            var dependencyStatus = task.Result;
            if (dependencyStatus == Firebase.DependencyStatus.Available) {
                // Create and hold a reference to your FirebaseApp,
                // where app is a Firebase.FirebaseApp property of your application class.
                app = Firebase.FirebaseApp.DefaultInstance;

                Debug.Log( "파이어 베이스 앱을 무사히 초기화 하였습니다." );

                // Set a flag here to indicate whether Firebase is ready to use by your app.
            } else {
                UnityEngine.Debug.LogError(System.String.Format(
                "Could not resolve all Firebase dependencies: {0}", dependencyStatus));
                // Firebase Unity SDK is not safe to use here.
            }

        });

    }


}

유니티에서 바로 실행해 보면, 앱이 잘 생성되어 줍니다. 저는 안드로이드, iOS 모두 정상적으로 잘 작동하였습니다.

 

4. Unity로 Firebase 클라우드 메시징 클라이언트 앱 설정.

앞서 다운로드 받았던, Firebase Unity SDK를 추가 하도록 합니다. Asset > Import Package > Custom Package 메뉴 항목을 선택합니다. 그리고 다운로드 받았던, Firebase 패키징 dotnet4 폴더 속에 있는, FirebaseMessaging.unitypackage 파일을 추가해 줍니다. 저는 그냥 이 파일을 유니티에 드래그앤 드랍으로 추가했습니다.

그리고, 파이어 베이스 메시징에 메시지를 받았을 경우 이벤트를 처리하는 코드만 추가하면됩니다. 추가할 코드는 아래와 같습니다.

public void Start() {
  Firebase.Messaging.FirebaseMessaging.TokenReceived += OnTokenReceived;
  Firebase.Messaging.FirebaseMessaging.MessageReceived += OnMessageReceived;
}

public void OnTokenReceived(object sender, Firebase.Messaging.TokenReceivedEventArgs token) {
  UnityEngine.Debug.Log("Received Registration Token: " + token.Token);
}

public void OnMessageReceived(object sender, Firebase.Messaging.MessageReceivedEventArgs e) {
  UnityEngine.Debug.Log("Received a new message from: " + e.Message.From);
}

위 코드와 합치면, 이렇게 됩니다. 저 같은 경우, 앱에서는 로그를 찍기 힘들기 때문에 레이블을 만들어 로그를 표시하도록 하였습니다. 주의 할점은 파이어 베이스로 인해 정의된 메소드들은 모두 메인 쓰레드가 아니므로, 레이블에 직접 접근할 수 없습니다. c#에서는 메인 쓰레드 외에 쓰레드에서 UI에 접근할 때, Invoke를 사용하였지만, 유니티에서는 어떻게 처리할 지 모르겠더군요. 그래서 update에서, 추가될 메시지를 확인해 메인 쓰레드에서 UI를 업데이트하도록 하였습니다.

하나의 스크립트에 모두 정의가 가능했고, 코드는 아래와 같습니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;


using Firebase;

public class PushTestScene : MonoBehaviour
{
    

    [Header("UI들")]
    public Text mainMessage = null;
    public string addMessage = "";


    [Header("초기화시 설정되는 값들")]
    public FirebaseApp app = null;

    void Start()
    {

        mainMessage.text = "";



        Firebase.Messaging.FirebaseMessaging.TokenReceived += OnTokenReceived;
        Firebase.Messaging.FirebaseMessaging.MessageReceived += OnMessageReceived;



        Firebase.FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task => {

            var dependencyStatus = task.Result;
            if (dependencyStatus == Firebase.DependencyStatus.Available) {
                // Create and hold a reference to your FirebaseApp,
                // where app is a Firebase.FirebaseApp property of your application class.
                app = Firebase.FirebaseApp.DefaultInstance;

                
                    lock(mainMessage)
                    {
                        addMessage += "파이어 베이스 앱을 무사히 초기화 하였습니다.";
                    }
                    // mainMessage.text = "파이어 베이스 앱을 무사히 초기화 하였습니다." ;
                    // this.SendMessage( "update_mainMessage", "파이어 베이스 앱을 무사히 초기화 하였습니다." );
                
                

                // Set a flag here to indicate whether Firebase is ready to use by your app.
            } else {

                lock( mainMessage )
                {
                    addMessage += string.Format("Could not resolve all Firebase dependencies: {0}", dependencyStatus);
                }
                // mainMessage.text = string.Format( "Could not resolve all Firebase dependencies: {0}", dependencyStatus ) ;

                // UnityEngine.Debug.LogError(System.String.Format(
                // "Could not resolve all Firebase dependencies: {0}", dependencyStatus));
                // Firebase Unity SDK is not safe to use here.
            }

        });

    }


    public void OnTokenReceived(object sender, Firebase.Messaging.TokenReceivedEventArgs token) {
        // UnityEngine.Debug.Log("Received Registration Token: " + token.Token);
        lock(mainMessage)
        {
            addMessage += string.Format( "Received Registration Token: " + token.Token );
        }
    }

    public void OnMessageReceived(object sender, Firebase.Messaging.MessageReceivedEventArgs e) {
        // UnityEngine.Debug.Log("Received a new message from: " + e.Message.From);
        lock(mainMessage)
        {


            var notification = e.Message.Notification;

            addMessage += string.Format(
                "Received a new message from: {0}, title: {1}, message: {2}" , e.Message.From
                , notification.Title
                , notification.Body
                );


        }
    }



    void Update()
    {

        lock(mainMessage)
        {
            // 추가할 메시지가 없는 경우 종료합니다.
            if( string.IsNullOrEmpty( addMessage ) ) return;

            // 기존 메시지에 메시지를 더해 주도록 합니다.
            mainMessage.text += addMessage;

            // 메시지를 지워 줍니다.
            addMessage = "";
        }

    }



}

그리고, 클라우드 메시지를 파이어 베이스에서 보내면 메시지가 잘 받아 집니다.

 

5. Android 메시지 보내기.

실제로 잘 받아 지는 지 테스트를 해봅시다. 안드로이드로 앱을 배포하고, 앱이 켜지면 아래 내용을 따라 실행해 주세요.

클라우드 콘솔에서, 왼쪽 메뉴중 Cloud Messaging 을 선택해 Cloud Messaging 페이지를 불러 옵니다. 불러온 페이지에서, Send your first message를 선택합니다.

나온 알림 작성 페이지의 각 항목에 적당한 값들을 입력하고 다음 버튼을 눌러 주도록 합니다.

다음 페이지에서 안드로이드 앱을 선택해 주고, 검토 버튼을 눌러 줍니다.

그럼 메시지 검토 창이 뜰텐데, 여기서 게시를 선택해 주도록 합니다.

저는 아래와 같이 무사히 결과를 받았습니다.

 

6. iOS 메시지 보내기.

위 프로비저닝 프로파일 만들기에서, 프로 비저닝은 이미 끝내서, 준비해야 합니다.

메시지를 iOS에 발신하기 위해서는, APN 키가 필요합니다. 애플 개발자 사이트로 접속해 Certificates, Identifiers & Profiles 로 이동합니다. 여기서 왼쪽 메뉴 중 Keys > All을 선택합니다. 그리고, + 버튼을 눌줍니다.

새 키 생성창에서 적당한 값들을 입력하고, Continue 버튼을 눌러 주도록 합니다.

그럼 만들것인지 다시 한 번 묻는 창이 나오는 데, Confirm을 눌러 주도록 합니다.

그럼 키 생성이 완료되었다는 창과 함께 키를 다운로드 받을 수 있습니다. Download 버튼을 눌러 키를 다운받습니다. 그리고, Key ID늘 잘 메모해 둡니다. 

파이어 베이스 콘솔로 돌아와 콘솔창에서 톱니바퀴 버튼을 눌러 프로젝트 설정 메뉴를 선택합니다.

설정키에서 클라우드 메시징 탭을 선택하고, APN 인증 키의 업로드 버튼을 선택해 줍니다.

APN 인증 키 업로드 다이얼로그가 뜨면 적당한 값들을 넣고, 업로드 버튼을 눌러 주도록 합니다.

이제 유니티로 돌아와 IOS로 플랫폼을 변경합니다.

플랫폼이 변경되면, 빌드를 눌러 xcode 프로젝트를 생성합니다.

내어 보내진 프로젝트에서, push를 허용해 줍니다.

그리고, 푸쉬 관련 프레임워크를 추가합니다. 다만, 공식 문서나, 기존에는 PushKit.framework를 사용했었습니다. 하지만, 파이어베이스에서는, UserNotifications.framework를 추가하라고 합니다. 일단, 저는 UserNotifications.framework를 추가하였습니다.

이렇게 빌드를해 정상적으로 앱을 호출된다면, 다음으로 넘어 가시면 됩니다.

이제 파이어 베이스에서 메시지를 보내어 봅시다. 파이어베이스 콘솔에서, 왼쪽의 Cloud Messaging 를 선택해 Cloud Messaging 페이지가 호출되면, 새 알림 버튼을 눌러 주도록 합니다.

그 다음 알림 전송 페이지가 호출되면, 적당한 값들을 넣고, 다음을 눌러 줍니다.

타겟 항목이 뜨면 iOS 앱을 선택하고, 검토를 눌러 줍니다.

메시지 검토 다이얼로그가 뜨면, 게시버튼을 눌러 메시지를 보냅니다.

저는 여기까지 메시지가 잘 도착하였습니다. 안드로이드와 마찮가지로.

 

 

 

'유니티' 카테고리의 다른 글

유니티 구글 플레이 연동  (0) 2019.04.29
unity Tapjoy offerwall  (1) 2019.04.29
탭조이 유니티 계정생성하기  (0) 2019.04.28
adpopcorn Unity 연동 - android  (0) 2019.04.28
adpopcorn Unity 연동 - 계정추가 (1/3)  (0) 2019.04.28
Posted by 창업닉군
,

iOS Firebase Push 사용하기

iOS 2019. 4. 26. 17:58

iOS 로 앱을 만들고, 푸쉬를 보내도 아무런 문제가 없습니다. 하지만, firebase와 통합을 하면, firebase를 통해 안드로이드 iOS까지 모두 문자를 보내 관리할 수 있습니다. 더구나, 유니티로 푸쉬를 보내려면, Firebase로 관리하면 깔끔 하므로, 그렇게 하는것이 좋습니다.

 

[선행작업]

프로비저닝 프로파일 만들기 (푸쉬 인증서 설명도 같이 있음) : https://nicgoon.tistory.com/202 .

 

[공식 문서]

iOS에서 Firebase 클라우드 메시징 클라이언트 앱 설정 : https://firebase.google.com/docs/cloud-messaging/ios/client?authuser=0 .
iOS 앱에서 메시지 수신 : https://firebase.google.com/docs/cloud-messaging/ios/receive?authuser=0 .

 

[주요링크]

파이어 베이스 : http://firebase.google.com/ .

 

 

 

1. xcode를 프로젝트 생성.

프로젝트를 생성하기전 푸쉬 관련 인증서 및 프로비저닝 파일을 모두 만들어 두시기 바랍니다.

먼저 싱글뷰 타입으로 프로젝트를 생성하기 바랍니다.

 

2. Firebase 프로젝트 생성.

파이어 베이스 첫 화면에서 콘솔로 이동 버튼을 눌러 콘솔로 이동합니다.

프로젝트 추가를 눌러 프로젝트를 생성합니다

프로젝트 추가 창이 뜨면, 적당한 값들을 입력하고, 프로젝트 만들기 버튼을 눌러 주도록 합니다.

그럼 프로젝트가 추가 되고 해당 프로젝트가 선택된 상태의 프로젝트 화면이 표시 됩니다.

 

3. 앱생성 및 Firebase SDK 추가.

아래의 프로젝트가 선택된 화면 상태에서, iOS 추가 버튼을 눌러 해당 프로젝트에 iOS앱을 추가하도록 합니다.

앱등록 항목이 뜨면, xcode를 만들때 입력한 iOS와 일치하는 번들 ID를 입력하도록 합니다. 그리고, 앱 등록 버튼을 눌러 주도록 합니다.

그럼 구성파일 다운로드 파일이 나오 데, 파일을 다운로드하고, 설명대로 파일을 추가하도록 합니다. 그리고, 다음 버튼을 누르면 됩니다.

Firebase SDK를 설치하기 위해서는 CocoaPods 파일이 설치되어 있어야 합니다. 코코아 Pods는 단 한줄의 명령으로 설치가 가능하며, Xcode가 설치되어 있어야 아래와 같이 설치할 수 있는 것으로 보입니다.

$ sudo gem install cocoapods

xcode로 만든 프로젝트 폴더로 이동합니다. 그리고, Podfile을 생성합니다. (이미 생성되어 있다면 할 필요 없습니다.) 주의할점은 앞에 sudo를 붙여 권한을 얻으려고 하면 오동작 합니다.

pod init

Podfile을 열고 다음 코드를 추가합니다. 

pod 'Firebase/Core'

그런데 처음 열면 어디에 넣어야할 지 모르실 수 있습니다. 하지만, 애플애들이 이런거 놔둘 애들이 아닙니다. 아래와 같이 코드가 되어 있을 것 입니다. #Pods for firebasepush 주석 아래 넣어 두시기 바랍니다.

파일을 저장하고 다음 명령어를 실행하세요. (권한을 얻기 위해 앞에 sudo를 붙이면 오동작합니다.) 정상적으로 동작을 한다면, 알아서, firebase sdk가 설치됩니다. 앞서 프레임 워크 포드가 설치되는데, 이게 시간을 좀 많이 잡아 먹습니다. (이미 설치되어 있다면 설치하지 않을 수도 있습니다.) 이후 파이어 베이스 SDK가 정상적으로 설치가 완료 됩니다.

pod install

설치가 완료되면, 프레임 워크 라이브러리가 자동으로 설정됩니다. 저는 pod를 처음 사용해 보아, 프레임워크를 추가하려고 갔는데, 자동으로 떡 들어와 있었습니다.

여기서 가장 중요한 것은, 아래 페이지에서, [앱에 사용할 .xcworkspace 파일이 생성됩니다. 향후 애플리케이션의 모든 개발 작업에 이 파일을 사용하세요] 라는 문구 입니다. 그대로 하지 않으면 초기화 코드를 추가할 때, import Firebase 구문을 추가하면, Could not build Objective-C module 'Firebase' 라는 오류를 만납니다. xcode를 종료하고, 프로젝트 폴더에서 새로 생긴 [프로젝트명.xcworkspace] 파일을 더블클릭해 프로젝트를 다시 열여 줍니다. 그리고 아래 이미지 처럼 다음 버튼을 눌러 주도록 합니다.

그럼 파이어 베이스 초기화 코드를 추가하는 코드가 표시 되는데, 추가할 것은 프레임워크 추가 구문과 초기화 메소드 추가 호출 부분뿐입니다. 단, 2줄. 그대로 추가하고, 다음 버튼을 눌러 주도록 합니다. (여기서 오류가 난다면, xcode를 xcworkspace 확장자를 가진 파일을 더블클릭해 실행하지 않아서 일 경우가 많습니다.)

다음을 누르면, 앱이 통신을 했는지 확인하려고 계속 대기 화면이 호출됩니다. 기기와 맥북을 연결후 한 번 실행해 줍니다.

이 화면에서 오래 동안 확인을 못하면, 이전을 눌러 준뒤 다시 다음을 눌러 5번 항목으로와 빌드를 하면, 아래와 같은 화면을 볼 수 있습니다. (몇 번 앱을 빌드하고, 실행하고를 반복하면됩니다. - 한 두번 정도 입니다.). 콘솔로 이동 버튼을 눌러 콘솔로 이동하도록 합니다.

 

4. firebase messaging SDK 추가하기

여기서 부터는 iOS에서 Firebse 클라우드 메시징 클라이언트 앱 설정 ( 링크 ) 이라는 공식 문서를 보고 작성하였습니다. 앞으로 공식문서라 함은 iOS에서 Firebase 클라우드 메시징 클라이언트 앱 설정 문서를 가리킵니다.

지금까지 우리는 Firebase 의 기본을 추가했습니다. 이제는 Cloud Messaging 관련 SDK를 추가하도록 하겠습니다. 공식 문서의 앞 페이지 부분은 이미 구현을 하였고, firebase messaging SDK 추가합니다. 문서 내용을 보시면 알겠지만 앞서 파이어 베이스 SDK를 구성할 때 함께 했어도 문제는 없었을 것이지만, 문서가 나눠져 있으므로, 문서대로 하도록 하겠습니다.

앞서 수정했던, podfile 파일을 열어 다음과 같이 Messaging 을 추가하면됩니다. Core에 Messaging 파일을 추가하도록 합니다.

pod 'Firebase/Core'
pod 'Firebase/Messaging'

그리고, 콘솔을 열어 프로젝트 폴더로 이동한 뒤 다음과 같이 입력해 줍니다. ( 앞에서도 이야기 했지만, sudo 를 통해 권한을 획득하려 하면 오류가 납니다.)

$ pod install

그리고, 파인더에서, 프로젝트명.scworkspace 파일을 더블 클릭해, xcode를 실행하도록 합니다.

 

5. PushKit.framework 등록.

iOS자체 푸쉬는 PushKit 프레임워크를 사용합니다. 이 것만으로 모두, 푸쉬가 구현 가능하며, 많은 분들이 관리상의 이유로 파이어 베이스만 으로, android iOS를 구성합니다. 파이어 베이스 또한 단독으로 푸쉬를 구성한 것은 아니며, PushKit. 프레임 워크를 이용해 푸쉬 기능을 구현했습니다.

그래서 우리 프로젝트에도 Pushkit을 넣어 주어야 정상적으로 푸쉬 기능을 사용할 수 있습니다. 

프로젝트를 선택하고, Capabilities 탭에서, Push를 활성화 합니다.

그리고 Genral 탭을 눌러 프레임 워크를 포함해 주도록 합니다.

 

6. APN 인증키 업로드.

메시지를 보내려면,  APN 인증키와 메시지등을 함께 구글 Apple Push Notification Server로 요청을 해야 합니다. 이 때 메시지와 함께 키를 보내야 하는 데, 이 때사용하는 것이 APN 키 입니다. APN키외에도 인증서를 이용해 메시지를 요청하는 방법이 있지만, Firebase에서는 APN 인증키를 통해 하는 것을 권장하고 있습니다. 저는 2가지 방식을 다 사용해 봤는데, 여러모로 APN 인증키를 사용하는 방법이 편합니다. 

개발자 사이트로 이동해 Certificates, Identifiers & Profile 페이지로 가 Keys All 메뉴를 선택합니다. 해당 페이지가 나오면, + 버튼을 눌러 APN 키 만들기를 시작합니다.

아래의 예시를 참고해 페이지를 작성하고, Continue 버튼을 눌러 주도록 합니다.

다음 페이지는 APNs 관련 설명 이며 키가 만들어 진다는 내용 입니다. Confirm 을 눌러 생성하도록 합니다.

생성하면, 아래와 같은 화면이 나옵니다. Download를 눌러 키를 다운로드 하고, Done 키를 눌러 종료 하도록 합니다.

여기서 다운로드한 파일을 그대로, 파이어 베이스 프로젝트에 셋팅해 주면됩니다. 파이어 베이스 프로젝트 콘솔로가 프로젝트 페이지의 톱니바퀴 버튼을 눌러 뜬 메뉴에서 프로젝트 설정을 선택합니다.

여기서 클라우드 메시징 탭을 선택하고, APN 인증키 업로드 버튼을 눌러 주도록 합니다.

여기서 인증키 업로드창이 뜨는 데, 앞서 내용을 확인해 적절한 값들을 넣어 주고 업로드 버튼을 눌러 키를 업로드합니다.

 

7. 원격 알림등록.

공식 문서에서는 적절한 시점에 원격 알림을 등록하라고 되어 있습니다. 예제에서는 AkppDelegate.swift 파일의 func application 속에 등록하고 있습니다. 소스를 그대로 복사해 해당 위치에 추가합니다.

        if #available(iOS 10.0, *) {
            // For iOS 10 display notification (sent via APNS)
            UNUserNotificationCenter.current().delegate = self
            
            let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
            UNUserNotificationCenter.current().requestAuthorization(
                options: authOptions,
                completionHandler: {_, _ in })
        } else {
            let settings: UIUserNotificationSettings =
                UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
            application.registerUserNotificationSettings(settings)
        }
        
        application.registerForRemoteNotifications()

그리고 파이어 베이스 메시징을 프레임워크를 사용할 것임을 코드에 선언합니다. (파이어 베이스 문서에는 잘못되어 있을 수 있습니다. 저는 이 내용이 빠져 있더군요)

import FirebaseMessaging

여기까지 했다면, UNUserNotificationCenter.current().delegate = self 이 부분에 오류가 났을 것 입니다. 해당 델리게이트를 포함해 필요메소드를 정의해 주면 오류가 사라집니다. AppDelegate.swift 파일 끝에, ( AppDelegate 클래스 밖 )에 아래 소스를 복사합니다.

// [START ios_10_message_handling]
@available(iOS 10, *)
extension AppDelegate : UNUserNotificationCenterDelegate {
    
    // Receive displayed notifications for iOS 10 devices.
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                willPresent notification: UNNotification,
                                withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        let userInfo = notification.request.content.userInfo
        
        // With swizzling disabled you must let Messaging know about the message, for Analytics
        // Messaging.messaging().appDidReceiveMessage(userInfo)
        // Print message ID.
        if let messageID = userInfo[gcmMessageIDKey] {
            print("Message ID: \(messageID)")
        }
        
        // Print full message.
        print(userInfo)
        
        // Change this to your preferred presentation option
        completionHandler([])
    }
    
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {
        let userInfo = response.notification.request.content.userInfo
        // Print message ID.
        if let messageID = userInfo[gcmMessageIDKey] {
            print("Message ID: \(messageID)")
        }
        
        // Print full message.
        print(userInfo)
        
        completionHandler()
    }
}
// [END ios_10_message_handling]

 

그러면, gcmMessageIDKey 값이 없어 오류가 날 것 입니다. AppDelegate 클래스의 멤버로, let gcmMessageIDKey = "gcm.message_id" 을 추가하면됩니다.

 

8. 메시지 대리자 설정.

앱이 시작되는 부분에 추가하면되며, 예제에는 AppDelegate.swift 파일에 application 메소드 속, FirebaseApp.configure() 메소드 아래에 포함되어 있습니다.

Messaging.messaging().delegate = self

이렇게 추가하면 MessagingDelegate 델리게이트를 상속하고 정의 해 주면됩니다. AppDelegate.swift 파일 속 AppDelegate 클래스 밖(문서 끝)에 아래의 코드를 추가해 주도록 합니다. 이 부분은 토큰을 받고 메시지를 받는 부분 입니다.

extension AppDelegate : MessagingDelegate {
  // [START refresh_token]
  func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String) {
    print("Firebase registration token: \(fcmToken)")
    
    let dataDict:[String: String] = ["token": fcmToken]
    NotificationCenter.default.post(name: Notification.Name("FCMToken"), object: nil, userInfo: dataDict)
    // TODO: If necessary send token to application server.
    // Note: This callback is fired at each app startup and whenever a new token is generated.
  }
  // [END refresh_token]
  // [START ios_10_data_message]
  // Receive data messages on iOS 10+ directly from FCM (bypassing APNs) when the app is in the foreground.
  // To enable direct data messages, you can set Messaging.messaging().shouldEstablishDirectChannel to true.
  func messaging(_ messaging: Messaging, didReceive remoteMessage: MessagingRemoteMessage) {
    print("Received data message: \(remoteMessage.appData)")
  }
  // [END ios_10_data_message]
}

 

9. 메시지 받는 부분 추가.

여기서 부터는 iOS 앱에서 메시지 수신 이라는 문서 ( 링크 ) 를 참고 합니다. 

소스는 AppDelegate.swift에 메소드 2개를 추가하는 것 뿐 입니다.

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
  // If you are receiving a notification message while your app is in the background,
  // this callback will not be fired till the user taps on the notification launching the application.
  // TODO: Handle data of notification

  // With swizzling disabled you must let Messaging know about the message, for Analytics
  // Messaging.messaging().appDidReceiveMessage(userInfo)

  // Print message ID.
  if let messageID = userInfo[gcmMessageIDKey] {
    print("Message ID: \(messageID)")
  }

  // Print full message.
  print(userInfo)
}

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                 fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
  // If you are receiving a notification message while your app is in the background,
  // this callback will not be fired till the user taps on the notification launching the application.
  // TODO: Handle data of notification

  // With swizzling disabled you must let Messaging know about the message, for Analytics
  // Messaging.messaging().appDidReceiveMessage(userInfo)

  // Print message ID.
  if let messageID = userInfo[gcmMessageIDKey] {
    print("Message ID: \(messageID)")
  }

  // Print full message.
  print(userInfo)

  completionHandler(UIBackgroundFetchResult.newData)
}

 

10. 메시지 보내기.

앱이 꺼져 있을 때는 아이폰의 헤드업 알림으로 표시가 되고, 켜저 있을 때는 내부 메시지 받는 부분에서 처리가됩니다.

파이어 베이스 콘솔에서, Cloud Messaging 버튼을 눌러, 클라우드 메시지 페이지를 열어 주도록 합니다.

그리고, 뜬 알림작성 페이지에, 적당한 문구를 입력하고 다음을 눌러 줍니다.

2번째 페이지가 뜨면, 우리가 만든 앱을 선택해 주고, 검토 버튼을 눌러 줍니다.

검토 화면이 나오면, 게시 버튼을 눌러 메시지를 보냅니다.

 

메시지가 잘 도착했나요? 저는 잘 도착했습니다. 아이폰은 안드로이드와 달리, 푸쉬를 받지 않도록 해 두면, 앱이 켜진 상태에서도 푸쉬 메시지를 받지 못합니다.

'iOS' 카테고리의 다른 글

iOS in-app purchase  (7) 2019.04.14
Push Notification (iOS, client)  (0) 2019.04.13
애플 앱을 기기에서, 실행하기 위한 방법  (0) 2019.04.12
웹 뷰 추가 및 웹통신 (UIWebView 이용)  (1) 2019.04.05
Posted by 창업닉군
,

이 문서를 따라해 보거나 적용해 보려면, 트위터 개발자 계정이 있어야 합니다. 트위터 계정 만드는 것은 너무 쉬운 일이므로, 여기서는 다루지 않습니다.

 

[공식 문서]

 

 

[주요 사이트]

트위터 개발자 사이트 : https://developer.twitter.com .

 

 

1. OAuth 로그인(sns 로그인). 을 위한 준비.

OAuth 로그인의 핵심은, 이 로그인 기능을 이용하는 사이트에서는 로그인 단계에서는 관여를 하지 않고, 유저와 sns 사이트에서 로그인처리를 하고, 결과값을 이용 사이트에 되돌려 줍니다.

그 과정에서 리디렉션 기능이 사용 되는데, 로그인할 주소를 유저에게 리디렉션해서 주면, sns 로그인 화면이 표시되고, 로그인을 무사히 완료 되면, 다시 리디렉션을 이용해, 사이트로 접속해 Get 타입으로 결과 코드를 돌려 줍니다. 마지막 결과 코드를 돌려 받을  url 주소는 트위터 서버에 등록할 필요가 있습니다. ip 주소는 안되며, 도메인으로 시작해야 하는데, 테스트를 위한 localhost도 허용을 합니다.

저는 로컬 서버에서 Apache 웹사이트를 등록해 사용하고 있습니다. 주소는 다음과 같이 정해 두었습니다.

로그인 요청 주소 : http://localhost/p999/twitter_OAuth/req_code.php .
토큰 교환 주소 : http://localhost/p999/twitter_OAuth/exchange_token.php .

 

2. 개발자 적용하기.

개발자 페이지에 접속 하면 다음과 같은 페이지를 만날 것 입니다. 아직 개발자 계정으로 전환이 되어 있지않다면, 오른쪽 상단에 Apply 레이블이 표시 될 것 입니다. 이 것이 표시 되지 않으면, 이미 개발자 계정으로 전환된 상태 이므로, 다음 항목으로 넘어 가면 되며, 아니라면, 저와 같이 개발자 적용을 하면 됩니다. Apply 버튼을 눌러 주도록 합니다.

그러면, 개발자 계정을 적용하는 화면이 나옵니다. 여기서 [ Apply for a developer account ] 눌러 개발자 계정 전환 신청단계로 들어 갑시다.

개발자 계정을 적용하면 바로 개발자 계정을 살 수 있을 지 모르겠지만, 저 같은 경우 유효한 전화번호를 입력하지 않아, 적용이 되지 않은 상태였습니다. 일단 전화번호를 등록하는 버튼을 눌러 전화번호를 등록하겠습니다.

다음 화면이 나오면 전화번호를 등록해 주도록 합니다.

그럼 아래와 같이 전화 번호 인증이 되고, 버튼 Continue 상태가 됩니다. Continue 버튼을 눌러 줍니다.

그 다음 질문은 개인의 개발자 계정인지, 조직의 개발자 계정인지 묻습니다. 상황에 따라 선택하면 되는 데 개인을 선택할 경우, 단 2개만 묻습니다. 이름과 사는 국가. 하지만, 계정 문자 @계정명 을 지정할 수 없이 이상한 값을 받습니다. 그러므로, 조직을 선택하도록 합니다.

아래와 같은 요령으로, 작성한 뒤 [Continue] 버튼을 눌러 주도록 합니다.

다음 페이지는 트위터 API를 이용해 어떤 프로젝트를 진행하려는 지 알려 달라고 합니다. 폼을 보시면 아시겠지만, 최소 300자 이상의 프로젝트 설명난이 있습니다. 한글도 재대로 못 쓰는 제가 영어로 300자 문장을 만들기란 여간 힘들지 않았습니다. 그래도 써야 한다니 써 드렸습니다. 구글 번역기와 함께. (영포자가 쓰기에는 매우 높은 벽이 였습니다.).

그 다음 서비스 약관을 허용하는지 묻는 것 입니다. Accept를 선택하고 스크롤을 쭉 내린 뒤 체크 박스가 활성화 돠면, 첫 항목만 체크 한뒤, [ Submit application ] 버튼을 눌러 주시면 됩니다.

마지막 작업으로 Email 인증 처리를 하는 작업을 합니다. 

그럼 아래와 같은 메일이 왔을 것 입니다. (오지 않았다면, 스팸으로 분류 되었는지 확인해 주세요.). [ Confirm your email ] 버튼을 눌러 이메일을 인증하도록 합니다.

 

3. 앱 만들기.

개발자 계정으로 전환을 완료했다면, 트위터를 API를 사용하려는 홈페이지, 모바일 앱, 등을 관리하는 일종의 프로젝트 개념의 앱을 만들어야 하며, 그곳에 우리가 무엇인가를 하기 위한 셋팅을 해야 합니다. 말로 설명하는 것 보다 일단 앱을 만들고 시작해 보도록 합시다.

처음 개발자 사이트에 접속하고 로그인 하면 다음과 같은 화면을 만날 수 있습니다. 여기서 Apps를 선택해 주도록 합니다.

그리고, 나온 화면에서, [ Create an app ] 버튼을 눌러 주도록 합니다.

여기 서 애플 개발자 계정을 적용하지 않았다면, 다음과 같은 창을 만날 수 있습니다.

우리는 이미 개발자 적용이 되어 있으므로, 뜨지 않을 것 입니다. 만약 뜬다면, 개발자 계정으로 전환이 완벽하게 이뤄 지지 않은 경우 이므로, 문서의 처음으로 돌아가 천천히 따라해 주시기 바랍니다. 정상적이라면 아래와 같은 폼이 뜨고, 값을 입력하고, Create 버튼을 눌러 주면됩니다.

대개의 값은 신경 쓸 필요 없지만, Enable Sign in with Twitter를 체크해, 나오는 Callback URL 은 유저가 트위터 로그인 후 결과를 돌려 받을 페이지를 입력해야 합니다. 우리는 문서 처음에 http://localhost/p999/twitter_OAuth/exchange_token.php 페이지를 결과를 받을 주소로 정했습니다. (여러 분은 다르게 정해도 상관은 없지만, 이 것으로 로그인 결과를 콜백 받는다는 사실은 알고 계셔야 합니다.).

저는 아래와 같이 작성했습니다. (트위터는 사용 목적등을 서술하는 것을 많이 요구해, 영포자인 저를 매우 힘들게 하는 군요.).

누르면, 붉은 색 3가지 항목이 나오는데, 전 뭔가 잘 못 입력했나하고, 살펴 보니, 우리가 앞서 승인했던 약관 내용을 상기 시키며, 3가지 목적으로 사용하면 안됨을 경고 하고 있었습니다. Create 버튼을 눌러 줍니다.

그럼 아래와 같이 앱이 생성된 정보가 표시됩니다.

여기서 Keys and tokens 텝을 눌러 api키와 Access token & access token secret 항목 아래, [Create] 버튼을 눌러 주도록 합니다.

다음에 계속 이어 쓰겠습니다.

 

 

 

 

 

 

 

 

 

Posted by 창업닉군
,

이 페이지는 따로 설명이 없고 Eclipse 에 대한 사용법 링크들만 있습니다. (계속 업데이트 예정).

node.js 사용법 : https://mainia.tistory.com/5653

'IDE Editor 사용법' 카테고리의 다른 글

Visual Studio Code에서 c# 코딩 하기  (0) 2019.05.01
Visual Studio Code IDE 사용법  (0) 2019.04.17
Atom IDE 사용법 (링크들)  (0) 2019.04.17
Posted by 창업닉군
,

이 페이지는 Visual Studio Code 사용법에 대한 링크들만 있습니다. (앞으로 계속 추가해 나갈 예정)

테마변경 : https://recoveryman.tistory.com/384

'IDE Editor 사용법' 카테고리의 다른 글

Visual Studio Code에서 c# 코딩 하기  (0) 2019.05.01
Eclipse IDE 사용법  (0) 2019.04.18
Atom IDE 사용법 (링크들)  (0) 2019.04.17
Posted by 창업닉군
,

아톰 사용법에 대한 링크들이 있는 페이지 입니다.

기본 사용법 (FTP 링크 있음) : http://blog.naver.com/dickymicky/220950885317

'IDE Editor 사용법' 카테고리의 다른 글

Visual Studio Code에서 c# 코딩 하기  (0) 2019.05.01
Eclipse IDE 사용법  (0) 2019.04.18
Visual Studio Code IDE 사용법  (0) 2019.04.17
Posted by 창업닉군
,

OAuth 2.0 을 이용해 Token을 얻어, 이  토큰으로 REST API를 통한 서버의 자원에 접근해 이용하는 방법은 이제 보편화된 방법 입니다. OAuth.2.0은 대부분 http/1.1을 이용하기 때문에, 대부분은 웹서버라면 다 이용할 수 있습니다.

요즘 많이 사용하게 되어 한 번쯤 정리해 둘 필요가 있을 것 같아 블로그에 글을 남겨 둡니다.

 

[공식 문서]

구글 OAuth 2.0 OverView : https://developers.google.com/identity/protocols/OAuth2?hl=ko .
구글 OAuth 2.0 for Web Server Applications : https://developers.google.com/identity/protocols/OAuth2WebServer?hl=ko .
유저 프로필 요청 : https://developers.google.com/gmail/api/v1/reference/users/getProfile .

 

[주요 사이트]

구글 클라우드 콘솔 : https://console.developers.google.com .

 

 

1. 구글 클라우드 콘솔에 프로젝트 및 Gamil 라이브러리 사용설정.

구글 로그인 기능은 자신의 구글 자원에 접근(구글 드라이브, 메일 등) 하기 위해 처음 로그인 하는 부분만 활용해 로그인 연동 처리를 하는 것 입니다. 그래서 최소 하나의 API를 사용하겠다 라고, 신청을 해야 그 다음 부터 이용이 가능합니다. 우리는 Gmail을 신청할 것 입니다. 구글 사용자라면 누구나, Gmail을 이용하고, 모든 유저가 활성화 되어 있기 때문입니다.

1) 프로젝트 생성.

목적별로 프로젝트를 생성한 후, 그 프로젝트에 Gmail을 생성합니다. 우리는 php google oauth 라는 프로젝트를 만들어 보도록 합시다. 처음 구글에 접속하면 다음과 같은 페이지가 표시될 것 입니다. 여기서 만들기를 눌러 프로젝트 만들기 창으로 접근합니다.

모든 사항을 입력한 후 만들기 버튼을 눌러 줍니다. (프로젝트 아이디를 변경해도 상관없습니다. 하지만 여기서는 하지 않았습니다.).

2) Gmail 라이브러리 추가.

그럼 php google oauth 프로젝트가 자동으로 선택되고 (하나 뿐 이므로), API 라이브러리를 추가하라는 메시지를 만납니다. 여기서 우리가 필요로 하는 Gmail API를 추가 해 보도록 합시다.

그럼.  API 라이브러리를 관리하는 화면이 나오는데, 왼쪽 메뉴의 이메일을 선택해 줍니다.

Gmail API를 선택합니다. (이메일은 Gmail API 하나만 있지만, 다른 카테고리는 여러개의, API가 표시될 수 있습니다.)

Gmail API 상세화면에서 사용설정 버튼을 눌러 사용할 수 있도록 해 줍니다.

 

2. OAuth 2.0 동의 화면 설정.

OAuth 로그인을 하는 과정에서 어떤 권한을 사용하는지등을 나타내는 화면이 표시 됩니다. 이 화면을 표시하기 위한 동의 화면을 구성하도록 하겠습니다.

아래와 같이 OAuth 동의 화면 탭을 선택해 나온 페이지에 애플리케이션 이름을 입력하고, 하단의 저장 버튼을 눌러 줍니다.

 

 

3. OAuth 2.0을 위한 클라이언트 ID 만들기.

구글 클라우드 API를 사용하기 위해서는 반드시 OAuth 2.0 방식을 통한 로그인 후 토큰을 획득해야 합니다. 이 과정에서 프로젝트를 구분하는 클라이언트 ID가 필요한데, 지금 만들어 보도록 하겠습니다.

아래와 같이 사용자 인증 정보 탭을 선택하고, OAuth 클라이언트 ID 생성을 눌러 줍니다.

클라이언트 성성 화면에서, 웹 애플리케이션을 선택하고, 이름은 적당히 입력해 주고, 생성 버튼을 눌러 줍니다.

그리고 나면 OAuth 클라이언트 정보를 확인시켜 주는 창이 뜰 텐데 일단 확인을 누르고 닫아 줍니다.

 

4. 리디렉션 주소 설정.

OAuth 2.0에서는 필연적으로 사용자가 계정의 아이디 및 패스워드를 입력하게 됩니다. 이런 과정을 통해 서버 운영자인 우리는, 사용자의 계정이나 패스워드를 저장할 필요성이 사라집니다. 사용자가 로그인을 무사히 완료하면, 서버쪽으로 성공 결과 및 인증코드를 돌려 주는 데, 그 주소를 리디렉션 주소라고 합니다. 이 값은 중요하므로, 구글에 등록된 주소로만 리디렉션 해 줍니다.

이 과정에서 도메인이 필요한 데, 우리는 테스트 과정이므로, localhost를 사용할 것 입니다. 실제 서비스를 한다면, 도메인을 이용해 구성해 주세요.

저는 웹페이지 중 http://localhost/p999/google_OAuth2/token_exchange.php 라는 주소를 설정할 것 입니다. 파일이름이 token_exchange.php 인 이유는 받은 인증 코드를 이용해 토큰으로 교환할 것 이기 때문입니다.

사용자 인증탭에 OAuth 2.0에 사용할 클라이언트 ID의 수정 버튼을 눌러 줍니다. 그리고, 나온 페이지에 승인된 리디렉션 URI에 리디렉션 주소를 입력하고( 꼭 엔터를 눌러 줍니다. ), 저장 버튼을 눌러 줍니다.

 

5. 인증을 구현을 위한 필요 정보 조사.

인증에 사용할 클라이언트 ID를 눌러 주도록 합니다.

페이지에서 각각의 정보를 수집합니다.

클라이언트 ID : 107557211118-lpfm0l4gs5ffpas8cbjn5bp71f2hbag5.apps.googleusercontent.com
크라이언트 보안 비밀 : NaJSvmGdShGg1kkXkGdvHp4T
승인된 리디렉션 URI : http://localhost/p999/google_OAuth2/token_exchange.php

 

6. 인증서 코드 요청 페이지 만들기.

저는 PC의 웹서버에, /p999/google_OAuth2/ 폴더 속에서 웹 파일들을 만들어 주고 있습니다. 코드를 요청하는 페이지 이름은 request_code.php 로 정하고 코딩하였습니다. 이 페이지에서 사용되는 내용은, Using OAuth 2.0 for Web Server Application (위에 링크 있습니다.)공식 문서에 HTTP/REST 부분의, 내용 입니다.

scope 의 권한 부분은 OAuth 2.0 API Scopes ( 링크 )에서 확인할 수 있는 데, 우리는 메일의 모든 권한을 요청할 것 입니다. 그 권한은 ( https://mail.google.com/ ) 입니다.

state 값은 리디렉션 시 그대로 다시 전달 받을 값 입니다. 로그인 과정에서 수많은 유저가 동시에 접속하면, 어떤 유저의 로그인인지 확인할 필요가 있습니다. 이때 사용하면 좋으며, 세션을 사용한다면, 사용하지 않아도 상관이 없습니다.

코드 자체는 로그인할 주소를 입력한 뒤, 리디렉션 해 주면 끝이 납니다. 아래는, request_code.php 페이지의 내용 입니다.

<?php



    // 주요 값들.
    $client_id = "107557211118-lpfm0l4gs5ffpas8cbjn5bp71f2hbag5.apps.googleusercontent.com";
    $client_secret = "NaJSvmGdShGg1kkXkGdvHp4T";
    $redirection_url = "http://localhost/p999/google_OAuth2/token_exchange.php";
    $scope = "https://mail.google.com/";

    // 인증 코드 주소 생성.
    $reqAddr = "https://accounts.google.com/o/oauth2/v2/auth"
    . "?client_id=" . $client_id
    . "&redirect_uri=" . urlencode( $redirection_url )
    . "&scope=" . urlencode( $scope )
    . "&state=OK"
    . "&access_type=offline"
    . "&include_granted_scopes=true"
    . "&response_type=code"
    ;

    // 지정한 주소로 리디렉션 시키기.
    header( 'Location: ' . $reqAddr );

?>

인증 코드를 무사히 넘겨 받았을 때 결과를 리디렉션 받아 토큰을 교환하는 페이지의 소스는 일단 아래와 같이 둡니다.

<?php


    print_r( $_GET );

자그럼 request_code.php 파일을 실행해 보도록 합시다. 정상적으로 리디렉션 된다면, 아래와 같은 페이지를 만날 수 있습니다.

일반적으로 구글 로그인 화면이므로, 쉽게 무엇을 넣어야 할 지 알 수 있을 것 입니다. 계속해서 로그인을 하도록 합니다.

로그인이 완료되면, 우리가 scope로 요청했던, Gmail의 모든 권한을 부여해달라는 페이지가 뜹니다. 우리는 현재 우리 계정으로 로그인하지만, 일반 유저가 우리 홈페이지에 접근할 때, 이 페이지를 보개 될 것이며, 자신의 Gmail을 이용할 것을 알게 됩니다. 일단 계속 해서 허용을 해 봅시다.

자 그럼 우리가 설정해 놓은 리디렉션 페이지로 GET을 통해 code, scope, state 값이 넘어 온 것을 확인할 수 있습니다.

 

7. 토큰 교환

우리가 리디렉션 받은 페이지에서, 받은 코드값을 사용해 토큰을 받아 오도록 하겠습니다. 이 부분은 서버에서 POST 통신을 해야 하므로, 약간의 난이도(?)가 있습니다.

POST 통신을 위해, fsocketopen 메소드를 상용하겠습니다. 공식 문서에 보면, 아래와 같이 데이터를 보내 도록 되어 있습니다. 우리도 그 값 그대로 보내면 되겠습니다. 공식 문서에는 endpoint가 https://www.googleapis.com/oauth2/v4/token 라고 되어 있습니다. 여기로 접속할 것 입니다.

POST /oauth2/v4/token HTTP/1.1
Host: www.googleapis.com
Content-Type: application/x-www-form-urlencoded

code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7&
client_id=your_client_id&
client_secret=your_client_secret&
redirect_uri=https://oauth2.example.com/code&
grant_type=authorization_code

아래는 토큰 교환을 하는 코드 입니다.

    // 주요 값들.
    $client_id = "107557211118-lpfm0l4gs5ffpas8cbjn5bp71f2hbag5.apps.googleusercontent.com";
    $client_secret = "NaJSvmGdShGg1kkXkGdvHp4T";
    $redirection_url = "http://localhost/p999/google_OAuth2/token_exchange.php";
    $scope = "https://mail.google.com/";



    // 보낼 쿼리 만들기.
    $sendQuery = ""
    . "code=" . $_GET['code']
    . "&client_id=" . $client_id
    . "&client_secret=" . $client_secret
    . "&redirect_uri=" . urlencode( $redirection_url )
    . "&grant_type=authorization_code"
    ;


    // 보낼 데이터를 만들어 주기 위한 기본값을 설정합니다.
    $endline = "\r\n";
    $req = "";
    
    
    // 구글에서 요청한 필수 헤더를 추가합니다.
    $req = "POST /oauth2/v4/token HTTP/1.1" . $endline
    . "Host: www.googleapis.com" . $endline
    . "Content-Type: application/x-www-form-urlencoded" . $endline
    ;

    
    // POST 데이터를 보내기 위한 기본 헤더를 추가힙니다.
    $req .= "Content-Length: " . strlen($sendQuery) . $endline
    . "Connection: Close" . $endline
    ;
    
    // 헤더의 끝을 표시하는 빈 문자열을 설정합니다.
    $req .= $endline;
    
    // 내용을 추가해 보내어 줍니다.
    $req .= $sendQuery;
    ;

    // 해당 구글 서버와 연결을 시도합니다.
    $fsock = @fsockopen( "ssl://www.googleapis.com", 443 );

    // 구글 서버에 접속 실패한 경우.
    if( !$fsock )
    {
        echo "구글 서버에 접속 실패하였습니다.!";
        exit;
    }
    
    // 데이터 보내기를 합니다.
    fwrite( $fsock, $req );


    // 데이터 받기를 위해 필요한 값들을 선언합니다.
    $headPassed = false;
    $TokenJson = "";


    // 데이터 받기가 완료될 때 까지 대기하면 데이터를 받아 출력합니다.
    while( !feof($fsock) )
    {

        // 한 줄 라인을 가지고 옵니다.
        $line = fgets($fsock, 128);

        // 아직 헤더는 아니지만, 헤더의 끝을 만난 경우, 헤더가 끝났음을 마킹하고, 종료합니다.
        if( $line == "\r\n" && !$headPassed )
        {
            $headPassed = true;
            continue;
        }

        // 헤더가 아닌 경우만, 값을 출력하도록 합니다.
        if( $headPassed )
        {
            $TokenJson .= $line;
        }

    }

    // 연결을 닫아 주도록 합니다.
    fclose( $fsock );

    // 받아 온 Token Json을 토큰 객체로 만들어 줍니다.
    $TokenObj = json_decode( $TokenJson );

    // 받아 온 토큰을 출랙해 줍니다.
    echo "받은 토큰은 아래와 같습니다.<br>";
    print_r( $TokenObj );
    echo "<br>---------------------------<br><br>";

 

8. 유저 프로필 요청.

gmail의 간단한 사용자의 정보를 가지고 옵니다. GET 타입이므로, 리디렉션 처리를 하면 간단하지만, 우리는 지금 학습단계이므로, fsockopen 을 이용해 구현해 보도록 하겠습니다.

gmail 의 유저 프로필 요청을 합니다. 공식 문서를 참고해 주시고, 아래 코드를 추가하면 됩니다. 아마 별다른 설명은 필요 없을 것이라 봅니다.

    // 보낼 문장을 반들어 줍니다.
    $userinfor_req = "GET /gmail/v1/users/me/profile HTTP/1.1" . $endline
    . "Authorization: Bearer " . $TokenObj->access_token . $endline
    . "Host: www.googleapis.com" . $endline
    . "Connection: Close" . $endline

    . $endline


    ;


    // 해당 구글 서버와 연결을 시도합니다.
    $fsock = @fsockopen( "ssl://www.googleapis.com", 443 );

    // 구글 서버에 접속 실패한 경우.
    if( !$fsock )
    {
        echo "구글 서버에 접속 실패하였습니다.!";
        exit;
    }
    
    // 데이터 보내기를 합니다.
    fwrite( $fsock, $userinfor_req );




    // 데이터 받기를 위해 필요한 값들을 선언합니다.
    $headPassed = false;
    $profileJson = "";


    while( !feof($fsock) )
    {

        // 한 줄 라인을 가지고 옵니다.
        $line = fgets($fsock, 128);

        // 아직 헤더는 아니지만, 헤더의 끝을 만난 경우, 헤더가 끝났음을 마킹하고, 종료합니다.
        if( $line == "\r\n" && !$headPassed )
        {
            $headPassed = true;
            continue;
        }

        // 헤더가 아닌 경우만, 값을 출력하도록 합니다.
        if( $headPassed )
        {
            $profileJson .= $line;
        }

    }


    // 연결을 닫아 주도록 합니다.
    fclose( $fsock );



    // 가지고 온 유저 데이터를 출력하도록 합니다.
    echo "확인한 유저 정보는 아래와 같습니다.<br>";
    echo $profileJson;
    

잘 실행되었다면, 아래와 같은 페이지를 볼수 있습니다.

 

 

마지막. 전체 코드 입니다.

<?php



    // 주요 값들.
    $client_id = "107557211118-lpfm0l4gs5ffpas8cbjn5bp71f2hbag5.apps.googleusercontent.com";
    $client_secret = "NaJSvmGdShGg1kkXkGdvHp4T";
    $redirection_url = "http://localhost/p999/google_OAuth2/token_exchange.php";
    $scope = "https://www.googleapis.com/auth/gmail.readonly";
    // $scope = "profile|email|openid";
    // $scope = urlencode( $scope );


    // 보낼 쿼리 만들기.
    $sendQuery = ""
    . "code=" . $_GET['code']
    . "&client_id=" . $client_id
    . "&client_secret=" . $client_secret
    . "&redirect_uri=" . urlencode( $redirection_url )
    . "&grant_type=authorization_code"
    ;


    // 보낼 데이터를 만들어 주기 위한 기본값을 설정합니다.
    $endline = "\r\n";
    $req = "";
    
    
    // 구글에서 요청한 필수 헤더를 추가합니다.
    $req = "POST /oauth2/v4/token HTTP/1.1" . $endline
    . "Host: www.googleapis.com" . $endline
    . "Content-Type: application/x-www-form-urlencoded" . $endline
    ;

    
    // POST 데이터를 보내기 위한 기본 헤더를 추가힙니다.
    $req .= "Content-Length: " . strlen($sendQuery) . $endline
    . "Connection: Close" . $endline
    ;
    
    // 헤더의 끝을 표시하는 빈 문자열을 설정합니다.
    $req .= $endline;
    
    // 내용을 추가해 보내어 줍니다.
    $req .= $sendQuery;
    ;

    // 해당 구글 서버와 연결을 시도합니다.
    $fsock = @fsockopen( "ssl://www.googleapis.com", 443 );

    // 구글 서버에 접속 실패한 경우.
    if( !$fsock )
    {
        echo "구글 서버에 접속 실패하였습니다.!";
        exit;
    }
    
    // 데이터 보내기를 합니다.
    fwrite( $fsock, $req );


    // 데이터 받기를 위해 필요한 값들을 선언합니다.
    $headPassed = false;
    $TokenJson = "";


    // 데이터 받기가 완료될 때 까지 대기하면 데이터를 받아 출력합니다.
    while( !feof($fsock) )
    {

        // 한 줄 라인을 가지고 옵니다.
        $line = fgets($fsock, 128);

        // 아직 헤더는 아니지만, 헤더의 끝을 만난 경우, 헤더가 끝났음을 마킹하고, 종료합니다.
        if( $line == "\r\n" && !$headPassed )
        {
            $headPassed = true;
            continue;
        }

        // 헤더가 아닌 경우만, 값을 출력하도록 합니다.
        if( $headPassed )
        {
            $TokenJson .= $line;
        }

    }

    // 연결을 닫아 주도록 합니다.
    fclose( $fsock );

    // 받아 온 Token Json을 토큰 객체로 만들어 줍니다.
    $TokenObj = json_decode( $TokenJson );

    // 받아 온 토큰을 출랙해 줍니다.
    echo "받은 토큰은 아래와 같습니다.<br>";
    print_r( $TokenObj );
    echo "<br>---------------------------<br><br>";





    // [-- 아래는 유저 프로필 가지고 오기 관련 --]

    
    // 보낼 문장을 반들어 줍니다.
    $userinfor_req = "GET /gmail/v1/users/me/profile HTTP/1.1" . $endline
    . "Authorization: Bearer " . $TokenObj->access_token . $endline
    . "Host: www.googleapis.com" . $endline
    . "Connection: Close" . $endline

    . $endline


    ;


    // 해당 구글 서버와 연결을 시도합니다.
    $fsock = @fsockopen( "ssl://www.googleapis.com", 443 );

    // 구글 서버에 접속 실패한 경우.
    if( !$fsock )
    {
        echo "구글 서버에 접속 실패하였습니다.!";
        exit;
    }
    
    // 데이터 보내기를 합니다.
    fwrite( $fsock, $userinfor_req );




    // 데이터 받기를 위해 필요한 값들을 선언합니다.
    $headPassed = false;
    $profileJson = "";


    while( !feof($fsock) )
    {

        // 한 줄 라인을 가지고 옵니다.
        $line = fgets($fsock, 128);

        // 아직 헤더는 아니지만, 헤더의 끝을 만난 경우, 헤더가 끝났음을 마킹하고, 종료합니다.
        if( $line == "\r\n" && !$headPassed )
        {
            $headPassed = true;
            continue;
        }

        // 헤더가 아닌 경우만, 값을 출력하도록 합니다.
        if( $headPassed )
        {
            $profileJson .= $line;
        }

    }


    // 연결을 닫아 주도록 합니다.
    fclose( $fsock );



    // 가지고 온 유저 데이터를 출력하도록 합니다.
    echo "확인한 유저 정보는 아래와 같습니다.<br>";
    echo $profileJson;
    

 

 

 

 

 

 

 

Posted by 창업닉군
,

node js OAuth 2.0 Login

Node.js 2019. 4. 16. 11:10

node js는 패스포트를 이용하면, 거의 모든 SNS 로그인을 구현할 수 있습니다. 하지만, 새로운 SNS 사이트나 OAuth 2.0 기능을 가진 사이트의 요청을 필요로 할 때를 대비해 기능을 잘 알아 둘 필요가 있습니다. 그리고 모듈을 쓸 필요도 없을 만큼 매우 간단하고 쉽습니다.

[공식 문서]

구글 OAuth 2.0 OverView : https://developers.google.com/identity/protocols/OAuth2?hl=ko .
구글 OAuth 2.0 for Web Server Applications : https://developers.google.com/identity/protocols/OAuth2WebServer?hl=ko .
유저 프로필 요청 : https://developers.google.com/gmail/api/v1/reference/users/getProfile .


[주요 사이트]

Google APIs 콘솔 : https://console.developers.google.com .

 

[참고 문서]

php를 이용한 Google OAuth2.0 Login : https://nicgoon.tistory.com/208 . (전반부 구글 클라우드 콘솔 설정 숙지.).

 

1. 준비사항.

1) 본 문서를 보기전, 구글 클라우드 API에서 클라이언트 ID를 만들어야 합니다. 해당 내용은 위 참고 문서 php를 이용한 Google OAuth2.0 Login 부분을 참고 하시면됩니다. 

2) 익스프레스 서버 설정.

익스프레스를 이용해 서버를 설정하였다면, 웹을 처리를 할 라우트를 만들어 주세요 저는 아래와 같이 라우트 주소를 설정하였습니다.

구글 로그인 라우트 기본 주소 : http://localhost/oauth2
인증코드 요청 페이지 : 
http://localhost/oauth2/request_code
토큰 교환 페이지 : 
http://localhost/oauth2/exchange_token

3) 클라이언트 아이디 관련 값들.

클라이언트 아이디 : 107557211118-lpfm0l4gs5ffpas8cbjn5bp71f2hbag5.apps.googleusercontent.com
클라이언트 보안 비밀 : NaJSvmGdShGg1kkXkGdvHp4T
리디렉션 페이지 : http://localhost/oauth2/exchange_token

 

2. 라우트 기본 뼈대.

구글 로그인에서 사용되는 페이지는 최소 2개이고, 이 것을 저는 라우트를 하나 만들어 다음과 같은 코딩을 했습니다. request_code 는 인증코드 요청페이지, exchange_token 은 리디렉션 후 토큰 교환 입니다.

var express = require('express');
var router = express.Router();

/* GET home page. */
router.use('/:name', function(req, res, next) {
    


    // 요청한 파일명을 가지고 오는 메소드 입니다.
    let methodName = req.params.name;


    
    // 인증 코드를 요청한 경우 처리 분기 입니다.
    if( methodName == "request_code" )
    {
        request_code(req, res, next);
        return;                                     // 리턴 중요.
    }


    // 토큰 교환을 요청한 경우 처리 분기 입니다.
    else if( methodName == "exchange_token" )
    {
        exchange_token(req, res, next);
        return;                                     // 리턴 중요.
    }




    // 처리 방법을 확인하지 못한 경우. next 처리를 하도록 합니다.
    next();


});



// 인증 코드를 요청한 경우 처리 하는 메소드 입니다.
function request_code( req, res, next )
{
    res.setHeader( "Content-Type", "text/html; charset=UTF-8" );
    res.end( "코드 요청하는 메소드." );
}



// 토큰 교환 및 사용자 정보를 확인하는 경우 처리하는 메소드 입니다.
function exchange_token( req, res, next )
{
    res.setHeader( "Content-Type", "text/html; charset=UTF-8" );
    res.end("토큰을 교환하는 메소드.");
}





module.exports = router;

 

3. 유저 로그인 페이지 리디렉션하기.

주소를 만들때, 사용되는 옵션은 문서 처음에 있는 [구글 OAuth 2.0 for Web Server Applications] 링크를 참고 하시기 바립니다.

request_code 부분에 인증을 위한 주소를 만들어 리디렉션 하면, 사용자는 구글 로그인 창을 보게 됩니다. 코드는 아래와 같습니다.

// 인증 코드를 요청한 경우 처리 하는 메소드 입니다.
function request_code( req, res, next )
{

    // 공통적으로 사용하는 상수들 입니다.
    var scope = "https://mail.google.com/";
    var client_id = "107557211118-lpfm0l4gs5ffpas8cbjn5bp71f2hbag5.apps.googleusercontent.com";
    var client_secret = "NaJSvmGdShGg1kkXkGdvHp4T";
    var redriect_url = "http://localhost/oauth2/exchange_token";



    // 인증을 위한 주소를 만들어 줍니다.
    let reqAddr = "https://accounts.google.com/o/oauth2/v2/auth"
    + "?client_id=" + client_id
    + "&redirect_uri=" + encodeURIComponent( redriect_url )
    + "&scope=" + encodeURIComponent( scope )
    + "&state=OK"
    + "&access_type=offline"
    + "&include_granted_scopes=true"
    + "&response_type=code"
    ;

    

    res.redirect( reqAddr );


}

로그인 하고, 권한도 모두 주면, 리디렉션 페이지가 호출 됩니다. 우리는 여기서, 토큰 교환을 시도할 것 입니다.

 

4. 토큰 교환 처리.

정식 문서를 보면, 아래와 같은 값을 보내면, 토큰을 주는 것으로 되어 있습니다. 

POST /oauth2/v4/token HTTP/1.1
Host: www.googleapis.com
Content-Type: application/x-www-form-urlencoded

code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7&
client_id=your_client_id&
client_secret=your_client_secret&
redirect_uri=https://oauth2.example.com/code&
grant_type=authorization_code

우리가 받은 토큰 값등을 활용해 위의 페이지를 만들 것 입니다.

1) 리퀘스트 설치.

페이지를 요청하는 데 리퀘스트 객체를 사용할 것 입니다. 아래와 같이 리퀘스트를 설치해 주도록 합니다.

npm i request

2) 토큰 교환.

보내야 할 데이터 중 헤더 부분은 request가 알아서 채워 줍니다. (POST로 보낼 때 당연히 적어야할 부분). 그래서 데이터부분만 적어 주면됩니다. exchange_token 메소드를 다음과 같이 변경해 보도록 합니다.

// 토큰 교환 및 사용자 정보를 확인하는 경우 처리하는 메소드 입니다.
function exchange_token( req, res, next )
{


    // 요청할 데이터를 만들어 줍니다.
    let requestOption = {
        url:'https://www.googleapis.com/oauth2/v4/token',
        method:'POST',
        form:{
            code:req.query.code,
            client_id:client_id,
            client_secret:client_secret,
            redirect_uri: redriect_url,
            grant_type: 'authorization_code'
        }
    };

    request.post( requestOption, function( err, httpResponse, body ){


        res.setHeader( "Content-Type", "text/html; charset=UTF-8" );
    

        // 오류가 난 경우 오류를 반환해 줍니다.
        if( err )
        {

            res.end("오류가 났습니다.<br>" + err);
            return;

        }

        res.end( body );


    });



}

그리고 실행하면 정상적인 경우 다음과 같은 응답을 화면에 뿌려 줍니다. (자세히 보면 Access Token을 받아 온 것을 확인할 수 있을 것 입니다.)

5. 사용자 정보 가지고 오기.

교환된 토큰으로 아래와 같이 함수를 만들어 body와 res 값을 넣어 주면, 화면에 사용자 정보를 표시해 줍니다.

// 토큰 교환 및 사용자 정보를 확인하는 경우 처리하는 메소드 입니다.
function exchange_token( req, res, next )
{


    // 요청할 데이터를 만들어 줍니다.
    let requestOption = {
        url:'https://www.googleapis.com/oauth2/v4/token',
        method:'POST',
        form:{
            code:req.query.code,
            client_id:client_id,
            client_secret:client_secret,
            redirect_uri: redriect_url,
            grant_type: 'authorization_code'
        }
    };

    request.post( requestOption, function( err, httpResponse, body ){


        res.setHeader( "Content-Type", "text/html; charset=UTF-8" );
    

        // 오류가 난 경우 오류를 반환해 줍니다.
        if( err )
        {

            res.end("오류가 났습니다.<br>" + err);
            return;

        }

        // res.end( body );
        bodyobj = JSON.parse( body );
        read_profile( res, bodyobj.access_token );


    });



}


// 유저 정보를 확인하는 메소드 입니다.
function read_profile( res, token )
{

    // 요청할 데이터를 만들어 줍니다.
    let requestOption = {
        url:'https://www.googleapis.com/gmail/v1/users/me/profile',
        headers:{
            'Authorization': 'Bearer ' + token
        },
        rejectUnauthorized:false
    };



    request.get( requestOption, function( err, httpResponse, body ){

        if( err )
        {

            res.end("사용자 정보를 확인하던 중 오류가 났습니다.<br>" + err);
            return;

        }

        // 사용자 정보를 출력합니다.
        res.end( body );

    });

}

별다은 오류가 없다면 다음과 같은 페이지를 볼 수 있을 것 입니다.

 

6. 전체 소스

익스프레서 서버에서 사용하기 위한 전체 소스 코드는 아래와 같습니다.

var express = require('express');
var router = express.Router();


var request = require('request');


// 공통적으로 사용하는 상수들 입니다.
var scope = "https://mail.google.com/";
var client_id = "107557211118-lpfm0l4gs5ffpas8cbjn5bp71f2hbag5.apps.googleusercontent.com";
var client_secret = "NaJSvmGdShGg1kkXkGdvHp4T";
var redriect_url = "http://localhost/oauth2/exchange_token";


/* GET home page. */
router.use('/:name', function(req, res, next) {
    


    // 요청한 파일명을 가지고 오는 메소드 입니다.
    let methodName = req.params.name;


    
    // 인증 코드를 요청한 경우 처리 분기 입니다.
    if( methodName == "request_code" )
    {
        request_code(req, res, next);
        return;                                     // 리턴 중요.
    }


    // 토큰 교환을 요청한 경우 처리 분기 입니다.
    else if( methodName == "exchange_token" )
    {
        exchange_token(req, res, next);
        return;                                     // 리턴 중요.
    }




    // 처리 방법을 확인하지 못한 경우. next 처리를 하도록 합니다.
    next();


});



// 인증 코드를 요청한 경우 처리 하는 메소드 입니다.
function request_code( req, res, next )
{



    // 인증을 위한 주소를 만들어 줍니다.
    let reqAddr = "https://accounts.google.com/o/oauth2/v2/auth"
    + "?client_id=" + client_id
    + "&redirect_uri=" + encodeURIComponent( redriect_url )
    + "&scope=" + encodeURIComponent( scope )
    + "&state=OK"
    + "&access_type=offline"
    + "&include_granted_scopes=true"
    + "&response_type=code"
    ;

    

    res.redirect( reqAddr );


}



// 토큰 교환 및 사용자 정보를 확인하는 경우 처리하는 메소드 입니다.
function exchange_token( req, res, next )
{


    // 요청할 데이터를 만들어 줍니다.
    let requestOption = {
        url:'https://www.googleapis.com/oauth2/v4/token',
        method:'POST',
        form:{
            code:req.query.code,
            client_id:client_id,
            client_secret:client_secret,
            redirect_uri: redriect_url,
            grant_type: 'authorization_code'
        }
    };

    request.post( requestOption, function( err, httpResponse, body ){


        res.setHeader( "Content-Type", "text/html; charset=UTF-8" );
    

        // 오류가 난 경우 오류를 반환해 줍니다.
        if( err )
        {

            res.end("오류가 났습니다.<br>" + err);
            return;

        }

        // res.end( body );
        bodyobj = JSON.parse( body );
        read_profile( res, bodyobj.access_token );


    });



}


// 유저 정보를 확인하는 메소드 입니다.
function read_profile( res, token )
{

    // 요청할 데이터를 만들어 줍니다.
    let requestOption = {
        url:'https://www.googleapis.com/gmail/v1/users/me/profile',
        headers:{
            'Authorization': 'Bearer ' + token
        },
        rejectUnauthorized:false
    };



    request.get( requestOption, function( err, httpResponse, body ){

        if( err )
        {

            res.end("사용자 정보를 확인하던 중 오류가 났습니다.<br>" + err);
            return;

        }

        // 사용자 정보를 출력합니다.
        res.end( body );

    });

}




module.exports = router;

 

 

 

 

 

 

 

 

Posted by 창업닉군
,

맥에는 이미 Apache 및 php가 이미 설정이 되어 있습니다. 활성화만 시켜 주면 끝이 납니다.

 

참고 문서.

Apache 활성화 및 설치 폴더 변경 : https://88240.tistory.com/476 .
php 활성화 : https://happycode.tistory.com/426 .

 

1. 아파치 활성화 시작.

$ sudo apachectl start

브라우저에서 http://localhost 라고 입력 하고, 다음과 같은 화면이 뜨면, 아파치가 활성화 된 것 입니다.

2. 루트 변경하기.

아파치 설정을 바꿔 주면됩니다. 아파치는 /etc/apache2/httpd.conf 파일을 변경하면 됩니다. DocumentRoot 부분을 찾아 [경로를 입력해 주면됩니다.]

DocumentRoot "[경로]"
<Directory "[경로]">

아파이를 종료하고 재 시작 해 줍니다.

$ sudo apachectl graceful

 

3. php 활성화 하기.

#LoadModule php7_module libexec/apache2/libphp7.so 이 부분의 가장 앞쪽 #을 해제해 활성화합니다. (버전에 따라 문자열이 조금 다를 수 있습니다.).

$ sudo apachectl graceful

 

 

 

 

 

 

 

 

 

Posted by 창업닉군
,

iOS in-app purchase

iOS 2019. 4. 14. 20:17

iOS 결제 모듈을 사용하려면, Provisioning Profile이 있어야 합니다.

 

선행작업.

프로비저닝 프로파일 및 푸쉬 인증서 생성 : https://nicgoon.tistory.com/202 .

 

공식 문서.

StoreKit : https://developer.apple.com/documentation/storekit .
In-App Purchase : https://developer.apple.com/documentation/storekit/in-app_purchase .

 

참고 문서.

참고 1 : https://medium.com/@bestiosdevelope/implement-in-app-purchase-iap-in-ios-applications-swift-4d1649509599 .
참고 2 : https://devmjun.github.io/archive/purchase-1 . (자세히 설명되어 있음).
참고 영상 2 : https://www.youtube.com/watch?v=o3hJ0rY1NNw .

 

참고 사이트.

Itunes Connect : https://itunesconnect.apple.com/ .
Apple ID : https://appleid.apple.com/

 

1. 테스터 등록.

테스터로 사용할 계정을 위해 메일을 만들고 애플 아이디에 접속해 계정을 생성할 필요가 없습니다. 애플 아이디에 메일을 그냥 입력하면 되며, 심지어는 실제 사용되고 있는 애플 아이디를 넣는 다면, 이미 사용중 이라는 오류가 발생합니다.

먼저 아이툰즈 컨넥트에 접속합니다. 사용자 및 액세스를 클릭 합니다.

사용자 및 액세스 화면이 나오면, Sandbox > 테스터를 선택합니다. 그리고 나온 화면에 + 키를 눌러 주도록 합니다.

그럼 아래와 같이 테스터를 초대 하는 화면이 나올 텐데, 적당히 입력하고 초대 버튼을 눌러 줍니다. 주의 할 점은 실제 사용중인 애플 아이디를 넣으면 오류가 납니다. 자신이 사용하지 않고, 다른 사람도 사용중이 않을 메일 계정을 넣어 테스터 계정으로 사용하도록 합니다.

2. 앱 등록.

테스트 앱을 등록해 주도록 합니다. 저는 번들 아이디가  com.tistory.yeslife.inapp 인 inapp 이라는 앱을 등록할 것 입니다.

먼저 애플 스토어 컨넥트 에서 나의 앱을 선택합니다.

나의 앱 페이지가 나타나면, + 를 눌러 앱을 추가하도록 합니다.

신규앱을 등록하는 폼이 나타나면 아래와 같이 입력해 줍니다. 이름은 앱스토어 내에서 고유해야 합니다. 이미 사용중인 경우, 오류가 납니다. SKU는 앱의 상점을 위한 아이디인데, 적당히 고유한 값을 주면됩니다. 다 입력했으면 생성 버튼을 눌러 주도록 합니다. 

앱 이 생성되고, 앱이 표시 되면, 메뉴의 [앱 내 추가 기능]을 선택해 주도록 합니다.

그럼 앱내 구입 항목을 선택할 수 있는데 여기서 + 버튼 눌러 소모성 상품을 추가하도록 합니다.

앱 내 생성하려는 구입 선택항 목에서, 소모품을 선택하고 생성 버튼을 눌러 주도록 합니다.

그 다음 상품을 넣는 창에서 각각 적당한 값을 넣으면 되지만, 제품 ID는 각 상품마다 고유한 값을 잘 넣어 줘야 합니다.

그럼 등록되었지만, [메타 데이터 누락됨] 이라고 나올 수 있는데, 이 것은 우리가 심사를 위한 데이터를 넣지 않아 생기는 일이고, 테스트를 위한 것은 여기까지 입력하면됩니다.

저는 아래와 같이 3개의 소모성 아이템을 구매 했습니다.

 

앱은 현재 메타 데이터가 누락되어 있는데, 메타데이터를 설정된 뒤, 다음과 같이 앱내 구입으로 선택되어야 합니다.

 

3. 결제 옵저버.

swift에서 옵저버는 멤버 프로퍼티의 값이 변할 때, 알려주는 일종의 콜백 입니다. 결제가 시작될 때, 거절 될때등, 결제 상태를 확인해 이를 처리할 옵저버가 필요합니다.

옵저버는 SKPaymentTransactionObserver 프로토콜(c#의 인터페이스)을 상속해 구현해야 합니다. 코드는 대략 아래와 같습니다.


class StoreObserver: NSObject, SKPaymentTransactionObserver {
    
    
    // 멤버 변수들.
    var purchased = [SKPaymentTransaction]();
    
    
    
    
    override init() {
        super.init()
        
        // 생성자를 위한 초기화 메소드.
        
    }
    
    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        
        
        
        for transaction in transactions
        {
            
            switch transaction.transactionState
            {
                
            // 결제가 진행 중인 경우.
            case .purchasing :
                print("결제가 진행되고 있습니다.");
                break;
                
            // 결제 창을 띄우는 데 실패했습니다.
            case .deferred:
                print("아이폰의 잠기는 등의 이유로 결제 창을 띄우지 못했습니다.");
                SKPaymentQueue.default().finishTransaction( transaction );
                break;
                
            // 결제를 성공한 경우.
            case .purchased:
                print("결제를 성공하였습니다.");
                handlePurchased( transaction );
                SKPaymentQueue.default().finishTransaction( transaction );
                break;
                
            // 결제를 실패한 경우.
            case .failed:
                print("결제를 실패하였습니다.");
                SKPaymentQueue.default().finishTransaction( transaction );
                break;
                
            // 결제 검증을 하였습니다.
            case .restored:
                print("상품 검증을 하였습니다.");
                SKPaymentQueue.default().finishTransaction( transaction );
                break;
                
            default :
                print("알수 없는 오류를 만났습니다.")
                SKPaymentQueue.default().finishTransaction( transaction );
                break;
                
            }
            
        }
        
        
        
    }
    
    
    func handlePurchased( _ transaction : SKPaymentTransaction ) {
        
        purchased.append(transaction)


        SKPaymentQueue.default().restoreCompletedTransactions();

        
        print( "영수증 주소 : \(Bundle.main.appStoreReceiptURL)" );
        
        
        let receiptData = NSData( contentsOf: Bundle.main.appStoreReceiptURL! );
        print(receiptData)
        
        let receiptString = receiptData!.base64EncodedString(options: NSData.Base64EncodingOptions());
        
        
        print ( "구매 성공 트랜젝션 아이디 : \(transaction.transactionIdentifier!)" );
        print ( "상품 아이디 : \(transaction.payment.productIdentifier)" );
        print ( "구매 영수증 : \(receiptString)" );
     
        // 결제를 마무리 하도록 합니다.
        SKPaymentQueue.default().finishTransaction( transaction );


        
    }
    
    
}

대게의 코드는 공식 문서를 통해 쉽게 알 수 있지만 중요한 부분은 영수증을 받는 부분 입니다. 이 부분은 크게 언급이 공식 문서에 없어, 애를 먹었는데, Bundle.main.appStoreReceiptURL 값에 영수증 파일명이 들어 있습니다. 이것을 NSData를 이용해 값을 가지고 오고, Base64  인코딩을 해 사용하면됩니다.

결제를 할 때마다 transaction 이 생성되는 데, 실패하던, 걸절하던, 꼭 finishTransaction 처리를 해 줘야 합니다. 그렇지 않으면, 껐다 키면 다시 트랜젝션값들이 계속 추가 됩니다. 이 것은 앱이 비정상적으로 종료되었을 때도, 처리를 하기 위함으로 보입니다.

 

4. 상품 리스트 가지고 오기 및 구매 처리.

결제는 대체로 뷰에서 일어 나므로, viewController에서 처리하는 것이 좋습니다.

처음 상품 리스트를 가지고 오는 부분이 있습니다. 이때, 위에서 등록한 상품의 ID들을 적어 주면됩니다. 코드는 아래와 같습니다.

import UIKit
import StoreKit

class ViewController: UIViewController, SKProductsRequestDelegate {
    
    
    // 멤버 변수들.
    let iapObserver = StoreObserver()
    var productRequest : SKProductsRequest?
    
    // 입력 받은 상풉 정보들을 저장하는 변수들.
    var validProductArray = [SKProduct]();
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        // 페이먼트 관련 설정을 해 주도록 합니다.
        SKPaymentQueue.default().add(iapObserver)
        
        // 상품 정보를 가지고 오는 리퀘스트를 설정하도록 합니다.
        let pIDs = Set(["gold500","gold100"]);
        productRequest = SKProductsRequest( productIdentifiers: pIDs );
        productRequest!.delegate = self;
        
        // 앱스토어 상품 정보를 요청합니다.
        productRequest!.start();
        
        
    }

    
    // 구매 진행 가능 여부를 확인합니다.
    @IBAction func checkPaymentAble(_ sender: Any) {
        
        if( SKPaymentQueue.canMakePayments() )
        {
            print("결제 요청이 가능합니다.");
        }else
        {
            print("결제 요청이 불가능합니다.");
        }
        
    }
    
    @IBAction func tryPaymentQueue(_ sender: Any) {
        
        print( "골드 100개 결제 할거에요." )
        
        let payment = SKMutablePayment( product: validProductArray[0] );
        
        SKPaymentQueue.default().add(payment)
        
        print( "변화가 있나요?" )
        
    }
    
    
    // 상품 정보를 받은 후 처리 메소드 입니다.
    func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
        
        
        print("상품갯수 \(response.products.count)")
        print("확인하지 못한 상품 갯수 \(response.invalidProductIdentifiers.count)")
        
        
        for product in response.products
        {
            print("이름:\(product.localizedTitle)\n가격:\(product.price)")
            validProductArray.append(product)
        }
        
        
        
        
        
    }
    
    
}

 

이 것만으로 결제는 코드는 끝 입니다. 실제 결제를 해 볼때는 아이폰에서, 애플 아이디를 로그아웃하는 것이 좋습니다.

 

5. 검증 서버. (php)

원래 node js만 구현하려고 했으나, 잘 되지 않아 php로 먼저 결제 서버를 구현하였고, 그것을 바탕으로 node.js를 구현하였습니다.

애플 결제는 서버로 검증하는 것이 100배 쉽습니다. 그냥 POST 로 영수증을 Json형태로 보내면 끝 입니다. 받은 값에, 결제 날자 구매 수량 등이 있으므로, 이 것을 비교해 앱의 정보를 확인하면 되겠습니다. 아래는 샌드박스 주소로 되어 있습니다. 실제 결제 주소는 https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html#//apple_ref/doc/uid/TP40010573-CH104-SW2 에서 확인할 수 있습니다.

                // 영수증.
                $receipt = "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";
                $addr = "https://sandbox.itunes.apple.com/verifyReceipt";
                $host = "sandbox.itunes.apple.com";
                $path = "/verifyReceipt";

                // 보낼 json 데이터를 만들어 주도록 합니다.
                $bodyJson = '{ "receipt-data" : "' . $receipt . '" }';


                // 요청을 위한 주소를 만들어 주도록 합니다.
                $endline = "\r\n";
                $req = "";


                // 주소 및 타입 관련 헤더를 추가합니다.
                $req = "POST {$path} HTTP/1.1" . $endline
                . "Host: {$host}" . $endline
                . "Content-Type: application/json" . $endline
                ;

                // 데이터를 보내기 위한 기본 헤더를 추가합니다.
                $req .= "Content-Length: " . strlen($bodyJson) . $endline
                . "Connection: Close" . $endline
                ;

                // 헤더의 끝을 표시하는 빈 문자열을 설정합니다.
                $req .= $endline;
                
                // 보낼 내용을 추가합니다.
                $req .= $bodyJson;
                ;




                // 영수증 서버에 접속을 시도합니다.
                $fsock = @fsockopen( "ssl://{$host}", 443 );

                // 영수증 서버에 접속을 실패한 경우 처리 입니다.
                if( !$fsock )
                {
                    echo "구글 서버에 접속 실패하였습니다.!";
                    exit;
                }
                
                // 데이터를 보내도록 합니다.
                fwrite( $fsock, $req );


                // 데이터 받기를 위해 필요한 값들을 선언합니다.
                $headPassed = false;
                $resDatas = "";


                // 데이터 받기가 완료될 때 까지 대기하면 데이터를 받아 출력합니다.
                while( !feof($fsock) )
                {

                    // 한 줄 라인을 가지고 옵니다.
                    $line = fgets($fsock, 128);

                    // 아직 헤더는 아니지만, 헤더의 끝을 만난 경우, 헤더가 끝났음을 마킹하고, 종료합니다.
                    if( $line == "\r\n" && !$headPassed )
                    {
                        $headPassed = true;
                        continue;
                    }

                    // 헤더가 아닌 경우만, 값을 출력하도록 합니다.
                    if( $headPassed )
                    {
                        $resDatas .= $line;
                    }

                }

                // 연결을 닫아 주도록 합니다.
                fclose( $fsock );



                // JSON 데이터를 표시하도록 합니다.
                echo "받은 데이터 : " . $resDatas;


                echo "보낸 데이터 :" . $req;

 

6. 검증서버 (node.js)

알고리즘 및 구현은 php와 동일하므로, 설명을 생략하겠습니다.

const request_json = require("request-json");



// 인앱 서버 주소.
const serverAddr = "https://sandbox.itunes.apple.com/verifyReceipt";
// const serverAddr = "https://buy.itunes.apple.com/verifyReceipt";

// 구매 영수증 주소.
const receipt = "MIIT9gYJKoZIhvcNAQcCoIIT5zCCE+MCAQExCzAJBgUrDgMCGgUAMIIDlwYJKoZIhvcNAQcBoIIDiASCA4QxggOAMAoCAQgCAQEEAhYAMAoCARQCAQEEAgwAMAsCAQECAQEEAwIBADALAgEDAgEBBAMMATEwCwIBCwIBAQQDAgEAMAsCAQ4CAQEEAwIBajALAgEPAgEBBAMCAQAwCwIBEAIBAQQDAgEAMAsCARkCAQEEAwIBAzAMAgEKAgEBBAQWAjQrMA0CAQ0CAQEEBQIDAdWIMA0CARMCAQEEBQwDMS4wMA4CAQkCAQEEBgIEUDI1MDAYAgEEAgECBBD4U0aQ9W0JB7xsSYEF+K+nMBsCAQACAQEEEwwRUHJvZHVjdGlvblNhbmRib3gwHAIBBQIBAQQUB3q751g4keQaTiLI8hJ8YuynkB4wHgIBDAIBAQQWFhQyMDE5LTA0LTI0VDA0OjQ3OjAxWjAeAgESAgEBBBYWFDIwMTMtMDgtMDFUMDc6MDA6MDBaMDACAQICAQEEKAwmY29tLnRpc3RvcnkubmljZ29vbi5pbmFwcC5uaWNnb29uaW5hcHAwVwIBBgIBAQRP+yKlGYRozYzYrwF6gQW7p4wrEAsLCr6yFfQV7+pRThnKk+lcIDvpWT2W/B2bc6JYBJ0lxZauXl9fkAAdSKcwLsDuIQ0hIavQf9YdMgaWeDBfAgEHAgEBBFd6ekMIrD9FiebvSUpyFmvb6xp6JgBCWFZxgpCLar2jamNiOfkTacHpX+eYoSYads7OBphTeZ+u1fJxJZ3llH2HNCS9KCtyxJEKe+7aZd+2Q8VdLddOvncwggFMAgERAgEBBIIBQjGCAT4wCwICBqwCAQEEAhYAMAsCAgatAgEBBAIMADALAgIGsAIBAQQCFgAwCwICBrICAQEEAgwAMAsCAgazAgEBBAIMADALAgIGtAIBAQQCDAAwCwICBrUCAQEEAgwAMAsCAga2AgEBBAIMADAMAgIGpQIBAQQDAgEBMAwCAgarAgEBBAMCAQEwDAICBq4CAQEEAwIBADAMAgIGrwIBAQQDAgEAMAwCAgaxAgEBBAMCAQAwEgICBqYCAQEECQwHZ29sZDEwMDAbAgIGpwIBAQQSDBAxMDAwMDAwNTIyMTY1NTczMBsCAgapAgEBBBIMEDEwMDAwMDA1MjIxNjU1NzMwHwICBqgCAQEEFhYUMjAxOS0wNC0yNFQwNDo0NzowMVowHwICBqoCAQEEFhYUMjAxOS0wNC0yNFQwNDo0NzowMVqggg5lMIIFfDCCBGSgAwIBAgIIDutXh+eeCY0wDQYJKoZIhvcNAQEFBQAwgZYxCzAJBgNVBAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTUxMTEzMDIxNTA5WhcNMjMwMjA3MjE0ODQ3WjCBiTE3MDUGA1UEAwwuTWFjIEFwcCBTdG9yZSBhbmQgaVR1bmVzIFN0b3JlIFJlY2VpcHQgU2lnbmluZzEsMCoGA1UECwwjQXBwbGUgV29ybGR3aWRlIERldmVsb3BlciBSZWxhdGlvbnMxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApc+B/SWigVvWh+0j2jMcjuIjwKXEJss9xp/sSg1Vhv+kAteXyjlUbX1/slQYncQsUnGOZHuCzom6SdYI5bSIcc8/W0YuxsQduAOpWKIEPiF41du30I4SjYNMWypoN5PC8r0exNKhDEpYUqsS4+3dH5gVkDUtwswSyo1IgfdYeFRr6IwxNh9KBgxHVPM3kLiykol9X6SFSuHAnOC6pLuCl2P0K5PB/T5vysH1PKmPUhrAJQp2Dt7+mf7/wmv1W16sc1FJCFaJzEOQzI6BAtCgl7ZcsaFpaYeQEGgmJjm4HRBzsApdxXPQ33Y72C3ZiB7j7AfP4o7Q0/omVYHv4gNJIwIDAQABo4IB1zCCAdMwPwYIKwYBBQUHAQEEMzAxMC8GCCsGAQUFBzABhiNodHRwOi8vb2NzcC5hcHBsZS5jb20vb2NzcDAzLXd3ZHIwNDAdBgNVHQ4EFgQUkaSc/MR2t5+givRN9Y82Xe0rBIUwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBSIJxcJqbYYYIvs67r2R1nFUlSjtzCCAR4GA1UdIASCARUwggERMIIBDQYKKoZIhvdjZAUGATCB/jCBwwYIKwYBBQUHAgIwgbYMgbNSZWxpYW5jZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRlIHBvbGljeSBhbmQgY2VydGlmaWNhdGlvbiBwcmFjdGljZSBzdGF0ZW1lbnRzLjA2BggrBgEFBQcCARYqaHR0cDovL3d3dy5hcHBsZS5jb20vY2VydGlmaWNhdGVhdXRob3JpdHkvMA4GA1UdDwEB/wQEAwIHgDAQBgoqhkiG92NkBgsBBAIFADANBgkqhkiG9w0BAQUFAAOCAQEADaYb0y4941srB25ClmzT6IxDMIJf4FzRjb69D70a/CWS24yFw4BZ3+Pi1y4FFKwN27a4/vw1LnzLrRdrjn8f5He5sWeVtBNephmGdvhaIJXnY4wPc/zo7cYfrpn4ZUhcoOAoOsAQNy25oAQ5H3O5yAX98t5/GioqbisB/KAgXNnrfSemM/j1mOC+RNuxTGf8bgpPyeIGqNKX86eOa1GiWoR1ZdEWBGLjwV/1CKnPaNmSAMnBjLP4jQBkulhgwHyvj3XKablbKtYdaG6YQvVMpzcZm8w7HHoZQ/Ojbb9IYAYMNpIr7N4YtRHaLSPQjvygaZwXG56AezlHRTBhL8cTqDCCBCIwggMKoAMCAQICCAHevMQ5baAQMA0GCSqGSIb3DQEBBQUAMGIxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpBcHBsZSBJbmMuMSYwJAYDVQQLEx1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEWMBQGA1UEAxMNQXBwbGUgUm9vdCBDQTAeFw0xMzAyMDcyMTQ4NDdaFw0yMzAyMDcyMTQ4NDdaMIGWMQswCQYDVQQGEwJVUzETMBEGA1UECgwKQXBwbGUgSW5jLjEsMCoGA1UECwwjQXBwbGUgV29ybGR3aWRlIERldmVsb3BlciBSZWxhdGlvbnMxRDBCBgNVBAMMO0FwcGxlIFdvcmxkd2lkZSBEZXZlbG9wZXIgUmVsYXRpb25zIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyjhUpstWqsgkOUjpjO7sX7h/JpG8NFN6znxjgGF3ZF6lByO2Of5QLRVWWHAtfsRuwUqFPi/w3oQaoVfJr3sY/2r6FRJJFQgZrKrbKjLtlmNoUhU9jIrsv2sYleADrAF9lwVnzg6FlTdq7Qm2rmfNUWSfxlzRvFduZzWAdjakh4FuOI/YKxVOeyXYWr9Og8GN0pPVGnG1YJydM05V+RJYDIa4Fg3B5XdFjVBIuist5JSF4ejEncZopbCj/Gd+cLoCWUt3QpE5ufXN4UzvwDtIjKblIV39amq7pxY1YNLmrfNGKcnow4vpecBqYWcVsvD95Wi8Yl9uz5nd7xtj/pJlqwIDAQABo4GmMIGjMB0GA1UdDgQWBBSIJxcJqbYYYIvs67r2R1nFUlSjtzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFCvQaUeUdgn+9GuNLkCm90dNfwheMC4GA1UdHwQnMCUwI6AhoB+GHWh0dHA6Ly9jcmwuYXBwbGUuY29tL3Jvb3QuY3JsMA4GA1UdDwEB/wQEAwIBhjAQBgoqhkiG92NkBgIBBAIFADANBgkqhkiG9w0BAQUFAAOCAQEAT8/vWb4s9bJsL4/uE4cy6AU1qG6LfclpDLnZF7x3LNRn4v2abTpZXN+DAb2yriphcrGvzcNFMI+jgw3OHUe08ZOKo3SbpMOYcoc7Pq9FC5JUuTK7kBhTawpOELbZHVBsIYAKiU5XjGtbPD2m/d73DSMdC0omhz+6kZJMpBkSGW1X9XpYh3toiuSGjErr4kkUqqXdVQCprrtLMK7hoLG8KYDmCXflvjSiAcp/3OIK5ju4u+y6YpXzBWNBgs0POx1MlaTbq/nJlelP5E3nJpmB6bz5tCnSAXpm4S6M9iGKxfh44YGuv9OQnamt86/9OBqWZzAcUaVc7HGKgrRsDwwVHzCCBLswggOjoAMCAQICAQIwDQYJKoZIhvcNAQEFBQAwYjELMAkGA1UEBhMCVVMxEzARBgNVBAoTCkFwcGxlIEluYy4xJjAkBgNVBAsTHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRYwFAYDVQQDEw1BcHBsZSBSb290IENBMB4XDTA2MDQyNTIxNDAzNloXDTM1MDIwOTIxNDAzNlowYjELMAkGA1UEBhMCVVMxEzARBgNVBAoTCkFwcGxlIEluYy4xJjAkBgNVBAsTHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRYwFAYDVQQDEw1BcHBsZSBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5JGpCR+R2x5HUOsF7V55hC3rNqJXTFXsixmJ3vlLbPUHqyIwAugYPvhQCdN/QaiY+dHKZpwkaxHQo7vkGyrDH5WeegykR4tb1BY3M8vED03OFGnRyRly9V0O1X9fm/IlA7pVj01dDfFkNSMVSxVZHbOU9/acns9QusFYUGePCLQg98usLCBvcLY/ATCMt0PPD5098ytJKBrI/s61uQ7ZXhzWyz21Oq30Dw4AkguxIRYudNU8DdtiFqujcZJHU1XBry9Bs/j743DN5qNMRX4fTGtQlkGJxHRiCxCDQYczioGxMFjsWgQyjGizjx3eZXP/Z15lvEnYdp8zFGWhd5TJLQIDAQABo4IBejCCAXYwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFCvQaUeUdgn+9GuNLkCm90dNfwheMB8GA1UdIwQYMBaAFCvQaUeUdgn+9GuNLkCm90dNfwheMIIBEQYDVR0gBIIBCDCCAQQwggEABgkqhkiG92NkBQEwgfIwKgYIKwYBBQUHAgEWHmh0dHBzOi8vd3d3LmFwcGxlLmNvbS9hcHBsZWNhLzCBwwYIKwYBBQUHAgIwgbYagbNSZWxpYW5jZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRlIHBvbGljeSBhbmQgY2VydGlmaWNhdGlvbiBwcmFjdGljZSBzdGF0ZW1lbnRzLjANBgkqhkiG9w0BAQUFAAOCAQEAXDaZTC14t+2Mm9zzd5vydtJ3ME/BH4WDhRuZPUc38qmbQI4s1LGQEti+9HOb7tJkD8t5TzTYoj75eP9ryAfsfTmDi1Mg0zjEsb+aTwpr/yv8WacFCXwXQFYRHnTTt4sjO0ej1W8k4uvRt3DfD0XhJ8rxbXjt57UXF6jcfiI1yiXV2Q/Wa9SiJCMR96Gsj3OBYMYbWwkvkrL4REjwYDieFfU9JmcgijNq9w2Cz97roy/5U2pbZMBjM3f3OgcsVuvaDyEO2rpzGU+12TZ/wYdV2aeZuTJC+9jVcZ5+oVK3G72TQiQSKscPHbZNnF5jyEuAF1CqitXa5PzQCQc3sHV1ITGCAcswggHHAgEBMIGjMIGWMQswCQYDVQQGEwJVUzETMBEGA1UECgwKQXBwbGUgSW5jLjEsMCoGA1UECwwjQXBwbGUgV29ybGR3aWRlIERldmVsb3BlciBSZWxhdGlvbnMxRDBCBgNVBAMMO0FwcGxlIFdvcmxkd2lkZSBEZXZlbG9wZXIgUmVsYXRpb25zIENlcnRpZmljYXRpb24gQXV0aG9yaXR5AggO61eH554JjTAJBgUrDgMCGgUAMA0GCSqGSIb3DQEBAQUABIIBADdl8DvcNx2iAwErd6D2AK38WCuCOI0Rj+WFMWQKDbLYbwuJy41BsvsG9BcOzqBVhoXwxiy+YsUwQHgJ+VwMcnqo1e0U1wVlns/pdHUfpfy5SWRRAMGFbNjon6iaKBmYv2fYghrvLxH7L6NgkJNeU0Ye3E+QzkDjyz/SCSfE6KAbh+nh94H9OdJ7MBDsbc8ilZuvoePnhxSWKSIr+V0F4F1bUU2G99K6IbajlXmWRy+i+UliJnFYvGgwxJIug0ubktGYmaKxCHVkLVadX7NIg/DsuWcmFTmm+Gj103Emb50ohc/DhEZGcoPvAo4cLYrPrGugfFtyZNqSuQGPSt1l62M=";



const host = "https://sandbox.itunes.apple.com";
const path = "/verifyReceipt";



const receiptJson = {
    'receipt-data': receipt
};



var client = request_json.createClient( host );
client.post( path, receiptJson, function( err, res, body ){

    if( err )
    {
        console.error( err );
        return;
    }

    console.log( JSON.stringify( body ) );

});

 

7. Restore

앱을 지우거나 했을 때, 기존의 결제 내역을 복구하는 기능입니다. 저 같은 경우 서버에서, 결제 검증을 하고, 데이터를 가지고 있을 것 이므로, 필요하지 않아 구현하지 않았습니다.

 

 

 

Posted by 창업닉군
,