diff --git a/nuget/readme.txt b/nuget/readme.txt index e621428..5ceb5f5 100644 --- a/nuget/readme.txt +++ b/nuget/readme.txt @@ -1,5 +1,13 @@ In-App Billing Plugin for .NET MAUI, Xamarin, & Windows +Version 7.0+ - Major Android updates +1.) You must compile and target against Android 12 or higher +2.) Android: Now using Android Billing v6 +3.) Android: Major changes to Android product details, subscriptions, and more + +Please read through: https://developer.android.com/google/play/billing/migrate-gpblv6 + + Version 5.0+ has significant updates! 1.) We have removed IInAppBillingVerifyPurchase from all methods. All data required to handle this yourself is returned. 2.) iOS ReceiptURL data is avaialble via ReceiptData diff --git a/src/InAppBillingTests/InAppBillingTests.Mac/InAppBillingTests.Mac.csproj b/src/InAppBillingTests/InAppBillingTests.Mac/InAppBillingTests.Mac.csproj index f4816b3..04d3e4f 100644 --- a/src/InAppBillingTests/InAppBillingTests.Mac/InAppBillingTests.Mac.csproj +++ b/src/InAppBillingTests/InAppBillingTests.Mac/InAppBillingTests.Mac.csproj @@ -95,10 +95,6 @@ {6D4D9135-F225-4626-A9CE-32BDF97AEA89} InAppBillingTests - - {C570E25E-259F-4D4C-88F0-B2982815192D} - Plugin.InAppBilling - \ No newline at end of file diff --git a/src/InAppBillingTests/InAppBillingTests.iOS/InAppBillingTests.iOS.csproj b/src/InAppBillingTests/InAppBillingTests.iOS/InAppBillingTests.iOS.csproj index 24f89de..b6b5bb7 100644 --- a/src/InAppBillingTests/InAppBillingTests.iOS/InAppBillingTests.iOS.csproj +++ b/src/InAppBillingTests/InAppBillingTests.iOS/InAppBillingTests.iOS.csproj @@ -159,9 +159,5 @@ {6D4D9135-F225-4626-A9CE-32BDF97AEA89} InAppBillingTests - - {C570E25E-259F-4D4C-88F0-B2982815192D} - Plugin.InAppBilling - \ No newline at end of file diff --git a/src/Plugin.InAppBilling/Converters.android.cs b/src/Plugin.InAppBilling/Converters.android.cs index 134ec7d..521b501 100644 --- a/src/Plugin.InAppBilling/Converters.android.cs +++ b/src/Plugin.InAppBilling/Converters.android.cs @@ -54,27 +54,39 @@ internal static InAppBillingPurchase ToIABPurchase(this PurchaseHistoryRecord pu }; } - internal static InAppBillingProduct ToIAPProduct(this SkuDetails product) + internal static InAppBillingProduct ToIAPProduct(this ProductDetails product) { + var oneTime = product.GetOneTimePurchaseOfferDetails(); + var subs = product.GetSubscriptionOfferDetails()?.Select(s => new SubscriptionOfferDetail + { + BasePlanId = s.BasePlanId, + OfferId = s.OfferId, + OfferTags = s.OfferTags?.ToList(), + OfferToken = s.OfferToken, + PricingPhases = s?.PricingPhases?.PricingPhaseList?.Select(p => + new PricingPhase + { + BillingCycleCount = p.BillingCycleCount, + BillingPeriod = p.BillingPeriod, + FormattedPrice = p.FormattedPrice, + PriceAmountMicros = p.PriceAmountMicros, + PriceCurrencyCode = p.PriceCurrencyCode, + RecurrenceMode = p.RecurrenceMode + }).ToList() + }); + return new InAppBillingProduct { Name = product.Title, Description = product.Description, - CurrencyCode = product.PriceCurrencyCode, - LocalizedPrice = product.Price, - ProductId = product.Sku, - MicrosPrice = product.PriceAmountMicros, + CurrencyCode = oneTime?.PriceCurrencyCode, + LocalizedPrice = oneTime?.FormattedPrice, + ProductId = product.ProductId, + MicrosPrice = oneTime?.PriceAmountMicros ?? 0, + AndroidExtras = new InAppBillingProductAndroidExtras { - SubscriptionPeriod = product.SubscriptionPeriod, - LocalizedIntroductoryPrice = product.IntroductoryPrice, - MicrosIntroductoryPrice = product.IntroductoryPriceAmountMicros, - FreeTrialPeriod = product.FreeTrialPeriod, - IconUrl = product.IconUrl, - IntroductoryPriceCycles = product.IntroductoryPriceCycles, - IntroductoryPricePeriod = product.IntroductoryPricePeriod, - MicrosOriginalPriceAmount = product.OriginalPriceAmountMicros, - OriginalPrice = product.OriginalPrice + } }; } diff --git a/src/Plugin.InAppBilling/InAppBilling.android.cs b/src/Plugin.InAppBilling/InAppBilling.android.cs index 8efbf84..b80ed71 100644 --- a/src/Plugin.InAppBilling/InAppBilling.android.cs +++ b/src/Plugin.InAppBilling/InAppBilling.android.cs @@ -3,12 +3,10 @@ using System.Linq; using System.Threading.Tasks; using Android.App; -using Java.Security; -using Java.Security.Spec; -using Java.Lang; -using System.Text; using Android.BillingClient.Api; using Android.Content; +using static Android.BillingClient.Api.BillingClient; +using BillingResponseCode = Android.BillingClient.Api.BillingResponseCode; #if NET using Microsoft.Maui.ApplicationModel; #else @@ -17,10 +15,10 @@ namespace Plugin.InAppBilling { - /// - /// Implementation for Feature - /// - [Preserve(AllMembers = true)] + /// + /// Implementation for Feature + /// + [Preserve(AllMembers = true)] public class InAppBillingImplementation : BaseInAppBilling { /// @@ -33,10 +31,10 @@ public class InAppBillingImplementation : BaseInAppBilling /// This is set from the MainApplication.cs file that was laid down by the plugin /// /// The context. - Activity Activity => - Platform.CurrentActivity ?? throw new NullReferenceException("Current Activity is null, ensure that the MainActivity.cs file is configuring Xamarin.Essentials in your source code so the In App Billing can use it."); + static Activity Activity => + Platform.CurrentActivity ?? throw new NullReferenceException("Current Activity is null, ensure that the MainActivity.cs file is configuring Xamarin.Essentials/.NET MAUI in your source code so the In App Billing can use it."); - Context Context => Android.App.Application.Context; + static Context Context => Application.Context; /// /// Default Constructor for In App Billing Implementation on Android @@ -67,7 +65,7 @@ public override Task ConnectAsync(bool enablePendingPurchases = true) tcsConnect?.TrySetCanceled(); tcsConnect = new TaskCompletionSource(); - BillingClientBuilder = BillingClient.NewBuilder(Context); + BillingClientBuilder = NewBuilder(Context); BillingClientBuilder.SetListener(OnPurchasesUpdated); if (enablePendingPurchases) BillingClient = BillingClientBuilder.EnablePendingPurchases().Build(); @@ -146,47 +144,51 @@ public async override Task> GetProductInfoAsync var skuType = itemType switch { - ItemType.InAppPurchase => BillingClient.SkuType.Inapp, - ItemType.InAppPurchaseConsumable => BillingClient.SkuType.Inapp, - _ => BillingClient.SkuType.Subs + ItemType.InAppPurchase => ProductType.Inapp, + ItemType.InAppPurchaseConsumable => ProductType.Inapp, + _ => ProductType.Subs }; - if(skuType == BillingClient.SkuType.Subs) + if (skuType == ProductType.Subs) { - var result = BillingClient.IsFeatureSupported(BillingClient.FeatureType.Subscriptions); + var result = BillingClient.IsFeatureSupported(FeatureType.Subscriptions); ParseBillingResult(result); } - var skuDetailsParams = SkuDetailsParams.NewBuilder() - .SetType(skuType) - .SetSkusList(productIds) - .Build(); - var skuDetailsResult = await BillingClient.QuerySkuDetailsAsync(skuDetailsParams); + var productList = productIds.Select(p => QueryProductDetailsParams.Product.NewBuilder() + .SetProductType(skuType) + .SetProductId(p) + .Build()).ToList(); + + var skuDetailsParams = QueryProductDetailsParams.NewBuilder().SetProductList(productList); + + var skuDetailsResult = await BillingClient.QueryProductDetailsAsync(skuDetailsParams.Build()); ParseBillingResult(skuDetailsResult?.Result); - return skuDetailsResult.SkuDetails.Select(product => product.ToIAPProduct()); + return skuDetailsResult.ProductDetails.Select(product => product.ToIAPProduct()); } - - public override Task> GetPurchasesAsync(ItemType itemType) + + public override async Task> GetPurchasesAsync(ItemType itemType) { if (BillingClient == null) throw new InAppBillingPurchaseException(PurchaseError.ServiceUnavailable, "You are not connected to the Google Play App store."); var skuType = itemType switch { - ItemType.InAppPurchase => BillingClient.SkuType.Inapp, - ItemType.InAppPurchaseConsumable => BillingClient.SkuType.Inapp, - _ => BillingClient.SkuType.Subs + ItemType.InAppPurchase => ProductType.Inapp, + ItemType.InAppPurchaseConsumable => ProductType.Inapp, + _ => ProductType.Subs }; - var purchasesResult = BillingClient.QueryPurchases(skuType); + var query = QueryPurchasesParams.NewBuilder().SetProductType(skuType).Build(); + var purchasesResult = await BillingClient.QueryPurchasesAsync(query); - ParseBillingResult(purchasesResult.BillingResult); + ParseBillingResult(purchasesResult.Result); - return Task.FromResult(purchasesResult.PurchasesList.Select(p => p.ToIABPurchase())); + return purchasesResult.Purchases.Select(p => p.ToIABPurchase()); } /// @@ -201,11 +203,12 @@ public override async Task> GetPurchasesHistor var skuType = itemType switch { - ItemType.InAppPurchase => BillingClient.SkuType.Inapp, - ItemType.InAppPurchaseConsumable => BillingClient.SkuType.Inapp, - _ => BillingClient.SkuType.Subs + ItemType.InAppPurchase => ProductType.Inapp, + ItemType.InAppPurchaseConsumable => ProductType.Inapp, + _ => ProductType.Subs }; + //TODO: Binding needs updated var purchasesResult = await BillingClient.QueryPurchaseHistoryAsync(skuType); @@ -219,7 +222,7 @@ public override async Task> GetPurchasesHistor /// Purchase token of original subscription /// Proration mode (1 - ImmediateWithTimeProration, 2 - ImmediateAndChargeProratedPrice, 3 - ImmediateWithoutProration, 4 - Deferred) /// Purchase details - public override async Task UpgradePurchasedSubscriptionAsync(string newProductId, string purchaseTokenOfOriginalSubscription,SubscriptionProrationMode prorationMode = SubscriptionProrationMode.ImmediateWithTimeProration) + public override async Task UpgradePurchasedSubscriptionAsync(string newProductId, string purchaseTokenOfOriginalSubscription, SubscriptionProrationMode prorationMode = SubscriptionProrationMode.ImmediateWithTimeProration) { if (BillingClient == null || !IsConnected) { @@ -240,25 +243,25 @@ public override async Task UpgradePurchasedSubscriptionAsy async Task UpgradePurchasedSubscriptionInternalAsync(string newProductId, string purchaseTokenOfOriginalSubscription, SubscriptionProrationMode prorationMode) { - var itemType = BillingClient.SkuType.Subs; + var itemType = ProductType.Subs; if (tcsPurchase?.Task != null && !tcsPurchase.Task.IsCompleted) { return null; } - var skuDetailsParams = SkuDetailsParams.NewBuilder() - .SetType(itemType) - .SetSkusList(new List { newProductId }) - .Build(); + var productList = QueryProductDetailsParams.Product.NewBuilder() + .SetProductType(itemType) + .SetProductId(newProductId) + .Build(); - var skuDetailsResult = await BillingClient.QuerySkuDetailsAsync(skuDetailsParams); - ParseBillingResult(skuDetailsResult?.Result); + var skuDetailsParams = QueryProductDetailsParams.NewBuilder().SetProductList(new[] { productList }).Build(); + + var skuDetailsResult = await BillingClient.QueryProductDetailsAsync(skuDetailsParams); - var skuDetails = skuDetailsResult?.SkuDetails.FirstOrDefault(); + ParseBillingResult(skuDetailsResult.Result); - if (skuDetails == null) - throw new ArgumentException($"{newProductId} does not exist"); + var skuDetails = skuDetailsResult.ProductDetails.FirstOrDefault() ?? throw new ArgumentException($"{newProductId} does not exist"); //1 - BillingFlowParams.ProrationMode.ImmediateWithTimeProration //2 - BillingFlowParams.ProrationMode.ImmediateAndChargeProratedPrice @@ -267,16 +270,21 @@ async Task UpgradePurchasedSubscriptionInternalAsync(strin //5 - BillingFlowParams.ProrationMode.ImmediateAndChargeFullPrice var updateParams = BillingFlowParams.SubscriptionUpdateParams.NewBuilder() - .SetOldSkuPurchaseToken(purchaseTokenOfOriginalSubscription) - .SetReplaceSkusProrationMode((int)prorationMode) + .SetOldPurchaseToken(purchaseTokenOfOriginalSubscription) + .SetReplaceProrationMode((int)prorationMode) + .Build(); + + var prodDetailsParams = BillingFlowParams.ProductDetailsParams.NewBuilder() + .SetProductDetails(skuDetails) + .SetOfferToken(skuDetails.GetSubscriptionOfferDetails()?.FirstOrDefault()?.OfferToken) .Build(); var flowParams = BillingFlowParams.NewBuilder() - .SetSkuDetails(skuDetails) + .SetProductDetailsParamsList(new[] { prodDetailsParams }) .SetSubscriptionUpdateParams(updateParams) .Build(); - tcsPurchase = new TaskCompletionSource<(BillingResult billingResult, IList purchases)>(); + tcsPurchase = new TaskCompletionSource<(BillingResult billingResult, IList purchases)>(); var responseCode = BillingClient.LaunchBillingFlow(Activity, flowParams); ParseBillingResult(responseCode); @@ -285,12 +293,12 @@ async Task UpgradePurchasedSubscriptionInternalAsync(strin ParseBillingResult(result.billingResult); //we are only buying 1 thing. - var androidPurchase = result.purchases?.FirstOrDefault(p => p.Skus.Contains(newProductId)); + var androidPurchase = result.purchases?.FirstOrDefault(p => p.Products.Contains(newProductId)); //for some reason the data didn't come back if (androidPurchase == null) { - var purchases = await GetPurchasesAsync(itemType == BillingClient.SkuType.Inapp ? ItemType.InAppPurchase : ItemType.Subscription); + var purchases = await GetPurchasesAsync(itemType == ProductType.Inapp ? ItemType.InAppPurchase : ItemType.Subscription); return purchases.FirstOrDefault(p => p.ProductId == newProductId); } @@ -323,58 +331,73 @@ public async override Task PurchaseAsync(string productId, { case ItemType.InAppPurchase: case ItemType.InAppPurchaseConsumable: - return await PurchaseAsync(productId, BillingClient.SkuType.Inapp, obfuscatedAccountId, obfuscatedProfileId); + return await PurchaseAsync(productId, ProductType.Inapp, obfuscatedAccountId, obfuscatedProfileId); case ItemType.Subscription: - var result = BillingClient.IsFeatureSupported(BillingClient.FeatureType.Subscriptions); + var result = BillingClient.IsFeatureSupported(FeatureType.Subscriptions); ParseBillingResult(result); - return await PurchaseAsync(productId, BillingClient.SkuType.Subs, obfuscatedAccountId, obfuscatedProfileId); + return await PurchaseAsync(productId, ProductType.Subs, obfuscatedAccountId, obfuscatedProfileId); } return null; } async Task PurchaseAsync(string productSku, string itemType, string obfuscatedAccountId = null, string obfuscatedProfileId = null) - { + { - var skuDetailsParams = SkuDetailsParams.NewBuilder() - .SetType(itemType) - .SetSkusList(new List { productSku }) + var productList = QueryProductDetailsParams.Product.NewBuilder() + .SetProductType(itemType) + .SetProductId(productSku) .Build(); - var skuDetailsResult = await BillingClient.QuerySkuDetailsAsync(skuDetailsParams); - ParseBillingResult(skuDetailsResult?.Result); + var skuDetailsParams = QueryProductDetailsParams.NewBuilder().SetProductList(new[] { productList }); + + var skuDetailsResult = await BillingClient.QueryProductDetailsAsync(skuDetailsParams.Build()); - var skuDetails = skuDetailsResult.SkuDetails.FirstOrDefault(); + ParseBillingResult(skuDetailsResult.Result); + + var skuDetails = skuDetailsResult.ProductDetails.FirstOrDefault() ?? throw new ArgumentException($"{productSku} does not exist"); + var productDetailsParamsList = itemType == ProductType.Subs ? + BillingFlowParams.ProductDetailsParams.NewBuilder() + .SetProductDetails(skuDetails) + .SetOfferToken(skuDetails.GetSubscriptionOfferDetails()?.FirstOrDefault()?.OfferToken) + .Build() + : BillingFlowParams.ProductDetailsParams.NewBuilder() + .SetProductDetails(skuDetails) + .Build(); + + var billingFlowParams = BillingFlowParams.NewBuilder() + .SetProductDetailsParamsList(new[] { productDetailsParamsList }); - if (skuDetails == null) - throw new ArgumentException($"{productSku} does not exist"); - var flowParamsBuilder = BillingFlowParams.NewBuilder() - .SetSkuDetails(skuDetails); if (!string.IsNullOrWhiteSpace(obfuscatedAccountId)) - flowParamsBuilder.SetObfuscatedAccountId(obfuscatedAccountId); + billingFlowParams.SetObfuscatedAccountId(obfuscatedAccountId); if (!string.IsNullOrWhiteSpace(obfuscatedProfileId)) - flowParamsBuilder.SetObfuscatedProfileId(obfuscatedProfileId); + billingFlowParams.SetObfuscatedProfileId(obfuscatedProfileId); + + var flowParams = billingFlowParams.Build(); + - var flowParams = flowParamsBuilder.Build(); tcsPurchase = new TaskCompletionSource<(BillingResult billingResult, IList purchases)>(); + + var responseCode = BillingClient.LaunchBillingFlow(Activity, flowParams); - ParseBillingResult(responseCode); + + ParseBillingResult(responseCode); var result = await tcsPurchase.Task; ParseBillingResult(result.billingResult); //we are only buying 1 thing. - var androidPurchase = result.purchases?.FirstOrDefault(p => p.Skus.Contains(productSku)); + var androidPurchase = result.purchases?.FirstOrDefault(p => p.Products.Contains(productSku)); //for some reason the data didn't come back if (androidPurchase == null) { - var purchases = await GetPurchasesAsync(itemType == BillingClient.SkuType.Inapp ? ItemType.InAppPurchase : ItemType.Subscription); + var purchases = await GetPurchasesAsync(itemType == ProductType.Inapp ? ItemType.InAppPurchase : ItemType.Subscription); return purchases.FirstOrDefault(p => p.ProductId == productSku); } @@ -403,10 +426,11 @@ async Task PurchaseAsync(string productSku, string itemTyp return items; } - //inapp:{Context.PackageName}:{productSku} + /// /// Consume a purchase with a purchase token. + /// in app:{Context.PackageName}:{productSku} /// /// Id or Sku of product /// Original Purchase Token @@ -418,7 +442,7 @@ public override async Task ConsumePurchaseAsync(string productId, string t throw new InAppBillingPurchaseException(PurchaseError.ServiceUnavailable, "You are not connected to the Google Play App store."); } - + var consumeParams = ConsumeParams.NewBuilder() .SetPurchaseToken(transactionIdentifier) .Build(); @@ -426,14 +450,17 @@ public override async Task ConsumePurchaseAsync(string productId, string t var result = await BillingClient.ConsumeAsync(consumeParams); - return ParseBillingResult(result.BillingResult); + return ParseBillingResult(result.BillingResult); } - bool ParseBillingResult(BillingResult result) + static bool ParseBillingResult(BillingResult result) { - if(result == null) + if (result == null) throw new InAppBillingPurchaseException(PurchaseError.GeneralError); + if ((int)result.ResponseCode == Android.BillingClient.Api.BillingClient.BillingResponseCode.NetworkError) + throw new InAppBillingPurchaseException(PurchaseError.ServiceTimeout);//Network connection is down + return result.ResponseCode switch { BillingResponseCode.Ok => true, @@ -450,7 +477,7 @@ bool ParseBillingResult(BillingResult result) BillingResponseCode.ItemUnavailable => throw new InAppBillingPurchaseException(PurchaseError.ItemUnavailable), _ => false, }; - } + } } } - + diff --git a/src/Plugin.InAppBilling/Plugin.InAppBilling.csproj b/src/Plugin.InAppBilling/Plugin.InAppBilling.csproj index 7750022..010af4f 100644 --- a/src/Plugin.InAppBilling/Plugin.InAppBilling.csproj +++ b/src/Plugin.InAppBilling/Plugin.InAppBilling.csproj @@ -1,8 +1,9 @@  - netstandard2.0;MonoAndroid10.0;Xamarin.iOS10;Xamarin.TVOS10;Xamarin.Mac20;net6.0-android;net6.0-ios;net6.0-maccatalyst;net6.0-tvos;net6.0-macos + + netstandard2.0;MonoAndroid12.0;Xamarin.iOS10;Xamarin.TVOS10;Xamarin.Mac20;net6.0-android;net6.0-ios;net6.0-maccatalyst;net6.0-tvos;net6.0-macos $(TargetFrameworks);uap10.0.19041;net6.0-windows10.0.19041; - 9.0 + 10.0 Plugin.InAppBilling Plugin.InAppBilling $(AssemblyName) ($(TargetFramework)) @@ -97,14 +98,14 @@ - - + + - - + + diff --git a/src/Plugin.InAppBilling/Shared/InAppBillingProduct.shared.cs b/src/Plugin.InAppBilling/Shared/InAppBillingProduct.shared.cs index a4f24a8..a8f8b59 100644 --- a/src/Plugin.InAppBilling/Shared/InAppBillingProduct.shared.cs +++ b/src/Plugin.InAppBilling/Shared/InAppBillingProduct.shared.cs @@ -26,13 +26,13 @@ public class InAppBillingProductAppleExtras public bool IsFamilyShareable { get; set; } /// - /// iOS 11.2: gets information about product discunt + /// iOS 11.2: gets information about product discount /// public InAppBillingProductDiscount IntroductoryOffer { get; set; } = null; /// - /// iOS 12.2: gets information about product discunt + /// iOS 12.2: gets information about product discount /// public List Discounts { get; set; } = null; } @@ -87,51 +87,56 @@ public class InAppBillingProductWindowsExtras public class InAppBillingProductAndroidExtras { /// - /// Subscription period, specified in ISO 8601 format. - /// - public string SubscriptionPeriod { get; set; } - - /// - /// Trial period, specified in ISO 8601 format. - /// - public string FreeTrialPeriod { get; set; } - - /// - /// Icon of the product if present - /// - public string IconUrl { get; set; } - - /// - /// Gets or sets the localized introductory price. - /// - /// The localized introductory price. - public string LocalizedIntroductoryPrice { get; set; } - - /// - /// Number of subscription billing periods for which the user will be given the introductory price, such as 3 - /// - public int IntroductoryPriceCycles { get; set; } - - /// - /// Billing period of the introductory price, specified in ISO 8601 format - /// - public string IntroductoryPricePeriod { get; set; } - - /// - /// Introductory price of the product in micro-units - /// - /// The introductory price. - public Int64 MicrosIntroductoryPrice { get; set; } - - /// - /// Formatted original price of the item, including its currency sign. - /// - public string OriginalPrice { get; set; } - - /// - /// Original price in micro-units, where 1,000,000, micro-units equal one unit of the currency + /// The period details for products that are subscriptions. /// - public long MicrosOriginalPriceAmount { get; set; } + public List SubscriptionOfferDetails { get; set; } + + ///// + ///// Subscription period, specified in ISO 8601 format. + ///// + //public string SubscriptionPeriod { get; set; } + + ///// + ///// Trial period, specified in ISO 8601 format. + ///// + //public string FreeTrialPeriod { get; set; } + + ///// + ///// Icon of the product if present + ///// + //public string IconUrl { get; set; } + + ///// + ///// Gets or sets the localized introductory price. + ///// + ///// The localized introductory price. + //public string LocalizedIntroductoryPrice { get; set; } + + ///// + ///// Number of subscription billing periods for which the user will be given the introductory price, such as 3 + ///// + //public int IntroductoryPriceCycles { get; set; } + + ///// + ///// Billing period of the introductory price, specified in ISO 8601 format + ///// + //public string IntroductoryPricePeriod { get; set; } + + ///// + ///// Introductory price of the product in micro-units + ///// + ///// The introductory price. + //public Int64 MicrosIntroductoryPrice { get; set; } + + ///// + ///// Formatted original price of the item, including its currency sign. + ///// + //public string OriginalPrice { get; set; } + + ///// + ///// Original price in micro-units, where 1,000,000, micro-units equal one unit of the currency + ///// + //public long MicrosOriginalPriceAmount { get; set; } } diff --git a/src/Plugin.InAppBilling/Shared/InAppBillingProductDiscount.shared.cs b/src/Plugin.InAppBilling/Shared/InAppBillingProductDiscount.shared.cs index 36d43f5..32212d4 100644 --- a/src/Plugin.InAppBilling/Shared/InAppBillingProductDiscount.shared.cs +++ b/src/Plugin.InAppBilling/Shared/InAppBillingProductDiscount.shared.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace Plugin.InAppBilling { @@ -128,4 +129,23 @@ public enum ProductDiscountType /// Unknown } + + public class SubscriptionOfferDetail + { + public string BasePlanId { get; set; } + public string OfferId { get; set; } + public List OfferTags { get; set; } + public string OfferToken { get; set; } + public List PricingPhases { get; set; } + } + + public class PricingPhase + { + public int BillingCycleCount { get; set; } + public string BillingPeriod { get; set; } + public string FormattedPrice { get; set; } + public long PriceAmountMicros { get; set; } + public string PriceCurrencyCode { get; set; } + public int RecurrenceMode { get; set; } + } }