我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。
这些代码大部分以Linux为目标但部分代码是纯C++的,可以在任何平台上使用。
源码指引:github源码指引_初级代码游戏的博客-CSDN博客
应用内购买。对开发者而言,订阅比一次性买断更好。
有点奇怪,应用本身没有订阅模式。但是内购有订阅模式。
更奇怪的是微软采用的术语“加载项”,英文"AddOn",感觉怪怪的,不如直接叫“内购”啊。
“加载项”就是内购,可以是一次性买断的,也可以是计数的,计数的还分应用自己计数和商店负责计数,也可以是订阅制的,订阅可以按月、按季、按年。
最大的困难不在技术上,在术语和翻译的混乱中。
目录
2.4 检查指定内购项目是否购买CheckIfUserHasSubscription
一、创建内购(加载项、AddOn)
在合作伙伴中心的应用和游戏列表里面看:
加载项就是内购,点击一个应用名称,查看应用:
这个地方叫作“管理附加设备”,右边有个按钮叫做“创建加载项”,点击这个就可以创建内购:
产品类型其实不复杂,点击“了解更多”看详细介绍。其实就是钻石、永久和订阅,买十个钻石,游戏里面买东西,永久解锁一个技能,或者按月为技能付钱。
产品ID就是个名字,而且只有你自己能看到,程序里面用的是商店为你生成的“StoreID”(是12个字母的随机字符串)。
后面的操作就没什么困难了,选择定价方式、周期、有没有试用,再给每种语言编写个说明给用户看。
提交之后很快就能在商店看到应用变成了“应用内购买”。
二、在代码中检查并引导用户购买
2.1 总代码
前面已经提交了内购,现在应用名称下面已经多了“应用内购买”:
但是应用内购买只能在应用里面触发购买,所以一定要写代码。
代码一般的功能就是检查用户是否已经购买了指定的内购项目,如果没有就调用商店的接口引导用户购买。
下面的例子改编自官方示例,但是更具有可操作性:
internal class ProcessLicense
{
private StoreContext context = StoreContext.GetDefault();
public StringBuilder sbState = new StringBuilder();
public bool bInited = false;//指示初始化完成,如果bInitError=false则应用商店信息和用户订购信息均已获得
public bool bInitError = false;//指示初始化出错,部分数据可能并没有获得,需要重试
public DateTime LastTryInit;//最后一次尝试的时间
public StoreAppLicense appLicense;
private StoreProductQueryResult storeProductQueryResult;
public async void InitLicense()
{
bInited = false;
bInitError = false;
LastTryInit = DateTime.Now;
//获取插件信息
storeProductQueryResult = await context.GetAssociatedStoreProductsAsync(new string[] { "Durable" });
if (storeProductQueryResult.ExtendedError != null)
{
sbState.AppendLine("Something went wrong while getting the add-ons. " +
"ExtendedError:" + storeProductQueryResult.ExtendedError);
bInitError = true;
}
//获取用户的许可证
appLicense = await context.GetAppLicenseAsync();
bInited = true;
}
public async Task GetSubscriptionsInfo()
{
// 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.
sbState.Append($"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)
{
sbState.AppendLine("StoreId " + sku.StoreId);
sbState.AppendLine(" IsInUserCollection " + sku.IsInUserCollection.ToString());
sbState.AppendLine(" IsSubscription " + sku.IsSubscription.ToString());
sbState.AppendLine(" IsTrial " + sku.IsTrial.ToString());
sbState.AppendLine(" Language " + sku.Language);
sbState.AppendLine(" Price " + sku.Price.FormattedPrice);
if (sku.IsSubscription)
{
StoreDurationUnit billingPeriodUnit = sku.SubscriptionInfo.BillingPeriodUnit;
uint billingPeriod = sku.SubscriptionInfo.BillingPeriod;
sbState.AppendLine(" BillingPeriodUnit " + sku.SubscriptionInfo.BillingPeriodUnit.ToString());
sbState.AppendLine(" BillingPeriod " + sku.SubscriptionInfo.BillingPeriod.ToString());
sbState.AppendLine(" HasTrialPeriod " + sku.SubscriptionInfo.HasTrialPeriod.ToString());
if (sku.SubscriptionInfo.HasTrialPeriod)
{
sbState.AppendLine(" TrialPeriodUnit " + sku.SubscriptionInfo.TrialPeriodUnit.ToString());
sbState.AppendLine(" TrialPeriod " + sku.SubscriptionInfo.TrialPeriod.ToString());
}
}
}
}
}
//用户购买
public async Task subscriptionPluginAsync(string subscriptionStoreId)
{
// Get the StoreProduct that represents the subscription add-on.
StoreProduct subscriptionStoreProduct = GetSubscriptionProduct(subscriptionStoreId);
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(subscriptionStoreProduct);
}
//检查用户是否已经订购指定的插件
public bool CheckIfUserHasSubscription(string subscriptionStoreId)
{
if (!bInited) return false;
// 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 StoreProduct GetSubscriptionProduct(string subscriptionStoreId)
{
// Look for the product that represents the subscription.
foreach (var item in storeProductQueryResult.Products)
{
StoreProduct product = item.Value;
if (product.StoreId == subscriptionStoreId)
{
return product;
}
}
sbState.AppendLine("The subscription "+ subscriptionStoreId + " was not found.");
return null;
}
private async Task PromptUserToPurchaseAsync(StoreProduct subscriptionStoreProduct)
{
// 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:
sbState.AppendLine("The purchase did not complete. " +
"The customer may have cancelled the purchase. ExtendedError: " + extendedError);
break;
case StorePurchaseStatus.ServerError:
case StorePurchaseStatus.NetworkError:
sbState.AppendLine("The purchase was unsuccessful due to a server or network error. " +
"ExtendedError: " + extendedError);
break;
case StorePurchaseStatus.AlreadyPurchased:
sbState.AppendLine("The customer already owns this subscription." +
"ExtendedError: " + extendedError);
break;
}
}
}
2.2 关键数据类型和初始化
2.2.1 StoreContext
这个我们已经用过了,上下文对象,总入口。
初始化:StoreContext.GetDefault()
2.2.2 StoreAppLicense
用户的许可证,如果没有联网,本地有缓存数据。
初始化:appLicense = await context.GetAppLicenseAsync()。
对于winforms程序,本地调试不会失败,我的电脑上返回的值是有效订购。
2.2.3 StoreProductQueryResult
应用数据查询结果,要先获得这个信息,然后根据这个信息调用商店接口。
初始化:
//获取插件信息
storeProductQueryResult = await context.GetAssociatedStoreProductsAsync(new string[] { "Durable" });
if (storeProductQueryResult.ExtendedError != null)
{
sbState.AppendLine("Something went wrong while getting the add-ons. " +
"ExtendedError:" + storeProductQueryResult.ExtendedError);
bInitError = true;
}
参数"Durable"表示订阅类型(为啥不给定义个枚举类型啊?)。
这个代码不和应用商店关联是无法获得结果的,对于UWP项目,可以直接和商店关联,本地调试代码就可以获得安装的应用的信息(必须先上架然后从商店安装到本地),而对于winforms项目,没法在项目上关联(是通过独立的MSIX安装项目关联的),所以本地调试是得不到结果的。
2.3 初始化InitLicense
初始化所有对象,获取用户订购信息和应用信息。
2.4 检查指定内购项目是否购买CheckIfUserHasSubscription
参数为内购项目的StoreID,在这里:
2.5 订购subscriptionPluginAsync
参数和上面的是一样的,这个函数最终会打开商店让用户购买,买不买用户自己决定。结果可以定时查询或注册许可证变化事件来获取。
(这里是文档结束)