1
0
mirror of https://github.com/bitwarden/server.git synced 2025-05-03 02:32:16 -05:00
bitwarden/src/Billing/Utilities/PayPalIpnClient.cs
Conner Turnbull abc7b35a3d
[AC-1783] PayPal IPN Changes (#3404)
* Reverted changes to the domain for the PayPal IPN client, and added more logging

* Removing User-Agent as it wasn't in the IPN client previously
2023-11-02 14:22:54 +00:00

177 lines
6.8 KiB
C#

using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;
using System.Web;
using Microsoft.Extensions.Options;
namespace Bit.Billing.Utilities;
public class PayPalIpnClient
{
private readonly HttpClient _httpClient = new HttpClient();
private readonly Uri _ipnUri;
private readonly ILogger<PayPalIpnClient> _logger;
public PayPalIpnClient(IOptions<BillingSettings> billingSettings, ILogger<PayPalIpnClient> logger)
{
var bSettings = billingSettings?.Value;
_logger = logger;
_ipnUri = new Uri(bSettings.PayPal.Production ? "https://www.paypal.com/cgi-bin/webscr" :
"https://www.sandbox.paypal.com/cgi-bin/webscr");
}
public async Task<bool> VerifyIpnAsync(string ipnBody)
{
_logger.LogInformation("Verifying IPN with PayPal at {Timestamp}: {VerificationUri}", DateTime.UtcNow, _ipnUri);
if (ipnBody == null)
{
_logger.LogError("No IPN body.");
throw new ArgumentException("No IPN body.");
}
var request = new HttpRequestMessage { Method = HttpMethod.Post, RequestUri = _ipnUri };
var cmdIpnBody = string.Concat("cmd=_notify-validate&", ipnBody);
request.Content = new StringContent(cmdIpnBody, Encoding.UTF8, "application/x-www-form-urlencoded");
var response = await _httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
_logger.LogError("Failed to receive a successful response from PayPal IPN verification service. Response: {Response}", response);
throw new Exception("Failed to verify IPN, status: " + response.StatusCode);
}
var responseContent = await response.Content.ReadAsStringAsync();
if (responseContent.Equals("VERIFIED"))
{
return true;
}
if (responseContent.Equals("INVALID"))
{
_logger.LogWarning("Received an INVALID response from PayPal: {ResponseContent}", responseContent);
return false;
}
_logger.LogError("Failed to verify IPN: {ResponseContent}", responseContent);
throw new Exception("Failed to verify IPN.");
}
public class IpnTransaction
{
private string[] _dateFormats = new string[]
{
"HH:mm:ss dd MMM yyyy PDT", "HH:mm:ss dd MMM yyyy PST", "HH:mm:ss dd MMM, yyyy PST",
"HH:mm:ss dd MMM, yyyy PDT","HH:mm:ss MMM dd, yyyy PST", "HH:mm:ss MMM dd, yyyy PDT"
};
public IpnTransaction(string ipnFormData)
{
if (string.IsNullOrWhiteSpace(ipnFormData))
{
return;
}
var qsData = HttpUtility.ParseQueryString(ipnFormData);
var dataDict = qsData.Keys.Cast<string>().ToDictionary(k => k, v => qsData[v].ToString());
TxnId = GetDictValue(dataDict, "txn_id");
TxnType = GetDictValue(dataDict, "txn_type");
ParentTxnId = GetDictValue(dataDict, "parent_txn_id");
PaymentStatus = GetDictValue(dataDict, "payment_status");
PaymentType = GetDictValue(dataDict, "payment_type");
McCurrency = GetDictValue(dataDict, "mc_currency");
Custom = GetDictValue(dataDict, "custom");
ItemName = GetDictValue(dataDict, "item_name");
ItemNumber = GetDictValue(dataDict, "item_number");
PayerId = GetDictValue(dataDict, "payer_id");
PayerEmail = GetDictValue(dataDict, "payer_email");
ReceiverId = GetDictValue(dataDict, "receiver_id");
ReceiverEmail = GetDictValue(dataDict, "receiver_email");
PaymentDate = ConvertDate(GetDictValue(dataDict, "payment_date"));
var mcGrossString = GetDictValue(dataDict, "mc_gross");
if (!string.IsNullOrWhiteSpace(mcGrossString) && decimal.TryParse(mcGrossString, out var mcGross))
{
McGross = mcGross;
}
var mcFeeString = GetDictValue(dataDict, "mc_fee");
if (!string.IsNullOrWhiteSpace(mcFeeString) && decimal.TryParse(mcFeeString, out var mcFee))
{
McFee = mcFee;
}
}
public string TxnId { get; set; }
public string TxnType { get; set; }
public string ParentTxnId { get; set; }
public string PaymentStatus { get; set; }
public string PaymentType { get; set; }
public decimal McGross { get; set; }
public decimal McFee { get; set; }
public string McCurrency { get; set; }
public string Custom { get; set; }
public string ItemName { get; set; }
public string ItemNumber { get; set; }
public string PayerId { get; set; }
public string PayerEmail { get; set; }
public string ReceiverId { get; set; }
public string ReceiverEmail { get; set; }
public DateTime PaymentDate { get; set; }
public Tuple<Guid?, Guid?> GetIdsFromCustom()
{
Guid? orgId = null;
Guid? userId = null;
if (!string.IsNullOrWhiteSpace(Custom) && Custom.Contains(":"))
{
var mainParts = Custom.Split(',');
foreach (var mainPart in mainParts)
{
var parts = mainPart.Split(':');
if (parts.Length > 1 && Guid.TryParse(parts[1], out var id))
{
if (parts[0] == "user_id")
{
userId = id;
}
else if (parts[0] == "organization_id")
{
orgId = id;
}
}
}
}
return new Tuple<Guid?, Guid?>(orgId, userId);
}
public bool IsAccountCredit()
{
return !string.IsNullOrWhiteSpace(Custom) && Custom.Contains("account_credit:1");
}
private string GetDictValue(IDictionary<string, string> dict, string key)
{
return dict.ContainsKey(key) ? dict[key] : null;
}
private DateTime ConvertDate(string dateString)
{
if (!string.IsNullOrWhiteSpace(dateString))
{
var parsed = DateTime.TryParseExact(dateString, _dateFormats,
CultureInfo.InvariantCulture, DateTimeStyles.None, out var paymentDate);
if (parsed)
{
var pacificTime = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time") :
TimeZoneInfo.FindSystemTimeZoneById("America/Los_Angeles");
return TimeZoneInfo.ConvertTimeToUtc(paymentDate, pacificTime);
}
}
return default(DateTime);
}
}
}