유니티용 now.gg 결제 모듈과 함께 유니티 내에서 인앱 구매를 사용하실 수 있습니다.
방법:
now.gg 결제 유니티 모듈은 유니티 패키지 파일 nowgg-payments-login.unitypackage
에 포함되어 있습니다.
방법:
nowgg-payments-login.unitypackage
를 선택합니다.Assets/Plugins/Android
디렉토리에 다운로드 및 추가합니다.now.gg 결제 모듈을 임포트하고 종속성이 추가되면 이제 앱/게임에 now.gg 결제를 추가하실 수 있습니다.
now.gg Payments를 초기화하기 전에 항상 now.gg IAP가 사용 가능한지 확인하세요. now.gg IAP를 사용할 수 없는 경우에만 다른 IAP 서비스를 초기화해야 합니다.
public void Start() { if (!NowGG.Sdk.NowGGPaymentsSdkManager.IsNowGGIapAvailable()) return; // 여기에 Unity IAP 코드를 입력하세요... }
아래는 유니티 내에서 호출할 수 있는 now.gg IAP 함수 예제입니다.
now.gg IAP가 포함된 클래스를 생성하고 인앱 구매를 초기화하는 데 사용할 게임 오브젝트에 추가합니다.
이 단계에서는 앱/게임에 인앱 구매 제품을 추가하고 SDK를 초기화할 수 있습니다.예를 들어, 다음 코드에서는 productId
‘coin1’과 ‘coin2’라는 두 개의 IAP 소모품 제품과 productId
‘monthlyPack’이라는 구독을 추가했습니다.
마찬가지로, nowStudio에 추가한 해당 productId
를 사용하여 다른 제품 및 구독을 추가할 수 있습니다. 아래 샘플 코드에 예시되어 있습니다:
public void Start() { // now.gg IAP가 사용 가능한 경우에만 Payments를 초기화하세요 if (!NowGG.Sdk.NowGGPaymentsSdkManager.IsNowGGIapAvailable()) return; // 소모품의 경우 NowGGProductBuilder.Instance.AddProduct("coin1", ProductType.Consumable); NowGGProductBuilder.Instance.AddProduct("coin2", ProductType.Consumable); // 구독의 경우 NowGGProductBuilder.Instance.AddProduct("monthlyPack", ProductType.Subscription); NowGGPaymentsSdkManager.Instance.OnInitSuccess += OnInitSuccess; NowGGPaymentsSdkManager.Instance.OnInitFailed += OnInitFailed; NowGGPaymentsSdkManager.Instance.OnPurchaseCompleted += OnPurchaseProduct; NowGGPaymentsSdkManager.Instance.OnPurchaseFailed += OnPurchaseFailed; NowGGPaymentsSdkManager.Instance.InitializeIap(PAYMENT_ID); }
설명:
NowGGProductBuilder
클래스에 추가되었습니다.PAYMENT_ID
를 사용하여 결제 모듈을 초기화하기 위해 NowGGPaymentsSdkManager
클래스의 InitializeIap
함수가 호출됩니다.초기화가 성공적으로 완료되면 productId
를 사용하여 상품의 세부정보를 얻을 수 있습니다.
아래는 예시입니다.
private void OnInitSuccess() { Debug.Log($"IAP init success"); Product coin1 = NowGGPaymentsSdkManager.Instance.GetProductWithID("coin1"); // now you can get product details using productId string price = coin1.price; string currencyCode = coin1.priceCurrencyCode; Product monthlyPack = NowGGPaymentsSdkManager.Instance.GetProductWithID("monthlyPack"); string monthlyPackSubscriptionPeriod = monthlyPack.subscriptionPeriod; } private void OnInitFailed(string reason) { Debug.Log($"IAP init failed {reason}"); }
참조:
상품의 세부정보를 확인한 후 구매를 시작하기 위해 아래와 같이 PurchaseProduct()
를 호출합니다.
public void PurchaseProduct(string productId, string developerPayload=null) { NowGGPaymentsSdkManager.Instance.PurchaseProduct(productId, developerPayload); }
참조:
방법:
private PurchaseProcessingResult OnPurchaseProduct(PurchasedProduct purchasedProduct) { string orderId = purchasedProduct.orderId; string purchaseToken = purchasedProduct.purchaseToken; string developerPayload = purchasedProduct.developerPayload; // 유저가 소모성 아이템을 구매했습니다. if (String.Equals(purchasedProduct.productId, "coin1", StringComparison.Ordinal)) { Debug.Log(string.Format("OnPurchaseProduct: PASS. Product: '{0}'", purchasedProduct.productId)); // 성공적으로 구매한 소모성 아이템인 100코인을 유저의 게임 내 재화에 추가합니다. // 예: coin += 100; } else if (String.Equals(purchasedProduct.productId, "coin2", StringComparison.Ordinal)) { Debug.Log(string.Format("OnPurchaseProduct: PASS. Product: '{0}'", purchasedProduct.productId)); // 성공적으로 구매한 소모성 아이템인 200코인을 유저의 게임 내 재화에 추가합니다. // 예: coin += 200; } else if (String.Equals(purchasedProduct.productId, "monthlyPack", StringComparison.Ordinal)) { Debug.Log(string.Format("OnPurchaseProduct: PASS. Product: '{0}'", purchasedProduct.productId)); Debug.Log("Subscription: " + purchasedProduct.subscriptionStatus + " " + purchasedProduct.subscriptionPeriod + " " + purchasedProduct.expiryTimeMillis + " " + purchasedProduct.subscriptionPurchaseDateMillis); if (purchasedProduct.subscriptionStatus == "ACTIVE" && purchaseProduct.isAcknowledged == false) { // 구독이 성공적으로 구매되었습니다. 구독의 혜택을 플레이어에게 전달하십시오. // 구매한 구독의 상태, 청구 기간, 만료를 반영하도록 UI를 업데이트하십시오. // 구매가 확인되었음을 인정하기 위해 PurchaseProcessingResult.Complete을 반환합니다 (구독). } } else { Debug.Log(string.Format("OnPurchaseProduct: FAIL. Unrecognized product: '{0}'", purchasedProduct.productId)); } // 해당 상품을 받았는지 또는 다음 앱 실행 시 애플리케이션이 해당 구매를 상기시켜야 하는지 여부를 나타내는 플래그를 반환합니다. // 다음에 앱/게임을 실행할 때 해당 구매에 대해 알림을 받기 위해 PurchaseProcessingResult.Pending을 반환합니다. // PurchaseProcessingResult.Complete를 반환하여 구매가 소비(소모품) 또는 승인(구독)되었으며 구매 혜택을 사용자에게 전달했음을 확인합니다. return PurchaseProcessingResult.Complete; }
참고:
OnPurchaseProduct
에서 PurchaseProcessingResult.Pending
을 반환해야 합니다.
참조:
purchaseToken
은 성공적인 구매 후에 생성되며 고유 식별자입니다.orderId
도 생성됩니다.
백엔드 서버가 있는 앱의 서버측 구매 소비 시 consumePurchase
API를 사용하시기 바랍니다.
구매를 인증한 후에는 소비됨으로 표시해야 합니다.
구매 소비에는 다음이 포함됩니다.
다음 샘플 코드는 consumePurchase
API를 통한 관련 요청 방법을 보여드립니다
import requests url = "https://payments-api.now.gg/v2/order/consumePurchase" headers = { "Authorization": "<payment_api_key_here>", "Content-Type": "application/x-www-form-urlencoded" } data = { "purchaseToken": "<purchase_token_here>", "productId": "<product_id_here>", // 멀티 스토어에만 필요 "developerPayload": "developerPayload", // 선택 사항 멀티 스토어에만 필요 "currency": "<currency>", // 멀티 스토어에만 필요 "type": "<store>" } response = requests.post(url, headers=headers, data=data) print("응답 상태 코드:", response.status_code) print("응답 본문:", response.text)
curl --location --request POST 'https://payments-api.now.gg/v2/order/consumePurchase' \ --header 'Authorization: <payment_api_key_here>' \ --header 'Content-Type': 'application/x-www-form-urlencoded' \ --data '{ "purchaseToken": "<purchase_token_here>", "productId": "<product_id_here>", //멀티 스토어에만 필요 "developerPayload" : developerPayload, // 선택 사항, 멀티 스토어에만 필요 "currency": "<currency>", // 멀티 스토어에만 필요 "type": "<store>" }'
참조:
consumePurchase
API를 호출하기 전에 사용자에게 할당해야 합니다.type
값이 없을 경우, 기본값은 nowgg입니다.
Payments API Key
는 nowStudio의 자격 증명에 있습니다. (자세히)acknowledgePurchase
API는 백엔드 서버가 있는 앱의 서버 측 구매 승인을 위해 사용되어야 합니다.
구매를 확인한 후에는 이를 승인된 것으로 표시해야 합니다.
다음 샘플 코드는 acknowledgePurchase
API를 통한 관련 요청 방법을 보여드립니다
import requests url = "https://payments-api.now.gg/v2/seller/order/acknowledgepurchase" payload = 'purchaseToken=nowgg-_purchase_token' headers = { 'Authorization': 'payment_api_key_here', 'Content-Type': 'application/x-www-form-urlencoded' } response = requests.request("POST", url, headers=headers, data=payload) print(response.text)
참조:
API 키 – Payments API Key
는 nowStudio의 자격 증명에 있습니다. (자세히)
구매가 성공적으로 완료되면 purchaseToken 가 반환됩니다. 더 자세한 내용은 다음 문서를 참조하시기 바랍니다.
구매일로부터 3일 이내에 권한이 부여되지 않으면 거래가 취소되고 사용자에게 환불이 시작됩니다.
백엔드 서버에서 구매를 처리하거나 구현에 따라 다른 방법을 사용하여 구매를 완료해야 하는 경우 OnPurchaseProduct
에서 PurchaseProcessingResult.Pending
을 반환합니다.
구매 흐름을 완료하려면 ConfirmPendingPurchase
메서드를 사용하여 now.gg IAP 서비스에 앱이 구매를 기록했음을 알릴 수 있습니다.
public void ConfirmPendingPurchase(string purchasetoken, ProductType productType) { if (productType == ProductType.Consumable) { ConsumeProduct(purchasetoken); } else { AcknowledgePurchase(purchasetoken); } }
참조:
앱 백엔드 서버를 사용하여 구매 흐름을 완료하고 구매 권한을 부여하려면 다음을 호출하십시오:
소모품 제품의 경우 consumePurchase API를 호출하십시오.
구독의 경우 acknowledgePurchase API를 호출하십시오.
대기 중인 구매가 확인되면 now.gg IAP 서비스는 해당 구매에 대해 앱에 알리지 않습니다.
대기 중인 구매가 있는 경우 앱/게임을 다음에 실행할 때 다시 알림이 표시됩니다.
구독 기간은 인식된 날짜부터 시작됩니다.
구매일로부터 3일 이내에 권한이 부여되지 않으면 거래가 취소되고 사용자에게 환불이 시작됩니다.
대기 중인 구매를 위해 NowGGPaymentsSdkManager
클래스의 QueryPendingPurchases
를 호출하실 수 있으며 이때 OnPurchaseProduct
콜백을 통해 처리하실 수 있습니다.
NowGGPaymentsSdkManager.Instance.QueryPendingPurchases();
참조: OnPurchaseProduct.
구매할 추가 상품을 가져오거나 기존 상품의 세부 정보/메타데이터를 갱신하려면 아래와 같이 FetchAdditionalProducts
를 사용하실 수 있습니다.
var newProducts = new System.Collections.Generic.Dictionary<string, ProductType>(); newProducts.Add("coin2", ProductType.Consumable); newProducts.Add("coin3", ProductType.Consumable); NowGGPaymentsSdkManager.Instance.FetchAdditionalProducts(newProducts, () => { // 가져온 추가 상품을 이제 구매할 수 있습니다. Debug.Log($"Successfully fetched additional products"); Product coin1 = NowGGPaymentsSdkManager.Instance.GetProductWithID("coin1"); string price = coin1.price; string currencyCode = coin1.priceCurrencyCode; }, reason => { Debug.Log($"Fetching additional products failed: {reason}"); });
다음을 포함한 여러 가지 이유로 인해 구매가 실패할 수도 있습니다.
네트워크/연결 문제
결제 실패
설정 관련 문제
아래와 같이 실패한 구매를 확인하고 처리하실 수 있습니다.
public void OnPurchaseFailed(int errorCode, string errorMessage) { Debug.Log($"OnPurchaseFailed: errorCode: {errorCode} and msg: {errorMessage}"); }
중요:
OnPurchaseFailed
관련 응답 코드는 여기에서 참조하실 수 있습니다.
구독 상태 업데이트에 대한 알림을 보내기 위해 SubscriptionStatusCallback
API를 제공하는 데 필요한 API입니다.
다음은 관련 워크플로입니다:
nowStudio는 이 API를 사용하여 요청 데이터를 게임 백엔드로 보냅니다.
귀하의 게임 백엔드는 당사가 제공한 API 키를 사용하여 요청을 인증할 수 있습니다. (웹훅 API 키).
구독의 현재 상태에 따라 사용자의 구매 권한을 할당할 수 있습니다. (여기에서 정의됨).
제공한 콜백 URL을 사용하여 요청 데이터를 보낸 후, 귀하의 API에서 응답을 기다립니다.
상태 코드 200과 성공 여부가 true인 응답을 받지 못하면 일정 시간 동안 재시도합니다.
중요: Subscription Status Callback API.
다음 두 가지 방법 중 하나를 사용하여 구매를 인증하실 수 있습니다.
verifyPurchase API
공개 키
구매를 인증하는 첫 번째 방법은 verifyPurchase API를 사용하는 것으로 해당 API를 사용하여 백엔드 서버를 통해 구매를 인증하실 수 있습니다.
먼저 아래와 같이 앱 백엔드 서버에서 purchaseToken
와 함께 verifyPurchase API를 호출합니다.
import requests url = "https://payments-api.now.gg/v2/sellers/order/verifyPurchase" headers = { "Authorization": "<payment_api_key_here>", "Content-Type": "application/x-www-form-urlencoded" } data = { "purchaseToken": "<purchase_token_here>", "productId": "<product_id_here>", // 멀티 스토어에만 필요 "type": "<store>" } response = requests.post(url, headers=headers, data=data) print("响应状态码:", response.status_code) print("响应正文:", response.text)
curl -X \ POST "https://payments-api.now.gg/v2/sellers/order/verifyPurchase" \ -H \ "Authorization: <payment_api_key_here>" \ -H \ "Content-Type: application/x-www-form-urlencoded" \ -d "purchaseToken=<purchase_token_here>" \ -d "productId=<product_id_here>" \ // Required only for multi-store -d "type=<store>"
type
값이 없을 경우, 기본값은 nowgg입니다.
구매는 로컬 또는 백엔드 서버를 통해 인증하실 수 있으며, 이때 백엔드 서버를 사용하여 구매를 인증하시는 것을 권장해 드립니다.
여기에 나열된 단계에 따라 공개 키를 생성할 수 있습니다.
아래와 같이 로컬 또는 백엔드 서버를 통해 인증하실 수 있으며 자세한 내용은 데모 프로젝트를 참조하시기 바랍니다.
private PurchaseProcessingResult OnPurchaseProduct(PurchasedProduct purchasedProduct) { // 로컬 인증을 하고 싶다면 해당 샘플 코드를 사용하실 수 있습니다. bool isValidPurchase = PurchaseVerification.Instance.VerifyPurchaseLocally( BASE_64_ENCODED_PUBLIC_KEY, purchasedProduct.originalJson, purchasedProduct.signature); // 백엔드 서버에서 구매를 인증하시는 것을 권장해 드립니다. // 인증을 위해 서명과 원본 데이터를 백엔드 서버로 보냅니다. PurchaseVerification.Instance.VerifyValidSignatureOnBackend( purchasedProduct.originalJson, purchasedProduct.signature); // 유저가 소모성 아이템을 구매했습니다. if (String.Equals(purchasedProduct.productId, "coin1", StringComparison.Ordinal)) { Debug.Log(string.Format("OnPurchaseProduct: PASS. Product: '{0}'", purchasedProduct.productId)); // 성공적으로 구매한 소모성 아이템인 100코인을 유저의 게임 내 재화에 추가합니다. // 예: coin += 100; } else if (String.Equals(purchasedProduct.productId, "coin2", StringComparison.Ordinal)) { Debug.Log(string.Format("OnPurchaseProduct: PASS. Product: '{0}'", purchasedProduct.productId)); // 성공적으로 구매한 소모성 아이템인 200코인을 유저의 게임 내 재화에 추가합니다 // 예: coin += 200; } else { Debug.Log(string.Format("OnPurchaseProduct: FAIL. Unrecognized product: '{0}'", purchasedProduct.productId)); } return PurchaseProcessingResult.Complete; }
다운로드 패키지에는 백엔드 서버를 사용하여 구매를 인증하는 샘플 코드가 포함되어 있습니다.
샘플 코드
PurchaseVerification 클래스: 데모 프로젝트에 포함된 클래스로 구매 데이터와 서명을 백엔드 서버로 보내는 유니티 코드가 포함되어 있습니다.
Server.py: 다운로드 패키지 내 별도로 위치하며, 로컬 서버를 실행하고 백엔드 인증을 수행하는 샘플 파이썬 코드가 포함되어 있습니다.
구매 로컬 인증 관련 샘플 코드는 다운로드 패키지 내에서 확인해 보실 수 있습니다.
샘플 코드
PurchaseVerification
클래스의 VerifyPurchaseLocally
를 참조해 보시기 바랍니다.
문서 Rev. 1.0