Skip to content

Commit

Permalink
Added ReceiptData and removed IVerify from methods
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesmontemagno committed Jul 18, 2021
1 parent e3c333a commit b281090
Show file tree
Hide file tree
Showing 11 changed files with 6,782 additions and 1,185 deletions.
4 changes: 4 additions & 0 deletions nuget/readme.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
In App Billing Plugin for Xamarin & Windows

Version 5.0 has more 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
3.) We are now using Android Billing version 4

Version 4.0 has significant updates.

Expand Down
7,713 changes: 6,686 additions & 1,027 deletions src/InAppBillingTests/InAppBillingTests.Android/Resources/Resource.designer.cs

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/Plugin.InAppBilling/Converters.android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public static InAppBillingPurchase ToIABPurchase(this Purchase purchase)
AutoRenewing = purchase.IsAutoRenewing,
ConsumptionState = ConsumptionState.NoYetConsumed,
Id = purchase.OrderId,
OriginalJson = purchase.OriginalJson,
Signature = purchase.Signature,
IsAcknowledged = purchase.IsAcknowledged,
Payload = purchase.DeveloperPayload,
ProductId = purchase.Skus.FirstOrDefault(),
Expand Down
71 changes: 20 additions & 51 deletions src/Plugin.InAppBilling/InAppBilling.android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,11 +204,10 @@ public override Task<IEnumerable<InAppBillingPurchase>> GetPurchasesAsync(ItemTy
/// </summary>
/// <param name="productId">Sku or ID of product</param>
/// <param name="itemType">Type of product being requested</param>
/// <param name="verifyPurchase">Interface to verify purchase</param>
/// <param name="obfuscatedAccountId">Specifies an optional obfuscated string that is uniquely associated with the user's account in your app.</param>
/// <param name="obfuscatedProfileId">Specifies an optional obfuscated string that is uniquely associated with the user's profile in your app.</param>
/// <returns></returns>
public async override Task<InAppBillingPurchase> PurchaseAsync(string productId, ItemType itemType, IInAppBillingVerifyPurchase verifyPurchase = null, string obfuscatedAccountId = null, string obfuscatedProfileId = null)
public async override Task<InAppBillingPurchase> PurchaseAsync(string productId, ItemType itemType, string obfuscatedAccountId = null, string obfuscatedProfileId = null)
{
if (BillingClient == null || !IsConnected)
{
Expand All @@ -225,18 +224,18 @@ public async override Task<InAppBillingPurchase> PurchaseAsync(string productId,
switch (itemType)
{
case ItemType.InAppPurchase:
return await PurchaseAsync(productId, BillingClient.SkuType.Inapp, verifyPurchase, obfuscatedAccountId, obfuscatedProfileId);
return await PurchaseAsync(productId, BillingClient.SkuType.Inapp, obfuscatedAccountId, obfuscatedProfileId);
case ItemType.Subscription:

var result = BillingClient.IsFeatureSupported(BillingClient.FeatureType.Subscriptions);
ParseBillingResult(result);
return await PurchaseAsync(productId, BillingClient.SkuType.Subs, verifyPurchase, obfuscatedAccountId, obfuscatedProfileId);
return await PurchaseAsync(productId, BillingClient.SkuType.Subs, obfuscatedAccountId, obfuscatedProfileId);
}

return null;
}

async Task<InAppBillingPurchase> PurchaseAsync(string productSku, string itemType, IInAppBillingVerifyPurchase verifyPurchase, string obfuscatedAccountId = null, string obfuscatedProfileId = null)
async Task<InAppBillingPurchase> PurchaseAsync(string productSku, string itemType, string obfuscatedAccountId = null, string obfuscatedProfileId = null)
{

var skuDetailsParams = SkuDetailsParams.NewBuilder()
Expand Down Expand Up @@ -280,14 +279,7 @@ async Task<InAppBillingPurchase> PurchaseAsync(string productSku, string itemTyp
return purchases.FirstOrDefault(p => p.ProductId == productSku);
}

var data = androidPurchase.OriginalJson;
var signature = androidPurchase.Signature;

var purchase = androidPurchase.ToIABPurchase();
if (verifyPurchase == null || await verifyPurchase.VerifyPurchase(data, signature, productSku, purchase.Id))
return purchase;

return null;
return androidPurchase.ToIABPurchase();
}


Expand Down Expand Up @@ -335,45 +327,22 @@ bool ParseBillingResult(BillingResult result)
if(result == null)
throw new InAppBillingPurchaseException(PurchaseError.GeneralError);

switch (result.ResponseCode)
return result.ResponseCode switch
{
case BillingResponseCode.Ok:
return true;
case BillingResponseCode.UserCancelled:
//User Cancelled, should try again
throw new InAppBillingPurchaseException(PurchaseError.UserCancelled);
case BillingResponseCode.ServiceUnavailable:
//Network connection is down
throw new InAppBillingPurchaseException(PurchaseError.ServiceUnavailable);
case BillingResponseCode.ServiceDisconnected:
//Network connection is down
throw new InAppBillingPurchaseException(PurchaseError.ServiceDisconnected);
case BillingResponseCode.ServiceTimeout:
//Network connection is down
throw new InAppBillingPurchaseException(PurchaseError.ServiceTimeout);
case BillingResponseCode.BillingUnavailable:
//Billing Unavailable
throw new InAppBillingPurchaseException(PurchaseError.BillingUnavailable);
case BillingResponseCode.ItemNotOwned:
//Item not owned
throw new InAppBillingPurchaseException(PurchaseError.NotOwned);
case BillingResponseCode.DeveloperError:
//Developer Error
throw new InAppBillingPurchaseException(PurchaseError.DeveloperError);
case BillingResponseCode.Error:
//Generic Error
throw new InAppBillingPurchaseException(PurchaseError.GeneralError);
case BillingResponseCode.FeatureNotSupported:
throw new InAppBillingPurchaseException(PurchaseError.FeatureNotSupported);

case BillingResponseCode.ItemAlreadyOwned:
throw new InAppBillingPurchaseException(PurchaseError.AlreadyOwned);

case BillingResponseCode.ItemUnavailable:
throw new InAppBillingPurchaseException(PurchaseError.ItemUnavailable);
default:
return false;
}
BillingResponseCode.Ok => true,
BillingResponseCode.UserCancelled => throw new InAppBillingPurchaseException(PurchaseError.UserCancelled),//User Cancelled, should try again
BillingResponseCode.ServiceUnavailable => throw new InAppBillingPurchaseException(PurchaseError.ServiceUnavailable),//Network connection is down
BillingResponseCode.ServiceDisconnected => throw new InAppBillingPurchaseException(PurchaseError.ServiceDisconnected),//Network connection is down
BillingResponseCode.ServiceTimeout => throw new InAppBillingPurchaseException(PurchaseError.ServiceTimeout),//Network connection is down
BillingResponseCode.BillingUnavailable => throw new InAppBillingPurchaseException(PurchaseError.BillingUnavailable),//Billing Unavailable
BillingResponseCode.ItemNotOwned => throw new InAppBillingPurchaseException(PurchaseError.NotOwned),//Item not owned
BillingResponseCode.DeveloperError => throw new InAppBillingPurchaseException(PurchaseError.DeveloperError),//Developer Error
BillingResponseCode.Error => throw new InAppBillingPurchaseException(PurchaseError.GeneralError),//Generic Error
BillingResponseCode.FeatureNotSupported => throw new InAppBillingPurchaseException(PurchaseError.FeatureNotSupported),
BillingResponseCode.ItemAlreadyOwned => throw new InAppBillingPurchaseException(PurchaseError.AlreadyOwned),
BillingResponseCode.ItemUnavailable => throw new InAppBillingPurchaseException(PurchaseError.ItemUnavailable),
_ => false,
};
}

/// <summary>
Expand Down
107 changes: 42 additions & 65 deletions src/Plugin.InAppBilling/InAppBilling.apple.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,6 @@ static bool HasIntroductoryPrice
}
}
#endif
/// <summary>
/// Determines if it is connected to the backend actively (Android).
/// </summary>
public override bool IsConnected { get; set; } = true;

