private WebhookInvoiceEvent GetWebhookEvent(InvoiceEvent invoiceEvent)
        {
            var eventCode = invoiceEvent.EventCode;

            switch (eventCode)
            {
            case InvoiceEventCode.Completed:
            case InvoiceEventCode.PaidAfterExpiration:
                return(null);

            case InvoiceEventCode.Confirmed:
            case InvoiceEventCode.MarkedCompleted:
                return(new WebhookInvoiceSettledEvent(WebhookEventType.InvoiceSettled)
                {
                    ManuallyMarked = eventCode == InvoiceEventCode.MarkedCompleted
                });

            case InvoiceEventCode.Created:
                return(new WebhookInvoiceEvent(WebhookEventType.InvoiceCreated));

            case InvoiceEventCode.Expired:
            case InvoiceEventCode.ExpiredPaidPartial:
                return(new WebhookInvoiceExpiredEvent(WebhookEventType.InvoiceExpired)
                {
                    PartiallyPaid = eventCode == InvoiceEventCode.ExpiredPaidPartial
                });

            case InvoiceEventCode.FailedToConfirm:
            case InvoiceEventCode.MarkedInvalid:
                return(new WebhookInvoiceInvalidEvent(WebhookEventType.InvoiceInvalid)
                {
                    ManuallyMarked = eventCode == InvoiceEventCode.MarkedInvalid
                });

            case InvoiceEventCode.PaidInFull:
                return(new WebhookInvoiceProcessingEvent(WebhookEventType.InvoiceProcessing)
                {
                    OverPaid = invoiceEvent.Invoice.ExceptionStatus == InvoiceExceptionStatus.PaidOver,
                });

            case InvoiceEventCode.ReceivedPayment:
                return(new WebhookInvoiceReceivedPaymentEvent(WebhookEventType.InvoiceReceivedPayment)
                {
                    AfterExpiration = invoiceEvent.Invoice.Status.ToModernStatus() == InvoiceStatus.Expired || invoiceEvent.Invoice.Status.ToModernStatus() == InvoiceStatus.Invalid
                });

            default:
                return(null);
            }
        }
        private void OnBilling(InvoiceEvent e)
        {
            var details = GetOrCreateInvoiceDetails(e);

            details.InvoiceNum = e.InvoiceNum;
            details.UserHours  = e.Hours;
            details.UserId     = e.UserId;
            details.UserSum    = e.InvoiceSum;

            var invoice = _invoices.GetOrAdd(e.InvoiceNum, new Invoice());

            invoice.At             = e.RegisteredAt;
            invoice.BilledByUserId = e.RegisteredByUserId;
            invoice.ProjectId      = e.ProjectId;
            invoice.InvoiceNum     = e.InvoiceNum;

            RecalculateInvoice(e.InvoiceNum);
        }
Exemple #3
0
        public IEnumerable <IEvent <ProjectTimeAggregateRoot, int> > Execute(BillProjectCommand command)
        {
            CheckUserBillingRights(command.ProjectId, command.ByUserId);

            if (!command.InvoiceUserRequest.Any())
            {
                throw new ArgumentException("Invoice is empty.");
            }

            var invoiceNum = InvoiceNum(command.ProjectId);

            command.Result = invoiceNum;

            foreach (var userInvoiceInfo in command.InvoiceUserRequest)
            {
                if (_projectService.GetProjectUserInfo(userInvoiceInfo.UserId, command.ProjectId) == null)
                {
                    throw new ArgumentException(String.Format("User {0} is not associated with project {1}.", userInvoiceInfo.UserId, command.ProjectId));
                }

                var maxBillableHours = State.MaxBillableHours(userInvoiceInfo.UserId);
                if (maxBillableHours < userInvoiceInfo.Hours)
                {
                    throw new ArgumentException(String.Format("Max {0} hours could be billed for user {1}.", maxBillableHours, userInvoiceInfo.UserId));
                }

                var sum = State.CalculateInvoiceSum(userInvoiceInfo.UserId, userInvoiceInfo.Hours);

                var ev = new InvoiceEvent
                {
                    At                 = DateTimeOffset.Now,
                    EventType          = InvoiceEventType.Billing,
                    Hours              = userInvoiceInfo.Hours,
                    InvoiceSum         = sum,
                    ProjectId          = command.ProjectId,
                    UserId             = userInvoiceInfo.UserId,
                    RegisteredAt       = DateTimeOffset.Now,
                    RegisteredByUserId = command.ByUserId,
                    InvoiceNum         = invoiceNum
                };

                yield return(ev);
            }
        }
