DocSpace-buildtools/common/ASC.Core.Common/Billing/BillingClient.cs

390 lines
14 KiB
C#
Raw Normal View History

2022-03-15 18:00:53 +00:00
// (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
2019-05-15 14:56:09 +00:00
using JsonSerializer = System.Text.Json.JsonSerializer;
2020-01-17 13:58:26 +00:00
2022-02-15 11:52:43 +00:00
namespace ASC.Core.Billing;
[Singletone]
public class BillingClient
2021-05-17 11:35:00 +00:00
{
2022-03-17 14:33:16 +00:00
public readonly bool Configured;
2022-02-15 11:52:43 +00:00
private readonly string _billingDomain;
private readonly string _billingKey;
private readonly string _billingSecret;
private readonly bool _test;
private readonly IHttpClientFactory _httpClientFactory;
2022-07-14 09:06:46 +00:00
private const int StripePaymentSystemId = 9;
2021-05-17 11:35:00 +00:00
2022-04-14 19:42:15 +00:00
public BillingClient(IConfiguration configuration, IHttpClientFactory httpClientFactory)
2022-02-15 11:52:43 +00:00
{
2022-07-14 09:08:44 +00:00
_test = Convert.ToBoolean(configuration["core:payment:test"] ?? "false");
2022-04-14 19:42:15 +00:00
_httpClientFactory = httpClientFactory;
var billingDomain = configuration["core:payment:url"];
2021-05-17 11:35:00 +00:00
2022-02-15 11:52:43 +00:00
_billingDomain = (billingDomain ?? "").Trim().TrimEnd('/');
if (!string.IsNullOrEmpty(_billingDomain))
{
_billingDomain += "/billing/";
2021-05-17 11:35:00 +00:00
_billingKey = configuration["core:payment:key"];
_billingSecret = configuration["core:payment:secret"];
2021-05-17 11:35:00 +00:00
2022-02-15 11:52:43 +00:00
Configured = true;
2020-01-17 13:58:26 +00:00
}
2022-02-15 11:52:43 +00:00
}
2020-01-17 13:58:26 +00:00
2022-07-14 13:27:34 +00:00
public string GetAccountLink(string portalId, string backUrl)
2022-07-14 13:12:01 +00:00
{
2022-07-14 13:27:34 +00:00
var result = Request("GetAccountLink", portalId, Tuple.Create("BackRef", backUrl));
2022-07-14 13:12:01 +00:00
var link = JsonConvert.DeserializeObject<string>(result);
return link;
}
public PaymentLast[] GetCurrentPayments(string portalId)
2022-02-15 11:52:43 +00:00
{
var result = Request("GetActiveResources", portalId);
var payments = JsonSerializer.Deserialize<PaymentLast[]>(result);
2022-02-15 11:52:43 +00:00
if (!_test)
2020-01-17 13:58:26 +00:00
{
payments = payments.Where(payment => payment.PaymentStatus != 4).ToArray();
2022-02-15 11:52:43 +00:00
}
2021-05-17 11:35:00 +00:00
return payments;
2022-02-15 11:52:43 +00:00
}
2020-01-17 13:58:26 +00:00
2022-02-15 11:52:43 +00:00
public IEnumerable<PaymentInfo> GetPayments(string portalId)
{
2022-03-17 14:55:19 +00:00
var result = Request("GetPayments", portalId);
2022-02-15 11:52:43 +00:00
var payments = JsonSerializer.Deserialize<List<PaymentInfo>>(result);
2020-01-17 13:58:26 +00:00
2022-02-15 11:52:43 +00:00
return payments;
}
2020-01-17 13:58:26 +00:00
2022-06-01 14:07:08 +00:00
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)
2022-02-15 11:52:43 +00:00
{
2022-06-01 14:07:08 +00:00
var urls = new Dictionary<string, Uri>();
2021-05-17 11:35:00 +00:00
2022-07-14 09:06:46 +00:00
var additionalParameters = new List<Tuple<string, string>>() { Tuple.Create("PaymentSystemId", StripePaymentSystemId.ToString()) };
2022-02-15 11:52:43 +00:00
if (!string.IsNullOrEmpty(affiliateId))
{
additionalParameters.Add(Tuple.Create("AffiliateId", affiliateId));
}
if (!string.IsNullOrEmpty(campaign))
{
additionalParameters.Add(Tuple.Create("campaign", campaign));
}
if (!string.IsNullOrEmpty(currency))
2020-01-17 13:58:26 +00:00
{
2022-02-15 11:52:43 +00:00
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));
}
2021-05-17 11:35:00 +00:00
2022-02-15 11:52:43 +00:00
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;
2022-06-01 14:07:08 +00:00
if (paymentUrls.TryGetValue(p, out url) && !string.IsNullOrEmpty(url = ToUrl(url)))
2020-01-17 13:58:26 +00:00
{
2022-06-01 14:07:08 +00:00
paymentUrl = new Uri(url);
2021-05-17 11:35:00 +00:00
}
2022-06-01 14:07:08 +00:00
urls[p] = paymentUrl;
2020-01-17 13:58:26 +00:00
}
2022-02-15 11:52:43 +00:00
return urls;
}
2022-07-14 13:27:34 +00:00
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)
2022-06-01 14:07:08 +00:00
{
2022-07-14 09:06:46 +00:00
var additionalParameters = new List<Tuple<string, string>>() { Tuple.Create("PaymentSystemId", StripePaymentSystemId.ToString()) };
2022-06-01 14:07:08 +00:00
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));
}
2022-07-14 11:19:23 +00:00
if (!string.IsNullOrEmpty(customerEmail))
2022-06-01 14:07:08 +00:00
{
2022-07-14 11:19:23 +00:00
additionalParameters.Add(Tuple.Create("CustomerEmail", customerEmail));
2022-06-01 14:07:08 +00:00
}
if (!string.IsNullOrEmpty(quantity))
{
additionalParameters.Add(Tuple.Create("Quantity", quantity));
}
2022-07-14 13:27:34 +00:00
if (!string.IsNullOrEmpty(backUrl))
{
additionalParameters.Add(Tuple.Create("BackRef", backUrl));
additionalParameters.Add(Tuple.Create("ShopUrl", backUrl));
}
2022-06-01 14:07:08 +00:00
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;
}
2022-08-05 12:36:00 +00:00
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;
}
2022-02-15 11:52:43 +00:00
public IDictionary<string, Dictionary<string, decimal>> GetProductPriceInfo(params string[] productIds)
{
2022-03-09 17:15:51 +00:00
ArgumentNullException.ThrowIfNull(productIds);
2021-05-17 11:35:00 +00:00
2022-02-15 11:52:43 +00:00
var parameters = productIds.Select(pid => Tuple.Create("ProductId", pid)).ToList();
2022-07-14 09:06:46 +00:00
parameters.Add(Tuple.Create("PaymentSystemId", StripePaymentSystemId.ToString()));
2021-05-17 11:35:00 +00:00
2022-02-15 11:52:43 +00:00
var result = Request("GetProductsPrices", null, parameters.ToArray());
var prices = JsonSerializer.Deserialize<Dictionary<int, Dictionary<string, Dictionary<string, decimal>>>>(result);
2021-05-17 11:35:00 +00:00
2022-07-14 09:06:46 +00:00
if (prices.TryGetValue(StripePaymentSystemId, out var pricesPaymentSystem))
2022-02-15 11:52:43 +00:00
{
return productIds.Select(productId =>
2020-01-17 13:58:26 +00:00
{
2022-02-15 11:52:43 +00:00
if (pricesPaymentSystem.TryGetValue(productId, out var prices))
2021-05-17 11:35:00 +00:00
{
2022-02-15 11:52:43 +00:00
return new { ProductId = productId, Prices = prices };
}
return new { ProductId = productId, Prices = new Dictionary<string, decimal>() };
})
.ToDictionary(e => e.ProductId, e => e.Prices);
2020-01-17 13:58:26 +00:00
}
2022-02-15 11:52:43 +00:00
return new Dictionary<string, Dictionary<string, decimal>>();
}
2020-01-17 13:58:26 +00:00
2022-02-15 11:52:43 +00:00
private string CreateAuthToken(string pkey, string machinekey)
{
using (var hasher = new HMACSHA1(Encoding.UTF8.GetBytes(machinekey)))
2020-01-17 13:58:26 +00:00
{
2022-02-15 11:52:43 +00:00
var now = DateTime.UtcNow.ToString("yyyyMMddHHmmss");
var hash = WebEncoders.Base64UrlEncode(hasher.ComputeHash(Encoding.UTF8.GetBytes(string.Join("\n", now, pkey))));
2022-02-15 11:52:43 +00:00
return "ASC " + pkey + ":" + now + ":" + hash;
2020-01-17 13:58:26 +00:00
}
2022-02-15 11:52:43 +00:00
}
2020-01-17 13:58:26 +00:00
2022-02-15 11:52:43 +00:00
private string Request(string method, string portalId, params Tuple<string, string>[] parameters)
{
var url = _billingDomain + method;
2021-05-17 11:35:00 +00:00
2022-04-14 19:23:57 +00:00
var request = new HttpRequestMessage
{
RequestUri = new Uri(url),
Method = HttpMethod.Post
};
2022-02-15 11:52:43 +00:00
if (!string.IsNullOrEmpty(_billingKey))
{
request.Headers.Add("Authorization", CreateAuthToken(_billingKey, _billingSecret));
}
2021-05-17 11:35:00 +00:00
2022-04-14 19:42:15 +00:00
var httpClient = _httpClientFactory.CreateClient();
httpClient.Timeout = TimeSpan.FromMilliseconds(60000);
2021-10-12 10:14:33 +00:00
2022-02-15 11:52:43 +00:00
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))
2020-01-17 13:58:26 +00:00
{
2022-02-15 11:52:43 +00:00
data.Add(parameter.Item1, new List<string>() { parameter.Item2 });
2020-01-17 13:58:26 +00:00
}
2022-02-15 11:52:43 +00:00
else
2020-01-17 13:58:26 +00:00
{
2022-02-15 11:52:43 +00:00
data[parameter.Item1].Add(parameter.Item2);
2020-01-17 13:58:26 +00:00
}
2022-02-15 11:52:43 +00:00
}
2021-05-17 11:35:00 +00:00
2022-02-15 11:52:43 +00:00
var body = JsonSerializer.Serialize(data);
request.Content = new StringContent(body, Encoding.UTF8, "application/json");
2021-05-17 11:35:00 +00:00
2022-02-15 11:52:43 +00:00
string result;
using (var response = httpClient.Send(request))
2022-02-15 11:52:43 +00:00
using (var stream = response.Content.ReadAsStream())
{
if (stream == null)
2021-05-17 11:35:00 +00:00
{
throw new BillingNotConfiguredException("Billing response is null");
}
2022-02-15 11:52:43 +00:00
using (var readStream = new StreamReader(stream))
2021-05-17 11:35:00 +00:00
{
2022-02-15 11:52:43 +00:00
result = readStream.ReadToEnd();
2021-05-17 11:35:00 +00:00
}
2022-02-15 11:52:43 +00:00
}
2022-02-15 11:52:43 +00:00
if (string.IsNullOrEmpty(result))
{
throw new BillingNotConfiguredException("Billing response is null");
}
if (!result.StartsWith("{\"Message\":\"error", true, null))
2022-02-15 11:52:43 +00:00
{
return result;
2021-05-17 11:35:00 +00:00
}
2022-02-15 11:52:43 +00:00
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 "))
2021-05-17 11:35:00 +00:00
{
2022-02-15 11:52:43 +00:00
throw new BillingNotFoundException(result, info);
}
2022-02-15 11:52:43 +00:00
throw new BillingException(result, info);
}
private string ToUrl(string s)
{
s = s.Trim();
if (s.StartsWith("error", StringComparison.InvariantCultureIgnoreCase))
{
return string.Empty;
}
2022-02-15 11:52:43 +00:00
if (_test && !s.Contains("&DOTEST = 1"))
{
s += "&DOTEST=1";
2020-01-17 13:58:26 +00:00
}
2022-02-15 11:52:43 +00:00
return s;
2020-01-17 13:58:26 +00:00
}
2022-02-15 11:52:43 +00:00
}
[ServiceContract]
public interface IService
{
[OperationContract]
Message Request(Message message);
}
[Serializable]
public class Message
{
public string Content { get; set; }
public MessageType Type { get; set; }
}
2020-01-17 13:58:26 +00:00
2022-02-15 11:52:43 +00:00
public enum MessageType
{
Undefined = 0,
Data = 1,
Error = 2,
}
2020-01-17 13:58:26 +00:00
2022-02-15 11:52:43 +00:00
[Serializable]
public class BillingException : Exception
{
public BillingException(string message, object debugInfo = null) : base(message + (debugInfo != null ? " Debug info: " + debugInfo : string.Empty))
2020-01-17 13:58:26 +00:00
{
}
2022-02-15 11:52:43 +00:00
public BillingException(string message, Exception inner) : base(message, inner)
2020-01-17 13:58:26 +00:00
{
}
2022-02-15 11:52:43 +00:00
protected BillingException(SerializationInfo info, StreamingContext context) : base(info, context)
2020-01-17 13:58:26 +00:00
{
}
2022-02-15 11:52:43 +00:00
}
2020-01-17 13:58:26 +00:00
2022-02-15 11:52:43 +00:00
[Serializable]
public class BillingNotFoundException : BillingException
{
public BillingNotFoundException(string message, object debugInfo = null) : base(message, debugInfo)
2020-01-17 13:58:26 +00:00
{
}
2022-02-15 11:52:43 +00:00
protected BillingNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context)
2020-01-17 13:58:26 +00:00
{
}
2022-02-15 11:52:43 +00:00
}
2020-01-17 13:58:26 +00:00
2022-02-15 11:52:43 +00:00
[Serializable]
public class BillingNotConfiguredException : BillingException
{
public BillingNotConfiguredException(string message, object debugInfo = null) : base(message, debugInfo)
2020-01-17 13:58:26 +00:00
{
2022-02-15 11:52:43 +00:00
}
2020-01-17 13:58:26 +00:00
2022-02-15 11:52:43 +00:00
public BillingNotConfiguredException(string message, Exception inner) : base(message, inner)
{
}
2020-01-17 13:58:26 +00:00
2022-02-15 11:52:43 +00:00
protected BillingNotConfiguredException(SerializationInfo info, StreamingContext context) : base(info, context)
{
2020-01-17 13:58:26 +00:00
}
2022-02-15 11:52:43 +00:00
}