Esempio n. 1
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 <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> 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 paymentMethodId = ppBlob.SupportedPaymentMethods.FirstOrDefault(id => vm.SelectedPaymentMethod == id.ToString());

            var payoutHandler = paymentMethodId is null ? null : _payoutHandlers.FindPayoutHandler(paymentMethodId);

            if (payoutHandler is null)
            {
                ModelState.AddModelError(nameof(vm.SelectedPaymentMethod), "Invalid destination with selected payment method");
                return(await ViewPullPayment(pullPaymentId));
            }
            var destination = await payoutHandler.ParseAndValidateClaimDestination(paymentMethodId, vm.Destination, ppBlob);

            if (destination.destination is null)
            {
                ModelState.AddModelError(nameof(vm.Destination), destination.error ?? "Invalid destination with selected payment method");
                return(await ViewPullPayment(pullPaymentId));
            }

            if (vm.ClaimedAmount == 0)
            {
                ModelState.AddModelError(nameof(vm.ClaimedAmount), "Amount is required");
            }
            else
            {
                var amount = ppBlob.Currency == "SATS" ? new Money(vm.ClaimedAmount, MoneyUnit.Satoshi).ToUnit(MoneyUnit.BTC) : vm.ClaimedAmount;
                if (destination.destination.Amount != null && amount != destination.destination.Amount)
                {
                    var implied  = _currencyNameTable.DisplayFormatCurrency(destination.destination.Amount.Value, paymentMethodId.CryptoCode);
                    var provided = _currencyNameTable.DisplayFormatCurrency(vm.ClaimedAmount, ppBlob.Currency);
                    ModelState.AddModelError(nameof(vm.ClaimedAmount),
                                             $"Amount implied in destination ({implied}) does not match the payout amount provided ({provided}).");
                }
            }

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

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

            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));
            }

            TempData.SetStatusMessageModel(new StatusMessageModel
            {
                Message  = $"Your claim request of {_currencyNameTable.DisplayFormatCurrency(vm.ClaimedAmount, ppBlob.Currency)} to {vm.Destination} has been submitted and is awaiting {(result.PayoutData.State == PayoutState.AwaitingApproval? "approval": "payment")}.",
                Severity = StatusMessageModel.StatusSeverity.Success
            });

            return(RedirectToAction(nameof(ViewPullPayment), new { pullPaymentId }));
        }