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
|
|
|
|
2022-01-26 10:43:08 +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;
|
2022-03-22 10:20:43 +00:00
|
|
|
private readonly IHttpClientFactory _httpClientFactory;
|
2022-03-25 16:26:06 +00:00
|
|
|
private const int AvangatePaymentSystemId = 1;
|
2021-05-17 11:35:00 +00:00
|
|
|
|
|
|
|
|
2022-04-14 19:42:15 +00:00
|
|
|
public BillingClient(IConfiguration configuration, IHttpClientFactory httpClientFactory)
|
|
|
|
: this(false, configuration, httpClientFactory)
|
2022-02-15 11:52:43 +00:00
|
|
|
{
|
|
|
|
}
|
2021-05-17 11:35:00 +00:00
|
|
|
|
2022-04-14 19:42:15 +00:00
|
|
|
public BillingClient(bool test, IConfiguration configuration, IHttpClientFactory httpClientFactory)
|
2022-02-15 11:52:43 +00:00
|
|
|
{
|
|
|
|
_test = test;
|
2022-04-14 19:42:15 +00:00
|
|
|
_httpClientFactory = httpClientFactory;
|
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)
|
|
|
|
{
|
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-03-25 16:26:06 +00:00
|
|
|
var additionalParameters = new List<Tuple<string, string>>() { Tuple.Create("PaymentSystemId", AvangatePaymentSystemId.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-06-01 14:07:08 +00:00
|
|
|
public string GetPaymentUrl(string portalId, string[] products, string affiliateId = null, string campaign = null, string currency = null, string language = null, string customerId = null, string quantity = null)
|
|
|
|
{
|
|
|
|
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))
|
|
|
|
{
|
|
|
|
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();
|
|
|
|
|
|
|
|
var result = Request("GetSinglePaymentUrl", portalId, parameters);
|
|
|
|
var paymentUrl = JsonConvert.DeserializeObject<string>(result);
|
|
|
|
|
|
|
|
return paymentUrl;
|
|
|
|
}
|
|
|
|
|
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-03-25 16:26:06 +00:00
|
|
|
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-03-25 16:26:06 +00:00
|
|
|
if (prices.TryGetValue(AvangatePaymentSystemId, 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-14 21:02:57 +00:00
|
|
|
|
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;
|
2022-03-22 10:20:43 +00:00
|
|
|
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-14 21:02:57 +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-14 21:02:57 +00:00
|
|
|
|
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-14 21:02:57 +00:00
|
|
|
|
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
|
|
|
}
|