using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net.Http; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using System.Web; using Microsoft.Extensions.Options; namespace Bit.Billing.Utilities { public class PayPalIpnClient { private readonly HttpClient _httpClient = new HttpClient(); private readonly Uri _ipnUri; public PayPalIpnClient(IOptions billingSettings) { var bSettings = billingSettings?.Value; _ipnUri = new Uri(bSettings.PayPal.Production ? "https://www.paypal.com/cgi-bin/webscr" : "https://www.sandbox.paypal.com/cgi-bin/webscr"); } public async Task VerifyIpnAsync(string ipnBody) { if(ipnBody == null) { 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) { throw new Exception("Failed to verify IPN, status: " + response.StatusCode); } var responseContent = await response.Content.ReadAsStringAsync(); if(responseContent.Equals("VERIFIED")) { return true; } else if(responseContent.Equals("INVALID")) { return false; } else { 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().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 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(orgId, userId); } public bool IsAccountCredit() { return !string.IsNullOrWhiteSpace(Custom) && Custom.Contains("account_credit:1"); } private string GetDictValue(IDictionary 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); } } } }