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

358 lines
12 KiB
C#
Raw Normal View History

2019-05-15 14:56:09 +00:00
/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL 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 more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
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-02-15 11:52:43 +00:00
public readonly bool Configured = false;
private readonly string _billingDomain;
private readonly string _billingKey;
private readonly string _billingSecret;
private readonly bool _test;
2021-05-17 11:35:00 +00:00
2022-02-15 11:52:43 +00:00
private const int AvangatePaymentSystemId = 1;
2021-05-17 11:35:00 +00:00
2022-02-15 11:52:43 +00:00
private static readonly HttpClient _httpClient = new HttpClient();
2022-01-13 11:19:39 +00:00
2021-05-17 11:35:00 +00:00
2022-02-15 11:52:43 +00:00
public BillingClient(IConfiguration configuration)
: this(false, configuration)
{
}
2021-05-17 11:35:00 +00:00
2022-02-15 11:52:43 +00:00
public BillingClient(bool test, IConfiguration configuration)
{
_test = test;
2021-05-17 11:35:00 +00:00
2022-02-15 11:52:43 +00:00
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
2022-02-15 11:52:43 +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-02-15 11:52:43 +00:00
public PaymentLast GetLastPayment(string portalId)
{
var result = Request("GetActiveResource", portalId);
var paymentLast = JsonSerializer.Deserialize<PaymentLast>(result);
if (!_test && paymentLast.PaymentStatus == 4)
2020-01-17 13:58:26 +00:00
{
2022-02-15 11:52:43 +00:00
throw new BillingException("Can not accept test payment.", new { PortalId = portalId });
}
2021-05-17 11:35:00 +00:00
2022-02-15 11:52:43 +00:00
return paymentLast;
}
2020-01-17 13:58:26 +00:00
2022-02-15 11:52:43 +00:00
public IEnumerable<PaymentInfo> GetPayments(string portalId)
{
string result = Request("GetPayments", portalId);
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-02-15 11:52:43 +00:00
public IDictionary<string, Tuple<Uri, 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, Tuple<Uri, Uri>>();
2021-05-17 11:35:00 +00:00
2022-02-15 11:52:43 +00:00
var additionalParameters = new List<Tuple<string, string>>() { Tuple.Create("PaymentSystemId", AvangatePaymentSystemId.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))
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);
var upgradeUrls = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(portalId)
//TODO: remove
&& false)
{
try
2021-05-17 11:35:00 +00:00
{
2022-02-15 11:52:43 +00:00
//max 100 products
result = Request("GetPaymentUpgradeUrl", portalId, parameters);
upgradeUrls = JsonSerializer.Deserialize<Dictionary<string, string>>(result);
2021-05-17 11:35:00 +00:00
}
2022-02-15 11:52:43 +00:00
catch (BillingNotFoundException)
2021-05-17 11:35:00 +00:00
{
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
foreach (var p in products)
{
string url;
var paymentUrl = (Uri)null;
var upgradeUrl = (Uri)null;
if (paymentUrls.TryGetValue(p, out url))
2021-05-17 11:35:00 +00:00
{
2022-02-15 11:52:43 +00:00
url = ToUrl(url);
if (!string.IsNullOrEmpty(url))
2021-05-17 11:35:00 +00:00
{
2022-02-15 11:52:43 +00:00
paymentUrl = new Uri(url);
2021-05-17 11:35:00 +00:00
}
}
2022-02-15 11:52:43 +00:00
if (upgradeUrls.TryGetValue(p, out url))
2020-01-17 13:58:26 +00:00
{
2022-02-15 11:52:43 +00:00
url = ToUrl(url);
if (!string.IsNullOrEmpty(url))
2021-05-17 11:35:00 +00:00
{
2022-02-15 11:52:43 +00:00
upgradeUrl = new Uri(url);
2021-05-17 11:35:00 +00:00
}
}
2022-02-15 11:52:43 +00:00
urls[p] = Tuple.Create(paymentUrl, upgradeUrl);
2020-01-17 13:58:26 +00:00
}
2022-02-15 11:52:43 +00:00
return urls;
}
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();
parameters.Add(Tuple.Create("PaymentSystemId", AvangatePaymentSystemId.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-02-15 11:52:43 +00:00
if (prices.TryGetValue(AvangatePaymentSystemId, out var pricesPaymentSystem))
{
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-02-15 11:52:43 +00:00
var request = new HttpRequestMessage();
request.RequestUri = new Uri(url);
request.Method = HttpMethod.Post;
if (!string.IsNullOrEmpty(_billingKey))
{
request.Headers.Add("Authorization", CreateAuthToken(_billingKey, _billingSecret));
}
2021-05-17 11:35:00 +00:00
2022-02-15 11:52:43 +00:00
_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))
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"))
{
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
}