소모성 추가 기능 구매 사용하기

이 문서는 Windows.Services.Store 네임스페이스에서 StoreContext 클래스의 메서드를 사용하여 UWP 앱에서 사용자의 소모성 추가 기능 이행을 관리하는 방법을 설명합니다. 다시 구입, 사용 및 구매할 수 있는 항목에 소모성 추가 기능을 사용합니다. 이는 특정 파워업을 구매하는 데 사용할 수 있는 게임 내 통화(금, 동전 등)에 특히 유용합니다.

참고 항목

Windows.Services.Store 네임스페이스는 Windows 10 버전 1607에 도입되었으며 Windows 10 Anniversary Edition(10.0, 빌드 14393) 또는 Visual Studio의 최신 릴리스를 대상으로 하는 프로젝트에만 사용할 수 있습니다. 앱이 이전 버전의 Windows 10을 대상으로 하는 경우에는 Windows.Services.Store 네임스페이스 대신 Windows.ApplicationModel.Store 네임스페이스를 사용해야 합니다. 자세한 내용은 이 문서를 참조하십시오.

소모성 추가 기능 개요

앱은 처리 관리 방법이 서로 다른 두 가지 유형의 소모성 추가 기능을 제공할 수 있습니다.

  • 개발자 관리 소모품. 이러한 유형의 소모품의 경우에는 추가 기능이 나타내는 항목의 사용자 잔액을 추적하고 사용자가 모든 항목을 소모한 뒤 Store에 추가 기능 구매가 이행된 것으로 보고해야 하는 책임이 있습니다. 앱이 이전 추가 기능 구매가 이행된 것으로 보고하기 전까지 사용자는 추가 기능을 다시 구매할 수 없습니다.

    예를 들어 추가 기능이 게임에서 100개의 동전을 나타내고 사용자가 10개의 동전을 사용하는 경우, 앱 또는 서비스는 90개 동전이라는 사용자의 새로운 잔액을 유지해야 합니다. 사용자가 100개 동전을 모두 사용한 뒤, 추가 기능이 처리된 것으로 앱이 보고하고 나면 사용자가 100개 동전 추가 기능을 다시 구매할 수 있습니다.

  • Store 관리 소모품. 이러한 유형의 소모품의 경우, Store는 추가 기능이 나타내는 사용자의 잔액을 추적합니다. 사용자가 항목을 소모할 때 해당 항목을 주문 처리된 것으로 Store에 보고해야 하며 Store는 사용자의 잔액을 업데이트합니다. 사용자는 원하는 만큼 추가 기능을 구입할 수 있습니다(항목을 먼저 소비할 필요는 없음). 앱은 언제든지 Microsoft Store에 사용자의 현재 잔액을 쿼리할 수 있습니다.

    예를 들어 게임에서 추가 기능의 초기 수량이 동전 100개이고 사용자가 동전 50개를 사용하면 앱은 추가 기능 50단위가 사용되었다고 Microsoft Store에 보고하고, Microsoft Store는 남은 잔액을 업데이트합니다. 그러면 사용자는 추가 기능을 다시 구입하여 동전을 100개 획득하고, 동전을 총 150개 갖게 됩니다.

    참고 항목

    Store 관리 소모품은 Windows 10 버전, 1607에 도입되었습니다.

사용자에게 소모성 추가 기능을 제공하려면 다음의 일반 프로세스를 따르세요.

  1. 사용자가 앱에서 추가 기능을 구매할 수 있도록 합니다.
  2. 사용자가 추가 기능을 사용하는 경우(예: 게임에서 동전을 소비하는 경우) 추가 기능이 처리된 것으로 보고합니다.

언제든지 Store 관리 소모품에 대해 남은 잔액 가져오기를 할 수 있습니다.

필수 조건

이러한 예제에는 다음과 같은 전제 조건이 있습니다.

  • Windows 10 Anniversary Edition(10.0, 빌드 14393) 이상 릴리스를 대상으로 하는 UWP(유니버설 Windows 플랫폼) 앱에 대한 Visual Studio 프로젝트
  • 파트너 센터에서 앱 제출 만들기를 완료하였으며 해당 앱은 Store에 게시되었습니다. 테스트하는 동안 Store에서 검색이 되지 않도록 앱을 구성할 수도 있습니다. 자세한 내용은 테스트 지침을 참조하세요.
  • 파트너 센터에서 앱에 소모성 추가 기능 만들기를 완료했습니다.

이 예제의 코드는 다음을 가정합니다.

  • 코드는 workingProgressRing(이)라고 이름이 지정된 ProgressRingtextBlock(이)라고 이름이 지정된 TextBlock을 포함하는 페이지의 컨텍스트에서 실행됩니다. 이러한 개체는 각각 비동기 작업의 발생을 나타내며 출력 메시지를 표시하는 데 사용됩니다.
  • 코드 파일에는 Windows.Services.Store 네임스페이스에 대한 using 문이 있습니다.
  • 이 앱은 단일 사용자 앱으로, 해당 앱을 실행한 사용자의 컨텍스트에서만 실행됩니다. 자세한 정보는 앱 내 구매 및 평가판을 참조하세요.

전체 샘플 애플리케이션은 Store 샘플을 참조하세요.

참고 항목

