예제 #1
0
        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());
        }
예제 #3
0
        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);
        }
예제 #4
0
        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);
        }
예제 #5
0
        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));
        }
예제 #7
0
 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);
             }
         }
     }
예제 #8
0
#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)));
        }
예제 #10
0
        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
        }
예제 #11
0
        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));
            }
        }
예제 #14
0
 public static PaymentMethodId GetPaymentMethodId(this PayoutData data)
 {
     return(PaymentMethodId.TryParse(data.PaymentMethodId, out var paymentMethodId)? paymentMethodId : null);
 }
예제 #15
0
        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);
    }
예제 #17
0
        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));
        }
예제 #18
0
 public static PaymentMethodId?GetDefaultPaymentId(this StoreData storeData)
 {
     PaymentMethodId.TryParse(storeData.DefaultCrypto, out var defaultPaymentId);
     return(defaultPaymentId);
 }
예제 #19
0
        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"
            });
        }
예제 #20
0
        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)));
        }
예제 #21
0
        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));
            }
        }