diff --git a/src/Core/Services/IAppleIapService.cs b/src/Core/Services/IAppleIapService.cs index 6d716e842b..73bfaca38a 100644 --- a/src/Core/Services/IAppleIapService.cs +++ b/src/Core/Services/IAppleIapService.cs @@ -1,9 +1,15 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; namespace Bit.Core.Services { public interface IAppleIapService { Task VerifyReceiptAsync(string receiptData); + Task GetVerifiedLastTransactionIdAsync(string receiptData); + Task GetVerifiedLastExpiresDateAsync(string receiptData); + string HashReceipt(string receiptData); + Task SaveReceiptAsync(string receiptData); + Task GetReceiptAsync(string hash); } } diff --git a/src/Core/Services/Implementations/AppleIapService.cs b/src/Core/Services/Implementations/AppleIapService.cs index d89fe8bbba..6e832fad12 100644 --- a/src/Core/Services/Implementations/AppleIapService.cs +++ b/src/Core/Services/Implementations/AppleIapService.cs @@ -1,7 +1,12 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Net.Http; +using System.Security.Cryptography; using System.Threading.Tasks; using Bit.Billing.Models; +using Bit.Core.Repositories; +using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -13,20 +18,79 @@ namespace Bit.Core.Services.Implementations private readonly HttpClient _httpClient = new HttpClient(); private readonly GlobalSettings _globalSettings; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IMetaDataRespository _metaDataRespository; private readonly ILogger _logger; public AppleIapService( GlobalSettings globalSettings, + IHostingEnvironment hostingEnvironment, + IMetaDataRespository metaDataRespository, ILogger logger) { _globalSettings = globalSettings; + _hostingEnvironment = hostingEnvironment; + _metaDataRespository = metaDataRespository; _logger = logger; } public async Task VerifyReceiptAsync(string receiptData) + { + var receiptStatus = await GetVerifiedReceiptStatusAsync(receiptData); + return receiptStatus != null; + } + + public async Task GetVerifiedLastTransactionIdAsync(string receiptData) + { + var receiptStatus = await GetVerifiedReceiptStatusAsync(receiptData); + return receiptStatus?.LatestReceiptInfo?.LastOrDefault()?.TransactionId; + } + + public async Task GetVerifiedLastExpiresDateAsync(string receiptData) + { + var receiptStatus = await GetVerifiedReceiptStatusAsync(receiptData); + return receiptStatus?.LatestReceiptInfo?.LastOrDefault()?.ExpiresDate; + } + + public string HashReceipt(string receiptData) + { + using(var sha256 = SHA256.Create()) + { + var hash = sha256.ComputeHash(Convert.FromBase64String(receiptData)); + return BitConverter.ToString(hash).Replace("-", string.Empty); + } + } + + public async Task SaveReceiptAsync(string receiptData) + { + var hash = HashReceipt(receiptData); + await _metaDataRespository.UpsertAsync("appleReceipt", hash, + new KeyValuePair("data", receiptData)); + } + + public async Task GetReceiptAsync(string hash) + { + var receipt = await _metaDataRespository.GetAsync("appleReceipt", hash); + return receipt != null && receipt.ContainsKey("data") ? receipt["data"] : null; + } + + private async Task GetVerifiedReceiptStatusAsync(string receiptData) { var receiptStatus = await GetReceiptStatusAsync(receiptData); - return receiptStatus?.Status == 0; + if(receiptStatus?.Status != 0) + { + return null; + } + var validEnvironment = (!_hostingEnvironment.IsProduction() && receiptStatus.Environment == "Sandbox") || + (_hostingEnvironment.IsProduction() && receiptStatus.Environment != "Sandbox"); + var validProductBundle = receiptStatus.Receipt.BundleId == "com.bitwarden.desktop" || + receiptStatus.Receipt.BundleId == "com.8bit.bitwarden"; + var validProduct = receiptStatus.LatestReceiptInfo.LastOrDefault()?.ProductId == "premium_annually"; + if(validEnvironment && validProductBundle && validProduct) + { + return receiptStatus; + } + return null; } private async Task GetReceiptStatusAsync(string receiptData, bool prod = true,