mirror of
https://github.com/bitwarden/server.git
synced 2025-04-26 15:22:19 -05:00
127 lines
5.3 KiB
C#
127 lines
5.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Threading.Tasks;
|
|
using Bit.Billing.Models;
|
|
using Bit.Core.Repositories;
|
|
using Microsoft.AspNetCore.Hosting;
|
|
using Microsoft.Extensions.Hosting;
|
|
using Microsoft.Extensions.Logging;
|
|
using Newtonsoft.Json;
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
namespace Bit.Core.Services
|
|
{
|
|
public class AppleIapService : IAppleIapService
|
|
{
|
|
private readonly HttpClient _httpClient = new HttpClient();
|
|
|
|
private readonly GlobalSettings _globalSettings;
|
|
private readonly IWebHostEnvironment _hostingEnvironment;
|
|
private readonly IMetaDataRepository _metaDataRespository;
|
|
private readonly ILogger<AppleIapService> _logger;
|
|
|
|
public AppleIapService(
|
|
GlobalSettings globalSettings,
|
|
IWebHostEnvironment hostingEnvironment,
|
|
IMetaDataRepository metaDataRespository,
|
|
ILogger<AppleIapService> logger)
|
|
{
|
|
_globalSettings = globalSettings;
|
|
_hostingEnvironment = hostingEnvironment;
|
|
_metaDataRespository = metaDataRespository;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task<AppleReceiptStatus> GetVerifiedReceiptStatusAsync(string receiptData)
|
|
{
|
|
var receiptStatus = await GetReceiptStatusAsync(receiptData);
|
|
if (receiptStatus?.Status != 0)
|
|
{
|
|
return null;
|
|
}
|
|
var validEnvironment = _globalSettings.AppleIap.AppInReview ||
|
|
(!_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";
|
|
var validIds = receiptStatus.GetOriginalTransactionId() != null &&
|
|
receiptStatus.GetLastTransactionId() != null;
|
|
var validTransaction = receiptStatus.GetLastExpiresDate()
|
|
.GetValueOrDefault(DateTime.MinValue) > DateTime.UtcNow;
|
|
if (validEnvironment && validProductBundle && validProduct && validIds && validTransaction)
|
|
{
|
|
return receiptStatus;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public async Task SaveReceiptAsync(AppleReceiptStatus receiptStatus, Guid userId)
|
|
{
|
|
var originalTransactionId = receiptStatus.GetOriginalTransactionId();
|
|
if (string.IsNullOrWhiteSpace(originalTransactionId))
|
|
{
|
|
throw new Exception("OriginalTransactionId is null");
|
|
}
|
|
await _metaDataRespository.UpsertAsync("AppleReceipt", originalTransactionId,
|
|
new Dictionary<string, string>
|
|
{
|
|
["Data"] = receiptStatus.GetReceiptData(),
|
|
["UserId"] = userId.ToString()
|
|
});
|
|
}
|
|
|
|
public async Task<Tuple<string, Guid?>> GetReceiptAsync(string originalTransactionId)
|
|
{
|
|
var receipt = await _metaDataRespository.GetAsync("AppleReceipt", originalTransactionId);
|
|
if (receipt == null)
|
|
{
|
|
return null;
|
|
}
|
|
return new Tuple<string, Guid?>(receipt.ContainsKey("Data") ? receipt["Data"] : null,
|
|
receipt.ContainsKey("UserId") ? new Guid(receipt["UserId"]) : (Guid?)null);
|
|
}
|
|
|
|
private async Task<AppleReceiptStatus> GetReceiptStatusAsync(string receiptData, bool prod = true,
|
|
int attempt = 0, AppleReceiptStatus lastReceiptStatus = null)
|
|
{
|
|
try
|
|
{
|
|
if (attempt > 4)
|
|
{
|
|
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 json = new JObject(new JProperty("receipt-data", receiptData),
|
|
new JProperty("password", _globalSettings.AppleIap.Password)).ToString();
|
|
|
|
var response = await _httpClient.PostAsync(url, new StringContent(json));
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
var responseJson = await response.Content.ReadAsStringAsync();
|
|
var receiptStatus = JsonConvert.DeserializeObject<AppleReceiptStatus>(responseJson);
|
|
if (receiptStatus.Status == 21007)
|
|
{
|
|
return await GetReceiptStatusAsync(receiptData, false, attempt + 1, receiptStatus);
|
|
}
|
|
else if (receiptStatus.Status == 21005)
|
|
{
|
|
await Task.Delay(2000);
|
|
return await GetReceiptStatusAsync(receiptData, prod, attempt + 1, receiptStatus);
|
|
}
|
|
return receiptStatus;
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_logger.LogWarning(e, "Error verifying Apple IAP receipt.");
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
}
|