앱에 구독 추가 기능을 사용하도록 설정하기

유니버설 Windows 플랫폼(UWP) 앱은 구독 추가 기능을 앱에서 바로 구매할 수 있는 옵션을 고객에게 제공할 수 있습니다. 앱에서 반복 자동 과금을 이용한 구독으로 디지털 제품(앱 기능 또는 디지털 콘텐츠 등)을 판매할 수 있습니다.

참고 항목

앱에서 구독 추가 기능을 구매할 수 있도록 하려면 , 프로젝트가 Visual Studio 내에서 Windows 10 Anniversary Edition(10.0, 빌드 14393) 이상 릴리스를 대상으로 하고(Windows 10, 버전 1607에 해당), Windows.ApplicationModel.Store 네임스페이스 대신 Windows.Services.Store 네임스페이스에 API를 사용하여 앱 내에서 바로 구매하는 환경을 구현해야 합니다. 이러한 네임스페이스 차이점에 대한 자세한 정보는 앱 내 구매 및 평가판을 참조하세요.

주요 기능

UWP 앱 구독 추가 기능은 다음의 기능을 지원합니다.

  • 구독 기간을 1개월, 3개월, 6개월, 1년, 2년 중에서 선택할 수 있습니다.
  • 구독에 1주나 1개월 평가판 사용 기간을 추가할 수 있습니다.
  • Windows SDK는 앱에서 사용할 수 있는 구독 추가 기능에 대한 정보를 얻고, 구독 추가 기능을 구현하기 위해 앱에서 사용할 수 있는 API를 제공합니다. 또한 사용자 구독을 관리하는 서비스를 호출할 수 있는 REST API를 제공합니다.
  • 지정된 기간 동안 취득된 구독, 활성 구독자, 취소된 구독 수를 제공하는 분석 보고서도 확인할 수 있습니다.
  • 고객은 Microsoft 계정의 https://account.microsoft.com/services페이지에서 자신의 구독을 관리할 수 있습니다. 고객은 이 페이지를 사용해 취득한 구독을 확인하고, 구독을 취소하고, 구독과 연결된 결제 유형을 변경할 수 있습니다.

앱에서 구독 추가 기능을 구현하는 단계

앱에서 구독 추가 기능 구매를 구현하려면 다음의 단계를 따르세요.

  1. 파트너 센터에서 구독에 대한 추가 기능 제출을 생성하고, 제출을 게시합니다. 추가 기능 제출 프로세스를 적용할 때 다음 속성에 주의하세요.

    • 제품 유형: 구독을 선택해야 합니다.

    • 구독 기간: 구독에 대한 반복 청구 기간을 선택합니다. 추가 기능을 게시한 후에는 구독 기간을 변경할 수 없습니다.

      각 구독 추가 기능은 단일 구독 기간 및 평가판 기간을 지원합니다. 앱에서 제공하려는 구독 유형 별로 다른 구독 추가 기능을 만들어야 합니다. 예를 들어 평가판이 없는 1개월 구독, 1개월 평가판이 제공되는 1개월 구독, 평가판이 없는 1년 구독, 1개월 평가판이 제공되는 1년 구독을 제공하고 싶다면 4가지 구독 추가 기능을 만들어야 합니다.

    • 평가판 기간: 구독에서 1주 또는 1개월 평가판 기간을 선택하여 사용자가 구매 전에 구독 콘텐츠를 평가할 수 있도록 합니다. 평가판 기간은 구독 추가 기능을 게시한 뒤 변경하거나 제거할 수 없습니다.

      사용자가 구독 평가판을 받으려면 유효한 결제 유형을 포함해 일반적인 앱에서 바로 구매 프로세스를 통해 구독형 서비스를 구입해야 합니다. 평가판 기간 동안에는 요금이 청구되지 않습니다. 평가판 기간이 끝나면 구독은 자동으로 유료 구독으로 변경되고, 사용자의 결제 수단에 첫 유료 구독 기간에 대한 요금이 청구됩니다. 사용자가 평가판 기간에 구독 취소를 선택해도 평가판 기간이 끝날 때까지 구독이 유지됩니다. 일부 평가판 기간은 모든 구독 기간에 사용할 수 없습니다.

      참고 항목

      각 고객은 구독 추가 기능에 대한 평가판을 한 번만 획득할 수 있습니다. 고객이 구독에 대한 평가판을 획득한 후 Microsoft Store는 동일한 고객이 동일한 평가판 구독을 다시 구입하는 것을 막습니다.

    • 표시 여부: 구독을 대상으로 앱에서 바로 구매 환경을 테스트하기 위해 테스트 추가 기능을 생성하고 싶다면, Microsoft Store에서 숨김 옵션 중 하나를 선택하는 것이 좋습니다. 그렇지 않으면 해당 시나리오에서 가장 좋은 표시 옵션을 선택할 수 있습니다.

    • 가격: 이 섹션에서 구독 가격을 선택합니다. 구독 가격은 추가 기능을 게시한 뒤 인상할 수 없습니다. 그러나 나중에 가격을 인하할 수는 있습니다.

      중요

      기본적으로 추가 기능을 생성할 때 처음 가격은 무료로 설정됩니다. 추가 기능 제출이 완료된 후에는 구독 추가 기능 가격을 인상할 수 없기 때문에 여기에서 구독 가격을 선택해야 합니다.

  2. 앱에서 Windows.Services.Store 네임스페이스의 API를 사용하여 현재 사용자가 구독 추가 기능을 이미 취득했는지 판별하고, 앱 내 구매 형식으로 사용자에게 판매용으로 제공합니다. 세부 사항은 이 문서의 코드 예제를 참조합니다.

  3. 앱에서 구독의 앱 내 구매를 테스트합니다. 개발자 디바이스가 테스트용 라이선스를 사용할 수 있도록 Store에서 앱을 1회 다운로드해야 합니다. 자세한 정보는 앱 내 구매에 대한 테스트 지침을 참조하세요.

  4. 테스트된 코드 등 업데이트된 앱 패키지를 포함한 앱 제출을 생성해 게시합니다. 자세한 정보는 앱 제출을 참조하세요.

