결제 API를 사용하면 디지털 상품과 정기 결제를 판매할 수 있습니다. C# 래퍼는 기본 Google Play 결제 라이브러리에 유형 안전 비동기 인터페이스를 제공합니다.
네임스페이스: PlayPcSdkManaged.Billing
클라이언트 클래스: BillingClient
클라이언트 만들기
항상 팩토리를 사용하여 BillingClient를 만드세요. 이렇게 하면 Unity에 안전한 콜백이 자동으로 등록됩니다.
using UnityEngine; using System; using System.Threading.Tasks; using System.Collections.Generic; // Required SDK Namespaces using PlayPcSdkManaged.Billing; using PlayPcSdkManaged.Unity; public class BillingManager : MonoBehaviour { private BillingClient _billingClient; public void SetupBilling() { try { // Creates the client with the required UnityBillingCallbacksHandler _billingClient = PlayPcSdkFactory.CreateBillingClient(); Debug.Log("Billing Client created successfully."); } catch (Exception ex) { Debug.LogError($"Failed to create Billing Client: {ex.Message}"); } } private void OnDestroy() { // Always dispose of the client to clean up native C++ resources _billingClient?.Dispose(); } }
제품 세부정보 쿼리
판매용 상품을 표시하기 전에 Google Play에서 가격, 제품명, 설명과 같은 세부정보를 쿼리해야 합니다.
public async Task GetProductDetailsAsync() { try { // Define the list of products you want to query var productList = new List{ // Use ProductType.InApp for consumables/non-consumables new ProductId { Id = "gem_pack_100", ProductType = ProductType.InApp }, // Use ProductType.Subs for subscriptions new ProductId { Id = "gold_subscription", ProductType = ProductType.InApp } }; var queryParams = new QueryProductDetailsParams { ProductIds = productList }; // Async call var result = await _billingClient.QueryProductDetailsAsync(queryParams); if (result.IsOk) { foreach (var product in result.Value.ProductDetailsList) { // The formatted price (e.g., "$0.99") is inside the ProductOffers list if (product.ProductOffers != null && product.ProductOffers.Count > 0) { var price = product.ProductOffers[0].FormattedPrice; var offerToken = product.ProductOffers[0].OfferToken; Debug.Log($"Product: {product.Title} | Price: {price} | Token: {offerToken}"); } } } else { Debug.LogError($"Query Failed: {result.Code} - {result.ErrorMessage}"); } } catch (Exception ex) { Debug.LogException(ex); } }
구매 흐름 시작
구매를 시작하려면 제품 세부정보 단계에서 OfferToken를 사용하여 LaunchPurchaseFlowAsync를 호출합니다.
public async Task BuyItemAsync(string offerToken) { try { var purchaseParams = new LaunchPurchaseFlowParams { OfferToken = offerToken, Quantity = 1, // Optional: Attach obfuscated IDs for fraud detection ObfuscatedAccountId = "user_12345_hash", ObfuscatedProfileId = "profile_abcde_hash" }; var result = await _billingClient.LaunchPurchaseFlowAsync(purchaseParams); if (result.IsOk) { var purchase = result.Value.ProductPurchaseDetails; Debug.Log($"Purchase Successful! Order ID: {purchase.OrderId}"); // IMPORTANT: You must now Acknowledge or Consume this purchase. // If you don't, Google Play will refund the user after a few days. if (!purchase.IsAcknowledged) { // Decide based on your game logic if it's consumable or permanent await HandlePurchaseAsync(purchase); } } else if (result.Code == BillingError.UserCanceled) { Debug.Log("User canceled the purchase flow."); } else { Debug.LogError($"Purchase Failed: {result.Code} - {result.ErrorMessage}"); } } catch (Exception ex) { Debug.LogException(ex); } }
기존 구매 쿼리 (복원)
게임 시작 시 QueryPurchasesAsync를 호출하여 게임 재설치 후와 같이 이미 소유한 항목을 복원하거나 대기 중인 거래를 확인합니다.
이 예에서는 영구 상품의 경우 Acknowledge로, 소모품의 경우 Consume으로 구매를 라우팅하는 방법을 보여줍니다.
public async Task CheckExistingPurchasesAsync() { try { // Fetches all purchases owned by the user var result = await _billingClient.QueryPurchasesAsync(); if (result.IsOk) { foreach (var purchase in result.Value.ProductPurchaseDetails) { Debug.Log($"User owns: {purchase.ProductId} | State: {purchase.PurchaseState}"); // Process any purchase that hasn't been acknowledged yet if (purchase.PurchaseState == PurchaseState.Purchased && !purchase.IsAcknowledged) { await HandlePurchaseAsync(purchase); } } } else { Debug.LogError($"Restore Failed: {result.Code} - {result.ErrorMessage}"); } } catch (Exception ex) { Debug.LogException(ex); } } // Helper method to route purchases private async Task HandlePurchaseAsync(ProductPurchaseDetails purchase) { // Example logic: "gem_pack" is consumable, everything else is permanent if (purchase.ProductId.Contains("gem_pack")) { await ConsumeItemAsync(purchase.PurchaseToken); } else { await AcknowledgeItemAsync(purchase.PurchaseToken); } }
구매 확인
비소비성 상품 (예: '프리미엄 업그레이드' 또는 '레벨 팩')은 확인해야 합니다. 이는 사용자에게 상품을 부여했음을 Google Play에 나타냅니다.
public async Task AcknowledgeItemAsync(string purchaseToken) { try { var acknowledgeParams = new AcknowledgePurchaseParams { PurchaseToken = purchaseToken }; var result = await _billingClient.AcknowledgePurchaseAsync(acknowledgeParams); if (result.IsOk) { Debug.Log("Purchase Acknowledged. Usage rights granted permanently."); } else { Debug.LogError($"Acknowledge Failed: {result.Code} - {result.ErrorMessage}"); } } catch (Exception ex) { Debug.LogException(ex); } }
구매 소비
'보석 100개' 또는 '체력 포션'과 같은 소비성 상품은 재구매를 허용하려면 소비해야 합니다.
public async Task ConsumeItemAsync(string purchaseToken) { try { var consumeParams = new ConsumePurchaseParams { PurchaseToken = purchaseToken }; var result = await _billingClient.ConsumePurchaseAsync(consumeParams); if (result.IsOk) { Debug.Log("Item Consumed. User can buy it again."); // Add the gems/coins to the user's inventory here } else { Debug.LogError($"Consume Failed: {result.Code} - {result.ErrorMessage}"); } } catch (Exception ex) { Debug.LogException(ex); } }