Overview
com.111percent.gamebase.scripting.percent-purchase
Unity IAP를 이용하여 결제 검증하는 기능을 제공하는 패키지 입니다.
- Unity IAP를 좀 더 쉽게 사용할 수 있도록 단순한 API를 제공합니다.
- 구매
- Pending 상품 처리
- 복원
- 결제 검증 서버가 없을 경우 로컬에서 결제를 검증할 수 있는 기능을 제공합니다.
- 회사 공용 결제 검증 서버를 통해 결제를 검증할 수 있는 기능을 제공합니다.
모듈 초기화
기존에는 로그인 이후에 이루어지도록 가이드 드렸으나, 로그인 이전에 호출해도 동작하도록 수정하였습니다.
public async UniTask Initialize()
{
if (PercentConfig.Platform.IsAndroid || PercentConfig.Platform.IsIOS)
{
// 프로젝트마다 데이터를 가져오는 코드 구조가 달라서 ShopProductList로 대체합니다.
PercentProductDefinition[] definitions = new PercentProductDefinition[ShopProductList.count];
// googleProductId / AppleProductId가 존재하는 데이터들을 모두 담습니다.
foreach (var item in ShopProductList)
{
var inAppId = item.GoogleProductId;
if (Application.platform == RuntimePlatform.IPhonePlayer)
inAppId = item.AppleProductId;
definitions[index++] = new PercentProductDefinition(id: inAppId, type: item.ProductType);
}
var result = await PercentPurchasing.Initialize(definitions);
}
}
구매하기
구매 → 영수증 검증 → Product Confirm 단계의 로직입니다.
// Perbase V2를 사용하지 않는 프로젝트의 구매 방식
public async UniTask<bool> Purchase(string stringId)
{
// ------------------ 테이블 받아와서 productId 가공 -----------------------
// ShopProductList에서 stringId를 기반으로 데이터를 가져옵니다.
var tableData = ShopProductList.GetData(stringId);
// 받아온 tableData에서 productId를 가져옵니다.
var productInAppId = tableData.GoogleProductId;
if (Application.platform == RuntimePlatform.IPhonePlayer)
{
productInAppId = tableData.AppleProductId;
}
// ------------------ 테이블 받아와서 productId 가공 -----------------------
**// ⭐️ 구매 시작, 네이티브 구매 팝업 출력**
**var purchaseResult = await PercentPurchasing.Purchase(productInAppId);**
if (purchaseResult.Success is false)
{
// 아래 항목들로 에러를 확인할 수 있습니다.
Debug.Log($"Reason : {purchaseResult.FailureReason}, ErrorMessage : {purchaseResult.ErrorMessage}");
return false;
}
// 안드로이드 / 구글 측에서 진행하는 결제가 완료되었습니다. 현재 상품은 **Pending** 상태.
var receiptData = new ProductTableData(purchaseResult, (IShopProductList)tableData);
var cashbuy_eventLog = [예시 클릭](https://www.notion.so/Percent-Purchase-51750aac02b94d3fae9239371a49f4f0?pvs=21)
// eventLogData는 없거나 테스트할 목적의 경우라면 null로 넘겨주세요.
**// ⭐️ 영수증 검증 진행. 검증이 Success일 경우 VerificationReceipt 내부에서 Appsflyer EventLog를 자동으로 보냅니다.**
**var verificationResult = await PercentPurchasing.VerificationReceipt(receiptData, cashbuy_eventLog);
// 영수증 검증 중 통신 에러가 났을 경우**
if (verificationResult.IsNetworkError)
{
// 에러 처리
return false;
}
****
if (verificationResult.Success)
{
// 서버에 영수증 검증까지 완료되었습니다. 이 시점에 아이템을 지급하시면 됩니다.
GetItem(purchaseResult);
///
**PercentPurchasing.ConfirmPurchase(purchaseResult.Product);
// ⭐️ 위 코드를 거치면 상품은 '청구됨' 처리가 되며, 결제 로직이 완료됩니다.**
return true;
}
else if(purchaseResult.FailureReason == PurchaseFailureReason.DuplicateTransaction)
{
// 이 로직도 꼭 추가해주세요!
**// 결제 하는 도중 앱을 종료 한 경우, 앱을 다시 실행하여 동일한 상품 구매 시
// 앱에서는 "이미 보유한 상품입니다." 라고 출력되고 Pending 상태에서 아무런 진행이 되지 않는 이유로
// 해당 메소드를 실행하여 상품을 Confirm 처리, 아이템 지급하여 결제를 완료합니다.**
await [**PendingPurchase**](https://www.notion.so/Percent-Purchase-51750aac02b94d3fae9239371a49f4f0?pvs=21)();
}
else
{
// 아래 항목들로 에러를 확인할 수 있습니다.
Debug.Log($"errorCode : {verificationResult.[resultCode](https://receipt-server-development-iqp7keuqcq-uc.a.run.app/docs)}, result : {verificationResult.result}");
return false;
}
}
Pending 처리
Pending 로직 설명 및 구현 방법
Pending 처리는 아래 두 가지의 상황 모두 구현해주세요.
- Title Scene에서 Initialize 후 Lobby Scene으로 넘어갔을 때 앱 실행마다 체크 후 Confirm 처리
- Purchase → 영수증 검증 후
PurchaseResult.FailureReason
이PurchaseFailureReason.DuplicateTransaction
상황일 경우 / 에러 코드가600
일 경우 Pending 리스트 조회 후 검증 로직 호출
앱 실행마다 체크 후 Confirm 처리 (꼭 호출 필요)
Unity IAP에서 Initialize 후 Pending 상품이 있을 경우 IStoreListener를 통해 ProcessPurchase로 데이터를 반환해주는데, 이 값을 수집하여 앱을 실행한 후 초기 단계에서 Pending 상품을 처리하는 로직입니다.
일단 Pending 상품을 받아오는 프로세스를 진행하려면 아래 사항들이 진행 된 이후여야 합니다.
- PercentPurchasing.Initialize 진행 완료
- accountId를 취득할 수 있는 상황인 경우
위 상황들이 다 진행이 된 시점인 **LobbyScene** 입장했을 때 호출을 권장합니다.
사용 예시 코드와 함께 설명드리겠습니다.
async Task PendingProcess()
{
**// Pending List를 받아옵니다.
var pendingResult = PercentPurchasing.GetPendingResultForInitialize();**
// Success가 false일 경우는 아래와 같습니다.
// 1. Initialize 되지 않았을 경우
// 2. 앱 실행 후 한번이라도 처리가 되었을 경우
// 3. Pending 처리 된 상품이 없을 경우
if (pendingResult.Success is false)
{
return;
}
// pending 할 상품이 있는 상태(true) 일 경우에만 처리합니다.
var pendingList = pendingResult.PendingPurchasedProducts;
for (int i = 0; i < pendingList.Count; i++)
{
var product = pendingList[i];
// 넘겨주는 데이터가 productId여서, productId로 TableData를 받아오는 로직이 필요합니다.
ShopProductList tableData = Application.platform switch
{
RuntimePlatform.Android => IAPManager.Instance.GetShopProductListByGoogleProductId(product.definition.id),
RuntimePlatform.IPhonePlayer => IAPManager.Instance.GetShopProductListByAppleProductId(product.definition.id),
};
var productData = new ProductTableData(product, tableData);
var verificationResult = await PercentPurchasing.VerificationReceipt(productData, null);
// 영수증 검증 중 통신 에러가 났을 경우
if (verificationResult.IsNetworkError)
{
// 에러 처리
return false;
}
// 2024.09.04 추가
// 이미 완료된 영수증이지만 Store에 Confirm 처리가 안 된 경우
// 이 처리가 없으면 영수증 검증이 Success되고 Store Confirm 처리가 안 된 경우 로비에 접근했을 때 이미 영수증 검증 및 지급을 마친 상품이 계속 넘어올 수 있습니다.
if (verificationResult.HasBeenPurchased)
{
PercentPurchasing.ConfirmPurchase(pendingItem);
continue;
}
if (verificationResult.Success is false)
{
// 영수증 검증 처리 실패
continue;
}
// 아이템 지급
// Product Confirm 처리**
PercentPurchasing.ConfirmPurchase(product);
}
}
Pending 리스트 조회 후 검증
Pending 리스트 조회 → 상품을 한 개씩 영수증 검증 → Product Confirm (모든 상품 반복) → 완료의 로직입니다.
async UniTask<bool> PendingPurchase()
{
var pendingResult = await PercentPurchasing.GetPendingPurchases();
if (pendingResult.Success is false)
{
// 아래 항목들로 에러를 확인할 수 있습니다.
Debug.Log($"Reason : {pendingResult.FailureReason}, ErrorMessage : {pendingResult.ErrorMessage}");
return false;
}
var pendingList = result.PendingPurchasedProducts;
if (list.Count == 0)
{
return true;
}
// 펜딩 상품은 영수증 검증을 한 이후에 Confirm 후 다음 상품을 처리합니다.
foreach (var pendingItem in pendingList)
{
// 받아온 리스트를 영수증 검증에 사용하는 데이터로 받아 옴
// id가 많아서 헷갈릴 수 있습니다. definition.id가 productId이기 때문에, 꼭 이 값을 넘겨주세요!**
var tableData = GetShopProductList(**pendingItem.definition.id**);
var data = new ProductTableData(pendingItem, (IShopProductList)tableData);
// 영수증 검증
var verificationResult = await PercentPurchasing.VerificationReceipt(data, null);
// 영수증 검증 중 통신 에러가 났을 경우
if (verificationResult.IsNetworkError)
{
// 에러 처리
return false;
}
// 2024.09.04 추가
// 이미 완료된 영수증이지만 Store에 Confirm 처리가 안 된 경우
// 이 처리가 없으면 영수증 검증이 Success되고 Store Confirm 처리가 안 된 경우 로비에 접근했을 때 이미 영수증 검증 및 지급을 마친 상품이 계속 넘어올 수 있습니다.
if (verificationResult.HasBeenPurchased)
{
PercentPurchasing.ConfirmPurchase(pendingItem);
continue;
}
if (verificationResult.Success)
{
// 여기서 상품 지급 로직이 필요합니다.
GetItem(verificationResult.purchaseResult);
// 성공 시 Confirm 처리
PercentPurchasing.ConfirmPurchase(pendingItem);
}
else
{
// 아래 항목들로 에러를 확인할 수 있습니다.
Debug.Log($"errorCode : {verificationResult.[resultCode](https://receipt-server-development-iqp7keuqcq-uc.a.run.app/docs)}, result : {verificationResult.result}");
}
}
return true;
}
구매 복원 처리
복원 리스트 조회 → 상품을 한 개씩 영수증 검증**(Android/iOS 별개)** → Product Confirm (모든 상품 반복) → 완료의 로직입니다.
public async UniTask<bool> Restore()
{
var result = await PercentPurchasing.RestoreTransactions();
if (result.Success is false)
{
Debug.Log($"ErrorMessage : {result.ErrorMessage}");
return false;
}
var restoreList = result.RestoredPurchasedProducts;
if (restoreList.Count == 0)
{
return true;
}
var container = Util.Table.ShopProductList;
// 구매 복원 상품은 영수증 검증을 한 이후에 Confirm 후 다음 상품을 처리합니다.
foreach (var restoreItem in restoreList)
{
// 받아온 리스트를 영수증 검증에 사용하는 데이터로 받아 옴
var tableData = GetShopProductList(restoreItem.definition.id);
var data = new ProductTableData(restoreItem, (IShopProductList)tableData);
VerificationReceiptResponse res = new VerificationReceiptResponse();
// 영수증 검증
// Restore의 경우에만 iOS에서 내부 처리 로직이 달라서 분기를 나눠 Restore 요청합니다.
if (Application.platform == RuntimePlatform.Android)
{
res = await PercentPurchasing.VerificationReceipt(data, null);
}
else if (Application.platform == RuntimePlatform.IPhonePlayer)
{
res = await PercentPurchasing.VerifyReceiptByIosRestore(data, null);
}
/**/ 영수증 검증 중 통신 에러가 났을 경우**
if (res.IsNetworkError)
{
// 에러 처리
continue;
}
if (res.Success)
{
// 여기서 상품 지급 로직이 필요합니다.
GetItem(restoreItem);
// 성공 시 Confirm 처리
PercentPurchasing.ConfirmPurchase(restoreItem);
}
}
return true;
}
cash_buy EventLog 데이터 예시
각 파라메터의 key는 name, value가 들어가야 합니다.
name과 value에 들어갈 값은 게임마다 다르니 확인이 필요합니다.
List<Dictionary<string, object>> data2 = new List<Dictionary<string, object>>() { new() { { "name", "buy_count" }, { "value", buyCount }, }, new() { { "name", "play_level" }, { "value", playLevel }, }, new() { { "name", "placement" }, { "value", placement }, }, new() { { "name", "stage_number" }, { "value", stageNumber }, } };
영수증 검증 및 에러 코드
구매, 복구, 펜딩 상품 처리 로직 시각화
Purchase
sequenceDiagram Percent Purchase->>Store(Google/Apple): 구매 시도 [**PercentPurchasing.Purchase**] Store(Google/Apple)->>Percent Purchase: 구매 결과 받음 [return PurchaseResult] Percent Purchase->>ReceiptServer: 영수증 검증 시도 [PercentPurchasing.VerificationReceipt] ReceiptServer->>Percent Purchase: 영수증 검증 성공 및 구매 성공, 아이템 지급
Pending
sequenceDiagram Percent Purchase->>Store(Google/Apple): 펜딩 상품 조회 [**PercentPurchasing.GetPendingPurchases**] Store(Google/Apple)->>Percent Purchase: 펜딩 상품들 받음 [return PendingPurchasesResult] Percent Purchase->>ReceiptServer: 상품 하나씩 영수증 검증 시도 [PercentPurchasing.VerificationReceipt] ReceiptServer->>Percent Purchase: 영수증 검증 성공 및 구매 성공, 아이템 지급, 상품이 끝날 때까지
Restore
sequenceDiagram Percent Purchase->>Store(Google/Apple): 구매 복원 상품 조회 [**PercentPurchasing.RestoreTransactions**] Store(Google/Apple)->>Percent Purchase: 펜딩 상품들 받음 [return RestoreTransactionsResult] Percent Purchase->>ReceiptServer: 상품 하나씩 영수증 검증 시도 [PercentPurchasing.VerificationReceipt] ReceiptServer->>Percent Purchase: 영수증 검증 성공 및 구매 성공, 아이템 지급, 상품이 끝날 때까지
Restore의 경우 영수증 검증 시도 단계에서 Android :
PercentPurchasing.VerificationReceipt
iOS :PercentPurchasing.VerifyReceiptByIosRestore
을 호출해주세요.
API에 대한 설명은 Swagger를 참조해주세요. 스키마에서 에러코드를 확인할 수 있습니다.
ErrorCode | ErrorMessage | Help |
---|---|---|
-1 | FATAL | 영수증 검증이 제대로 동작하지 않았습니다. 테스트 내용과 함께 TS팀에 문의해주세요. |
0 | SUCCESS | 정상적으로 완료되었습니다. |
100 | NOT_FOUND_APP_ID | |
101 | NOT_FOUND_APP | |
200 | HTTP_EXCEPTION | 다양한 이유가 있겠으나 영수증 검증 시 필요한 파라메터의 값이 잘못 된 경우일 확률이 큽니다. |
300 | NOT_SUPPORT_PLATFORM | |
301 | NOT_FOUND_RECEIPT | |
302 | TRY_LATER | |
400 | NOT_FOUND_PUBLISHER | |
500 | NOT_FOUND_SECRET | |
600 | USED_RECEIPT | 600번 코드를 사용하여 중복 처리를 구현합니다. |
601 | OLD_RECEIPT |
Pending 상태
Pending 상태 : PercentPurchasing.Purchase
호출하여 네이티브 팝업을 통해 구매한 후 Confirm이 불리기 전 단계.
- Pending 상태에서 테스트 결제의 경우
5분
, 실제 결제의 경우3일
의 시간을 가지며 이 시간 안에PercentPurchasing.ConfirmPurchase
를 호출하지 않으면 결제는 취소 처리 됩니다. - 아이템 지급은 Pending 상태에서가 아니라 Confirm이 된 이후에 이루어져야 합니다.