코드 예제

이 섹션의 코드 예제는 Windows.Services.Store 네임스페이스의 API를 사용해 앱의 구독 추가 기능에 대한 정보를 얻고, 현재 사용자를 대신해 구독 추가 기능 구매를 요청하는 방법을 보여 줍니다.

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

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

이러한 예제의 코드에서는 다음을 가정합니다.

  • 코드 파일에는 Windows.Services.StoreSystem.Threading.Tasks 네임스페이스에 대한 using 문이 있습니다.
  • 앱은 해당 앱을 실행한 사용자의 컨텍스트에서만 실행되는 단일 사용자 앱입니다. 자세한 정보는 앱 내 구매 및 평가판을 참조하세요.

참고 항목

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

구독 추가 기능 구매

이 예제에서는 현재 고객을 대신하여 앱의 알려진 구독 추가 기능을 구매하도록 요청하는 방법을 설명합니다. 또한 이 예제에서는 구독에 평가판 기간이 있는 경우를 처리하는 방법을 보여 줍니다.

  1. 먼저 코드는 고객이 구독에 대한 활성 라이선스를 이미 가지고 있는지 여부를 판별합니다. 고객이 이미 활성 라이선스를 가지고 있는 경우 코드는 필요에 따라 구독 기능을 잠금 해제해야 합니다(라이선스는 앱에 대한 소유권이므로 예제에서 주석으로 식별됨).
  2. 다음으로 코드는 고객을 대신하여 구매하려는 구독을 나타내는 StoreProduct 개체를 가져옵니다. 코드에서는 구매하려는 구독 추가 기능의 Store ID를 이미 알고 있으며 이 값을 subscriptionStoreId 변수에 할당했다고 가정합니다.
  3. 그런 다음, 코드는 구독에 평가판을 사용할 수 있는지 여부를 판별합니다. 필요에 따라 앱은 이 정보를 사용하여 고객에 사용 가능한 평가판 또는 전체 구독에 대한 세부 정보를 표시합니다.
  4. 마지막으로 코드는 RequestPurchaseAsync 메서드를 사용하여 구독을 구매하도록 요청합니다. 구독에 평가판을 사용할 수 있는 경우 평가판은 구매를 위해 고객에게 제공됩니다. 그렇지 않으면 구매를 위해 전체 구독이 제공됩니다.
private StoreContext context = null;
StoreProduct subscriptionStoreProduct;

// Assign this variable to the Store ID of your subscription add-on.
private string subscriptionStoreId = "";  

