Example #1
0
        public async Task SendReconcileNotification(ReconcileNotification notification)
        {
            // find invoice
            var invoice = await _dbContext.Invoices
                          .FirstOrDefaultAsync(i => i.Id == notification.InvoiceId);

            if (invoice == null)
            {
                return;
            }

            // send webhook notifications
            var hooks   = invoice.Team.WebHooks.Where(h => h.IsActive && h.TriggerOnReconcile);
            var payload = new ReconcilePayload()
            {
                InvoiceId = invoice.Id,
            };

            foreach (var webHook in hooks)
            {
                await SendWebHookPayload(webHook, payload);
            }
        }
Example #2
0
        public async Task FindBankReconcileTransactions(ILogger log)
        {
            using (var ts = _dbContext.Database.BeginTransaction())
            {
                try
                {
                    // get all invoices that are waiting for reconcile
                    var invoices = _dbContext.Invoices
                                   .Where(i => i.Status == Invoice.StatusCodes.Paid)
                                   .Include(i => i.Account)
                                   .Include(i => i.Team)
                                   .ThenInclude(t => t.Accounts)
                                   .ToList();

                    log.Information("{count} invoices found expecting reconciliation", invoices.Count);

                    foreach (var invoice in invoices)
                    {
                        var transaction = await _slothService.GetTransactionsByProcessorId(invoice.PaymentProcessorId);

                        if (transaction == null)
                        {
                            log.Warning("No reconciliation found for invoice id: {id} Paid Date: {PaidAt}", invoice.Id, invoice.PaidAt);
                            continue;
                        }

                        log.Information("Invoice {id} reconciliation found with transaction: {transactionId}",
                                        invoice.Id, transaction.Id);

                        // get team account info
                        var team = invoice.Team;
                        if (team.DefaultAccount == null)
                        {
                            log.Warning("Team {team} has no default account for payments", team.Name);
                            continue;
                        }

                        // transaction found, bank reconcile was successful
                        invoice.KfsTrackingNumber = transaction.KfsTrackingNumber;
                        invoice.Status            = Invoice.StatusCodes.Processing;

                        // calculate fees
                        var feeAmount    = Math.Round(invoice.CalculatedTotal * FeeSchedule.StandardRate, 2);
                        var incomeAmount = invoice.CalculatedTotal - feeAmount;

                        var incomeAccountChart = team.DefaultAccount.Chart;
                        var incomeAccount      = team.DefaultAccount.Account;
                        var incomeSubAccount   = team.DefaultAccount.SubAccount;

                        if (invoice.Account != null && !string.IsNullOrWhiteSpace(invoice.Account.Account) && invoice.Account.IsActive)
                        {
                            // the invoice has a specified account, use it instead of the team's default
                            incomeAccountChart = invoice.Account.Chart;
                            incomeAccount      = invoice.Account.Account;
                            incomeSubAccount   = invoice.Account.SubAccount;
                        }

                        // create transfers
                        var debitHolding = new CreateTransfer()
                        {
                            Amount      = invoice.CalculatedTotal,
                            Direction   = Transfer.CreditDebit.Debit,
                            Chart       = _financeSettings.ClearingChart,
                            Account     = _financeSettings.ClearingAccount,
                            ObjectCode  = ObjectCodes.Income,
                            Description = $"Funds Distribution INV {invoice.GetFormattedId()}".SafeTruncate(40)
                        };

                        var feeCredit = new CreateTransfer()
                        {
                            Amount      = feeAmount,
                            Direction   = Transfer.CreditDebit.Credit,
                            Chart       = _financeSettings.FeeChart,
                            Account     = _financeSettings.FeeAccount,
                            ObjectCode  = ObjectCodes.Income,
                            Description = $"Processing Fee INV {invoice.GetFormattedId()}".SafeTruncate(40)
                        };

                        var incomeCredit = new CreateTransfer()
                        {
                            Amount      = incomeAmount,
                            Direction   = Transfer.CreditDebit.Credit,
                            Chart       = incomeAccountChart,
                            Account     = incomeAccount,
                            SubAccount  = incomeSubAccount,
                            ObjectCode  = ObjectCodes.Income,
                            Description = $"Funds Distribution INV {invoice.GetFormattedId()}".SafeTruncate(40)
                        };

                        // setup transaction
                        var merchantUrl = $"https://payments.ucdavis.edu/{invoice.Team.Slug}/invoices/details/{invoice.Id}";

                        var response = await _slothService.CreateTransaction(new CreateTransaction()
                        {
                            AutoApprove            = true,
                            MerchantTrackingNumber = transaction.MerchantTrackingNumber,
                            MerchantTrackingUrl    = merchantUrl,
                            KfsTrackingNumber      = transaction.KfsTrackingNumber,
                            TransactionDate        = DateTime.UtcNow,
                            Transfers = new List <CreateTransfer>()
                            {
                                debitHolding,
                                feeCredit,
                                incomeCredit,
                            },
                            Source     = "Payments",
                            SourceType = "CyberSource",
                        });

                        log.Information("Transaction created with ID: {id}", response.Id);

                        // send notifications
                        try
                        {
                            var notification = new ReconcileNotification()
                            {
                                InvoiceId = invoice.Id,
                            };
                            await _notificationService.SendReconcileNotification(notification);
                        }
                        catch (Exception ex)
                        {
                            log.Error(ex, "Error while sending notification");
                        }
                    }

                    log.Information("Finishing Job");
                    await _dbContext.SaveChangesAsync();

                    ts.Commit();
                }
                catch (Exception ex)
                {
                    log.Error(ex, ex.Message);
                    ts.Rollback();
                    throw;
                }
            }
        }