mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Support specifying payment method through apps (#1539)
* Support specifying payment method through apps closes #1525 This is great if you want to offer items with an incentive to use a specific payment method without messing with the rates. For example, you can have `Item A` which costs 25$ and your store is configured for USDT and BTC. You can create two items, with different prices, where Item A costs 20$ if you pay with bitcoin or 25$ if you paid in dirty fiat pegs * make code cleaner * Add Test * fix test * fix test
This commit is contained in:
@@ -2265,14 +2265,17 @@ namespace BTCPayServer.Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Trait("Altcoins", "Altcoins")]
|
||||
public async Task CanUsePoSApp()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.ActivateLTC();
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
user.RegisterDerivationScheme("LTC");
|
||||
var apps = user.GetController<AppsController>();
|
||||
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp().Result).Model);
|
||||
vm.Name = "test";
|
||||
@@ -2443,6 +2446,41 @@ noninventoryitem:
|
||||
Assert.Equal(1,
|
||||
appService.Parse(vmpos.Template, "BTC").Single(item => item.Id == "inventoryitem").Inventory);
|
||||
}, 10000);
|
||||
|
||||
|
||||
//test payment methods option
|
||||
|
||||
vmpos = Assert.IsType<UpdatePointOfSaleViewModel>(Assert
|
||||
.IsType<ViewResult>(apps.UpdatePointOfSale(appId).Result).Model);
|
||||
vmpos.Title = "hello";
|
||||
vmpos.Currency = "BTC";
|
||||
vmpos.Template = @"
|
||||
btconly:
|
||||
price: 1.0
|
||||
title: good apple
|
||||
payment_methods:
|
||||
- BTC
|
||||
normal:
|
||||
price: 1.0";
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(appId, vmpos).Result);
|
||||
Assert.IsType<RedirectToActionResult>(publicApps
|
||||
.ViewPointOfSale(appId, 1, null, null, null, null, "btconly").Result);
|
||||
Assert.IsType<RedirectToActionResult>(publicApps
|
||||
.ViewPointOfSale(appId, 1, null, null, null, null, "normal").Result);
|
||||
invoices = user.BitPay.GetInvoices();
|
||||
var normalInvoice = invoices.Single(invoice => invoice.ItemCode == "normal");
|
||||
var btcOnlyInvoice = invoices.Single(invoice => invoice.ItemCode == "btconly");
|
||||
Assert.Single(btcOnlyInvoice.CryptoInfo);
|
||||
Assert.Equal("BTC",
|
||||
btcOnlyInvoice.CryptoInfo.First().CryptoCode);
|
||||
Assert.Equal(PaymentTypes.BTCLike.ToString(),
|
||||
btcOnlyInvoice.CryptoInfo.First().PaymentType);
|
||||
|
||||
Assert.Equal(2, normalInvoice.CryptoInfo.Length);
|
||||
Assert.Contains(
|
||||
normalInvoice.CryptoInfo,
|
||||
s => PaymentTypes.BTCLike.ToString() == s.PaymentType && new[] {"BTC", "LTC"}.Contains(
|
||||
s.CryptoCode));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitpayClient;
|
||||
using static BTCPayServer.Controllers.AppsController;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
@@ -111,6 +112,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
string title = null;
|
||||
var price = 0.0m;
|
||||
Dictionary<string, InvoiceSupportedTransactionCurrency> paymentMethods = null;
|
||||
ViewPointOfSaleViewModel.Item choice = null;
|
||||
if (!string.IsNullOrEmpty(choiceKey))
|
||||
{
|
||||
@@ -130,6 +132,12 @@ namespace BTCPayServer.Controllers
|
||||
return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId });
|
||||
}
|
||||
}
|
||||
|
||||
if (choice?.PaymentMethods?.Any() is true)
|
||||
{
|
||||
paymentMethods = choice?.PaymentMethods.ToDictionary(s => s,
|
||||
s => new InvoiceSupportedTransactionCurrency() {Enabled = true});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -182,6 +190,7 @@ namespace BTCPayServer.Controllers
|
||||
ExtendedNotifications = true,
|
||||
PosData = string.IsNullOrEmpty(posData) ? null : posData,
|
||||
RedirectAutomatically = settings.RedirectAutomatically,
|
||||
SupportedTransactionCurrencies = paymentMethods,
|
||||
}, store, HttpContext.Request.GetAbsoluteRoot(),
|
||||
new List<string>() { AppService.GetAppInternalTag(appId) },
|
||||
cancellationToken);
|
||||
@@ -270,6 +279,7 @@ namespace BTCPayServer.Controllers
|
||||
var store = await _AppService.GetStore(app);
|
||||
var title = settings.Title;
|
||||
var price = request.Amount;
|
||||
Dictionary<string, InvoiceSupportedTransactionCurrency> paymentMethods = null;
|
||||
ViewPointOfSaleViewModel.Item choice = null;
|
||||
if (!string.IsNullOrEmpty(request.ChoiceKey))
|
||||
{
|
||||
@@ -290,6 +300,13 @@ namespace BTCPayServer.Controllers
|
||||
return NotFound("Option was out of stock");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (choice?.PaymentMethods?.Any() is true)
|
||||
{
|
||||
paymentMethods = choice?.PaymentMethods.ToDictionary(s => s,
|
||||
s => new InvoiceSupportedTransactionCurrency() {Enabled = true});
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAdmin && (settings.EnforceTargetAmount && info.TargetAmount.HasValue && price >
|
||||
@@ -311,6 +328,7 @@ namespace BTCPayServer.Controllers
|
||||
NotificationURL = settings.NotificationUrl,
|
||||
FullNotifications = true,
|
||||
ExtendedNotifications = true,
|
||||
SupportedTransactionCurrencies = paymentMethods,
|
||||
RedirectURL = request.RedirectUrl ??
|
||||
new Uri(new Uri( new Uri(HttpContext.Request.GetAbsoluteRoot()), _BtcPayServerOptions.RootPath), $"apps/{appId}/crowdfund").ToString()
|
||||
}, store, HttpContext.Request.GetAbsoluteRoot(),
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace BTCPayServer.Models.AppViewModels
|
||||
public string Title { get; set; }
|
||||
public bool Custom { get; set; }
|
||||
public int? Inventory { get; set; } = null;
|
||||
public string[] PaymentMethods { get; set; }
|
||||
}
|
||||
|
||||
public class CurrencyInfoData
|
||||
|
||||
@@ -333,7 +333,9 @@ namespace BTCPayServer.Services.Apps
|
||||
Formatted = Currencies.FormatCurrency(cc.Value.Value, currency)
|
||||
}).Single(),
|
||||
Custom = c.GetDetailString("custom") == "true",
|
||||
Inventory = string.IsNullOrEmpty(c.GetDetailString("inventory")) ?(int?) null: int.Parse(c.GetDetailString("inventory"), CultureInfo.InvariantCulture)
|
||||
Inventory = string.IsNullOrEmpty(c.GetDetailString("inventory")) ?(int?) null: int.Parse(c.GetDetailString("inventory"), CultureInfo.InvariantCulture),
|
||||
PaymentMethods = c.GetDetailStringList("payment_methods")
|
||||
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
@@ -411,6 +413,14 @@ namespace BTCPayServer.Services.Apps
|
||||
{
|
||||
return GetDetail(field).FirstOrDefault()?.Value?.Value;
|
||||
}
|
||||
public string[] GetDetailStringList(string field)
|
||||
{
|
||||
if (!Value.Children.ContainsKey(field) || !( Value.Children[field] is YamlSequenceNode sequenceNode))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return sequenceNode.Children.Select(node => (node as YamlScalarNode)?.Value).Where( s => s!= null).ToArray();
|
||||
}
|
||||
}
|
||||
private class PosScalar
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user