Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a few more helper functions #492

Merged
merged 12 commits into from
Sep 22, 2022
7 changes: 6 additions & 1 deletion docs/PurchaseConsumable.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ Task<InAppBillingPurchase> PurchaseAsync(string productId, ItemType itemType, II
```

### Consume Purchase
* Android & Windows: You must consume your purchase when your user uses it before buying another one.

* iOS: Beta - In version 4 we auto finalized all transactions and after testing I decided to keep this feature on in 5/6... you can no turn that off in your iOS application with `InAppBillingImplementation.FinishAllTransactions = false;`. This would be required if you are using consumables and don't want to auto finish. You will need to finalize manually with `ConsumePurchaseAsync` or `FinalizePurchaseAsync`.


```csharp
/// <summary>
/// Consume a purchase with a purchase token.
Expand Down Expand Up @@ -69,7 +74,7 @@ public async Task<bool> PurchaseItem(string productId)
// purchased, we can now consume the item or do it later
// here you may want to call your backend or process something in your app.


//only required on Android & Windows
var wasConsumed = await CrossInAppBilling.Current.ConsumePurchaseAsync(purchase.ProductId, purchase.TransactionIdentifier);

if(wasConsumed)
Expand Down
6 changes: 4 additions & 2 deletions docs/PurchaseNonConsumable.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ Task<InAppBillingPurchase> PurchaseAsync(string productId, ItemType itemType, II

On Android you must call `FinalizePurchaseAsync` within 3 days when a purchase is validated. Please read the [Android documentation on Pending Transactions](https://developer.android.com/google/play/billing/integrate#pending) for more information.

On iOS you must also call `FinalizePurchaseAsync`. This is a change from earlier versions of the plugin which would finalize the purchase for you.
* iOS: Beta - In version 4 we auto finalized all transactions and after testing I decided to keep this feature on in 5/6... you can no turn that off in your iOS application with `InAppBillingImplementation.FinishAllTransactions = false;`. This would be required if you are using consumables and don't want to auto finish. You will need to finalize manually with `FinalizePurchaseAsync`.


Example:
```csharp
Expand All @@ -48,7 +49,8 @@ public async Task<bool> PurchaseItem(string productId)
//did not purchase
}
else if(purchase.State == PurchaseState.Purchased)
{
{
// only need to finalize if on Android unless you turn off auto finalize on iOS
var ack = await CrossInAppBilling.Current.FinalizePurchaseAsync(purchase.TransactionIdentifier);

// Handle if acknowledge was successful or not
Expand Down
3 changes: 2 additions & 1 deletion docs/PurchaseSubscription.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Task<InAppBillingPurchase> PurchaseAsync(string productId, ItemType itemType, II
On Android you must call `FinalizePurchaseAsync` within 3 days when a purchase is validated. Please read the [Android documentation on Pending Transactions](https://developer.android.com/google/play/billing/integrate#pending) for more information.


You must also call this on iOS to finalize and acknowlege the transaction.
* iOS: Beta - In version 4 we auto finalized all transactions and after testing I decided to keep this feature on in 5/6... you can no turn that off in your iOS application with `InAppBillingImplementation.FinishAllTransactions = false;`. This would be required if you are using consumables and don't want to auto finish. You will need to finalize manually with `FinalizePurchaseAsync`.

Example:
```csharp
Expand All @@ -52,6 +52,7 @@ public async Task<bool> PurchaseItem(string productId, string payload)
}
else if(purchase.State == PurchaseState.Purchased)
{
//only needed on android unless you turn off auto finalize
var ack = await CrossInAppBilling.Current.FinalizePurchaseAsync(purchase.TransactionIdentifier);

// Handle if acknowledge was successful or not
Expand Down
2 changes: 1 addition & 1 deletion src/InAppBilling.sln
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InAppBillingTests.Mac", "In
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InAppBillingTests.UWP", "InAppBillingTests\InAppBillingTests.UWP\InAppBillingTests.UWP.csproj", "{EBD6A824-A56E-4F33-B352-B4E72D711B00}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InAppBillingMauiTest", "InAppBillingTests\InAppBillingMauiTest\InAppBillingMauiTest.csproj", "{BAE4393A-4E17-4E60-BF53-E916505F44E1}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InAppBillingMauiTest", "InAppBillingTests\InAppBillingMauiTest\InAppBillingMauiTest.csproj", "{BAE4393A-4E17-4E60-BF53-E916505F44E1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 13 additions & 5 deletions src/Plugin.InAppBilling/InAppBilling.android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -382,17 +382,25 @@ async Task<InAppBillingPurchase> PurchaseAsync(string productSku, string itemTyp
}


public async override Task<bool> FinalizePurchaseAsync(string transactionIdentifier)
public async override Task<IEnumerable<(string Id, bool Success)>> FinalizePurchaseAsync(params string[] transactionIdentifier)
{
if (BillingClient == null || !IsConnected)
throw new InAppBillingPurchaseException(PurchaseError.ServiceUnavailable, "You are not connected to the Google Play App store.");

var acknowledgeParams = AcknowledgePurchaseParams.NewBuilder()
.SetPurchaseToken(transactionIdentifier).Build();

var result = await BillingClient.AcknowledgePurchaseAsync(acknowledgeParams);
var items = new List<(string Id, bool Success)>();
foreach (var t in transactionIdentifier)
{

var acknowledgeParams = AcknowledgePurchaseParams.NewBuilder()
.SetPurchaseToken(t).Build();

var result = await BillingClient.AcknowledgePurchaseAsync(acknowledgeParams);

items.Add((t, ParseBillingResult(result)));
}

return ParseBillingResult(result);
return items;
}

//inapp:{Context.PackageName}:{productSku}
Expand Down
132 changes: 109 additions & 23 deletions src/Plugin.InAppBilling/InAppBilling.apple.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class InAppBillingImplementation : BaseInAppBilling
/// <summary>
/// Backwards compat flag that may be removed in the future to auto finish all transactions like in v4
/// </summary>
public static bool FinishAllTransactions { get; set; } = false;
public static bool FinishAllTransactions { get; set; } = true;

#if __IOS__ || __TVOS__
internal static bool HasIntroductoryOffer => UIKit.UIDevice.CurrentDevice.CheckSystemVersion(11, 2);
Expand Down Expand Up @@ -459,41 +459,127 @@ public override string ReceiptData
/// <param name="transactionIdentifier">Original Purchase Token</param>
/// <returns>If consumed successful</returns>
/// <exception cref="InAppBillingPurchaseException">If an error occurs during processing</exception>
public override Task<bool> ConsumePurchaseAsync(string productId, string transactionIdentifier) =>
FinalizePurchaseAsync(transactionIdentifier);
public override async Task<bool> ConsumePurchaseAsync(string productId, string transactionIdentifier)
{
var items = await FinalizePurchaseAsync(transactionIdentifier);
var item = items.FirstOrDefault();

return item.Success;
}


/// <summary>
///
/// </summary>
/// <param name="productIds"></param>
/// <returns></returns>
public override async Task<IEnumerable<(string Id, bool Success)>> FinalizePurchaseOfProductAsync(params string[] productIds)
{
var purchases = await RestoreAsync();

var items = new List<(string Id, bool Success)>();


if (purchases == null)
{
return items;
}


foreach (var t in productIds)
{
if (string.IsNullOrWhiteSpace(t))
{
items.Add((t, false));
continue;
}


var transactions = purchases.Where(p => p.Payment?.ProductIdentifier == t);

if ((transactions?.Count() ?? 0) == 0)
{
items.Add((t, false));
continue;
}

var success = true;
foreach (var transaction in transactions)
{

try
{
SKPaymentQueue.DefaultQueue.FinishTransaction(transaction);
}
catch (Exception ex)
{
Debug.WriteLine("Unable to finish transaction: " + ex);

success = false;
}
}


items.Add((t, success));
}

return items;
}

/// <summary>
/// Finish a transaction manually
/// </summary>
/// <param name="transactionIdentifier"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public async override Task<bool> FinalizePurchaseAsync(string transactionIdentifier)
public async override Task<IEnumerable<(string Id, bool Success)>> FinalizePurchaseAsync(params string[] transactionIdentifier)
{
if (string.IsNullOrWhiteSpace(transactionIdentifier))
throw new ArgumentException("Purchase Token must be valid", nameof(transactionIdentifier));
var purchases = await RestoreAsync();

var purchases = await RestoreAsync();
var items = new List<(string Id, bool Success)>();

if (purchases == null)
return false;

var transactions = purchases.Where(p => p.TransactionIdentifier == transactionIdentifier);
if (purchases == null)
{
return items;
}

if ((transactions?.Count() ?? 0) == 0)
return false;
try
{
foreach(var transaction in transactions)
SKPaymentQueue.DefaultQueue.FinishTransaction(transaction);
}
catch(Exception ex)
{
Debug.WriteLine("Unable to finish transaction: " + ex);
return false;
}

return true;
foreach (var t in transactionIdentifier)
{
if (string.IsNullOrWhiteSpace(t))
{
items.Add((t, false));
continue;
}

var transactions = purchases.Where(p => p.TransactionIdentifier == t);

if ((transactions?.Count() ?? 0) == 0)
{
items.Add((t, false));
continue;
}

var success = true;
foreach (var transaction in transactions)
{

try
{
SKPaymentQueue.DefaultQueue.FinishTransaction(transaction);
}
catch (Exception ex)
{
Debug.WriteLine("Unable to finish transaction: " + ex);
success = false;
}
}

items.Add((t, success));
}

return items;
}

PaymentObserver paymentObserver;
Expand Down
9 changes: 8 additions & 1 deletion src/Plugin.InAppBilling/Shared/BaseInAppBilling.shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,14 @@ public virtual void Dispose(bool disposing)
/// </summary>
/// <param name="transactionIdentifier"></param>
/// <returns></returns>
public virtual Task<bool> FinalizePurchaseAsync(string transactionIdentifier) => Task.FromResult(true);
public virtual Task<IEnumerable<(string Id, bool Success)>> FinalizePurchaseAsync(params string[] transactionIdentifier) => Task.FromResult(new List<(string Id, bool Success)>().AsEnumerable());

/// <summary>
///
/// </summary>
/// <param name="productIds"></param>
/// <returns></returns>
public virtual Task<IEnumerable<(string Id, bool Success)>> FinalizePurchaseOfProductAsync(params string[] productIds) => Task.FromResult(new List<(string Id, bool Success)>().AsEnumerable());

/// <summary>
/// iOS: Displays a sheet that enables users to redeem subscription offer codes that you configure in App Store Connect.
Expand Down
14 changes: 11 additions & 3 deletions src/Plugin.InAppBilling/Shared/IInAppBilling.shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,19 @@ public interface IInAppBilling : IDisposable


/// <summary>
/// Manually acknowledge a purchase
/// Manually acknowledge/finalize a purchase
/// </summary>
/// <param name="transactionIdentifier"></param>
/// <returns></returns>
Task<bool> FinalizePurchaseAsync(string transactionIdentifier);
/// <returns>if all were acknowledged/finalized</returns>
Task<IEnumerable<(string Id, bool Success)>> FinalizePurchaseAsync(params string[] transactionIdentifier);

/// <summary>
/// Manually acknowledge/finalize a product id
/// </summary>
/// <param name="productIds"></param>
/// <returns>if all were acknowledged/finalized</returns>
Task<IEnumerable<(string Id, bool Success)>> FinalizePurchaseOfProductAsync(params string[] productIds);


/// <summary>
/// Connect to billing service
Expand Down
7 changes: 5 additions & 2 deletions src/Plugin.InAppBilling/Shared/InAppBillingPurchase.shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,11 @@ public InAppBillingPurchase()
/// </summary>
public ConsumptionState ConsumptionState { get; set; }

public bool IsAcknowledged { get; set; }

/// <summary>
/// Only valid on Android, tells us if our purchase is acknowledged or not and needs to be finalized
/// </summary>
public bool? IsAcknowledged { get; set; }

public string ObfuscatedAccountId { get; set; }

public string ObfuscatedProfileId { get; set; }
Expand Down