Beispiel #1
0
 public static bool TryParse(string destination, BTCPayNetwork network, out IClaimDestination claimDestination)
 {
     if (destination == null)
     {
         throw new ArgumentNullException(nameof(destination));
     }
     destination = destination.Trim();
     try
     {
         if (destination.StartsWith($"{network.UriScheme}:", StringComparison.OrdinalIgnoreCase))
         {
             claimDestination = new UriClaimDestination(new BitcoinUrlBuilder(destination, network.NBitcoinNetwork));
         }
         else
         {
             claimDestination = new AddressClaimDestination(BitcoinAddress.Create(destination, network.NBitcoinNetwork));
         }
         return(true);
     }
     catch
     {
         claimDestination = null;
         return(false);
     }
 }
Beispiel #2
0
        public async Task <IActionResult> ClaimPullPayment(string pullPaymentId, ViewPullPaymentModel vm)
        {
            using var ctx = _dbContextFactory.CreateContext();
            var pp = await ctx.PullPayments.FindAsync(pullPaymentId);

            if (pp is null)
            {
                ModelState.AddModelError(nameof(pullPaymentId), "This pull payment does not exists");
            }

            var ppBlob  = pp.GetBlob();
            var network = _networkProvider.GetNetwork <BTCPayNetwork>(ppBlob.SupportedPaymentMethods.Single().CryptoCode);

            var paymentMethodId           = ppBlob.SupportedPaymentMethods.Single();
            var payoutHandler             = _payoutHandlers.FirstOrDefault(handler => handler.CanHandle(paymentMethodId));
            IClaimDestination destination = await payoutHandler?.ParseClaimDestination(paymentMethodId, vm.Destination);

            if (destination is null)
            {
                ModelState.AddModelError(nameof(vm.Destination), $"Invalid destination");
            }

            if (!ModelState.IsValid)
            {
                return(await ViewPullPayment(pullPaymentId));
            }

            var result = await _pullPaymentHostedService.Claim(new ClaimRequest()
            {
                Destination     = destination,
                PullPaymentId   = pullPaymentId,
                Value           = vm.ClaimedAmount,
                PaymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike)
            });

            if (result.Result != ClaimRequest.ClaimResult.Ok)
            {
                if (result.Result == ClaimRequest.ClaimResult.AmountTooLow)
                {
                    ModelState.AddModelError(nameof(vm.ClaimedAmount), ClaimRequest.GetErrorMessage(result.Result));
                }
                else
                {
                    ModelState.AddModelError(string.Empty, ClaimRequest.GetErrorMessage(result.Result));
                }
                return(await ViewPullPayment(pullPaymentId));
            }
            else
            {
                TempData.SetStatusMessageModel(new StatusMessageModel()
                {
                    Message  = $"Your claim request of {_currencyNameTable.DisplayFormatCurrency(vm.ClaimedAmount, ppBlob.Currency)} to {vm.Destination} has been submitted and is awaiting approval.",
                    Severity = StatusMessageModel.StatusSeverity.Success
                });
            }
            return(RedirectToAction(nameof(ViewPullPayment), new { pullPaymentId = pullPaymentId }));
        }
    public async Task TrackClaim(PaymentMethodId paymentMethodId, IClaimDestination claimDestination)
    {
        var network        = _btcPayNetworkProvider.GetNetwork <BTCPayNetwork>(paymentMethodId.CryptoCode);
        var explorerClient = _explorerClientProvider.GetExplorerClient(network);

        if (claimDestination is IBitcoinLikeClaimDestination bitcoinLikeClaimDestination)
        {
            await explorerClient.TrackAsync(TrackedSource.Create(bitcoinLikeClaimDestination.Address));
        }
    }
        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 Task <decimal> GetMinimumPayoutAmount(PaymentMethodId paymentMethodId, IClaimDestination claimDestination)
    {
        if (_btcPayNetworkProvider.GetNetwork <BTCPayNetwork>(paymentMethodId.CryptoCode)?
            .NBitcoinNetwork?
            .Consensus?
            .ConsensusFactory?
            .CreateTxOut() is TxOut txout &&
            claimDestination is IBitcoinLikeClaimDestination bitcoinLikeClaimDestination)
        {
            txout.ScriptPubKey = bitcoinLikeClaimDestination.Address.ScriptPubKey;
            return(Task.FromResult(txout.GetDustThreshold().ToDecimal(MoneyUnit.BTC)));
        }

        return(Task.FromResult(0m));
    }
