public async Task <IActionResult> UpdateStoreEmailSettings(string storeId, EmailSettings request) { var store = HttpContext.GetStoreData(); if (store == null) { return(StoreNotFound()); } if (!string.IsNullOrEmpty(request.From) && !EmailValidator.IsEmail(request.From)) { request.AddModelError(e => e.From, "Invalid email address", this); return(this.CreateValidationError(ModelState)); } var blob = store.GetStoreBlob(); blob.EmailSettings = request; if (store.SetStoreBlob(blob)) { await _storeRepository.UpdateStore(store); } return(Ok(FromModel(store))); }
internal async Task <DataWrapper <InvoiceResponse> > CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl, double expiryMinutes = 15) { //TODO: expiryMinutes (time before a new invoice can become paid) and monitoringMinutes (time before a paid invoice becomes invalid) should be configurable at store level var derivationStrategy = store.DerivationStrategy; var entity = new InvoiceEntity { InvoiceTime = DateTimeOffset.UtcNow, DerivationStrategy = derivationStrategy ?? throw new BitpayHttpException(400, "This store has not configured the derivation strategy") }; var storeBlob = store.GetStoreBlob(_Network); Uri notificationUri = Uri.IsWellFormedUriString(invoice.NotificationURL, UriKind.Absolute) ? new Uri(invoice.NotificationURL, UriKind.Absolute) : null; if (notificationUri == null || (notificationUri.Scheme != "http" && notificationUri.Scheme != "https")) //TODO: Filer non routable addresses ? { notificationUri = null; } EmailAddressAttribute emailValidator = new EmailAddressAttribute(); entity.ExpirationTime = entity.InvoiceTime.AddMinutes(expiryMinutes); entity.MonitoringExpiration = entity.ExpirationTime + TimeSpan.FromMinutes(storeBlob.MonitoringExpiration); entity.OrderId = invoice.OrderId; entity.ServerUrl = serverUrl; entity.FullNotifications = invoice.FullNotifications; entity.NotificationURL = notificationUri?.AbsoluteUri; entity.BuyerInformation = Map <Invoice, BuyerInformation>(invoice); //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; } entity.ProductInformation = Map <Invoice, ProductInformation>(invoice); entity.RedirectURL = invoice.RedirectURL ?? store.StoreWebsite; entity.Status = "new"; entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy); var getFeeRate = _FeeProvider.GetFeeRateAsync(); var getRate = _RateProvider.GetRateAsync(invoice.Currency); var getAddress = _Wallet.ReserveAddressAsync(ParseDerivationStrategy(derivationStrategy)); entity.TxFee = storeBlob.NetworkFeeDisabled ? Money.Zero : (await getFeeRate).GetFee(100); // assume price for 100 bytes entity.Rate = (double)await getRate; entity.PosData = invoice.PosData; entity.DepositAddress = await getAddress; entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity); _Watcher.Watch(entity.Id); var resp = entity.EntityToDTO(); return(new DataWrapper <InvoiceResponse>(resp) { Facade = "pos/invoice" }); }
internal async Task <DataWrapper <InvoiceResponse> > CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl) { var derivationStrategy = store.DerivationStrategy; var entity = new InvoiceEntity { InvoiceTime = DateTimeOffset.UtcNow, DerivationStrategy = derivationStrategy ?? throw new BitpayHttpException(400, "This store has not configured the derivation strategy") }; Uri notificationUri = Uri.IsWellFormedUriString(invoice.NotificationURL, UriKind.Absolute) ? new Uri(invoice.NotificationURL, UriKind.Absolute) : null; if (notificationUri == null || (notificationUri.Scheme != "http" && notificationUri.Scheme != "https")) //TODO: Filer non routable addresses ? { notificationUri = null; } EmailAddressAttribute emailValidator = new EmailAddressAttribute(); entity.ExpirationTime = entity.InvoiceTime + TimeSpan.FromMinutes(15.0); entity.ServerUrl = serverUrl; entity.FullNotifications = invoice.FullNotifications; entity.NotificationURL = notificationUri?.AbsoluteUri; entity.BuyerInformation = Map <Invoice, BuyerInformation>(invoice); //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; } entity.ProductInformation = Map <Invoice, ProductInformation>(invoice); entity.RedirectURL = invoice.RedirectURL ?? store.StoreWebsite; entity.Status = "new"; entity.SpeedPolicy = store.SpeedPolicy; entity.TxFee = (await _FeeProvider.GetFeeRateAsync()).GetFee(100); // assume price for 100 bytes entity.Rate = (double)await _RateProvider.GetRateAsync(invoice.Currency); entity.PosData = invoice.PosData; entity.DepositAddress = await _Wallet.ReserveAddressAsync(ParseDerivationStrategy(derivationStrategy)); entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity); await _Wallet.MapAsync(entity.DepositAddress.ScriptPubKey, entity.Id); await _Watcher.WatchAsync(entity.Id); var resp = entity.EntityToDTO(); return(new DataWrapper <InvoiceResponse>(resp) { Facade = "pos/invoice" }); }
public async Task <(IClaimDestination destination, string error)> ParseClaimDestination(PaymentMethodId paymentMethodId, string destination, bool validate) { destination = destination.Trim(); var network = _btcPayNetworkProvider.GetNetwork <BTCPayNetwork>(paymentMethodId.CryptoCode); try { string lnurlTag = null; var lnurl = EmailValidator.IsEmail(destination) ? LNURL.LNURL.ExtractUriFromInternetIdentifier(destination) : LNURL.LNURL.Parse(destination, out lnurlTag); if (lnurlTag is null) { var info = (LNURLPayRequest)(await LNURL.LNURL.FetchInformation(lnurl, CreateClient(lnurl))); lnurlTag = info.Tag; } if (lnurlTag.Equals("payRequest", StringComparison.InvariantCultureIgnoreCase)) { return(new LNURLPayClaimDestinaton(destination), null); } } catch (FormatException) { } catch { return(null, "The LNURL / Lightning Address provided was not online."); } var result = BOLT11PaymentRequest.TryParse(destination, out var invoice, network.NBitcoinNetwork) ? new BoltInvoiceClaimDestination(destination, invoice) : null; if (result == null) { return(null, "A valid BOLT11 invoice (with 30+ day expiry) or LNURL Pay or Lightning address was not provided."); } if (validate && (invoice.ExpiryDate.UtcDateTime - DateTime.UtcNow).Days < 30) { return(null, $"The BOLT11 invoice must have an expiry date of at least 30 days from submission (Provided was only {(invoice.ExpiryDate.UtcDateTime - DateTime.UtcNow).Days})."); } if (invoice.ExpiryDate.UtcDateTime < DateTime.UtcNow) { return(null, "The BOLT11 invoice submitted has expired."); } return(result, null); }
public virtual JsonResult Submit(SendMessageModel model) { var response = new ActionResponse <bool>(); var validMessageType = new[] { MessagingType.Email, MessagingType.Sms }; if (!ModelState.IsValid) { response.Message = LocalMessage.InvalidFormData; } else if (!validMessageType.Contains(model.MessagingType)) { response.Message = LocalMessage.InvalidEmailAddress; return(Json(response, JsonRequestBehavior.AllowGet)); } else if ((model.MessagingType == MessagingType.Sms).And(!CellPhoneNumberValidator.IsCellPhoneNumber(model.Reciver))) { response.Message = LocalMessage.InvalidMobileNumber; return(Json(response, JsonRequestBehavior.AllowGet)); } else if ((model.MessagingType == MessagingType.Email).And(!EmailValidator.IsEmail(model.Reciver))) { response.Message = LocalMessage.InvalidEmailAddress; return(Json(response, JsonRequestBehavior.AllowGet)); } else { var result = _messageBusiness.Insert(new Message { Content = model.Message, Receiver = model.Reciver, Type = model.MessagingType }); response.IsSuccessful = response.Result = result.IsSuccessful; response.Message = result.Message; } return(Json(response, JsonRequestBehavior.AllowGet)); }
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(InvoiceEntity entity, StoreData store, IPaymentFilter?invoicePaymentMethodFilter, string[]?additionalSearchTerms = null, CancellationToken cancellationToken = default) { InvoiceLogs logs = new InvoiceLogs(); logs.Write("Creation of invoice starting", InvoiceEventData.EventSeverity.Info); var storeBlob = store.GetStoreBlob(); if (string.IsNullOrEmpty(entity.Currency)) { entity.Currency = storeBlob.DefaultCurrency; } entity.Currency = entity.Currency.Trim().ToUpperInvariant(); entity.Price = Math.Max(0.0m, entity.Price); var currencyInfo = _CurrencyNameTable.GetNumberFormatInfo(entity.Currency, false); if (currencyInfo != null) { entity.Price = entity.Price.RoundToSignificant(currencyInfo.CurrencyDecimalDigits); } if (entity.Metadata.TaxIncluded is decimal taxIncluded) { if (currencyInfo != null) { taxIncluded = taxIncluded.RoundToSignificant(currencyInfo.CurrencyDecimalDigits); } taxIncluded = Math.Max(0.0m, taxIncluded); taxIncluded = Math.Min(taxIncluded, entity.Price); entity.Metadata.TaxIncluded = taxIncluded; } var getAppsTaggingStore = _InvoiceRepository.GetAppsTaggingStore(store.Id); if (entity.Metadata.BuyerEmail != null) { if (!EmailValidator.IsEmail(entity.Metadata.BuyerEmail)) { throw new BitpayHttpException(400, "Invalid email"); } entity.RefundMail = entity.Metadata.BuyerEmail; } entity.Status = InvoiceStatusLegacy.New; 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 (invoicePaymentMethodFilter != null) { excludeFilter = PaymentFilter.Or(excludeFilter, invoicePaymentMethodFilter); } foreach (var network in store.GetSupportedPaymentMethods(_NetworkProvider) .Where(s => !excludeFilter.Match(s.PaymentId)) .Select(c => _NetworkProvider.GetNetwork <BTCPayNetworkBase>(c.PaymentId.CryptoCode)) .Where(c => c != null)) { currencyPairsToFetch.Add(new CurrencyPair(network.CryptoCode, entity.Currency)); foreach (var paymentMethodCriteria in storeBlob.PaymentMethodCriteria) { if (paymentMethodCriteria.Value != null) { currencyPairsToFetch.Add(new CurrencyPair(network.CryptoCode, paymentMethodCriteria.Value.Currency)); } } } var rateRules = storeBlob.GetRateRules(_NetworkProvider); var fetchingByCurrencyPair = _RateProvider.FetchRates(currencyPairsToFetch, rateRules, cancellationToken); var fetchingAll = WhenAllFetched(logs, fetchingByCurrencyPair); List <ISupportedPaymentMethod> supported = new List <ISupportedPaymentMethod>(); var paymentMethods = new PaymentMethodDictionary(); bool noNeedForMethods = entity.Type != InvoiceType.TopUp && entity.Price == 0m; if (!noNeedForMethods) { // This loop ends with .ToList so we are querying all payment methods at once // instead of sequentially to improve response time foreach (var o in store.GetSupportedPaymentMethods(_NetworkProvider) .Where(s => !excludeFilter.Match(s.PaymentId) && _paymentMethodHandlerDictionary.Support(s.PaymentId)) .Select(c => (Handler: _paymentMethodHandlerDictionary[c.PaymentId], SupportedPaymentMethod: c, Network: _NetworkProvider.GetNetwork <BTCPayNetworkBase>(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()) { var paymentMethod = await o.PaymentMethod; if (paymentMethod == null) { continue; } supported.Add(o.SupportedPaymentMethod); paymentMethods.Add(paymentMethod); } if (supported.Count == 0) { StringBuilder errors = new StringBuilder(); if (!store.GetSupportedPaymentMethods(_NetworkProvider).Any()) { 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/WalletSetup/)"); } foreach (var error in logs.ToList()) { errors.AppendLine(error.ToString()); } throw new BitpayHttpException(400, errors.ToString()); } } entity.SetSupportedPaymentMethods(supported); entity.SetPaymentMethods(paymentMethods); 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, additionalSearchTerms); } _ = Task.Run(async() => { try { await fetchingAll; } catch (AggregateException ex) { ex.Handle(e => { logs.Write($"Error while fetching rates {ex}", InvoiceEventData.EventSeverity.Error); return(true); }); } await _InvoiceRepository.AddInvoiceLogs(entity.Id, logs); }); _EventAggregator.Publish(new Events.InvoiceEvent(entity, InvoiceEvent.Created)); return(entity); }
internal async Task <DataWrapper <InvoiceResponse> > CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl) { if (!store.HasClaim(Policies.CanCreateInvoice.Key)) { throw new UnauthorizedAccessException(); } InvoiceLogs logs = new InvoiceLogs(); logs.Write("Creation of invoice starting"); var entity = new InvoiceEntity { InvoiceTime = DateTimeOffset.UtcNow }; var storeBlob = store.GetStoreBlob(); Uri notificationUri = Uri.IsWellFormedUriString(invoice.NotificationURL, UriKind.Absolute) ? new Uri(invoice.NotificationURL, UriKind.Absolute) : null; if (notificationUri == null || (notificationUri.Scheme != "http" && notificationUri.Scheme != "https")) //TODO: Filer non routable addresses ? { notificationUri = null; } 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; entity.NotificationURL = notificationUri?.AbsoluteUri; entity.NotificationEmail = invoice.NotificationEmail; entity.BuyerInformation = Map <Invoice, BuyerInformation>(invoice); entity.PaymentTolerance = storeBlob.PaymentTolerance; //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; } entity.ProductInformation = Map <Invoice, ProductInformation>(invoice); entity.RedirectURL = invoice.RedirectURL ?? store.StoreWebsite; if (!Uri.IsWellFormedUriString(entity.RedirectURL, UriKind.Absolute)) { entity.RedirectURL = null; } entity.Status = "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() 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); 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("No payment method available for this store"); 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; entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, logs, _NetworkProvider); await fetchingAll; _EventAggregator.Publish(new Events.InvoiceEvent(entity.EntityToDTO(_NetworkProvider), 1001, "invoice_created")); var resp = entity.EntityToDTO(_NetworkProvider); return(new DataWrapper <InvoiceResponse>(resp) { Facade = "pos/invoice" }); }
internal async Task <DataWrapper <InvoiceResponse> > CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl) { var entity = new InvoiceEntity { InvoiceTime = DateTimeOffset.UtcNow }; var storeBlob = store.GetStoreBlob(); Uri notificationUri = Uri.IsWellFormedUriString(invoice.NotificationURL, UriKind.Absolute) ? new Uri(invoice.NotificationURL, UriKind.Absolute) : null; if (notificationUri == null || (notificationUri.Scheme != "http" && notificationUri.Scheme != "https")) //TODO: Filer non routable addresses ? { notificationUri = null; } 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; entity.NotificationURL = notificationUri?.AbsoluteUri; entity.BuyerInformation = Map <Invoice, BuyerInformation>(invoice); entity.PaymentTolerance = storeBlob.PaymentTolerance; //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; } entity.ProductInformation = Map <Invoice, ProductInformation>(invoice); entity.RedirectURL = invoice.RedirectURL ?? store.StoreWebsite; if (!Uri.IsWellFormedUriString(entity.RedirectURL, UriKind.Absolute)) { entity.RedirectURL = null; } entity.Status = "new"; entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy); HashSet <CurrencyPair> currencyPairsToFetch = new HashSet <CurrencyPair>(); var rules = storeBlob.GetRateRules(_NetworkProvider); await UpdateCLightningConnectionStringIfNeeded(store); foreach (var network in store.GetSupportedPaymentMethods(_NetworkProvider) .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); var supportedPaymentMethods = store.GetSupportedPaymentMethods(_NetworkProvider) .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))) .ToList(); List <string> invoiceLogs = new List <string>(); List <ISupportedPaymentMethod> supported = new List <ISupportedPaymentMethod>(); var paymentMethods = new PaymentMethodDictionary(); foreach (var pair in fetchingByCurrencyPair) { var rateResult = await pair.Value; invoiceLogs.Add($"{pair.Key}: The rating rule is {rateResult.Rule}"); invoiceLogs.Add($"{pair.Key}: The evaluated rating rule is {rateResult.EvaluatedRule}"); if (rateResult.Errors.Count != 0) { var allRateRuleErrors = string.Join(", ", rateResult.Errors.ToArray()); invoiceLogs.Add($"{pair.Key}: Rate rule error ({allRateRuleErrors})"); } if (rateResult.ExchangeExceptions.Count != 0) { foreach (var ex in rateResult.ExchangeExceptions) { invoiceLogs.Add($"{pair.Key}: Exception reaching exchange {ex.ExchangeName} ({ex.Exception.Message})"); } } } foreach (var o in supportedPaymentMethods) { try { var paymentMethod = await o.PaymentMethod; if (paymentMethod == null) { throw new PaymentMethodUnavailableException("Payment method unavailable"); } supported.Add(o.SupportedPaymentMethod); paymentMethods.Add(paymentMethod); } catch (PaymentMethodUnavailableException ex) { invoiceLogs.Add($"{o.SupportedPaymentMethod.PaymentId.CryptoCode} ({o.SupportedPaymentMethod.PaymentId.PaymentType}): Payment method unavailable ({ex.Message})"); } catch (Exception ex) { invoiceLogs.Add($"{o.SupportedPaymentMethod.PaymentId.CryptoCode} ({o.SupportedPaymentMethod.PaymentId.PaymentType}): Unexpected exception ({ex.ToString()})"); } } if (supported.Count == 0) { StringBuilder errors = new StringBuilder(); errors.AppendLine("No payment method available for this store"); foreach (var error in invoiceLogs) { errors.AppendLine(error); } throw new BitpayHttpException(400, errors.ToString()); } entity.SetSupportedPaymentMethods(supported); entity.SetPaymentMethods(paymentMethods); entity.PosData = invoice.PosData; entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, invoiceLogs, _NetworkProvider); _EventAggregator.Publish(new Events.InvoiceEvent(entity.EntityToDTO(_NetworkProvider), 1001, "invoice_created")); var resp = entity.EntityToDTO(_NetworkProvider); return(new DataWrapper <InvoiceResponse>(resp) { Facade = "pos/invoice" }); }
internal async Task <DataWrapper <InvoiceResponse> > CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl) { var entity = new InvoiceEntity { InvoiceTime = DateTimeOffset.UtcNow }; var storeBlob = store.GetStoreBlob(); Uri notificationUri = Uri.IsWellFormedUriString(invoice.NotificationURL, UriKind.Absolute) ? new Uri(invoice.NotificationURL, UriKind.Absolute) : null; if (notificationUri == null || (notificationUri.Scheme != "http" && notificationUri.Scheme != "https")) //TODO: Filer non routable addresses ? { notificationUri = null; } 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; entity.NotificationURL = notificationUri?.AbsoluteUri; entity.BuyerInformation = Map <Invoice, BuyerInformation>(invoice); //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; } entity.ProductInformation = Map <Invoice, ProductInformation>(invoice); entity.RedirectURL = invoice.RedirectURL ?? store.StoreWebsite; entity.Status = "new"; entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy); var supportedPaymentMethods = store.GetSupportedPaymentMethods(_NetworkProvider) .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(o.Handler, o.SupportedPaymentMethod, o.Network, entity, store))) .ToList(); List <string> paymentMethodErrors = new List <string>(); List <ISupportedPaymentMethod> supported = new List <ISupportedPaymentMethod>(); var paymentMethods = new PaymentMethodDictionary(); foreach (var o in supportedPaymentMethods) { try { var paymentMethod = await o.PaymentMethod; if (paymentMethod == null) { throw new PaymentMethodUnavailableException("Payment method unavailable (The handler returned null)"); } supported.Add(o.SupportedPaymentMethod); paymentMethods.Add(paymentMethod); } catch (PaymentMethodUnavailableException ex) { paymentMethodErrors.Add($"{o.SupportedPaymentMethod.PaymentId.CryptoCode} ({o.SupportedPaymentMethod.PaymentId.PaymentType}): Payment method unavailable ({ex.Message})"); } catch (Exception ex) { paymentMethodErrors.Add($"{o.SupportedPaymentMethod.PaymentId.CryptoCode} ({o.SupportedPaymentMethod.PaymentId.PaymentType}): Unexpected exception ({ex.ToString()})"); } } if (supported.Count == 0) { StringBuilder errors = new StringBuilder(); errors.AppendLine("No payment method available for this store"); foreach (var error in paymentMethodErrors) { errors.AppendLine(error); } throw new BitpayHttpException(400, errors.ToString()); } entity.SetSupportedPaymentMethods(supported); entity.SetPaymentMethods(paymentMethods); #pragma warning disable CS0618 // Legacy Bitpay clients expect information for BTC information, even if the store do not support it var legacyBTCisSet = paymentMethods.Any(p => p.GetId().IsBTCOnChain); if (!legacyBTCisSet && _NetworkProvider.BTC != null) { var btc = _NetworkProvider.BTC; var feeProvider = ((IFeeProviderFactory)_ServiceProvider.GetService(typeof(IFeeProviderFactory))).CreateFeeProvider(btc); var rateProvider = _RateProviders.GetRateProvider(btc, storeBlob.GetRateRules()); if (feeProvider != null && rateProvider != null) { var gettingFee = feeProvider.GetFeeRateAsync(); var gettingRate = rateProvider.GetRateAsync(invoice.Currency); entity.TxFee = GetTxFee(storeBlob, await gettingFee); entity.Rate = await gettingRate; } #pragma warning restore CS0618 } entity.PosData = invoice.PosData; entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, paymentMethodErrors, _NetworkProvider); _EventAggregator.Publish(new Events.InvoiceEvent(entity, 1001, "invoice_created")); var resp = entity.EntityToDTO(_NetworkProvider); return(new DataWrapper <InvoiceResponse>(resp) { Facade = "pos/invoice" }); }
internal async Task <DataWrapper <InvoiceResponse> > CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl) { var derivationStrategies = store.GetDerivationStrategies(_NetworkProvider).Where(c => _ExplorerClients.IsAvailable(c.Network.CryptoCode)).ToList(); if (derivationStrategies.Count == 0) { throw new BitpayHttpException(400, "No derivation strategy are available now for this store"); } var entity = new InvoiceEntity { InvoiceTime = DateTimeOffset.UtcNow }; entity.SetDerivationStrategies(derivationStrategies); var storeBlob = store.GetStoreBlob(); Uri notificationUri = Uri.IsWellFormedUriString(invoice.NotificationURL, UriKind.Absolute) ? new Uri(invoice.NotificationURL, UriKind.Absolute) : null; if (notificationUri == null || (notificationUri.Scheme != "http" && notificationUri.Scheme != "https")) //TODO: Filer non routable addresses ? { notificationUri = null; } 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; entity.NotificationURL = notificationUri?.AbsoluteUri; entity.BuyerInformation = Map <Invoice, BuyerInformation>(invoice); //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; } entity.ProductInformation = Map <Invoice, ProductInformation>(invoice); entity.RedirectURL = invoice.RedirectURL ?? store.StoreWebsite; entity.Status = "new"; entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy); var queries = derivationStrategies .Select(derivationStrategy => (Wallet: _WalletProvider.GetWallet(derivationStrategy.Network), DerivationStrategy: derivationStrategy.DerivationStrategyBase, Network: derivationStrategy.Network, RateProvider: _RateProviders.GetRateProvider(derivationStrategy.Network, false), FeeRateProvider: _FeeProviderFactory.CreateFeeProvider(derivationStrategy.Network))) .Where(_ => _.Wallet != null && _.FeeRateProvider != null && _.RateProvider != null) .Select(_ => { return(new { network = _.Network, getFeeRate = _.FeeRateProvider.GetFeeRateAsync(), getRate = storeBlob.ApplyRateRules(_.Network, _.RateProvider).GetRateAsync(invoice.Currency), getAddress = _.Wallet.ReserveAddressAsync(_.DerivationStrategy) }); }); bool legacyBTCisSet = false; var cryptoDatas = new Dictionary <string, CryptoData>(); foreach (var q in queries) { CryptoData cryptoData = new CryptoData(); cryptoData.CryptoCode = q.network.CryptoCode; cryptoData.FeeRate = (await q.getFeeRate); cryptoData.TxFee = GetTxFee(storeBlob, cryptoData.FeeRate); // assume price for 100 bytes cryptoData.Rate = await q.getRate; cryptoData.DepositAddress = (await q.getAddress).ToString(); #pragma warning disable CS0618 if (q.network.IsBTC) { legacyBTCisSet = true; entity.TxFee = cryptoData.TxFee; entity.Rate = cryptoData.Rate; entity.DepositAddress = cryptoData.DepositAddress; } #pragma warning restore CS0618 cryptoDatas.Add(cryptoData.CryptoCode, cryptoData); } if (!legacyBTCisSet) { // Legacy Bitpay clients expect information for BTC information, even if the store do not support it #pragma warning disable CS0618 var btc = _NetworkProvider.BTC; var feeProvider = _FeeProviderFactory.CreateFeeProvider(btc); var rateProvider = storeBlob.ApplyRateRules(btc, _RateProviders.GetRateProvider(btc, false)); if (feeProvider != null && rateProvider != null) { var gettingFee = feeProvider.GetFeeRateAsync(); var gettingRate = rateProvider.GetRateAsync(invoice.Currency); entity.TxFee = GetTxFee(storeBlob, await gettingFee); entity.Rate = await gettingRate; } #pragma warning restore CS0618 } entity.SetCryptoData(cryptoDatas); entity.PosData = invoice.PosData; entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, _NetworkProvider); _EventAggregator.Publish(new Events.InvoiceEvent(entity, 1001, "invoice_created")); var resp = entity.EntityToDTO(_NetworkProvider); return(new DataWrapper <InvoiceResponse>(resp) { Facade = "pos/invoice" }); }
internal async Task <DataWrapper <InvoiceResponse> > CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl, double expiryMinutes = 15) { var derivationStrategy = store.DerivationStrategy; var entity = new InvoiceEntity { InvoiceTime = DateTimeOffset.UtcNow, DerivationStrategy = derivationStrategy ?? throw new BitpayHttpException(400, "This store has not configured the derivation strategy") }; var storeBlob = store.GetStoreBlob(); Uri notificationUri = Uri.IsWellFormedUriString(invoice.NotificationURL, UriKind.Absolute) ? new Uri(invoice.NotificationURL, UriKind.Absolute) : null; if (notificationUri == null || (notificationUri.Scheme != "http" && notificationUri.Scheme != "https")) //TODO: Filer non routable addresses ? { notificationUri = null; } EmailAddressAttribute emailValidator = new EmailAddressAttribute(); entity.ExpirationTime = entity.InvoiceTime.AddMinutes(expiryMinutes); entity.MonitoringExpiration = entity.ExpirationTime + TimeSpan.FromMinutes(storeBlob.MonitoringExpiration); entity.OrderId = invoice.OrderId; entity.ServerUrl = serverUrl; entity.FullNotifications = invoice.FullNotifications; entity.NotificationURL = notificationUri?.AbsoluteUri; entity.BuyerInformation = Map <Invoice, BuyerInformation>(invoice); //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; } entity.ProductInformation = Map <Invoice, ProductInformation>(invoice); entity.RedirectURL = invoice.RedirectURL ?? store.StoreWebsite; entity.Status = "new"; entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy); var queries = storeBlob.GetSupportedCryptoCurrencies() .Select(n => _NetworkProvider.GetNetwork(n)) .Where(n => n != null) .Select(network => { return(new { network = network, getFeeRate = _FeeProviderFactory.CreateFeeProvider(network).GetFeeRateAsync(), getRate = _RateProvider.GetRateAsync(invoice.Currency), getAddress = _Wallet.ReserveAddressAsync(ParseDerivationStrategy(derivationStrategy, network)) }); }); var cryptoDatas = new Dictionary <string, CryptoData>(); foreach (var q in queries) { CryptoData cryptoData = new CryptoData(); cryptoData.CryptoCode = q.network.CryptoCode; cryptoData.FeeRate = (await q.getFeeRate); cryptoData.TxFee = storeBlob.NetworkFeeDisabled ? Money.Zero : cryptoData.FeeRate.GetFee(100); // assume price for 100 bytes cryptoData.Rate = await q.getRate; cryptoData.DepositAddress = (await q.getAddress).ToString(); #pragma warning disable CS0618 if (q.network.CryptoCode == "BTC") { entity.TxFee = cryptoData.TxFee; entity.Rate = cryptoData.Rate; entity.DepositAddress = cryptoData.DepositAddress; } #pragma warning restore CS0618 cryptoDatas.Add(cryptoData.CryptoCode, cryptoData); } entity.SetCryptoData(cryptoDatas); entity.PosData = invoice.PosData; entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, _NetworkProvider); _Watcher.Watch(entity.Id); var resp = entity.EntityToDTO(_NetworkProvider); return(new DataWrapper <InvoiceResponse>(resp) { Facade = "pos/invoice" }); }
internal async Task <DataWrapper <InvoiceResponse> > CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl) { var supportedPaymentMethods = store.GetSupportedPaymentMethods(_NetworkProvider) .Select(c => (Handler: (IPaymentMethodHandler)_ServiceProvider.GetService(typeof(IPaymentMethodHandler <>).MakeGenericType(c.GetType())), SupportedPaymentMethod: c, Network: _NetworkProvider.GetNetwork(c.PaymentId.CryptoCode), IsAvailable: Task.FromResult(false))) .Where(c => c.Network != null) .Select(c => { c.IsAvailable = c.Handler.IsAvailable(c.SupportedPaymentMethod, c.Network); return(c); }) .ToList(); foreach (var supportedPaymentMethod in supportedPaymentMethods.ToList()) { if (!await supportedPaymentMethod.IsAvailable) { supportedPaymentMethods.Remove(supportedPaymentMethod); } } if (supportedPaymentMethods.Count == 0) { throw new BitpayHttpException(400, "No derivation strategy are available now for this store"); } var entity = new InvoiceEntity { InvoiceTime = DateTimeOffset.UtcNow }; entity.SetSupportedPaymentMethods(supportedPaymentMethods.Select(s => s.SupportedPaymentMethod)); var storeBlob = store.GetStoreBlob(); Uri notificationUri = Uri.IsWellFormedUriString(invoice.NotificationURL, UriKind.Absolute) ? new Uri(invoice.NotificationURL, UriKind.Absolute) : null; if (notificationUri == null || (notificationUri.Scheme != "http" && notificationUri.Scheme != "https")) //TODO: Filer non routable addresses ? { notificationUri = null; } 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; entity.NotificationURL = notificationUri?.AbsoluteUri; entity.BuyerInformation = Map <Invoice, BuyerInformation>(invoice); //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; } entity.ProductInformation = Map <Invoice, ProductInformation>(invoice); entity.RedirectURL = invoice.RedirectURL ?? store.StoreWebsite; entity.Status = "new"; entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy); var methods = supportedPaymentMethods .Select(async o => { var rate = await storeBlob.ApplyRateRules(o.Network, _RateProviders.GetRateProvider(o.Network, false)).GetRateAsync(invoice.Currency); PaymentMethod paymentMethod = new PaymentMethod(); paymentMethod.ParentEntity = entity; paymentMethod.SetId(o.SupportedPaymentMethod.PaymentId); paymentMethod.Rate = rate; var paymentDetails = await o.Handler.CreatePaymentMethodDetails(o.SupportedPaymentMethod, paymentMethod, o.Network); if (storeBlob.NetworkFeeDisabled) { paymentDetails.SetNoTxFee(); } paymentMethod.SetPaymentMethodDetails(paymentDetails); #pragma warning disable CS0618 if (paymentMethod.GetId().IsBTCOnChain) { entity.TxFee = paymentMethod.TxFee; entity.Rate = paymentMethod.Rate; entity.DepositAddress = paymentMethod.DepositAddress; } #pragma warning restore CS0618 return(paymentMethod); }); var paymentMethods = new PaymentMethodDictionary(); foreach (var method in methods) { paymentMethods.Add(await method); } #pragma warning disable CS0618 // Legacy Bitpay clients expect information for BTC information, even if the store do not support it var legacyBTCisSet = paymentMethods.Any(p => p.GetId().IsBTCOnChain); if (!legacyBTCisSet && _NetworkProvider.BTC != null) { var btc = _NetworkProvider.BTC; var feeProvider = ((IFeeProviderFactory)_ServiceProvider.GetService(typeof(IFeeProviderFactory))).CreateFeeProvider(btc); var rateProvider = storeBlob.ApplyRateRules(btc, _RateProviders.GetRateProvider(btc, false)); if (feeProvider != null && rateProvider != null) { var gettingFee = feeProvider.GetFeeRateAsync(); var gettingRate = rateProvider.GetRateAsync(invoice.Currency); entity.TxFee = GetTxFee(storeBlob, await gettingFee); entity.Rate = await gettingRate; } #pragma warning restore CS0618 } entity.SetPaymentMethods(paymentMethods); entity.PosData = invoice.PosData; entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, _NetworkProvider); _EventAggregator.Publish(new Events.InvoiceEvent(entity, 1001, "invoice_created")); var resp = entity.EntityToDTO(_NetworkProvider); return(new DataWrapper <InvoiceResponse>(resp) { Facade = "pos/invoice" }); }