Exemple #4
0
        public void On(InvoiceEvent e)
        {
            var at = Date(e.At);

            if (e.EventType == InvoiceEventType.Cancel)
            {
                Invoices.Where(i => i.UserId == e.UserId &&
                               i.InvoiceNum == e.InvoiceNum &&
                               i.At == at)
                .ToList()
                .ForEach(i => i.IsCancelled = true);
            }
            if (e.EventType == InvoiceEventType.Billing)
            {
                var invoice = Invoices.SingleOrDefault(i => i.UserId == e.UserId &&
                                                       i.InvoiceNum == e.InvoiceNum &&
                                                       i.At == at);
                if (invoice == null)
                {
                    invoice = new UserInvoiceInfo
                    {
                        UserId     = e.UserId,
                        At         = at,
                        InvoiceNum = e.InvoiceNum
                    };
                    Invoices.Add(invoice);
                }

                invoice.Hours = e.Hours;
                invoice.Sum   = e.InvoiceSum;
            }
            if (e.EventType == InvoiceEventType.Paid)
            {
                var invoice = Invoices.SingleOrDefault(i => i.UserId == e.UserId &&
                                                       i.InvoiceNum == e.InvoiceNum &&
                                                       i.At == at);
                if (invoice != null)
                {
                    invoice.IsPaid = true;
                }
            }

            UpdateUserBilling(e.UserId);
        }
 public void On(InvoiceEvent e, ProjectTimeAggregateRootState state)
 {
     if (e.EventType == InvoiceEventType.Billing)
     {
         OnBilling(e);
     }
     if (e.EventType == InvoiceEventType.EarningDistribution)
     {
         OnDistributeEarnings(e);
     }
     if (e.EventType == InvoiceEventType.Cancel)
     {
         OnCancelInvoice(e);
     }
     if (e.EventType == InvoiceEventType.Paid)
     {
         OnPaidInvoice(e);
     }
 }