Beispiel #6
0
        public (bool valid, string error) ValidateClaimDestination(IClaimDestination claimDestination, PullPaymentBlob pullPaymentBlob)
        {
            if (claimDestination is not BoltInvoiceClaimDestination bolt)
            {
                return(true, null);
            }
            var invoice = bolt.PaymentRequest;

            if (pullPaymentBlob is not null && (invoice.ExpiryDate.UtcDateTime - DateTime.UtcNow) < pullPaymentBlob.BOLT11Expiration)
            {
                return(false,
                       $"The BOLT11 invoice must have an expiry date of at least {(long)pullPaymentBlob.BOLT11Expiration.TotalDays} days from submission (Provided was only {(invoice.ExpiryDate.UtcDateTime - DateTime.UtcNow).Days}).");
            }
            return(true, null);
        }
    public Task <(IClaimDestination destination, string error)> ParseClaimDestination(PaymentMethodId paymentMethodId, string destination)
    {
        var network = _btcPayNetworkProvider.GetNetwork <BTCPayNetwork>(paymentMethodId.CryptoCode);

        destination = destination.Trim();
        try
        {
            if (destination.StartsWith($"{network.NBitcoinNetwork.UriScheme}:", StringComparison.OrdinalIgnoreCase))
            {
                return(Task.FromResult <(IClaimDestination, string)>((new UriClaimDestination(new BitcoinUrlBuilder(destination, network.NBitcoinNetwork)), null)));
            }

            return(Task.FromResult <(IClaimDestination, string)>((new AddressClaimDestination(BitcoinAddress.Create(destination, network.NBitcoinNetwork)), null)));
        }
        catch
        {
            return(Task.FromResult <(IClaimDestination, string)>(
                       (null, "A valid address was not provided")));
        }
    }
    public Task <(IClaimDestination destination, string error)> ParseClaimDestination(PaymentMethodId paymentMethodId, string destination, bool validate)
    {
        var network = _btcPayNetworkProvider.GetNetwork <BTCPayNetwork>(paymentMethodId.CryptoCode);

        destination = destination.Trim();
        try
        {
            // This doesn't work properly, (payouts are not detected), we can reactivate later when we fix the bug https://github.com/btcpayserver/btcpayserver/issues/2765
            //if (destination.StartsWith($"{network.UriScheme}:", StringComparison.OrdinalIgnoreCase))
            //{
            //    return Task.FromResult<IClaimDestination>(new UriClaimDestination(new BitcoinUrlBuilder(destination, network.NBitcoinNetwork)));
            //}

            return(Task.FromResult <(IClaimDestination, string)>((new AddressClaimDestination(BitcoinAddress.Create(destination, network.NBitcoinNetwork)), null)));
        }
        catch
        {
            return(Task.FromResult <(IClaimDestination, string)>(
                       (null, "A valid address was not provided")));
        }
    }
        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 (bool valid, string error) ValidateClaimDestination(IClaimDestination claimDestination, PullPaymentBlob pullPaymentBlob)
 {
     return(true, null);
 }
Beispiel #11
0
 public Task TrackClaim(PaymentMethodId paymentMethodId, IClaimDestination claimDestination)
 {
     return(Task.CompletedTask);
 }
Beispiel #12
0
 public Task <decimal> GetMinimumPayoutAmount(PaymentMethodId paymentMethodId, IClaimDestination claimDestination)
 {
     return(Task.FromResult(Money.Satoshis(1).ToDecimal(MoneyUnit.BTC)));
 }