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); } }
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)); }
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); }
public Task TrackClaim(PaymentMethodId paymentMethodId, IClaimDestination claimDestination) { return(Task.CompletedTask); }
public Task <decimal> GetMinimumPayoutAmount(PaymentMethodId paymentMethodId, IClaimDestination claimDestination) { return(Task.FromResult(Money.Satoshis(1).ToDecimal(MoneyUnit.BTC))); }