Exemple #6
0
        private async Task CreateEventAsync(dynamic data, string type)
        {
            var moment = DateTime.UtcNow;

            var partitionKey = $"{moment.Year}-{moment.Month}";

            var invoiceEvent = new InvoiceEvent
            {
                Id           = Guid.NewGuid().ToString(),
                PartitionKey = partitionKey,
                Type         = type,
                Moment       = moment,
                ObjectId     = data.AggregateId
            };

            var client    = _contextEvents.Database.GetCosmosClient();
            var database  = client.GetDatabase("InvoicingEventSourcing");
            var container = database.GetContainer("InvoicingEvents");
            var jObject   = JObject.FromObject(invoiceEvent);

            jObject["Data"] = JObject.FromObject(data);
            await container.CreateItemAsync(jObject);
        }
        async Task Notify(InvoiceEntity invoice, InvoiceEvent invoiceEvent, bool extendedNotification, bool sendMail)
        {
            var dto          = invoice.EntityToDTO();
            var notification = new InvoicePaymentNotificationEventWrapper()
            {
                Data = new InvoicePaymentNotification()
                {
                    Id              = dto.Id,
                    Currency        = dto.Currency,
                    CurrentTime     = dto.CurrentTime,
                    ExceptionStatus = dto.ExceptionStatus,
                    ExpirationTime  = dto.ExpirationTime,
                    InvoiceTime     = dto.InvoiceTime,
                    PosData         = dto.PosData,
                    Price           = dto.Price,
                    Status          = dto.Status,
                    BuyerFields     = invoice.RefundMail == null ? null : new Newtonsoft.Json.Linq.JObject()
                    {
                        new JProperty("buyerEmail", invoice.RefundMail)
                    },
                    PaymentSubtotals = dto.PaymentSubtotals,
                    PaymentTotals    = dto.PaymentTotals,
                    AmountPaid       = dto.AmountPaid,
                    ExchangeRates    = dto.ExchangeRates,
                    OrderId          = dto.OrderId
                },
                Event = new InvoicePaymentNotificationEvent()
                {
                    Code = (int)invoiceEvent.EventCode,
                    Name = invoiceEvent.Name
                },
                ExtendedNotification = extendedNotification,
                NotificationURL      = invoice.NotificationURL?.AbsoluteUri
            };

            // For lightning network payments, paid, confirmed and completed come all at once.
            // So despite the event is "paid" or "confirmed" the Status of the invoice is technically complete
            // This confuse loggers who think their endpoint get duplicated events
            // So here, we just override the status expressed by the notification
            if (invoiceEvent.Name == InvoiceEvent.Confirmed)
            {
                notification.Data.Status = InvoiceState.ToString(InvoiceStatusLegacy.Confirmed);
            }
            if (invoiceEvent.Name == InvoiceEvent.PaidInFull)
            {
                notification.Data.Status = InvoiceState.ToString(InvoiceStatusLegacy.Paid);
            }
            //////////////////

            // We keep backward compatibility with bitpay by passing BTC info to the notification
            // we don't pass other info, as it is a bad idea to use IPN data for logic processing (can be faked)
            var btcCryptoInfo = dto.CryptoInfo.FirstOrDefault(c => c.GetpaymentMethodId() == new PaymentMethodId("BTC", Payments.PaymentTypes.BTCLike) && !string.IsNullOrEmpty(c.Address));

            if (btcCryptoInfo != null)
            {
#pragma warning disable CS0618
                notification.Data.Rate     = dto.Rate;
                notification.Data.Url      = dto.Url;
                notification.Data.BTCDue   = dto.BTCDue;
                notification.Data.BTCPaid  = dto.BTCPaid;
                notification.Data.BTCPrice = dto.BTCPrice;
#pragma warning restore CS0618
            }

            if (sendMail && !String.IsNullOrEmpty(invoice.NotificationEmail))
            {
                var json  = NBitcoin.JsonConverters.Serializer.ToString(notification);
                var store = await _StoreRepository.FindStore(invoice.StoreId);

                var storeName = store.StoreName ?? "BTCPay Server";
                var emailBody = $"Store: {storeName}<br>" +
                                $"Invoice ID: {notification.Data.Id}<br>" +
                                $"Status: {notification.Data.Status}<br>" +
                                $"Amount: {notification.Data.Price} {notification.Data.Currency}<br>" +
                                $"<br><details><summary>Details</summary><pre>{json}</pre></details>";

                (await _EmailSenderFactory.GetEmailSender(invoice.StoreId)).SendEmail(
                    invoice.NotificationEmail,
                    $"{storeName} Invoice Notification - ${invoice.StoreId}",
                    emailBody);
            }

            if (invoice.NotificationURL != null)
            {
                _Queue.Enqueue(invoice.Id, (cancellationToken) => NotifyHttp(new ScheduledJob()
                {
                    TryCount = 0, Notification = notification
                }, cancellationToken));
            }
        }
