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); }
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); } }
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); } }
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)); } }
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); } }
public void On(InvoiceEvent @event) { State.On(@event); }
public void On(InvoiceEvent @event, ProjectTimeAggregateRootState state) { RecalculateSummary(@event.UserId, @event.ProjectId, state); }
public void Append(InvoiceEvent @event) { Db.InsertWithIdentity(@event); }