// This is the entry point method for the example.
public async Task SetupSubscriptionInfoAsync()
{
    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.
    }

    bool userOwnsSubscription = await CheckIfUserHasSubscriptionAsync();
    if (userOwnsSubscription)
    {
        // Unlock all the subscription add-on features here.
        return;
    }

    // Get the StoreProduct that represents the subscription add-on.
    subscriptionStoreProduct = await GetSubscriptionProductAsync();
    if (subscriptionStoreProduct == null)
    {
        return;
    }

    // Check if the first SKU is a trial and notify the customer that a trial is available.
    // If a trial is available, the Skus array will always have 2 purchasable SKUs and the
    // first one is the trial. Otherwise, this array will only have one SKU.
    StoreSku sku = subscriptionStoreProduct.Skus[0];
    if (sku.SubscriptionInfo.HasTrialPeriod)
    {
        // You can display the subscription trial info to the customer here. You can use 
        // sku.SubscriptionInfo.TrialPeriod and sku.SubscriptionInfo.TrialPeriodUnit 
        // to get the trial details.
    }
    else
    {
        // You can display the subscription purchase info to the customer here. You can use 
        // sku.SubscriptionInfo.BillingPeriod and sku.SubscriptionInfo.BillingPeriodUnit
        // to provide the renewal details.
    }

    // Prompt the customer to purchase the subscription.
    await PromptUserToPurchaseAsync();
}

private async Task<bool> CheckIfUserHasSubscriptionAsync()
{
    StoreAppLicense appLicense = await context.GetAppLicenseAsync();

    // Check if the customer has the rights to the subscription.
    foreach (var addOnLicense in appLicense.AddOnLicenses)
    {
        StoreLicense license = addOnLicense.Value;
        if (license.SkuStoreId.StartsWith(subscriptionStoreId))
        {
            if (license.IsActive)
            {
                // The expiration date is available in the license.ExpirationDate property.
                return true;
            }
        }
    }

    // The customer does not have a license to the subscription.
    return false;
}

private async Task<StoreProduct> GetSubscriptionProductAsync()
{
    // Load the sellable add-ons for this app and check if the trial is still 
    // available for this customer. If they previously acquired a trial they won't 
    // be able to get a trial again, and the StoreProduct.Skus property will 
    // only contain one SKU.
    StoreProductQueryResult result =
        await context.GetAssociatedStoreProductsAsync(new string[] { "Durable" });

    if (result.ExtendedError != null)
    {
        System.Diagnostics.Debug.WriteLine("Something went wrong while getting the add-ons. " +
            "ExtendedError:" + result.ExtendedError);
        return null;
    }

    // Look for the product that represents the subscription.
    foreach (var item in result.Products)
    {
        StoreProduct product = item.Value;
        if (product.StoreId == subscriptionStoreId)
        {
            return product;
        }
    }

    System.Diagnostics.Debug.WriteLine("The subscription was not found.");
    return null;
}

private async Task PromptUserToPurchaseAsync()
{
    // Request a purchase of the subscription product. If a trial is available it will be offered 
    // to the customer. Otherwise, the non-trial SKU will be offered.
    StorePurchaseResult result = await subscriptionStoreProduct.RequestPurchaseAsync();

    // 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 StorePurchaseStatus.Succeeded:
            // Show a UI to acknowledge that the customer has purchased your subscription 
            // and unlock the features of the subscription. 
            break;

        case StorePurchaseStatus.NotPurchased:
            System.Diagnostics.Debug.WriteLine("The purchase did not complete. " +
                "The customer may have cancelled the purchase. ExtendedError: " + extendedError);
            break;

        case StorePurchaseStatus.ServerError:
        case StorePurchaseStatus.NetworkError:
            System.Diagnostics.Debug.WriteLine("The purchase was unsuccessful due to a server or network error. " +
                "ExtendedError: " + extendedError);
            break;

        case StorePurchaseStatus.AlreadyPurchased:
            System.Diagnostics.Debug.WriteLine("The customer already owns this subscription." +
                    "ExtendedError: " + extendedError);
            break;
    }
}

현재 앱에 사용할 수 있는 구독 추가 기능에 대한 정보 가져오기

이 코드 예제는 앱에서 사용할 수 있는 모든 구독 추가 기능에 대한 정보를 가져오는 방법을 보여 줍니다. 이 정보를 가져오려면, 먼저 GetAssociatedStoreProductsAsync 메서드를 사용해 앱에서 사용할 수 있는 각각의 추가 기능을 나타내는 StoreProduct 개체 컬렉션을 가져옵니다. 그런 다음, 각 제품에 대한 StoreSku를 가져오고, IsSubscriptionSubscriptionInfo 속성을 사용해 구독 정보에 액세스합니다.

