겨울팥죽 여름빙수
article thumbnail

1. 구글 개발자 콘솔에서 인증서 발급하고 다운받기(json 키)

 

 

위 이미지 대로,

 

[구글 플레이 콘솔 api액세스 -> 새서비스계정만들기 -> 서비스계정 생성 -> 계정 key다운로드(json파일) -> 재무데이터 권한 주기]

 

를 완료하면 준비는 끝.!!

 

키 json 파일을 다운받아 열어보면, 위와같은 텍스트 들이 있다.

 

2. Firebase Functions 설정

Firebase 계정을 만들고, 내 컴퓨터에도 firefunction기능을 쓸 수 있도록 세팅. 세팅은 구글페이지 가이드를 참고 바람.

https://firebase.google.com/docs/functions/get-started?hl=ko 

 

시작하기: 첫 번째 함수 작성, 테스트 및 배포  |  Firebase Documentation

Check out what’s new from Firebase at Google I/O 2022. Learn more 의견 보내기 시작하기: 첫 번째 함수 작성, 테스트 및 배포 Cloud Functions를 시작하려면 이 튜토리얼을 따라해 보세요. 먼저 필수 설정 작업을 설

firebase.google.com

 

 

{
  "name": "functions",
  "description": "Cloud Functions for Firebase",
  "scripts": {
    "serve": "firebase emulators:start --only functions",
    "shell": "firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "engines": {
    "node": "16"
  },
  "main": "index.js",
  "dependencies": {
    "firebase-admin": "^9.8.0",
    "firebase-functions": "^3.14.1",
    "iap": "^1.1.1",
  },
  "devDependencies": {
    "firebase-functions-test": "^0.2.0"
  },
  "private": true
}

세팅한 firebase 디렉토리/functions/package.json에 iap모듈을 추가한다.

"iap":"^1.1.1"

해달 모듈 사용법은 아래 링크를 참고해주세요.

https://www.npmjs.com/package/iap

 

iap

In-app purchase validation for Apple, Google, Amazon, Roku. Latest version: 1.1.1, last published: 2 years ago. Start using iap in your project by running `npm i iap`. There are 9 other projects in the npm registry using iap.

www.npmjs.com

 

 

3.  함수 작성(index.js)

index.js를 열어 아래와 같이 작성한다. 여기서는 onRequest가 아닌 onCall을 이용해 함수를 구현했다.

onRequest는 http통신방식으로 function을 이용할 수 있고,

onCall는 유니티 firefunction라이브러리 함수로 간단하게 함수를 호출 할 수 있다. 대신 인앱 검증이 비동기 함수라, 서버에서 받아 올 수 없다.(있겠지만 귀찮아서 안함)

만약 클라이언트에서 인앱검증 실패에대한 처리를 따로 하고 싶다면 onRequest방식으로 구현하길 바란다.

 

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const iap = require('iap');
admin.initializeApp(functions.config().firebase);

//이미 검증한 영수증인지 확인
function checkTranscationId(transactionId) {
    return new Promise((resolve, reject) => {
        admin.firestore()
        .collection('iap')
        .doc(transactionId)
        .get()
        .then(doc => {
            if (doc.exists) {
                reject(new Error("Duplicate Transaction"));
            }
            else {
                resolve();
            }
            return Promise.resolve();
        })
        .catch((error) => {
            reject(error)
        });
    });
}

//검증할 영수증 firestore에 기록
function saveTransactionId(transactionId, key) {
    admin.firestore()
    .collection('iap')
    .doc(transactionId)
    .set({"exist": true,
        "user_key":key
    });

    return Promise.resolve();
}


exports.verifyInAppPurchase = functions.https.onCall((data, context)  => {
    let user_key = data.user_key;
    let date_time = data.date_time;
    let transactionId = data.transactionId;
    
    let payment = {
        receipt:  data.purchaseToken,
        productId: data.productId,
        packageName: data.packageName,
        subscription: false,

		//다운로드 받은 key json파일에서 참고
        keyObject: {
            "type": "service_account",
            "project_id": "{...}",
            "private_key_id": "{...}",
            "private_key": "-----BEGIN PRIVATE KEY-----\{...}\n-----END PRIVATE KEY-----\n",
            "client_email": "{...}",
            "client_id": "{...}",
            "auth_uri": "https://accounts.google.com/o/oauth2/auth",
            "token_uri": "https://oauth2.googleapis.com/token",
            "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
            "client_x509_cert_url": "{...}"
          }
      };
      
    
    
    return checkTranscationId(transactionId)
    .then(() => saveTransactionId(transactionId,user_key))
    .then(() =>iap.verifyPayment("google", payment, function(error, response) {
        if(error == null && response != null)
        {
        	//성공 처리
            console.log('success');
		
        }
        else
        {
        	//실패 처리. firestore에 해당 유저의 계정을 저장
            console.log('failed : ' + error);
            admin.firestore()
            .collection('IapFault')
            .add({
                "user_key": user_key,
                "date_time" : date_time,
                "transactionId" : transactionId,
                "productId" : data.productId
            });

        }
      }))
    .catch((error) =>{
        //검증 실패, 이미 검수한 영수증인 경우 처리
        console.log('end error : '+error);  
        
        //해당 유저정보 저장
        admin.firestore()
            .collection('IapFault')
            .add({
                "user_key": user_key,
                "date_time" : date_time,
                "transactionId" : transactionId,
                "productId" : data.productId
            });
    } );
});

 

 

4. Client(Unity)에서  Function 호출

public class Receipt
{
    //        {
    //    packageName: 'The packge name of the item purchased',
    //    productId: 'The product ID of the item purchased',
    //    purchaseToken: 'PurchaseToken of the receipt from Google',
    //    subscription: true/false // if the receipt is a subscription, then true
    //}
    public string packageName;
    public string productId;
    public string purchaseToken;        
    public bool subscription;
}


public void IapVerify(string user_key, string type, string transactionId, Receipt receipt)
{
    var data = new Dictionary<string, object>();
    data["type"] = type;
    data["transactionId"]   = transactionId;

    data["purchaseToken"] 	= receipt.purchaseToken;
    data["productId"]       = receipt.productId;
    data["packageName"]     = receipt.packageName;
    data["user_key"]        = user_key;
    data["date_time"]       = DateTime.Now.ToLocalTime().ToString("yyyy'-'MM'-'dd' 'HH':'mm':'ss");


    var function = m_instance.GetHttpsCallable("verifyInAppPurchase");
    function.CallAsync(data).ContinueWith((task) => {
    	//클라이언트에서 인앱 검증에 대한 정보를 따로 받아오지 않음..
        if (task.IsFaulted || task.IsCanceled)
        {

        }
        else
        {

        }
    });
}

 

다시 말하지만, 위 Function호출은 onRequest가 아니라서, 검증 실패/성공에 대한 정보를 받을 수 없다. 

profile

겨울팥죽 여름빙수

@여름빙수

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!