Exemple #8
0
        void Notify(InvoiceEntity invoice, InvoiceEvent invoiceEvent, bool extendedNotification)
        {
            var dto          = invoice.EntityToDTO(_NetworkProvider);
            var notification = new InvoicePaymentNotificationEventWrapper()
            {
                Data = new InvoicePaymentNotification()
                {
                    Id              = dto.Id,
                    Currency        = dto.Currency,
                    CurrentTime     = dto.CurrentTime,
                    ExceptionStatus = dto.ExceptionStatus,
                    ExpirationTime  = dto.ExpirationTime,
                    InvoiceTime     = dto.InvoiceTime,
                    PosData         = dto.PosData,
                    Price           = dto.Price,
                    Status          = dto.Status,
                    BuyerFields     = invoice.RefundMail == null ? null : new Newtonsoft.Json.Linq.JObject()
                    {
                        new JProperty("buyerEmail", invoice.RefundMail)
                    },
                    PaymentSubtotals = dto.PaymentSubtotals,
                    PaymentTotals    = dto.PaymentTotals,
                    AmountPaid       = dto.AmountPaid,
                    ExchangeRates    = dto.ExchangeRates,
                },
                Event = new InvoicePaymentNotificationEvent()
                {
                    Code = invoiceEvent.EventCode,
                    Name = invoiceEvent.Name
                },
                ExtendedNotification = extendedNotification,
                NotificationURL      = invoice.NotificationURL
            };

            // For lightning network payments, paid, confirmed and completed come all at once.
            // So despite the event is "paid" or "confirmed" the Status of the invoice is technically complete
            // This confuse loggers who think their endpoint get duplicated events
            // So here, we just override the status expressed by the notification
            if (invoiceEvent.Name == InvoiceEvent.Confirmed)
            {
                notification.Data.Status = InvoiceState.ToString(InvoiceStatus.Confirmed);
            }
            if (invoiceEvent.Name == InvoiceEvent.PaidInFull)
            {
                notification.Data.Status = InvoiceState.ToString(InvoiceStatus.Paid);
            }
            //////////////////

            // We keep backward compatibility with bitpay by passing BTC info to the notification
            // we don't pass other info, as it is a bad idea to use IPN data for logic processing (can be faked)
            var btcCryptoInfo = dto.CryptoInfo.FirstOrDefault(c => c.GetpaymentMethodId() == new PaymentMethodId("BTC", Payments.PaymentTypes.BTCLike));

            if (btcCryptoInfo != null)
            {
#pragma warning disable CS0618
                notification.Data.Rate     = dto.Rate;
                notification.Data.Url      = dto.Url;
                notification.Data.BTCDue   = dto.BTCDue;
                notification.Data.BTCPaid  = dto.BTCPaid;
                notification.Data.BTCPrice = dto.BTCPrice;
#pragma warning restore CS0618
            }

            CancellationTokenSource cts = new CancellationTokenSource(10000);

            if (!String.IsNullOrEmpty(invoice.NotificationEmail))
            {
                var emailBody = NBitcoin.JsonConverters.Serializer.ToString(notification);

                _EmailSenderFactory.GetEmailSender(invoice.StoreId).SendEmail(
                    invoice.NotificationEmail,
                    $"BtcPayServer Invoice Notification - ${invoice.StoreId}",
                    emailBody);
            }
            if (string.IsNullOrEmpty(invoice.NotificationURL) || !Uri.IsWellFormedUriString(invoice.NotificationURL, UriKind.Absolute))
            {
                return;
            }
            var invoiceStr = NBitcoin.JsonConverters.Serializer.ToString(new ScheduledJob()
            {
                TryCount = 0, Notification = notification
            });
            if (!string.IsNullOrEmpty(invoice.NotificationURL))
            {
                _JobClient.Schedule(() => NotifyHttp(invoiceStr), TimeSpan.Zero);
            }
        }
Exemple #9
0
 public void On(InvoiceEvent @event)
 {
     State.On(@event);
 }
 public void On(InvoiceEvent @event, ProjectTimeAggregateRootState state)
 {
     RecalculateSummary(@event.UserId, @event.ProjectId, state);
 }
Exemple #11
0
 public void Append(InvoiceEvent @event)
 {
     Db.InsertWithIdentity(@event);
 }