protected static ImportStats ProcessImportedData(ImportResult import, Organization organization, Person importingPerson) { FinancialAccount payPalAccount = organization.FinancialAccounts.AssetsPaypal; FinancialAccount bankFees = organization.FinancialAccounts.CostsBankFees; FinancialAccount donations = organization.FinancialAccounts.IncomeDonations; int autoDepositLimit = 1000; // TODO: Get from organization parameters int autoWithdrawalLimit = 0; ImportStats result = new ImportStats(); foreach (ImportedRow row in import.Rows) { // Each row is at least a stub, probably more. // If too old, ignore. if (row.DateTime < new DateTime(2008, 12, 4)) { continue; } string importKey = row.SuppliedTransactionId; // If importKey is empty, construct a hash from the data fields. if (string.IsNullOrEmpty(importKey)) { string hashKey = row.HashBase + row.Comment + (row.AmountCentsNet / 100.0).ToString(CultureInfo.InvariantCulture) + row.CurrentBalance.ToString(CultureInfo.InvariantCulture) + row.DateTime.ToString("yyyy-MM-dd-hh-mm-ss"); importKey = SHA1.Hash(hashKey).Replace(" ", ""); } if (importKey.Length > 30) { importKey = importKey.Substring(0, 30); } Int64 amountCents = row.AmountCentsNet; if (amountCents == 0) { amountCents = row.AmountCentsGross; } Dictionary <int, long> nominalTransaction = new Dictionary <int, long>(); FinancialTransaction transaction = null; try { transaction = FinancialTransaction.FromImportKey(organization, importKey); } catch (Exception) { // if we get here, that means the transaction did not yet exist transaction = FinancialTransaction.ImportWithStub(organization.Identity, row.DateTime, payPalAccount.Identity, amountCents, row.Comment, importKey, "" /* new SHA256 field */, importingPerson.Identity); result.ImportedTransactionCount++; if (transaction == null) { // No transaction was created. This is an error condition as it should have been created if it didn't // exist, and the "exist" case is handled in the FromImportKey attempt above. Abort with error. // Throw new exception? continue; } } result.ProcessedTransactionCount++; nominalTransaction[payPalAccount.Identity] = amountCents; // The transaction was created. Examine if the autobook criteria are true. if (amountCents < 0) { if ((-amountCents) < autoWithdrawalLimit * 100) { // Book against autoWithdrawal account. nominalTransaction[bankFees.Identity] = -amountCents; } } else if (amountCents > 0) { if (row.FeeCents < 0) { // This is always an autodeposit, if there is a fee (which is never > 0.0) nominalTransaction[bankFees.Identity] = -row.FeeCents; nominalTransaction[donations.Identity] = -row.AmountCentsGross; } else if (amountCents < autoDepositLimit * 100) { // Book against autoDeposit account. nominalTransaction[donations.Identity] = -amountCents; } } if (transaction.Rows.AmountCentsTotal != 0) // If transaction is unbalanced, balance it { if (transaction.RecalculateTransaction(nominalTransaction, importingPerson)) { result.ModifiedTransactionCount++; } } } return(result); }
protected void ProcessImportedData(ImportResult import) { FinancialAccount assetAccount = FinancialAccount.FromIdentity(Int32.Parse(this.DropAssetAccount.SelectedValue)); FinancialAccount autoDepositAccount = FinancialAccount.FromIdentity(Int32.Parse(this.DropAutoDeposits.SelectedValue)); FinancialAccount autoWithdrawalAccount = FinancialAccount.FromIdentity(Int32.Parse(this.DropAutoWithdrawals.SelectedValue)); int autoDepositLimit = Int32.Parse(this.TextDepositLimit.Text); int autoWithdrawalLimit = Int32.Parse(this.TextWithdrawalLimit.Text); int organizationId = Int32.Parse(this.DropOrganizations.SelectedValue); Organization organization = Organization.FromIdentity(organizationId); int importedTransactionCount = 0; foreach (ImportedRow row in import.Rows) { // Each row is at least a stub, probably more. // If too old, ignore. if (row.DateTime < new DateTime(2008, 12, 4)) { continue; } string importKey = row.SuppliedTransactionId; // If importKey is empty, construct a hash from the data fields. if (string.IsNullOrEmpty(importKey)) { string hashKey = row.HashBase + row.Comment + (row.AmountCentsNet / 100.0).ToString(CultureInfo.InvariantCulture) + row.CurrentBalance.ToString(CultureInfo.InvariantCulture) + row.DateTime.ToString("yyyy-MM-dd-hh-mm-ss"); importKey = SHA1.Hash(hashKey).Replace(" ", ""); } if (importKey.Length > 30) { importKey = importKey.Substring(0, 30); } Int64 amountCents = row.AmountCentsNet; if (amountCents == 0) { amountCents = row.AmountCentsGross; } FinancialTransaction transaction = FinancialTransaction.ImportWithStub(organizationId, row.DateTime, assetAccount.Identity, amountCents, row.Comment, importKey, _currentUser.Identity); if (transaction != null) { // The transaction was created. Examine if the autobook criteria are true. importedTransactionCount++; FinancialAccounts accounts = FinancialAccounts.FromBankTransactionTag(row.Comment); if (accounts.Count == 1) { // This is a labelled local donation. Geography geography = accounts[0].AssignedGeography; FinancialAccount localAccount = accounts[0]; transaction.AddRow(organization.FinancialAccounts.IncomeDonations, -amountCents, _currentUser); transaction.AddRow(organization.FinancialAccounts.CostsLocalDonationTransfers, amountCents, _currentUser); transaction.AddRow(localAccount, -amountCents, _currentUser); Activizr.Logic.Support.PWEvents.CreateEvent(EventSource.PirateWeb, EventType.LocalDonationReceived, _currentUser.Identity, organizationId, geography.Identity, 0, transaction.Identity, localAccount.Identity.ToString()); } else if (row.Comment.ToLowerInvariant().StartsWith("bg 451-0061 ")) // TODO: Organization.Parameters.FinancialTrackedTransactionPrefix { // Check for previously imported payment group PaymentGroup group = PaymentGroup.FromTag(organization, "SEBGM" + DateTime.Today.Year.ToString() + // TODO: Get tagging from org row.Comment.Substring(11)); if (group != null) { // There was a previously imported and not yet closed payment group matching this transaction // Close the payment group and match the transaction against accounts receivable transaction.Dependency = group; group.Open = false; transaction.AddRow(organization.FinancialAccounts.AssetsOutboundInvoices, -amountCents, _currentUser); } } else if (amountCents < 0) { if ((-amountCents) < autoWithdrawalLimit * 100) { // Book against autoWithdrawal account. transaction.AddRow(autoWithdrawalAccount, -amountCents, _currentUser); } } else if (amountCents > 0) { if (row.Fee < 0) { // This is always an autodeposit, if there is a fee (which is never > 0.0) transaction.AddRow(organization.FinancialAccounts.CostsBankFees, -row.Fee, _currentUser); transaction.AddRow(autoDepositAccount, -row.AmountCentsGross, _currentUser); } else if (amountCents < autoDepositLimit * 100) { // Book against autoDeposit account. transaction.AddRow(autoDepositAccount, -amountCents, _currentUser); } } } } // Import complete. Examine if we expect more transactions -- if the imported balance differs from // the database balance: double databaseAccountBalance = assetAccount.BalanceTotal; bool mismatch = false; if (databaseAccountBalance != import.CurrentBalance) { mismatch = true; } string message = importedTransactionCount.ToString() + " transactions were imported."; if (importedTransactionCount == 0) { message = "No transactions were imported. "; } else if (importedTransactionCount == 1) { message = "One transaction was imported. "; } if (import.CurrentBalance > 0) { if (mismatch) { message += " Transactions are missing from the database. Import more transactions."; } else { message += " The account balance is up to date. No further import is necessary."; ScriptManager.RegisterStartupScript(this, Page.GetType(), "alldone", "alert ('The account balance is up to date. No further import is necessary.');", true); // Auto-match open payouts against new transactions Payouts.AutomatchAgainstUnbalancedTransactions(organization); } } this.LabelImportResultText.Text = message; }
protected void Page_Load(object sender, EventArgs e) { // THIS CODE IS TAKEN FROM PAYPAL'S TEMPLATE. Person notifyPerson = Person.FromIdentity(1); //Post back to either sandbox or live string strLive = "https://www.paypal.com/cgi-bin/webscr"; HttpWebRequest req = (HttpWebRequest)WebRequest.Create(strLive); //Set values for the request back req.Method = "POST"; req.ContentType = "application/x-www-form-urlencoded"; byte[] param = Request.BinaryRead(HttpContext.Current.Request.ContentLength); string strRequest = Encoding.ASCII.GetString(param); string originalRequest = strRequest; strRequest += "&cmd=_notify-validate"; req.ContentLength = strRequest.Length; //Send the request to PayPal and get the response StreamWriter streamOut = new StreamWriter(req.GetRequestStream(), System.Text.Encoding.ASCII); streamOut.Write(strRequest); streamOut.Close(); StreamReader streamIn = new StreamReader(req.GetResponse().GetResponseStream()); string strResponse = streamIn.ReadToEnd(); streamIn.Close(); // Decode parameters List <string> temp = new List <string>(); Dictionary <string, string> parameters = new Dictionary <string, string>(); string[] parameterStrings = originalRequest.Split('&'); foreach (string parameter in parameterStrings) { string[] data = parameter.Split('='); string dataKey = Server.UrlDecode(data[0]); string dataValue = Server.UrlDecode(data[1]); parameters[dataKey] = dataValue; temp.Add(string.Format("{0,-20} {1}", dataKey, dataValue)); } notifyPerson.SendNotice("Paypal listener point A", String.Join("\r\n", temp.ToArray()), 1); bool proceed = true; if (strResponse == "INVALID") { notifyPerson.SendNotice("Paypal listener FAIL - Response is INVALID", string.Empty, 1); proceed = false; } if (!parameters.ContainsKey("payment_status")) { notifyPerson.SendNotice("Paypal listener FAIL - payment_status was not supplied", string.Empty, 1); proceed = false; } else if (parameters["payment_status"] != "Completed" && parameters["payment_status"] != "Refunded") { notifyPerson.SendNotice("Paypal listener FAIL - payment_status is not Completed / Refunded", string.Empty, 1); proceed = false; } if (parameters["receiver_email"] != "*****@*****.**") { notifyPerson.SendNotice("Paypal listener FAIL - receiver_email is not [email protected]", string.Empty, 1); proceed = false; // HACK -- adjust for multiple orgs } if (parameters["mc_currency"] != "SEK") { notifyPerson.SendNotice("Paypal listener FAIL - mc_currency is not SEK", string.Empty, 1); proceed = false; // HACK -- adjust for multiple orgs } if (!parameters.ContainsKey("mc_gross") || String.IsNullOrEmpty(parameters["mc_gross"])) { notifyPerson.SendNotice("Paypal listener FAIL - mc_net is null or empty", string.Empty, 1); proceed = false; } if (strResponse == "VERIFIED" && proceed) { string transactionId = parameters["txn_id"]; string identifier = null; if (parameters.ContainsKey("invoice")) { identifier = parameters["invoice"]; } OutboundInvoice invoice = null; if (!String.IsNullOrEmpty(identifier)) { invoice = OutboundInvoice.FromReference(identifier); notifyPerson.SendNotice("Paypal listener - invoice identified as #" + invoice.Identity.ToString(), string.Empty, 1); } string grossString = parameters["mc_gross"]; string feeString = "0.0"; if (parameters.ContainsKey("mc_fee") && !string.IsNullOrEmpty(parameters["mc_fee"])) { feeString = parameters["mc_fee"]; } string comment = "Paypal "; if (parameters["payment_status"] == "Completed") { comment += parameters["txn_type"]; } else { // Refund comment += "Refund"; } if (parameters.ContainsKey("item_name")) { comment = parameters["item_name"]; } if (invoice != null) { comment = "Outbound Invoice #" + invoice.Identity.ToString() + " payment Paypal"; } DateTime dateTime = DateTime.ParseExact(parameters["payment_date"].Replace("PDT", "-07").Replace("PST", "-08"), "HH:mm:ss MMM dd, yyyy zz", CultureInfo.InvariantCulture).ToLocalTime(); Organization org = Organization.PPSE; Int64 feeCents = Int64.Parse(feeString.Replace(".", "")); Int64 grossCents = Int64.Parse(grossString.Replace(".", "")); Int64 amountCents = grossCents - feeCents; FinancialTransaction transaction = FinancialTransaction.ImportWithStub(org.Identity, dateTime, org.FinancialAccounts.AssetsPaypal.Identity, amountCents, comment, transactionId, 0); // TODO: Convert to cents if (transaction != null) { // The transaction was created. Examine if the autobook criteria are true. if (amountCents < 0 && parameters["payment_status"] == "Completed") { // Do not balance the transaction -- will have to be balanced manually } else if (amountCents > 0) { if (invoice != null) { Int64 expectedNetCents = invoice.AmountCents; transaction.AddRow(org.FinancialAccounts.CostsBankFees, expectedNetCents - amountCents, null); // can be negative transaction.AddRow(org.FinancialAccounts.AssetsOutboundInvoices, -expectedNetCents, null); // Hack: Add a line to the outbound invoice HERE with the PayPal surcharge invoice.AddItem("PayPal/Credit Card surcharge, 5%", (Int64)(invoice.AmountCents * 0.05)); Payment payment = Payment.CreateSingle(invoice.Organization, DateTime.Now, invoice.Currency, expectedNetCents, invoice, null); // HACK HACK HACK: says we always received the expected amount payment.AddInformation(PaymentInformationType.RefundInformation, "PayPal " + transactionId); payment.AddInformation(PaymentInformationType.Name, parameters["first_name"] + " " + parameters["last_name"]); payment.AddInformation(PaymentInformationType.Email, parameters["payer_email"]); transaction.Dependency = payment.Group; } else if (feeCents > 0) { // This is always an autodeposit transaction.AddRow(org.FinancialAccounts.CostsBankFees, feeCents, null); transaction.AddRow(org.FinancialAccounts.IncomeDonations, -grossCents, null); } else { transaction.AddRow(org.FinancialAccounts.IncomeDonations, -amountCents, null); } } } } }
public static ImportExternalTransactionDataResults ImportExternalTransactionData(ExternalBankData import, ImportExternalTransactionDataArgs args) { FinancialAccount assetAccount = args.Account; FinancialAccount autoDepositAccount = args.Organization.FinancialAccounts.IncomeDonations; int autoDepositLimit = 0; // Disabled; TODO: this.CurrentOrganization.Parameters.AutoDonationLimit; bool autosetInitialBalance = false; ImportExternalTransactionDataResults result = new ImportExternalTransactionDataResults(); int count = 0; int progressUpdateInterval = import.Records.Length / 40; Int64 importedCentsTotal = 0; if (progressUpdateInterval > 100) { progressUpdateInterval = 100; } ProgressBarBackend progressDisplay = new ProgressBarBackend(args.Guid); Currency organizationCurrency = assetAccount.Organization.Currency; Currency accountCurrency = assetAccount.ForeignCurrency; if (accountCurrency == null) { accountCurrency = organizationCurrency; } FinancialAccountRows existingRows = assetAccount.GetRows(Constants.DateTimeLow, Constants.DateTimeHigh); // gets all if (existingRows.Count == 0) { autosetInitialBalance = true; } foreach (ExternalBankDataRecord row in import.Records) { // Update progress. count++; if (progressUpdateInterval < 2 || count % progressUpdateInterval == 0) { int percent = (count * 99) / import.Records.Length; progressDisplay.Set(percent); } // Update high- and low-water marks. if (row.DateTime < result.EarliestTransaction) { result.EarliestTransaction = row.DateTime; } if (row.DateTime > result.LatestTransaction) { result.LatestTransaction = row.DateTime; } string importKey = row.ImportHash; Int64 amountCents = row.TransactionNetCents; if (amountCents == 0) // defensive programming - these _should_ be duplicated in the interpreter if no "fee" field { amountCents = row.TransactionGrossCents; } Int64 foreignCents = amountCents; importedCentsTotal += amountCents; if (accountCurrency.Identity != organizationCurrency.Identity) { amountCents = new Money(amountCents, accountCurrency, row.DateTime).ToCurrency(organizationCurrency).Cents; } FinancialTransaction transaction = FinancialTransaction.ImportWithStub(args.Organization.Identity, row.DateTime, assetAccount.Identity, amountCents, row.Description, importKey, Sha256.Compute(row.RawData), args.CurrentUser.Identity); if (transaction != null) { // The transaction was created. result.TransactionsImported++; // If non-presentation currency, log the account currency amount as well. if (accountCurrency.Identity != organizationCurrency.Identity) { transaction.Rows[0].AmountForeignCents = new Money(foreignCents, accountCurrency); } if (row.Description.ToLowerInvariant().StartsWith(args.Organization.IncomingPaymentTag)) { // Check for previously imported payment group // TODO: MAKE FLEXIBLE - CALL PAYMENTREADERINTERFACE! // HACK HACK HACK HACK PaymentGroup group = PaymentGroup.FromTag(args.Organization, "SEBGM" + DateTime.Today.Year + // TODO: Get tags from org row.Description.Substring(args.Organization.IncomingPaymentTag.Length).Trim()); if (group != null && group.Open) { // There was a previously imported and not yet closed payment group matching this transaction // Close the payment group and match the transaction against accounts receivable transaction.Dependency = group; group.Open = false; transaction.AddRow(args.Organization.FinancialAccounts.AssetsOutboundInvoices, -amountCents, args.CurrentUser); } } else if (amountCents < 0) { // Autowithdrawal mechanisms removed, condition kept because of downstream else-if conditions } else if (amountCents > 0) { if (row.FeeCents < 0) { // This is always an autodeposit, if there is a fee (which is never > 0.0) transaction.AddRow(args.Organization.FinancialAccounts.CostsBankFees, -row.FeeCents, args.CurrentUser); transaction.AddRow(autoDepositAccount, -row.TransactionGrossCents, args.CurrentUser); } else if (amountCents < autoDepositLimit * 100) { // Book against autoDeposit account. transaction.AddRow(autoDepositAccount, -amountCents, args.CurrentUser); } } } else { // Transaction was not imported; assume duplicate result.DuplicateTransactions++; } } // Import complete. Return true if the bookkeeping account matches the bank data. Int64 databaseAccountBalanceCents; if (accountCurrency.Identity == organizationCurrency.Identity) { databaseAccountBalanceCents = assetAccount.BalanceTotalCents; } else { // foreign-currency account databaseAccountBalanceCents = assetAccount.ForeignCurrencyBalance.Cents; } // Subtract any transactions made after the most recent imported transaction. // This is necessary in case of Paypal and others which continuously feed the // bookkeeping account with new transactions; it will already have fed transactions // beyond the end-of-file. Int64 beyondEofCents = assetAccount.GetDeltaCents(result.LatestTransaction.AddSeconds(1), DateTime.Now.AddDays(2)); // Caution: the "AddSeconds(1)" is not foolproof, there may be other new txs on the same second. if (databaseAccountBalanceCents - beyondEofCents == import.LatestAccountBalanceCents) { Payouts.AutomatchAgainstUnbalancedTransactions(args.Organization); OutboundInvoices.AutomatchAgainstUnbalancedTransactions(args.Organization, args.CurrentUser); result.AccountBalanceMatchesBank = true; result.BalanceMismatchCents = 0; } else { result.AccountBalanceMatchesBank = false; result.BalanceMismatchCents = (databaseAccountBalanceCents - beyondEofCents) - import.LatestAccountBalanceCents; if (autosetInitialBalance) { Int64 newInitialBalanceCents = -result.BalanceMismatchCents; Money initialBalance = new Money(newInitialBalanceCents, accountCurrency); assetAccount.InitialBalance = initialBalance; result.InitialBalanceCents = newInitialBalanceCents; result.InitialBalanceCurrencyCode = accountCurrency.Code; // make an approximation of conversion rate set for initial balance in presentation to tell user initialBalance.ValuationDateTime = new DateTime(assetAccount.Organization.FirstFiscalYear, 1, 1); result.BalanceMismatchCents = initialBalance.ToCurrency(assetAccount.Organization.Currency).Cents; } } result.CurrencyCode = args.Organization.Currency.Code; GuidCache.Set(args.Guid + "-Results", result); return(result); }