private StoreContext context = null;

public async Task GetSubscriptionsInfo()
{
    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.
    }

    // Subscription add-ons are Durable products.
    string[] productKinds = { "Durable" };
    List<String> filterList = new List<string>(productKinds);

    StoreProductQueryResult queryResult =
        await context.GetAssociatedStoreProductsAsync(productKinds);

    if (queryResult.ExtendedError != null)
    {
        // The user may be offline or there might be some other server failure.
        System.Diagnostics.Debug.WriteLine($"ExtendedError: {queryResult.ExtendedError.Message}");
        return;
    }

    foreach (KeyValuePair<string, StoreProduct> item in queryResult.Products)
    {
        // Access the Store product info for the add-on.
        StoreProduct product = item.Value;

        // For each add-on, the subscription info is available in the SKU objects in the add-on. 
        foreach (StoreSku sku in product.Skus)
        {
            if (sku.IsSubscription)
            {
                // Use the sku.SubscriptionInfo property to get info about the subscription. 
                // For example, the following code gets the units and duration of the 
                // subscription billing period.
                StoreDurationUnit billingPeriodUnit = sku.SubscriptionInfo.BillingPeriodUnit;
                uint billingPeriod = sku.SubscriptionInfo.BillingPeriod;
            }
        }
    }
}

서비스에서 구독 관리하기

업데이트된 앱이 Store에 있고 고객이 구독 추가 기능을 구매할 수 있게 된 후, 고객을 위해 구독을 관리해야 하는 상황들이 있습니다. 당사는 다음과 같은 구독 관리 작업을 수행하기 위해 서비스에서 호출할 수 있는 REST API를 제공합니다.

취소

고객은 자신의 Microsoft 계정에 대한 https://account.microsoft.com/services 페이지를 사용해 취득한 모든 구독을 확인하고, 구독을 취소하고, 구독과 연결된 결제 유형을 변경할 수 있습니다. 고객이 이 페이지를 사용해 구독을 취소하는 경우에도 현재 청구 기간 동안의 구독은 계속 액세스할 수 있습니다. 현재 청구 기간의 일부에 대해 환불을 받을 수 없습니다. 현재 청구 기간이 끝날 때 구독이 비활성화됩니다.

또한 REST API를 사용하여 특정 사용자의 구독 청구 상태를 변경하고 사용자를 대신하여 구독을 취소할 수 있습니다.

구독 갱신 및 유예 기간

매 청구 기간 중 어느 시점에서 다음 청구 기간 동안 고객의 신용 카드를 청구하려고 시도합니다. 청구에 실패하는 경우 고객의 구독은 독촉 중 상태가 됩니다. 즉, 현재 남아 있는 청구 기간 동안 구독은 여전히 활성화되어 있지만 구독을 자동으로 갱신하기 위해 정기적으로 신용 카드 결제를 시도합니다. 이 상태는 현재 청구 기간이 끝나고 다음 청구 기간에 대한 갱신 날짜가 될 때까지 최대 2주간 지속될 수 있습니다.

구독 요금 청구에 대한 유예 기간은 제공하지 않습니다. 현재 청구 기간이 끝날 때까지 고객의 신용 카드로 결제할 수 없는 경우 구독은 취소되며 현재 청구 기간 이후 고객은 더 이상 구독에 액세스할 수 없게 됩니다.

지원되지 않는 시나리오

다음 시나리오는 현재 구독 추가 기능에서 지원되지 않습니다.

  • 현재 스토어를 통해 직접 고객에게 구독을 판매하는 기능은 지원되지 않습니다. 구독은 디지털 제품의 앱 내 구매에만 사용할 수 있습니다.
  • 고객은 Microsoft 계정의 https://account.microsoft.com/services 페이지에서 구독을 전환할 수 없습니다. 다른 구독 기간으로 변경하려면 고객은 앱에서 현재 구독을 취소한 다음, 다른 구독 기간으로 구독을 구매해야 합니다.
  • 현재 구독 추가 기능은 전환을 지원하지 않습니다(예를 들어, 고객은 기본 구독에서 기능이 더 많은 프리미엄 구독으로 전환할 수 없음).
  • 구독 추가 기능은 현재 할인 판매프로모션 코드를 지원하지 않습니다.
  • 구독 추가 기능의 표시 여부를 취득 중지로 설정한 후 기존 구독 갱신. 자세한 정보는 추가 기능 가격 책정 및 표시 여부 설정하기를 참조하세요.