DocSpace-client/common/ASC.Core.Common/Billing/BillingClient.cs
2022-11-07 12:34:50 +03:00

381 lines
13 KiB
C#

// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Core.Billing;
[Singletone]
public class BillingClient
{
public readonly bool Configured;
private readonly PaymentConfiguration _configuration;
private readonly IHttpClientFactory _httpClientFactory;
private const int StripePaymentSystemId = 9;
public BillingClient(IConfiguration configuration, IHttpClientFactory httpClientFactory)
{
_configuration = configuration.GetSection("core:payment").Get<PaymentConfiguration>();
_httpClientFactory = httpClientFactory;
_configuration.Url = (_configuration.Url ?? "").Trim().TrimEnd('/');
if (!string.IsNullOrEmpty(_configuration.Url))
{
_configuration.Url += "/billing/";
Configured = true;
}
}
public string GetAccountLink(string portalId, string backUrl)
{
var result = Request("GetAccountLink", portalId, Tuple.Create("BackRef", backUrl));
var link = JsonConvert.DeserializeObject<string>(result);
return link;
}
public PaymentLast[] GetCurrentPayments(string portalId)
{
var result = Request("GetActiveResources", portalId);
var payments = JsonSerializer.Deserialize<PaymentLast[]>(result);
if (!_configuration.Test)
{
payments = payments.Where(payment => payment.PaymentStatus != 4).ToArray();
}
return payments;
}
public IEnumerable<PaymentInfo> GetPayments(string portalId)
{
var result = Request("GetPayments", portalId);
var payments = JsonSerializer.Deserialize<List<PaymentInfo>>(result);
return payments;
}
public IDictionary<string, Uri> GetPaymentUrls(string portalId, string[] products, string affiliateId = null, string campaign = null, string currency = null, string language = null, string customerId = null, string quantity = null)
{
var urls = new Dictionary<string, Uri>();
var additionalParameters = new List<Tuple<string, string>>() { Tuple.Create("PaymentSystemId", StripePaymentSystemId.ToString()) };
if (!string.IsNullOrEmpty(affiliateId))
{
additionalParameters.Add(Tuple.Create("AffiliateId", affiliateId));
}
if (!string.IsNullOrEmpty(campaign))
{
additionalParameters.Add(Tuple.Create("campaign", campaign));
}
if (!string.IsNullOrEmpty(currency))
{
additionalParameters.Add(Tuple.Create("Currency", currency));
}
if (!string.IsNullOrEmpty(language))
{
additionalParameters.Add(Tuple.Create("Language", language));
}
if (!string.IsNullOrEmpty(customerId))
{
additionalParameters.Add(Tuple.Create("CustomerID", customerId));
}
if (!string.IsNullOrEmpty(quantity))
{
additionalParameters.Add(Tuple.Create("Quantity", quantity));
}
var parameters = products
.Distinct()
.Select(p => Tuple.Create("ProductId", p))
.Concat(additionalParameters)
.ToArray();
//max 100 products
var result = Request("GetPaymentUrl", portalId, parameters);
var paymentUrls = JsonSerializer.Deserialize<Dictionary<string, string>>(result);
foreach (var p in products)
{
string url;
var paymentUrl = (Uri)null;
if (paymentUrls.TryGetValue(p, out url) && !string.IsNullOrEmpty(url = ToUrl(url)))
{
paymentUrl = new Uri(url);
}
urls[p] = paymentUrl;
}
return urls;
}
public string GetPaymentUrl(string portalId, string[] products, string affiliateId = null, string campaign = null, string currency = null, string language = null, string customerEmail = null, string quantity = null, string backUrl = null)
{
var additionalParameters = new List<Tuple<string, string>>() { Tuple.Create("PaymentSystemId", StripePaymentSystemId.ToString()) };
if (!string.IsNullOrEmpty(affiliateId))
{
additionalParameters.Add(Tuple.Create("AffiliateId", affiliateId));
}
if (!string.IsNullOrEmpty(campaign))
{
additionalParameters.Add(Tuple.Create("campaign", campaign));
}
if (!string.IsNullOrEmpty(currency))
{
additionalParameters.Add(Tuple.Create("Currency", currency));
}
if (!string.IsNullOrEmpty(language))
{
additionalParameters.Add(Tuple.Create("Language", language));
}
if (!string.IsNullOrEmpty(customerEmail))
{
additionalParameters.Add(Tuple.Create("CustomerEmail", customerEmail));
}
if (!string.IsNullOrEmpty(quantity))
{
additionalParameters.Add(Tuple.Create("Quantity", quantity));
}
if (!string.IsNullOrEmpty(backUrl))
{
additionalParameters.Add(Tuple.Create("BackRef", backUrl));
additionalParameters.Add(Tuple.Create("ShopUrl", backUrl));
}
var parameters = products
.Distinct()
.Select(p => Tuple.Create("ProductId", p))
.Concat(additionalParameters)
.ToArray();
var result = Request("GetSinglePaymentUrl", portalId, parameters);
var paymentUrl = JsonConvert.DeserializeObject<string>(result);
return paymentUrl;
}
public bool ChangePayment(string portalId, string[] products, int[] quantity)
{
var parameters = products.Select(p => Tuple.Create("ProductId", p))
.Concat(quantity.Select(q => Tuple.Create("ProductQty", q.ToString())))
.ToArray();
var result = Request("ChangeSubscription", portalId, parameters);
var changed = JsonConvert.DeserializeObject<bool>(result);
return changed;
}
public IDictionary<string, Dictionary<string, decimal>> GetProductPriceInfo(params string[] productIds)
{
ArgumentNullException.ThrowIfNull(productIds);
var parameters = productIds.Select(pid => Tuple.Create("ProductId", pid)).ToList();
parameters.Add(Tuple.Create("PaymentSystemId", StripePaymentSystemId.ToString()));
var result = Request("GetProductsPrices", null, parameters.ToArray());
var prices = JsonSerializer.Deserialize<Dictionary<int, Dictionary<string, Dictionary<string, decimal>>>>(result);
if (prices.TryGetValue(StripePaymentSystemId, out var pricesPaymentSystem))
{
return productIds.Select(productId =>
{
if (pricesPaymentSystem.TryGetValue(productId, out var prices))
{
return new { ProductId = productId, Prices = prices };
}
return new { ProductId = productId, Prices = new Dictionary<string, decimal>() };
})
.ToDictionary(e => e.ProductId, e => e.Prices);
}
return new Dictionary<string, Dictionary<string, decimal>>();
}
private string CreateAuthToken(string pkey, string machinekey)
{
using (var hasher = new HMACSHA1(Encoding.UTF8.GetBytes(machinekey)))
{
var now = DateTime.UtcNow.ToString("yyyyMMddHHmmss");
var hash = WebEncoders.Base64UrlEncode(hasher.ComputeHash(Encoding.UTF8.GetBytes(string.Join("\n", now, pkey))));
return "ASC " + pkey + ":" + now + ":" + hash;
}
}
private string Request(string method, string portalId, params Tuple<string, string>[] parameters)
{
var url = _configuration.Url + method;
var request = new HttpRequestMessage
{
RequestUri = new Uri(url),
Method = HttpMethod.Post
};
if (!string.IsNullOrEmpty(_configuration.Key))
{
request.Headers.Add("Authorization", CreateAuthToken(_configuration.Key, _configuration.Secret));
}
var httpClient = _httpClientFactory.CreateClient();
httpClient.Timeout = TimeSpan.FromMilliseconds(60000);
var data = new Dictionary<string, List<string>>();
if (!string.IsNullOrEmpty(portalId))
{
data.Add("PortalId", new List<string>() { portalId });
}
foreach (var parameter in parameters)
{
if (!data.ContainsKey(parameter.Item1))
{
data.Add(parameter.Item1, new List<string>() { parameter.Item2 });
}
else
{
data[parameter.Item1].Add(parameter.Item2);
}
}
var body = JsonSerializer.Serialize(data);
request.Content = new StringContent(body, Encoding.UTF8, "application/json");
string result;
using (var response = httpClient.Send(request))
using (var stream = response.Content.ReadAsStream())
{
if (stream == null)
{
throw new BillingNotConfiguredException("Billing response is null");
}
using (var readStream = new StreamReader(stream))
{
result = readStream.ReadToEnd();
}
}
if (string.IsNullOrEmpty(result))
{
throw new BillingNotConfiguredException("Billing response is null");
}
if (!result.StartsWith("{\"Message\":\"error", true, null))
{
return result;
}
var @params = parameters.Select(p => p.Item1 + ": " + p.Item2);
var info = new { Method = method, PortalId = portalId, Params = string.Join(", ", @params) };
if (result.Contains("{\"Message\":\"error: cannot find "))
{
throw new BillingNotFoundException(result, info);
}
throw new BillingException(result, info);
}
private string ToUrl(string s)
{
s = s.Trim();
if (s.StartsWith("error", StringComparison.InvariantCultureIgnoreCase))
{
return string.Empty;
}
if (_configuration.Test && !s.Contains("&DOTEST = 1"))
{
s += "&DOTEST=1";
}
return s;
}
}
[ServiceContract]
public interface IService
{
[OperationContract]
Message Request(Message message);
}
[Serializable]
public class Message
{
public string Content { get; set; }
public MessageType Type { get; set; }
}
public enum MessageType
{
Undefined = 0,
Data = 1,
Error = 2,
}
[Serializable]
public class BillingException : Exception
{
public BillingException(string message, object debugInfo = null) : base(message + (debugInfo != null ? " Debug info: " + debugInfo : string.Empty))
{
}
public BillingException(string message, Exception inner) : base(message, inner)
{
}
protected BillingException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
[Serializable]
public class BillingNotFoundException : BillingException
{
public BillingNotFoundException(string message, object debugInfo = null) : base(message, debugInfo)
{
}
protected BillingNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
[Serializable]
public class BillingNotConfiguredException : BillingException
{
public BillingNotConfiguredException(string message, object debugInfo = null) : base(message, debugInfo)
{
}
public BillingNotConfiguredException(string message, Exception inner) : base(message, inner)
{
}
protected BillingNotConfiguredException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}