데스크톱 브리지를 사용하는 데스크톱 애플리케이션이 있는 경우, 이 예제에서 표시되지 않는 별도의 코드를 추가하여 StoreContext 개체를 구성해야 할 수도 있습니다. 자세한 정보는 데스크톱 브리지를 사용하는 데스크톱 애플리케이션에서 StoreContext 클래스 사용하기를 참조하세요.

소모성 추가 기능을 처리됨으로 보고하기

사용자가 앱에서 추가 기능을 구매하고 추가 기능을 사용한 뒤, 앱은 StoreContext 클래스의 ReportConsumableFulfillmentAsync 메서드를 호출하여 추가 기능이 처리된 것으로 보고해야 합니다. 다음의 정보를 이 메서드에 전달해야 합니다.

  • 처리됨으로 보고하려는 추가 기능의 Store ID.
  • 처리됨으로 보고하려는 추가 기능의 단위.
    • 개발자 관리 소모품의 경우 수량 매개 변수를 1로 지정합니다. 이로 인해 소모품이 처리되었음을 Store에 알릴 수 있고, 고객은 소모성 제품을 다시 구매할 수 있습니다. 소모성 제품이 처리되었음을 앱이 Store에 알리기 전까지 사용자는 소모성 제품을 다시 구매할 수 없습니다.
    • Store 관리 소모품의 경우에는 사용된 실제 단위 수를 지정합니다. Store는 소모품에 대해 남은 잔액을 업데이트합니다.
  • 처리에 대한 추적 ID. 이 개발자 제공 GUID는 처리 작업이 연결된 특정 트랜잭션을 추적 용도로 식별합니다. 자세한 정보는 ReportConsumableFulfillmentAsync의 설명을 참조하세요.

이 예제는 Store 관리 소모품이 처리되었음을 보고하는 방법을 보여 줍니다.

private StoreContext context = null;

public async void ConsumeAddOn(string addOnStoreId)
{
    if (context == null)
    {
        context = StoreContext.GetDefault();
        // If your app is a desktop app that uses the Desktop Bridge, you
        // may need additional code to configure the StoreContext object.
        // For more info, see https://aka.ms/storecontext-for-desktop.
    }

    // This is an example for a Store-managed consumable, where you specify the actual number
    // of units that you want to report as consumed so the Store can update the remaining
    // balance. For a developer-managed consumable where you maintain the balance, specify 1
    // to just report the add-on as fulfilled to the Store.
    uint quantity = 10;
    Guid trackingId = Guid.NewGuid();

    workingProgressRing.IsActive = true;
    StoreConsumableResult result = await context.ReportConsumableFulfillmentAsync(
        addOnStoreId, quantity, trackingId);
    workingProgressRing.IsActive = false;

    // Capture the error message for the operation, if any.
    string extendedError = string.Empty;
    if (result.ExtendedError != null)
    {
        extendedError = result.ExtendedError.Message;
    }

    switch (result.Status)
    {
        case StoreConsumableStatus.Succeeded:
            textBlock.Text = "The fulfillment was successful. " + 
                $"Remaining balance: {result.BalanceRemaining}";
            break;

        case StoreConsumableStatus.InsufficentQuantity:
            textBlock.Text = "The fulfillment was unsuccessful because the remaining " +
                $"balance is insufficient. Remaining balance: {result.BalanceRemaining}";
            break;

        case StoreConsumableStatus.NetworkError:
            textBlock.Text = "The fulfillment was unsuccessful due to a network error. " +
                "ExtendedError: " + extendedError;
            break;

        case StoreConsumableStatus.ServerError:
            textBlock.Text = "The fulfillment was unsuccessful due to a server error. " +
                "ExtendedError: " + extendedError;
            break;

        default:
            textBlock.Text = "The fulfillment was unsuccessful due to an unknown error. " +
                "ExtendedError: " + extendedError;
            break;
    }
}

Store 관리 소모품에 대한 남은 잔액 가져오기

이 예제는 StoreContext 클래스의 GetConsumableBalanceRemainingAsync 메서드를 사용하여 Store 관리 소모성 추가 기능에 대한 남은 잔액을 가져오는 방법을 보여 줍니다.

private StoreContext context = null;

public async void GetRemainingBalance(string addOnStoreId)
{
    if (context == null)
    {
        context = StoreContext.GetDefault();
        // If your app is a desktop app that uses the Desktop Bridge, you
        // may need additional code to configure the StoreContext object.
        // For more info, see https://aka.ms/storecontext-for-desktop.
    }

    workingProgressRing.IsActive = true;
    StoreConsumableResult result = await context.GetConsumableBalanceRemainingAsync(addOnStoreId);
    workingProgressRing.IsActive = false;

    // Capture the error message for the operation, if any.
    string extendedError = string.Empty;
    if (result.ExtendedError != null)
    {
        extendedError = result.ExtendedError.Message;
    }

    switch (result.Status)
    {
        case StoreConsumableStatus.Succeeded:
            textBlock.Text = "Remaining balance: " + result.BalanceRemaining;
            break;

        case StoreConsumableStatus.NetworkError:
            textBlock.Text = "Could not retrieve balance due to a network error. " +
                "ExtendedError: " + extendedError;
            break;

        case StoreConsumableStatus.ServerError:
            textBlock.Text = "Could not retrieve balance due to a server error. " +
                "ExtendedError: " + extendedError;
            break;

        default:
            textBlock.Text = "Could not retrieve balance due to an unknown error. " +
                "ExtendedError: " + extendedError;
            break;
    }
}