Files
server/src/Core/Services/Implementations/AppleIapService.cs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

138 lines
5.6 KiB
C#
Raw Normal View History

2019-09-16 09:22:22 -04:00
using System;
2019-09-17 19:48:40 -04:00
using System.Collections.Generic;
using System.Linq;
2019-09-16 09:22:22 -04:00
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
2019-09-16 09:22:22 -04:00
using System.Threading.Tasks;
using Bit.Billing.Models;
2019-09-17 19:48:40 -04:00
using Bit.Core.Repositories;
using Bit.Core.Settings;
2019-09-17 19:48:40 -04:00
using Microsoft.AspNetCore.Hosting;
2020-01-10 08:47:58 -05:00
using Microsoft.Extensions.Hosting;
2019-09-16 09:22:22 -04:00
using Microsoft.Extensions.Logging;
2019-09-18 09:46:26 -04:00
namespace Bit.Core.Services
2019-09-16 09:22:22 -04:00
{
public class AppleIapService : IAppleIapService
{
private readonly HttpClient _httpClient = new HttpClient();
private readonly GlobalSettings _globalSettings;
2020-01-10 08:47:58 -05:00
private readonly IWebHostEnvironment _hostingEnvironment;
2019-09-18 09:47:42 -04:00
private readonly IMetaDataRepository _metaDataRespository;
2019-09-16 09:22:22 -04:00
private readonly ILogger<AppleIapService> _logger;
public AppleIapService(
GlobalSettings globalSettings,
2020-01-10 08:47:58 -05:00
IWebHostEnvironment hostingEnvironment,
2019-09-18 09:47:42 -04:00
IMetaDataRepository metaDataRespository,
2019-09-16 09:22:22 -04:00
ILogger<AppleIapService> logger)
{
_globalSettings = globalSettings;
2019-09-17 19:48:40 -04:00
_hostingEnvironment = hostingEnvironment;
_metaDataRespository = metaDataRespository;
2019-09-16 09:22:22 -04:00
_logger = logger;
}
2019-09-17 22:58:38 -04:00
public async Task<AppleReceiptStatus> GetVerifiedReceiptStatusAsync(string receiptData)
2019-09-16 09:22:22 -04:00
{
var receiptStatus = await GetReceiptStatusAsync(receiptData);
if (receiptStatus?.Status != 0)
2019-09-17 19:48:40 -04:00
{
return null;
}
var validEnvironment = _globalSettings.AppleIap.AppInReview ||
(!(_hostingEnvironment.IsProduction() || _hostingEnvironment.IsEnvironment("QA")) && receiptStatus.Environment == "Sandbox") ||
((_hostingEnvironment.IsProduction() || _hostingEnvironment.IsEnvironment("QA")) && receiptStatus.Environment != "Sandbox");
2019-09-17 19:48:40 -04:00
var validProductBundle = receiptStatus.Receipt.BundleId == "com.bitwarden.desktop" ||
receiptStatus.Receipt.BundleId == "com.8bit.bitwarden";
var validProduct = receiptStatus.LatestReceiptInfo.LastOrDefault()?.ProductId == "premium_annually";
2019-09-19 10:04:15 -04:00
var validIds = receiptStatus.GetOriginalTransactionId() != null &&
receiptStatus.GetLastTransactionId() != null;
var validTransaction = receiptStatus.GetLastExpiresDate()
.GetValueOrDefault(DateTime.MinValue) > DateTime.UtcNow;
if (validEnvironment && validProductBundle && validProduct && validIds && validTransaction)
2019-09-17 19:48:40 -04:00
{
return receiptStatus;
}
return null;
2019-09-16 09:22:22 -04:00
}
2019-09-19 09:36:26 -04:00
public async Task SaveReceiptAsync(AppleReceiptStatus receiptStatus, Guid userId)
2019-09-17 22:58:38 -04:00
{
var originalTransactionId = receiptStatus.GetOriginalTransactionId();
if (string.IsNullOrWhiteSpace(originalTransactionId))
2019-09-17 22:58:38 -04:00
{
throw new Exception("OriginalTransactionId is null");
}
await _metaDataRespository.UpsertAsync("AppleReceipt", originalTransactionId,
new Dictionary<string, string>
{
["Data"] = receiptStatus.GetReceiptData(),
2019-09-19 09:36:26 -04:00
["UserId"] = userId.ToString()
2019-09-17 22:58:38 -04:00
});
}
public async Task<Tuple<string, Guid?>> GetReceiptAsync(string originalTransactionId)
{
var receipt = await _metaDataRespository.GetAsync("AppleReceipt", originalTransactionId);
if (receipt == null)
2019-09-17 22:58:38 -04:00
{
return null;
}
return new Tuple<string, Guid?>(receipt.ContainsKey("Data") ? receipt["Data"] : null,
receipt.ContainsKey("UserId") ? new Guid(receipt["UserId"]) : (Guid?)null);
}
2019-09-16 09:22:22 -04:00
private async Task<AppleReceiptStatus> GetReceiptStatusAsync(string receiptData, bool prod = true,
int attempt = 0, AppleReceiptStatus lastReceiptStatus = null)
{
try
{
if (attempt > 4)
2019-09-16 09:22:22 -04:00
{
throw new Exception("Failed verifying Apple IAP after too many attempts. Last attempt status: " +
lastReceiptStatus?.Status ?? "null");
}
var url = string.Format("https://{0}.itunes.apple.com/verifyReceipt", prod ? "buy" : "sandbox");
var response = await _httpClient.PostAsJsonAsync(url, new AppleVerifyReceiptRequestModel
{
ReceiptData = receiptData,
Password = _globalSettings.AppleIap.Password
});
if (response.IsSuccessStatusCode)
2019-09-16 09:22:22 -04:00
{
var receiptStatus = await response.Content.ReadFromJsonAsync<AppleReceiptStatus>();
if (receiptStatus.Status == 21007)
2019-09-16 09:22:22 -04:00
{
return await GetReceiptStatusAsync(receiptData, false, attempt + 1, receiptStatus);
}
else if (receiptStatus.Status == 21005)
2019-09-16 09:22:22 -04:00
{
await Task.Delay(2000);
return await GetReceiptStatusAsync(receiptData, prod, attempt + 1, receiptStatus);
}
return receiptStatus;
}
}
catch (Exception e)
2019-09-16 09:22:22 -04:00
{
_logger.LogWarning(e, "Error verifying Apple IAP receipt.");
}
return null;
}
}
public class AppleVerifyReceiptRequestModel
{
[JsonPropertyName("receipt-data")]
public string ReceiptData { get; set; }
[JsonPropertyName("password")]
public string Password { get; set; }
}
2019-09-16 09:22:22 -04:00
}