internal async Task <InvoiceEntity> CreateInvoiceCoreRaw(CreateInvoiceRequest invoice, StoreData store, string serverUrl, List <string> additionalTags = null, CancellationToken cancellationToken = default) { var storeBlob = store.GetStoreBlob(); var entity = _InvoiceRepository.CreateNewInvoice(); entity.ExpirationTime = entity.InvoiceTime + (invoice.Checkout.Expiration ?? storeBlob.InvoiceExpiration); entity.MonitoringExpiration = entity.ExpirationTime + (invoice.Checkout.Monitoring ?? storeBlob.MonitoringExpiration); if (invoice.Metadata != null) { entity.Metadata = InvoiceMetadata.FromJObject(invoice.Metadata); } invoice.Checkout ??= new CreateInvoiceRequest.CheckoutOptions(); entity.Currency = invoice.Currency; entity.Price = invoice.Amount; entity.SpeedPolicy = invoice.Checkout.SpeedPolicy ?? store.SpeedPolicy; entity.DefaultLanguage = invoice.Checkout.DefaultLanguage; IPaymentFilter excludeFilter = null; if (invoice.Checkout.PaymentMethods != null) { var supportedTransactionCurrencies = invoice.Checkout.PaymentMethods .Select(c => PaymentMethodId.TryParse(c, out var p) ? p : null) .ToHashSet(); excludeFilter = PaymentFilter.Where(p => !supportedTransactionCurrencies.Contains(p)); } entity.PaymentTolerance = invoice.Checkout.PaymentTolerance ?? storeBlob.PaymentTolerance; entity.RedirectURLTemplate = invoice.Checkout.RedirectURL?.Trim(); if (additionalTags != null) { entity.InternalTags.AddRange(additionalTags); } return(await CreateInvoiceCoreRaw(entity, store, excludeFilter, cancellationToken)); }
internal async Task <DataWrapper <InvoiceResponse> > CreateInvoiceCore(CreateInvoiceRequest invoice, StoreData store, string serverUrl, List <string> additionalTags = null, CancellationToken cancellationToken = default) { if (!store.HasClaim(Policies.CanCreateInvoice.Key)) { throw new UnauthorizedAccessException(); } InvoiceLogs logs = new InvoiceLogs(); logs.Write("Creation of invoice starting"); var entity = new InvoiceEntity { Version = InvoiceEntity.Lastest_Version, InvoiceTime = DateTimeOffset.UtcNow, Networks = _NetworkProvider }; var getAppsTaggingStore = _InvoiceRepository.GetAppsTaggingStore(store.Id); var storeBlob = store.GetStoreBlob(); EmailAddressAttribute emailValidator = new EmailAddressAttribute(); entity.ExpirationTime = entity.InvoiceTime.AddMinutes(storeBlob.InvoiceExpiration); entity.MonitoringExpiration = entity.ExpirationTime + TimeSpan.FromMinutes(storeBlob.MonitoringExpiration); entity.OrderId = invoice.OrderId; entity.ServerUrl = serverUrl; entity.FullNotifications = invoice.FullNotifications || invoice.ExtendedNotifications; entity.ExtendedNotifications = invoice.ExtendedNotifications; if (invoice.NotificationURL != null && Uri.TryCreate(invoice.NotificationURL, UriKind.Absolute, out var notificationUri) && (notificationUri.Scheme == "http" || notificationUri.Scheme == "https")) { entity.NotificationURL = notificationUri.AbsoluteUri; } entity.NotificationEmail = invoice.NotificationEmail; entity.BuyerInformation = Map <CreateInvoiceRequest, BuyerInformation>(invoice); entity.PaymentTolerance = storeBlob.PaymentTolerance; if (additionalTags != null) { entity.InternalTags.AddRange(additionalTags); } //Another way of passing buyer info to support FillBuyerInfo(invoice.Buyer, entity.BuyerInformation); if (entity?.BuyerInformation?.BuyerEmail != null) { if (!EmailValidator.IsEmail(entity.BuyerInformation.BuyerEmail)) { throw new BitpayHttpException(400, "Invalid email"); } entity.RefundMail = entity.BuyerInformation.BuyerEmail; } var taxIncluded = invoice.TaxIncluded.HasValue ? invoice.TaxIncluded.Value : 0m; var currencyInfo = _CurrencyNameTable.GetNumberFormatInfo(invoice.Currency, false); if (currencyInfo != null) { int divisibility = currencyInfo.CurrencyDecimalDigits; invoice.Price = invoice.Price.RoundToSignificant(ref divisibility); divisibility = currencyInfo.CurrencyDecimalDigits; invoice.TaxIncluded = taxIncluded.RoundToSignificant(ref divisibility); } invoice.Price = Math.Max(0.0m, invoice.Price); invoice.TaxIncluded = Math.Max(0.0m, taxIncluded); invoice.TaxIncluded = Math.Min(taxIncluded, invoice.Price); entity.ProductInformation = Map <CreateInvoiceRequest, ProductInformation>(invoice); entity.RedirectURL = invoice.RedirectURL ?? store.StoreWebsite; if (!Uri.IsWellFormedUriString(entity.RedirectURL, UriKind.Absolute)) { entity.RedirectURL = null; } entity.RedirectAutomatically = invoice.RedirectAutomatically.GetValueOrDefault(storeBlob.RedirectAutomatically); entity.Status = InvoiceStatus.New; entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy); HashSet <CurrencyPair> currencyPairsToFetch = new HashSet <CurrencyPair>(); var rules = storeBlob.GetRateRules(_NetworkProvider); var excludeFilter = storeBlob.GetExcludedPaymentMethods(); // Here we can compose filters from other origin with PaymentFilter.Any() if (invoice.SupportedTransactionCurrencies != null && invoice.SupportedTransactionCurrencies.Count != 0) { var supportedTransactionCurrencies = invoice.SupportedTransactionCurrencies .Where(c => c.Value.Enabled) .Select(c => PaymentMethodId.TryParse(c.Key, out var p) ? p : null) .ToHashSet(); excludeFilter = PaymentFilter.Or(excludeFilter, PaymentFilter.Where(p => !supportedTransactionCurrencies.Contains(p))); } foreach (var network in store.GetSupportedPaymentMethods(_NetworkProvider) .Where(s => !excludeFilter.Match(s.PaymentId)) .Select(c => _NetworkProvider.GetNetwork(c.PaymentId.CryptoCode)) .Where(c => c != null)) { currencyPairsToFetch.Add(new CurrencyPair(network.CryptoCode, invoice.Currency)); if (storeBlob.LightningMaxValue != null) { currencyPairsToFetch.Add(new CurrencyPair(network.CryptoCode, storeBlob.LightningMaxValue.Currency)); } if (storeBlob.OnChainMinValue != null) { currencyPairsToFetch.Add(new CurrencyPair(network.CryptoCode, storeBlob.OnChainMinValue.Currency)); } } var rateRules = storeBlob.GetRateRules(_NetworkProvider); var fetchingByCurrencyPair = _RateProvider.FetchRates(currencyPairsToFetch, rateRules, cancellationToken); var fetchingAll = WhenAllFetched(logs, fetchingByCurrencyPair); var supportedPaymentMethods = store.GetSupportedPaymentMethods(_NetworkProvider) .Where(s => !excludeFilter.Match(s.PaymentId)) .Select(c => (Handler: (IPaymentMethodHandler)_ServiceProvider.GetService(typeof(IPaymentMethodHandler <>).MakeGenericType(c.GetType())), SupportedPaymentMethod: c, Network: _NetworkProvider.GetNetwork(c.PaymentId.CryptoCode))) .Where(c => c.Network != null) .Select(o => (SupportedPaymentMethod: o.SupportedPaymentMethod, PaymentMethod: CreatePaymentMethodAsync(fetchingByCurrencyPair, o.Handler, o.SupportedPaymentMethod, o.Network, entity, store, logs))) .ToList(); List <ISupportedPaymentMethod> supported = new List <ISupportedPaymentMethod>(); var paymentMethods = new PaymentMethodDictionary(); foreach (var o in supportedPaymentMethods) { var paymentMethod = await o.PaymentMethod; if (paymentMethod == null) { continue; } supported.Add(o.SupportedPaymentMethod); paymentMethods.Add(paymentMethod); } if (supported.Count == 0) { StringBuilder errors = new StringBuilder(); errors.AppendLine("Warning: No wallet has been linked to your BTCPay Store. See the following link for more information on how to connect your store and wallet. (https://docs.btcpayserver.org/btcpay-basics/gettingstarted#connecting-btcpay-store-to-your-wallet)"); foreach (var error in logs.ToList()) { errors.AppendLine(error.ToString()); } throw new BitpayHttpException(400, errors.ToString()); } entity.SetSupportedPaymentMethods(supported); entity.SetPaymentMethods(paymentMethods); entity.PosData = invoice.PosData; foreach (var app in await getAppsTaggingStore) { entity.InternalTags.Add(AppService.GetAppInternalTag(app.Id)); } using (logs.Measure("Saving invoice")) { entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity); } _ = Task.Run(async() => { try { await fetchingAll; } catch (AggregateException ex) { ex.Handle(e => { logs.Write($"Error while fetching rates {ex}"); return(true); }); } await _InvoiceRepository.AddInvoiceLogs(entity.Id, logs); }); _EventAggregator.Publish(new Events.InvoiceEvent(entity, 1001, InvoiceEvent.Created)); var resp = entity.EntityToDTO(); return(new DataWrapper <InvoiceResponse>(resp) { Facade = "pos/invoice" }); }
internal async Task <InvoiceEntity> CreateInvoiceCoreRaw(BitpayCreateInvoiceRequest invoice, StoreData store, string serverUrl, List <string>?additionalTags = null, CancellationToken cancellationToken = default) { var storeBlob = store.GetStoreBlob(); var entity = _InvoiceRepository.CreateNewInvoice(); entity.ExpirationTime = invoice.ExpirationTime is DateTimeOffset v ? v : entity.InvoiceTime + storeBlob.InvoiceExpiration; entity.MonitoringExpiration = entity.ExpirationTime + storeBlob.MonitoringExpiration; if (entity.ExpirationTime - TimeSpan.FromSeconds(30.0) < entity.InvoiceTime) { throw new BitpayHttpException(400, "The expirationTime is set too soon"); } entity.Metadata.OrderId = invoice.OrderId; entity.Metadata.PosData = invoice.PosData; entity.ServerUrl = serverUrl; entity.FullNotifications = invoice.FullNotifications || invoice.ExtendedNotifications; entity.ExtendedNotifications = invoice.ExtendedNotifications; entity.NotificationURLTemplate = invoice.NotificationURL; entity.NotificationEmail = invoice.NotificationEmail; if (additionalTags != null) { entity.InternalTags.AddRange(additionalTags); } FillBuyerInfo(invoice, entity); var taxIncluded = invoice.TaxIncluded.HasValue ? invoice.TaxIncluded.Value : 0m; var price = invoice.Price; entity.Metadata.ItemCode = invoice.ItemCode; entity.Metadata.ItemDesc = invoice.ItemDesc; entity.Metadata.Physical = invoice.Physical; entity.Metadata.TaxIncluded = invoice.TaxIncluded; entity.Currency = invoice.Currency; if (price is decimal vv) { entity.Price = vv; entity.Type = InvoiceType.Standard; } else { entity.Price = 0m; entity.Type = InvoiceType.TopUp; } entity.RedirectURLTemplate = invoice.RedirectURL ?? store.StoreWebsite; entity.RedirectAutomatically = invoice.RedirectAutomatically.GetValueOrDefault(storeBlob.RedirectAutomatically); entity.RequiresRefundEmail = invoice.RequiresRefundEmail; entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy); IPaymentFilter?excludeFilter = null; if (invoice.PaymentCurrencies?.Any() is true) { invoice.SupportedTransactionCurrencies ??= new Dictionary <string, InvoiceSupportedTransactionCurrency>(); foreach (string paymentCurrency in invoice.PaymentCurrencies) { invoice.SupportedTransactionCurrencies.TryAdd(paymentCurrency, new InvoiceSupportedTransactionCurrency() { Enabled = true }); } } if (invoice.SupportedTransactionCurrencies != null && invoice.SupportedTransactionCurrencies.Count != 0) { var supportedTransactionCurrencies = invoice.SupportedTransactionCurrencies .Where(c => c.Value.Enabled) .Select(c => PaymentMethodId.TryParse(c.Key, out var p) ? p : null) .Where(c => c != null) .ToHashSet(); excludeFilter = PaymentFilter.Where(p => !supportedTransactionCurrencies.Contains(p)); } entity.PaymentTolerance = storeBlob.PaymentTolerance; entity.DefaultPaymentMethod = invoice.DefaultPaymentMethod; entity.RequiresRefundEmail = invoice.RequiresRefundEmail; return(await CreateInvoiceCoreRaw(entity, store, excludeFilter, null, cancellationToken)); }
internal async Task <InvoiceEntity> CreateInvoiceCoreRaw(BitpayCreateInvoiceRequest invoice, StoreData store, string serverUrl, List <string> additionalTags = null, CancellationToken cancellationToken = default) { var storeBlob = store.GetStoreBlob(); var entity = _InvoiceRepository.CreateNewInvoice(); entity.ExpirationTime = invoice.ExpirationTime is DateTimeOffset v ? v : entity.InvoiceTime + storeBlob.InvoiceExpiration; entity.MonitoringExpiration = entity.ExpirationTime + storeBlob.MonitoringExpiration; if (entity.ExpirationTime - TimeSpan.FromSeconds(30.0) < entity.InvoiceTime) { throw new BitpayHttpException(400, "The expirationTime is set too soon"); } invoice.Currency = invoice.Currency?.Trim().ToUpperInvariant() ?? "USD"; entity.Metadata.OrderId = invoice.OrderId; entity.Metadata.PosData = invoice.PosData; entity.ServerUrl = serverUrl; entity.FullNotifications = invoice.FullNotifications || invoice.ExtendedNotifications; entity.ExtendedNotifications = invoice.ExtendedNotifications; entity.NotificationURLTemplate = invoice.NotificationURL; entity.NotificationEmail = invoice.NotificationEmail; if (additionalTags != null) { entity.InternalTags.AddRange(additionalTags); } FillBuyerInfo(invoice, entity); var taxIncluded = invoice.TaxIncluded.HasValue ? invoice.TaxIncluded.Value : 0m; var currencyInfo = _CurrencyNameTable.GetNumberFormatInfo(invoice.Currency, false); if (currencyInfo != null) { int divisibility = currencyInfo.CurrencyDecimalDigits; invoice.Price = invoice.Price.RoundToSignificant(ref divisibility); divisibility = currencyInfo.CurrencyDecimalDigits; invoice.TaxIncluded = taxIncluded.RoundToSignificant(ref divisibility); } invoice.Price = Math.Max(0.0m, invoice.Price); invoice.TaxIncluded = Math.Max(0.0m, taxIncluded); invoice.TaxIncluded = Math.Min(taxIncluded, invoice.Price); entity.Metadata.ItemCode = invoice.ItemCode; entity.Metadata.ItemDesc = invoice.ItemDesc; entity.Metadata.Physical = invoice.Physical; entity.Metadata.TaxIncluded = invoice.TaxIncluded; entity.Currency = invoice.Currency; entity.Price = invoice.Price; entity.RedirectURLTemplate = invoice.RedirectURL ?? store.StoreWebsite; entity.RedirectAutomatically = invoice.RedirectAutomatically.GetValueOrDefault(storeBlob.RedirectAutomatically); entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy); IPaymentFilter excludeFilter = null; if (invoice.PaymentCurrencies?.Any() is true) { invoice.SupportedTransactionCurrencies ??= new Dictionary <string, InvoiceSupportedTransactionCurrency>(); foreach (string paymentCurrency in invoice.PaymentCurrencies) { invoice.SupportedTransactionCurrencies.TryAdd(paymentCurrency, new InvoiceSupportedTransactionCurrency() { Enabled = true }); } } if (invoice.SupportedTransactionCurrencies != null && invoice.SupportedTransactionCurrencies.Count != 0) { var supportedTransactionCurrencies = invoice.SupportedTransactionCurrencies .Where(c => c.Value.Enabled) .Select(c => PaymentMethodId.TryParse(c.Key, out var p) ? p : null) .Where(c => c != null) .ToHashSet(); excludeFilter = PaymentFilter.Where(p => !supportedTransactionCurrencies.Contains(p)); } entity.PaymentTolerance = storeBlob.PaymentTolerance; return(await CreateInvoiceCoreRaw(entity, store, excludeFilter, cancellationToken)); }