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)); }
public async Task <IActionResult> ActivateInvoicePaymentMethod(string storeId, string invoiceId, string paymentMethod) { var store = HttpContext.GetStoreData(); if (store == null) { return(NotFound()); } var invoice = await _invoiceRepository.GetInvoice(invoiceId, true); if (invoice?.StoreId != store.Id) { return(NotFound()); } if (PaymentMethodId.TryParse(paymentMethod, out var paymentMethodId)) { await _invoiceRepository.ActivateInvoicePaymentMethod(_eventAggregator, _btcPayNetworkProvider, _paymentMethodHandlerDictionary, store, invoice, paymentMethodId); return(Ok()); } return(BadRequest()); }
public PaymentMethodDictionary GetPaymentMethods() { PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary(); var serializer = new Serializer(null); #pragma warning disable CS0618 if (PaymentMethod != null) { foreach (var prop in PaymentMethod.Properties()) { var r = serializer.ToObject <PaymentMethod>(prop.Value.ToString()); if (!PaymentMethodId.TryParse(prop.Name, out var paymentMethodId)) { continue; } r.CryptoCode = paymentMethodId.CryptoCode; r.PaymentType = paymentMethodId.PaymentType.ToString(); r.ParentEntity = this; if (Networks != null) { r.Network = Networks.GetNetwork <BTCPayNetworkBase>(r.CryptoCode); if (r.Network is null) { continue; } } paymentMethods.Add(r); } } #pragma warning restore CS0618 return(paymentMethods); }
public Task BindModelAsync(ModelBindingContext bindingContext) { if (!typeof(PaymentMethodIdModelBinder).GetTypeInfo().IsAssignableFrom(bindingContext.ModelType)) { return(Task.CompletedTask); } ValueProviderResult val = bindingContext.ValueProvider.GetValue( bindingContext.ModelName); if (val == null) { return(Task.CompletedTask); } string key = val.FirstValue as string; if (key == null) { return(Task.CompletedTask); } if (PaymentMethodId.TryParse(key, out var paymentId)) { bindingContext.Result = ModelBindingResult.Success(paymentId); } else { bindingContext.Result = ModelBindingResult.Failed(); bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Invalid payment id"); } return(Task.CompletedTask); }
public static IEnumerable <ISupportedPaymentMethod> GetSupportedPaymentMethods(this StoreData storeData, BTCPayNetworkProvider networks) { ArgumentNullException.ThrowIfNull(storeData); #pragma warning disable CS0618 bool btcReturned = false; if (!string.IsNullOrEmpty(storeData.DerivationStrategies)) { JObject strategies = JObject.Parse(storeData.DerivationStrategies); foreach (var strat in strategies.Properties()) { if (!PaymentMethodId.TryParse(strat.Name, out var paymentMethodId)) { continue; } var network = networks.GetNetwork <BTCPayNetworkBase>(paymentMethodId.CryptoCode); if (network != null) { if (network == networks.BTC && paymentMethodId.PaymentType == PaymentTypes.BTCLike && btcReturned) { continue; } if (strat.Value.Type == JTokenType.Null) { continue; } yield return (paymentMethodId.PaymentType.DeserializeSupportedPaymentMethod(network, strat.Value)); } } } #pragma warning restore CS0618 }
public async Task <IActionResult> ActivateInvoicePaymentMethod(string storeId, string invoiceId, string paymentMethod) { var store = HttpContext.GetStoreData(); if (store == null) { return(InvoiceNotFound()); } var invoice = await _invoiceRepository.GetInvoice(invoiceId, true); if (invoice?.StoreId != store.Id) { return(InvoiceNotFound()); } if (PaymentMethodId.TryParse(paymentMethod, out var paymentMethodId)) { await _invoiceRepository.ActivateInvoicePaymentMethod(_eventAggregator, _btcPayNetworkProvider, _paymentMethodHandlerDictionary, store, invoice, paymentMethodId); return(Ok()); } ModelState.AddModelError(nameof(paymentMethod), "Invalid payment method"); return(this.CreateValidationError(ModelState)); }
public async Task <IActionResult> CreatePullPayment(string storeId, CreatePullPaymentRequest request) { if (request is null) { ModelState.AddModelError(string.Empty, "Missing body"); return(this.CreateValidationError(ModelState)); } if (request.Amount <= 0.0m) { ModelState.AddModelError(nameof(request.Amount), "The amount should more than 0."); } if (request.Name is String name && name.Length > 50) { ModelState.AddModelError(nameof(request.Name), "The name should be maximum 50 characters."); } if (request.Currency is String currency) { request.Currency = currency.ToUpperInvariant().Trim(); if (_currencyNameTable.GetCurrencyData(request.Currency, false) is null) { ModelState.AddModelError(nameof(request.Currency), "Invalid currency"); } } else { ModelState.AddModelError(nameof(request.Currency), "This field is required"); } if (request.ExpiresAt is DateTimeOffset expires && request.StartsAt is DateTimeOffset start && expires < start) { ModelState.AddModelError(nameof(request.ExpiresAt), $"expiresAt should be higher than startAt"); } if (request.Period <= TimeSpan.Zero) { ModelState.AddModelError(nameof(request.Period), $"The period should be positive"); } if (request.BOLT11Expiration <= TimeSpan.Zero) { ModelState.AddModelError(nameof(request.BOLT11Expiration), $"The BOLT11 expiration should be positive"); } PaymentMethodId?[]? paymentMethods = null; if (request.PaymentMethods is { } paymentMethodsStr) { paymentMethods = paymentMethodsStr.Select(s => { PaymentMethodId.TryParse(s, out var pmi); return(pmi); }).ToArray(); var supported = (await _payoutHandlers.GetSupportedPaymentMethods(HttpContext.GetStoreData())).ToArray(); for (int i = 0; i < paymentMethods.Length; i++) { if (!supported.Contains(paymentMethods[i])) { request.AddModelError(paymentRequest => paymentRequest.PaymentMethods[i], "Invalid or unsupported payment method", this); } } }
#pragma warning disable CS0618 public static PaymentMethodId GetDefaultPaymentId(this StoreData storeData, BTCPayNetworkProvider networks) { PaymentMethodId[] paymentMethodIds = storeData.GetEnabledPaymentIds(networks); PaymentMethodId.TryParse(storeData.DefaultCrypto, out var defaultPaymentId); var chosen = paymentMethodIds.FirstOrDefault(f => f == defaultPaymentId) ?? paymentMethodIds.FirstOrDefault(f => f.CryptoCode == defaultPaymentId?.CryptoCode) ?? paymentMethodIds.FirstOrDefault(); return(chosen); }
public async Task <IActionResult> CreateStore(CreateStoreRequest request) { var validationResult = Validate(request); if (validationResult != null) { return(validationResult); } var store = new Data.StoreData(); PaymentMethodId.TryParse(request.DefaultPaymentMethod, out var defaultPaymentMethodId); ToModel(request, store, defaultPaymentMethodId); await _storeRepository.CreateStore(_userManager.GetUserId(User), store); return(Ok(FromModel(store))); }
public static void SetSupportedPaymentMethod(this StoreData storeData, PaymentMethodId?paymentMethodId, ISupportedPaymentMethod?supportedPaymentMethod) { if (supportedPaymentMethod != null && paymentMethodId != null && paymentMethodId != supportedPaymentMethod.PaymentId) { throw new InvalidOperationException("Incoherent arguments, this should never happen"); } if (supportedPaymentMethod == null && paymentMethodId == null) { throw new ArgumentException($"{nameof(supportedPaymentMethod)} or {nameof(paymentMethodId)} should be specified"); } if (supportedPaymentMethod != null && paymentMethodId == null) { paymentMethodId = supportedPaymentMethod.PaymentId; } #pragma warning disable CS0618 JObject strategies = string.IsNullOrEmpty(storeData.DerivationStrategies) ? new JObject() : JObject.Parse(storeData.DerivationStrategies); bool existing = false; foreach (var strat in strategies.Properties().ToList()) { if (!PaymentMethodId.TryParse(strat.Name, out var stratId)) { continue; } if (stratId == paymentMethodId) { if (supportedPaymentMethod == null) { strat.Remove(); } else { strat.Value = PaymentMethodExtensions.Serialize(supportedPaymentMethod); } existing = true; break; } } if (!existing && supportedPaymentMethod != null) { strategies.Add(new JProperty(supportedPaymentMethod.PaymentId.ToString(), PaymentMethodExtensions.Serialize(supportedPaymentMethod))); } storeData.DerivationStrategies = strategies.ToString(); #pragma warning restore CS0618 }
public async Task <IActionResult> UpdateStore(string storeId, UpdateStoreRequest request) { var store = HttpContext.GetStoreData(); if (store == null) { return(NotFound()); } var validationResult = Validate(request); if (validationResult != null) { return(validationResult); } PaymentMethodId.TryParse(request.DefaultPaymentMethod, out var defaultPaymnetMethodId); ToModel(request, store, defaultPaymnetMethodId); await _storeRepository.UpdateStore(store); return(Ok(FromModel(store))); }
private IActionResult Validate(StoreBaseData request) { if (request is null) { return(BadRequest()); } if (!string.IsNullOrEmpty(request.DefaultPaymentMethod) && !PaymentMethodId.TryParse(request.DefaultPaymentMethod, out var defaultPaymentMethodId)) { ModelState.AddModelError(nameof(request.Name), "DefaultPaymentMethod is invalid"); } if (string.IsNullOrEmpty(request.Name)) { ModelState.AddModelError(nameof(request.Name), "Name is missing"); } else if (request.Name.Length < 1 || request.Name.Length > 50) { ModelState.AddModelError(nameof(request.Name), "Name can only be between 1 and 50 characters"); } if (!string.IsNullOrEmpty(request.Website) && !Uri.TryCreate(request.Website, UriKind.Absolute, out _)) { ModelState.AddModelError(nameof(request.Website), "Website is not a valid url"); } if (request.InvoiceExpiration < TimeSpan.FromMinutes(1) && request.InvoiceExpiration > TimeSpan.FromMinutes(60 * 24 * 24)) { ModelState.AddModelError(nameof(request.InvoiceExpiration), "InvoiceExpiration can only be between 1 and 34560 mins"); } if (request.MonitoringExpiration < TimeSpan.FromMinutes(10) && request.MonitoringExpiration > TimeSpan.FromMinutes(60 * 24 * 24)) { ModelState.AddModelError(nameof(request.MonitoringExpiration), "InvoiceExpiration can only be between 10 and 34560 mins"); } if (request.PaymentTolerance < 0 && request.PaymentTolerance > 100) { ModelState.AddModelError(nameof(request.PaymentTolerance), "PaymentTolerance can only be between 0 and 100 percent"); } return(!ModelState.IsValid ? this.CreateValidationError(ModelState) : null); }
public async Task <IActionResult> CreateInvoice(string storeId, CreateInvoiceRequest request) { var store = HttpContext.GetStoreData(); if (store == null) { return(StoreNotFound()); } if (request.Amount < 0.0m) { ModelState.AddModelError(nameof(request.Amount), "The amount should be 0 or more."); } request.Checkout = request.Checkout ?? new CreateInvoiceRequest.CheckoutOptions(); if (request.Checkout.PaymentMethods?.Any() is true) { for (int i = 0; i < request.Checkout.PaymentMethods.Length; i++) { if (!PaymentMethodId.TryParse(request.Checkout.PaymentMethods[i], out _)) { request.AddModelError(invoiceRequest => invoiceRequest.Checkout.PaymentMethods[i], "Invalid payment method", this); } } } if (request.Checkout.Expiration != null && request.Checkout.Expiration < TimeSpan.FromSeconds(30.0)) { request.AddModelError(invoiceRequest => invoiceRequest.Checkout.Expiration, "Expiration time must be at least 30 seconds", this); } if (request.Checkout.PaymentTolerance != null && (request.Checkout.PaymentTolerance < 0 || request.Checkout.PaymentTolerance > 100)) { request.AddModelError(invoiceRequest => invoiceRequest.Checkout.PaymentTolerance, "PaymentTolerance can only be between 0 and 100 percent", this); } if (request.Checkout.DefaultLanguage != null) { var lang = LanguageService.FindLanguage(request.Checkout.DefaultLanguage); if (lang == null) { request.AddModelError(invoiceRequest => invoiceRequest.Checkout.DefaultLanguage, "The requested defaultLang does not exists, Browse the ~/misc/lang page of your GRSPay Server instance to see the list of supported languages.", this); } else { // Ensure this is good case request.Checkout.DefaultLanguage = lang.Code; } } if (!ModelState.IsValid) { return(this.CreateValidationError(ModelState)); } try { var invoice = await _invoiceController.CreateInvoiceCoreRaw(request, store, Request.GetAbsoluteRoot()); return(Ok(ToModel(invoice))); } catch (BitpayHttpException e) { return(this.CreateAPIError(null, e.Message)); } }
public static PaymentMethodId GetPaymentMethodId(this PayoutData data) { return(PaymentMethodId.TryParse(data.PaymentMethodId, out var paymentMethodId)? paymentMethodId : null); }
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)); }
public async Task <StatusMessageModel> DoSpecificAction(string action, string[] payoutIds, string storeId) { switch (action) { case "mark-paid": await using (var context = _dbContextFactory.CreateContext()) { var payouts = (await context.Payouts .Include(p => p.PullPaymentData) .Include(p => p.PullPaymentData.StoreData) .Where(p => payoutIds.Contains(p.Id)) .Where(p => p.PullPaymentData.StoreId == storeId && !p.PullPaymentData.Archived && p.State == PayoutState.AwaitingPayment) .ToListAsync()).Where(data => PaymentMethodId.TryParse(data.PaymentMethodId, out var paymentMethodId) && CanHandle(paymentMethodId)) .Select(data => (data, ParseProof(data) as PayoutTransactionOnChainBlob)).Where(tuple => tuple.Item2 != null && tuple.Item2.TransactionId != null && tuple.Item2.Accounted == false); foreach (var valueTuple in payouts) { valueTuple.Item2.Accounted = true; valueTuple.data.State = PayoutState.InProgress; SetProofBlob(valueTuple.data, valueTuple.Item2); } await context.SaveChangesAsync(); } return(new StatusMessageModel() { Message = "Payout payments have been marked confirmed", Severity = StatusMessageModel.StatusSeverity.Success }); case "reject-payment": await using (var context = _dbContextFactory.CreateContext()) { var payouts = (await context.Payouts .Include(p => p.PullPaymentData) .Include(p => p.PullPaymentData.StoreData) .Where(p => payoutIds.Contains(p.Id)) .Where(p => p.PullPaymentData.StoreId == storeId && !p.PullPaymentData.Archived && p.State == PayoutState.AwaitingPayment) .ToListAsync()).Where(data => PaymentMethodId.TryParse(data.PaymentMethodId, out var paymentMethodId) && CanHandle(paymentMethodId)) .Select(data => (data, ParseProof(data) as PayoutTransactionOnChainBlob)).Where(tuple => tuple.Item2 != null && tuple.Item2.TransactionId != null && tuple.Item2.Accounted == true); foreach (var valueTuple in payouts) { valueTuple.Item2.TransactionId = null; SetProofBlob(valueTuple.data, valueTuple.Item2); } await context.SaveChangesAsync(); } return(new StatusMessageModel() { Message = "Payout payments have been unmarked", Severity = StatusMessageModel.StatusSeverity.Success }); } return(null); }
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)); }
public static PaymentMethodId?GetDefaultPaymentId(this StoreData storeData) { PaymentMethodId.TryParse(storeData.DefaultCrypto, out var defaultPaymentId); return(defaultPaymentId); }
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" }); }
public async Task <IActionResult> CreatePayout(string pullPaymentId, CreatePayoutRequest request) { if (request is null) { return(NotFound()); } if (!PaymentMethodId.TryParse(request?.PaymentMethod, out var paymentMethodId)) { ModelState.AddModelError(nameof(request.PaymentMethod), "Invalid payment method"); return(this.CreateValidationError(ModelState)); } var payoutHandler = _payoutHandlers.FirstOrDefault(handler => handler.CanHandle(paymentMethodId)); if (payoutHandler is null) { ModelState.AddModelError(nameof(request.PaymentMethod), "Invalid payment method"); return(this.CreateValidationError(ModelState)); } await using var ctx = _dbContextFactory.CreateContext(); var pp = await ctx.PullPayments.FindAsync(pullPaymentId); if (pp is null) { return(PullPaymentNotFound()); } var ppBlob = pp.GetBlob(); IClaimDestination destination = await payoutHandler.ParseClaimDestination(paymentMethodId, request.Destination); if (destination is null) { ModelState.AddModelError(nameof(request.Destination), "The destination must be an address or a BIP21 URI"); return(this.CreateValidationError(ModelState)); } if (request.Amount is decimal v && (v < ppBlob.MinimumClaim || v == 0.0m)) { ModelState.AddModelError(nameof(request.Amount), $"Amount too small (should be at least {ppBlob.MinimumClaim})"); return(this.CreateValidationError(ModelState)); } var cd = _currencyNameTable.GetCurrencyData(pp.GetBlob().Currency, false); var result = await _pullPaymentService.Claim(new ClaimRequest() { Destination = destination, PullPaymentId = pullPaymentId, Value = request.Amount, PaymentMethodId = paymentMethodId }); switch (result.Result) { case ClaimRequest.ClaimResult.Ok: break; case ClaimRequest.ClaimResult.Duplicate: return(this.CreateAPIError("duplicate-destination", ClaimRequest.GetErrorMessage(result.Result))); case ClaimRequest.ClaimResult.Expired: return(this.CreateAPIError("expired", ClaimRequest.GetErrorMessage(result.Result))); case ClaimRequest.ClaimResult.NotStarted: return(this.CreateAPIError("not-started", ClaimRequest.GetErrorMessage(result.Result))); case ClaimRequest.ClaimResult.Archived: return(this.CreateAPIError("archived", ClaimRequest.GetErrorMessage(result.Result))); case ClaimRequest.ClaimResult.Overdraft: return(this.CreateAPIError("overdraft", ClaimRequest.GetErrorMessage(result.Result))); case ClaimRequest.ClaimResult.AmountTooLow: return(this.CreateAPIError("amount-too-low", ClaimRequest.GetErrorMessage(result.Result))); case ClaimRequest.ClaimResult.PaymentMethodNotSupported: return(this.CreateAPIError("payment-method-not-supported", ClaimRequest.GetErrorMessage(result.Result))); default: throw new NotSupportedException("Unsupported ClaimResult"); } return(Ok(ToModel(result.PayoutData, cd))); }
public async Task <IActionResult> CreateInvoice(string storeId, CreateInvoiceRequest request) { var store = HttpContext.GetStoreData(); if (store == null) { return(NotFound()); } if (request.Amount < 0.0m) { ModelState.AddModelError(nameof(request.Amount), "The amount should be 0 or more."); } if (string.IsNullOrEmpty(request.Currency)) { ModelState.AddModelError(nameof(request.Currency), "Currency is required"); } if (request.Checkout.PaymentMethods?.Any() is true) { for (int i = 0; i < request.Checkout.PaymentMethods.Length; i++) { if (!PaymentMethodId.TryParse(request.Checkout.PaymentMethods[i], out _)) { request.AddModelError(invoiceRequest => invoiceRequest.Checkout.PaymentMethods[i], "Invalid payment method", this); } } } if (request.Checkout.Expiration != null && request.Checkout.Expiration < TimeSpan.FromSeconds(30.0)) { request.AddModelError(invoiceRequest => invoiceRequest.Checkout.Expiration, "Expiration time must be at least 30 seconds", this); } if (request.Checkout.PaymentTolerance != null && (request.Checkout.PaymentTolerance < 0 || request.Checkout.PaymentTolerance > 100)) { request.AddModelError(invoiceRequest => invoiceRequest.Checkout.PaymentTolerance, "PaymentTolerance can only be between 0 and 100 percent", this); } if (!ModelState.IsValid) { return(this.CreateValidationError(ModelState)); } try { var invoice = await _invoiceController.CreateInvoiceCoreRaw(request, store, Request.GetAbsoluteUri("")); return(Ok(ToModel(invoice))); } catch (BitpayHttpException e) { return(this.CreateAPIError(null, e.Message)); } }