/// <summary>
/// Gets or sets a callback for out of band purchases to complete.
Expand Down Expand Up @@ -185,11 +181,10 @@ static SKPaymentTransaction FindOriginalTransaction(SKPaymentTransaction transac
/// </summary>
/// <param name="productId">Sku or ID of product</param>
/// <param name="itemType">Type of product being requested</param>
/// <param name="verifyPurchase">Interface to verify purchase</param>
/// <param name="obfuscatedAccountId">Specifies an optional obfuscated string that is uniquely associated with the user's account in your app.</param>
/// <param name="obfuscatedProfileId">Specifies an optional obfuscated string that is uniquely associated with the user's profile in your app.</param>
/// <returns></returns>
public async override Task<InAppBillingPurchase> PurchaseAsync(string productId, ItemType itemType, IInAppBillingVerifyPurchase verifyPurchase = null, string obfuscatedAccountId = null, string obfuscatedProfileId = null)
public async override Task<InAppBillingPurchase> PurchaseAsync(string productId, ItemType itemType, string obfuscatedAccountId = null, string obfuscatedProfileId = null)
{
var p = await PurchaseAsync(productId);

Expand All @@ -207,32 +202,22 @@ public async override Task<InAppBillingPurchase> PurchaseAsync(string productId,
#endif
};

if (verifyPurchase == null)
return purchase;

var validated = await ValidateReceipt(verifyPurchase, purchase.ProductId, purchase.Id);

return validated ? purchase : null;
}

Task<bool> ValidateReceipt(IInAppBillingVerifyPurchase verifyPurchase, string productId, string transactionId)
{
if (verifyPurchase == null)
return Task.FromResult(true);

// Get the receipt data for (server-side) validation.
// See: https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Introduction.html#//apple_ref/doc/uid/TP40010573
NSData receiptUrl = null;
if(NSBundle.MainBundle.AppStoreReceiptUrl != null)
receiptUrl = NSData.FromUrl(NSBundle.MainBundle.AppStoreReceiptUrl);

var receipt = receiptUrl?.GetBase64EncodedString(NSDataBase64EncodingOptions.None);

return verifyPurchase.VerifyPurchase(receipt, string.Empty, productId, transactionId);
return purchase;
}


TaskCompletionSource<SKProduct> productTCS;
public override string ReceiptData
{
get
{
// Get the receipt data for (server-side) validation.
// See: https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Introduction.html#//apple_ref/doc/uid/TP40010573
NSData receiptUrl = null;
if (NSBundle.MainBundle.AppStoreReceiptUrl != null)
receiptUrl = NSData.FromUrl(NSBundle.MainBundle.AppStoreReceiptUrl);

return receiptUrl?.GetBase64EncodedString(NSDataBase64EncodingOptions.None);
}
}

async Task<SKPaymentTransaction> PurchaseAsync(string productId)
{
Expand Down Expand Up @@ -353,15 +338,8 @@ public override async Task<bool> FinishTransaction(string purchaseId)

PaymentObserver paymentObserver;

static DateTime NSDateToDateTimeUtc(NSDate date)
{
var reference = new DateTime(2001, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);


return reference.AddSeconds(date?.SecondsSinceReferenceDate ?? 0);
}

private bool disposed = false;
bool disposed = false;


/// <summary>
Expand Down Expand Up @@ -401,7 +379,7 @@ public override void Dispose(bool disposing)
[Preserve(AllMembers = true)]
class ProductRequestDelegate : NSObject, ISKProductsRequestDelegate, ISKRequestDelegate
{
TaskCompletionSource<IEnumerable<SKProduct>> tcsResponse = new TaskCompletionSource<IEnumerable<SKProduct>>();
readonly TaskCompletionSource<IEnumerable<SKProduct>> tcsResponse = new();

public Task<IEnumerable<SKProduct>> WaitForResponse() =>
tcsResponse.Task;
Expand Down Expand Up @@ -437,22 +415,19 @@ class PaymentObserver : SKPaymentTransactionObserver
public event Action<SKPaymentTransaction, bool> TransactionCompleted;
public event Action<SKPaymentTransaction[]> TransactionsRestored;

List<SKPaymentTransaction> restoredTransactions = new List<SKPaymentTransaction>();
private readonly Action<InAppBillingPurchase> onPurchaseSuccess;
private readonly Func<SKPaymentQueue, SKPayment, SKProduct, bool> onShouldAddStorePayment;
readonly List<SKPaymentTransaction> restoredTransactions = new ();
readonly Action<InAppBillingPurchase> onPurchaseSuccess;
readonly Func<SKPaymentQueue, SKPayment, SKProduct, bool> onShouldAddStorePayment;

public PaymentObserver(Action<InAppBillingPurchase> onPurchaseSuccess, Func<SKPaymentQueue, SKPayment, SKProduct, bool> onShouldAddStorePayment)
{
this.onPurchaseSuccess = onPurchaseSuccess;
this.onShouldAddStorePayment = onShouldAddStorePayment;
}

public override bool ShouldAddStorePayment(SKPaymentQueue queue, SKPayment payment, SKProduct product)
{
return onShouldAddStorePayment?.Invoke(queue, payment, product) ?? false;
}
public override bool ShouldAddStorePayment(SKPaymentQueue queue, SKPayment payment, SKProduct product) => onShouldAddStorePayment?.Invoke(queue, payment, product) ?? false;

public override void UpdatedTransactions(SKPaymentQueue queue, SKPaymentTransaction[] transactions)
public override void UpdatedTransactions(SKPaymentQueue queue, SKPaymentTransaction[] transactions)
{
var rt = transactions.Where(pt => pt.TransactionState == SKPaymentTransactionState.Restored);

Expand Down Expand Up @@ -561,28 +536,30 @@ public static PurchaseState GetPurchaseState(this SKPaymentTransaction transacti
if (transaction?.TransactionState == null)
return PurchaseState.Unknown;

switch (transaction.TransactionState)
{
case SKPaymentTransactionState.Restored:
return PurchaseState.Restored;
case SKPaymentTransactionState.Purchasing:
return PurchaseState.Purchasing;
case SKPaymentTransactionState.Purchased:
return PurchaseState.Purchased;
case SKPaymentTransactionState.Failed:
return PurchaseState.Failed;
case SKPaymentTransactionState.Deferred:
return PurchaseState.Deferred;
}

return PurchaseState.Unknown;
}
switch (transaction.TransactionState)
{
case SKPaymentTransactionState.Restored:
return PurchaseState.Restored;
case SKPaymentTransactionState.Purchasing:
return PurchaseState.Purchasing;
case SKPaymentTransactionState.Purchased:
return PurchaseState.Purchased;
case SKPaymentTransactionState.Failed:
return PurchaseState.Failed;
case SKPaymentTransactionState.Deferred:
return PurchaseState.Deferred;
default:
break;
}

return PurchaseState.Unknown;
}


}
}


[Preserve(AllMembers = true)]
[Preserve(AllMembers = true)]
static class SKProductExtension
{

Expand Down
26 changes: 8 additions & 18 deletions src/Plugin.InAppBilling/InAppBilling.uwp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ public class InAppBillingImplementation : BaseInAppBilling
public InAppBillingImplementation()
{
}
/// <summary>
/// Determines if it is connected to the backend actively (Android).
/// </summary>
public override bool IsConnected { get; set; } = true;

/// <summary>
/// Gets or sets if in testing mode. Only for UWP
Expand Down Expand Up @@ -82,7 +78,7 @@ public async override Task<IEnumerable<InAppBillingPurchase>> GetPurchasesAsync(
/// <param name="obfuscatedProfileId">Specifies an optional obfuscated string that is uniquely associated with the user's profile in your app.</param>
/// <returns></returns>
/// <exception cref="InAppBillingPurchaseException">If an error occurs during processing</exception>
public async override Task<InAppBillingPurchase> PurchaseAsync(string productId, ItemType itemType, IInAppBillingVerifyPurchase verifyPurchase = null, string obfuscatedAccountId = null, string obfuscatedProfileId = null)
public async override Task<InAppBillingPurchase> PurchaseAsync(string productId, ItemType itemType, string obfuscatedAccountId = null, string obfuscatedProfileId = null)
{
// Get purchase result from store or simulator
var purchaseResult = await CurrentAppMock.RequestProductPurchaseAsync(InTestingMode, productId);
Expand All @@ -109,20 +105,14 @@ public async override Task<InAppBillingPurchase> PurchaseAsync(string productId,
public async override Task<bool> ConsumePurchaseAsync(string productId, string purchaseToken)
{
var result = await CurrentAppMock.ReportConsumableFulfillmentAsync(InTestingMode, productId, new Guid(purchaseToken));
switch(result)
return result switch
{
case FulfillmentResult.ServerError:
throw new InAppBillingPurchaseException(PurchaseError.AppStoreUnavailable);
case FulfillmentResult.NothingToFulfill:
throw new InAppBillingPurchaseException(PurchaseError.ItemUnavailable);
case FulfillmentResult.PurchasePending:
case FulfillmentResult.PurchaseReverted:
throw new InAppBillingPurchaseException(PurchaseError.GeneralError);
case FulfillmentResult.Succeeded:
return true;
default:
return false;
}
FulfillmentResult.ServerError => throw new InAppBillingPurchaseException(PurchaseError.AppStoreUnavailable),
FulfillmentResult.NothingToFulfill => throw new InAppBillingPurchaseException(PurchaseError.ItemUnavailable),
FulfillmentResult.PurchasePending or FulfillmentResult.PurchaseReverted => throw new InAppBillingPurchaseException(PurchaseError.GeneralError),
FulfillmentResult.Succeeded => true,
_ => false,
};
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Plugin.InAppBilling/Plugin.InAppBilling.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<AssemblyFileVersion>1.0.0.0</AssemblyFileVersion>
<Version>1.0.0.0</Version>
<LangVersion>8.0</LangVersion>
<LangVersion>9.0</LangVersion>
<Authors>James Montemagno</Authors>
<PackageId>Plugin.InAppBilling</PackageId>
<PackOnBuild>true</PackOnBuild>
Expand Down
Loading

0 comments on commit b281090

Please sign in to comment.