플랫폼별 사전등록 보상 지급하기 (Unity3D)
# Introduce
사전등록이란 등록된 앱이 프로덕션으로 제출되기 이전에
'정식 출시되면 다운로드를 하겠습니다.' 하고 예약하는 기능을 의미합니다.
출시 이전에 미리 다운로드를 예약 할 수 있다는 점에서 모바일 생명주기에 가장 중요하다고 볼 수 있는
초반 일주일에 대한 유저 유입량을 보다 더 효과적으로 확보 할 수 있는 방법입니다.
위와 같은 이유만으로도 사전등록은 충분한 강점을 가지고 있지만, 우리는 추가적으로 소정의 상품을 내걸음으로써 훨씬 더 매력적인 사전등록의 의미를 만들어 낼 수 있습니다.
오늘은 이러한 사전등록 보상을 구현하는 방법에 대한 내용을 공유하려 합니다.
다음의 내용은 UnityIAP를 사용함에 있어 이해가 충분하다 가정하고 제작되었습니다.
# For example
1. 안드로이드와 iOS의 처리방식을 동일하게 가져 갈 수 없다.
플랫폼별로 제공되는 사전등록에 대한 보상 처리 방식을 간단하게 정리하면 다음과 같습니다.
<Android> 관리자가 사전등록 보상으로 지정한 인앱상품 구매 영수증이 Pending상태로 추가됩니다. <iOS> 사전등록에 관련된 필드가 포함된 앱 구매 영수증이 추가됩니다. 추가적인 정보를 확인하시려면 Reference항목을 확인하세요. |
앱구매와 인앱상품 구매는 확연히 다른 성격의 영수증입니다.
따라서 두 플랫폼간의 처리방식도 다른 것이 당연합니다.
다만 호환성이 뛰어난 유니티니까 UnityIAP만으로도 해결이 가능하지 않을까 라는 기대를 해보긴 했지만 (사전등록 '보상' 이니까..)
아쉽게도 사전등록에 대한 특별한 지원은 없는것으로 확인되었습니다.
그렇다면 혹시 UnityIAP만을 이용하여 성격이 다른 두가지 영수증을 확보 할 수는 있는지 확인해봤지만
이것 역시 아쉽게도 불가능했습니다. 바로 UnityIAP의 영수증 취급 범위 때문인데요.
UnityIAP는 인앱상품에 대한 API이기 때문에 앱 구매 영수증에 대한 처리는 하지 않습니다.
따라서 인앱상품이 아닌 앱 구매 영수증에 사전등록 정보를 포함하는
iOS의 경우에는 UnityIAP에서 처리 할 수 없게 됩니다.
그러나 다행스럽게도(?) 안드로이드의 경우에는 콘솔에서 정의된 인앱상품을 제공하는 방식이기 때문에 UnityIAP로 처리가 가능했습니다.
결과적으로 안드로이드는 UnityIAP를 이용하였고, iOS는 자체적인 영수증검증을 구현하였다고 볼 수 있습니다.
2. 안드로이드에서의 처리 방법.
안드로이드의 경우 사전등록이 된 앱 설치시 지정된 인앱상품이 Pending상태로 추가됩니다.
또한 UnityIAP는 IStoreListener에 의해 앱 실행시 초기화가 진행되는데,
이 때 Pending상품에 대한 콜백이 발생합니다.
따라서 안드로이드의 경우 UnityIAP가 적절히 구현되어 있다면 특별한 처리 없이도 사전등록 보상은 가능합니다.
다만 이것이 사전등록 상품임을 나타내기 위해 아래와 같이 다른 인앱상품과 UI를 따로 처리해 주는 것이 좋겠습니다.
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
{
if (string.Equals(args.purchasedProduct.definition.id, "사전등록 보상 인앱ID")) {
//사전등록 인앱으로 확인됨.
return PurchaseProcessingResult.Pending;
}
...
return PurchaseProcessingResult.Complete;
}
앱 재설치시에도 언제든지 다시 수령이 가능하도록 Pending으로 처리하여 영수증을 보존시키도록 합니다.
다만 이렇게 될 경우 앱 실행시마다 항상 사전등록 보상이 진행되므로, 내부적으로 다음과 같이 방지해야 합니다.
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
{
if (Playerprefs.GetInt("ReceivePreOrderReward", 0) <= 0 && string.Equals(args.purchasedProduct.definition.id, "사전등록 보상으로 지정된 인앱 ID")) {
//사전등록 인앱으로 확인됨.
//사전 등록 보상 지급.
Playerprefs.SetInt("ReceivePreOrderReward", 1);
return PurchaseProcessingResult.Pending;
}
...
return PurchaseProcessingResult.Complete;
}
상품 재수령 방지 방법은 자유롭게 하셔도 됩니다.
자체적으로 데이터를 백엔드에 백업하시는분은 Playerprefs보다 더 좋은 다른 방법을 사용하세요.
3. iOS에서의 처리 방법.
iOS의 경우 사전등록이 된 앱 설치 시 사전등록에 관한 필드가 포함된 앱 구매 영수증이 발행됩니다.
우리는 그 앱 구매 영수증에대해 접근을 할 필요가 있습니다.
따라서 아래와 같은 Objective-C 코드를 통해 네이티브에서 영수증을 확인해줍니다. (Swift로도 가능합니다.)
// Get the receipt if it's available
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
if (!receipt) {
NSLog(@"-------------no receipt---------------");
/* No local receipt -- handle the error. */
} else {
/* Get the receipt in encoded format */
NSString *encodedReceipt = [receipt base64EncodedStringWithOptions:0];
}
encodedReceipt는 앱이 보유하고있는 모든 영수증을 나타내는 암호화된 String 입니다.
이 String값을 애플 서버로 영수증 검증 요청을 보내야 합니다.
영수증 검증 작업은 백엔드에서 구현하는것이 정석이지만 이번엔 간단히 유니티에서 구현 해 보겠습니다.
일단 네이티브에서 추출된 영수증 String값을 유니티로 보내야 합니다. UnitySendMessage를 이용해주도록 하겠습니다.
자세한 구현 방법은 Unity3D iOS Plugin 구현방법을 참고하시면 됩니다.
extern "C"
{
void GetReceipt(const char *objectName, const char *callbackMethodName) //영수증 찾기.
{
// Get the receipt if it's available
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
if (!receipt) {
NSLog(@"-------------no receipt---------------");
/* No local receipt -- handle the error. */
} else {
/* Get the receipt in encoded format */
NSString *encodedReceipt = [receipt base64EncodedStringWithOptions:0];
UnitySendMessage(objectName, callbackMethodName, [encodedReceipt UTF8String]);
}
}
}
[DllImport("__Internal")]
private static extern void GetReceipt(string objName, string methodName);
public void CheckPreOrderReward()
{
GetReceipt(gameObject.name, "CheckReceiptCallback");
}
public void GetReceiptCallback(string encodeReceiptString)
{
Debug.Log("네이티브에서 추출한 암호화된 영수증 값" + encodeReceiptString);
}
위와 같이 구현하여 유니티의 GetReceiptCallback(string encodeReceiptString) 함수에서 암호화된 영수증값을 받아왔습니다.
이제 이 암호화된 영수증값을 UnityWebRequest를 사용하여 POST 방식으로 애플의 영수증 검증 서버에 넘겨줍니다.
애플 영수증검증 서버의 URI는 샌드박스모드와 라이브모드 두가지로 나뉘게 됩니다. 환경에 맞춰서 사용하시면 되겠습니다.
제 경우에는 Hashtable과 JSON의 변환이 가능한 라이브러리를 사용하고 있지만,
JSON을 사용 할 수 있는 다른 방법이 있다면 사용하셔도 좋습니다.
주의! : encodeReceiptString 값이 들어갈 json의 키값은 반드시 "receipt-data" 여야 합니다.
public void GetReceiptCallback(string encodeReceiptString)
{
//애플 서버에 전달 될 영수증 정보가 포함된 JSON.
string json = "{\"receipt-data\":\"" + encodeReceiptString + "\"}";
//전달 할 URI.
string uri = IsSandbox ? "https://sandbox.itunes.apple.com/verifyReceipt" : "https://buy.itunes.apple.com/verifyReceipt";
POST(uri, json, (string error, string responseText) =>
{
if (string.IsNullOrEmpty(error) && !string.IsNullOrEmpty(responseText))
{
Hashtable result = Procurios.Public.JSON.JsonDecode(responseText) as Hashtable;
if (result != null && result.ContainsKey("receipt"))
{
//받아온 JSON값중 receipt 필드를 Hashtable 형태로 변환하였음.
Hashtable receipt = result["receipt"] as Hashtable;
}
}
});
}
void POST(string uri, string JSONBody, Action<string, string> callback)
{
UnityWebRequest request = new UnityWebRequest(uri, "POST");
byte[] jsonToSend = new UTF8Encoding().GetBytes(JSONBody);
request.uploadHandler = new UploadHandlerRaw(jsonToSend);
request.SetRequestHeader("Content-Type", "application/json");
StartCoroutine(WaitForRequest(request, callback));
}
IEnumerator WaitForRequest(UnityWebRequest request, Action<string, string> callback)
{
request.downloadHandler = new DownloadHandlerBuffer();
yield return request.SendWebRequest();
callback(request.error, request.downloadHandler.text);
}
추출된 receipt값에서 사전등록임을 확인 할 수 있는 필드가 포함되어있는지 확인 해 줍니다.
사전등록을 확인 할 수 있는 필드는 preorder_date, preorder_date_ms, preorder_date_pst 이렇게 3가지 필드입니다.
사전등록이 아닌 앱 구매 영수증의 경우에는 preorder_date_ms 필드가 포함되지 않기 때문에, preorder_date_ms 필드의 존재 여부로 사전등록을 진행한 앱인지 아닌지 판별이 가능합니다. (오피셜입니다!)
//받아온 JSON값을 Hashtable 형태로 변환하였음.
Hashtable receipt = Procurios.Public.JSON.JsonDecode(result["receipt"].ToString()) as Hashtable;
if (receipt.ContainsKey("preorder_date_ms"))
{
//사전 등록 보상 지급.
}
보상 지급시에 Playerprefs 등을 활용하여 앱에 사전등록 보상 받았음을 체크해 줍니다.
if (receipt.ContainsKey("preorder_date_ms"))
{
//사전 등록 보상 지급.
Playerprefs.SetInt("ReceivePreOrderReward", 1);
}
하나의 코드로 정리하면 다음과 같습니다.
[DllImport("__Internal")]
private static extern void GetReceipt(string objName, string methodName);
public void CheckPreOrderReward()
{
if(Playerprefs.GetInt("ReceivePreOrderReward", 0) > 0)
return;
GetReceipt(gameObject.name, "CheckReceiptCallback");
}
public void GetReceiptCallback(string encodeReceiptString)
{
//애플 서버에 전달 될 영수증 정보가 포함된 JSON.
string json = "{\"receipt-data\":\"" + encodeReceiptString + "\"}";
//전달 할 URI.
string uri = IsSandbox ? "https://sandbox.itunes.apple.com/verifyReceipt" : "https://buy.itunes.apple.com/verifyReceipt";
POST(uri, json, (string error, string responseText) =>
{
if (string.IsNullOrEmpty(error) && !string.IsNullOrEmpty(responseText))
{
Hashtable result = Procurios.Public.JSON.JsonDecode(responseText) as Hashtable;
if (result != null && result.ContainsKey("receipt"))
{
//받아온 JSON값중 receipt 필드를 Hashtable 형태로 변환하였음.
Hashtable receipt = result["receipt"] as Hashtable;
if (receipt.ContainsKey("preorder_date_ms"))
{
//사전 등록 보상 지급.
Playerprefs.SetInt("ReceivePreOrderReward", 1);
}
}
}
});
}
void POST(string uri, string JSONBody, Action<string, string> callback)
{
UnityWebRequest request = new UnityWebRequest(uri, "POST");
byte[] jsonToSend = new UTF8Encoding().GetBytes(JSONBody);
request.uploadHandler = new UploadHandlerRaw(jsonToSend);
request.SetRequestHeader("Content-Type", "application/json");
StartCoroutine(WaitForRequest(request, callback));
}
IEnumerator WaitForRequest(UnityWebRequest request, Action<string, string> callback)
{
request.downloadHandler = new DownloadHandlerBuffer();
yield return request.SendWebRequest();
callback(request.error, request.downloadHandler.text);
}
이렇게 작성하게 되면 CheckPreOrderReward() 함수를 실행 할 때 사전등록 보상 수령 여부에 따라 보상을 지급 할 수 있게 됩니다.
# Comment
앞서 설명된 사전등록 구별 방법에 대한 내용은 모두 공식문서에서 권장하는 방법입니다.
추가적으로 UnityIAP 연동과, UnityWebRequest, Http프로토콜, iOS 네이티브 연동에 관한 기본적인 지식이 필요합니다.
# Epilogue
사전 등록 보상은 사전등록이라는 강력한 장치에 힘을 실어주는 매력적인 방법입니다.
간단하게 구현 할 수 있으므로 사전등록 진행 시 이를 활용하여 더 효과있는 마케팅이 되시길 바라겠습니다.
감사합니다.
# Reference
https://docs.unity3d.com/kr/2018.4/Manual/UnityIAPProcessingPurchases.html
https://developer.apple.com/documentation/appstorereceipts/responsebody/receipt
https://support.google.com/googleplay/android-developer/answer/9084187?hl=ko
https://developer.apple.com/kr/app-store/pre-orders/