1
0
mirror of https://github.com/bitwarden/server synced 2026-01-02 16:43:25 +00:00

[AC-1800] PayPal IPN Refactor (#3619)

* Add more logging to PayPal IPN webhook

* Add PayPalIPNClient tests

* Add PayPalControllerTests

---------

Co-authored-by: aelinton <95626935+aelinton@users.noreply.github.com>
This commit is contained in:
Alex Morask
2024-01-30 09:03:50 -05:00
committed by GitHub
parent 6ebb408a97
commit cc2a81ae3f
19 changed files with 1546 additions and 326 deletions

View File

@@ -0,0 +1,110 @@
using System.Globalization;
using System.Runtime.InteropServices;
using System.Web;
namespace Bit.Billing.Models;
public class PayPalIPNTransactionModel
{
public string TransactionId { get; }
public string TransactionType { get; }
public string ParentTransactionId { get; }
public string PaymentStatus { get; }
public string PaymentType { get; }
public decimal MerchantGross { get; }
public string MerchantCurrency { get; }
public string ReceiverId { get; }
public DateTime PaymentDate { get; }
public Guid? UserId { get; }
public Guid? OrganizationId { get; }
public bool IsAccountCredit { get; }
public PayPalIPNTransactionModel(string formData)
{
var queryString = HttpUtility.ParseQueryString(formData);
var data = queryString
.AllKeys
.ToDictionary(key => key, key => queryString[key]);
TransactionId = Extract(data, "txn_id");
TransactionType = Extract(data, "txn_type");
ParentTransactionId = Extract(data, "parent_txn_id");
PaymentStatus = Extract(data, "payment_status");
PaymentType = Extract(data, "payment_type");
var merchantGross = Extract(data, "mc_gross");
if (!string.IsNullOrEmpty(merchantGross))
{
MerchantGross = decimal.Parse(merchantGross);
}
MerchantCurrency = Extract(data, "mc_currency");
ReceiverId = Extract(data, "receiver_id");
var paymentDate = Extract(data, "payment_date");
PaymentDate = ToUTCDateTime(paymentDate);
var custom = Extract(data, "custom");
if (string.IsNullOrEmpty(custom))
{
return;
}
var metadata = custom.Split(',')
.Where(field => !string.IsNullOrEmpty(field) && field.Contains(':'))
.Select(field => field.Split(':'))
.ToDictionary(parts => parts[0], parts => parts[1]);
if (metadata.TryGetValue("user_id", out var userIdStr) &&
Guid.TryParse(userIdStr, out var userId))
{
UserId = userId;
}
if (metadata.TryGetValue("organization_id", out var organizationIdStr) &&
Guid.TryParse(organizationIdStr, out var organizationId))
{
OrganizationId = organizationId;
}
IsAccountCredit = custom.Contains("account_credit:1");
}
private static string Extract(IReadOnlyDictionary<string, string> data, string key)
{
var success = data.TryGetValue(key, out var value);
return success ? value : null;
}
private static DateTime ToUTCDateTime(string input)
{
if (string.IsNullOrEmpty(input))
{
return default;
}
var success = DateTime.TryParseExact(input,
new[]
{
"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"
}, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTime);
if (!success)
{
return default;
}
var pacificTime = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time")
: TimeZoneInfo.FindSystemTimeZoneById("America/Los_Angeles");
return TimeZoneInfo.ConvertTimeToUtc(dateTime, pacificTime);
}
}