mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-21 22:11:48 +01:00
Merge pull request #2299 from dennisreimann/pos-item-button-text
PoS: Custom buy button text per product
This commit is contained in:
commit
6d9b93a407
8 changed files with 68 additions and 31 deletions
|
@ -26,6 +26,21 @@ You can also generate blocks:
|
|||
.\docker-bitcoin-generate.ps1 3
|
||||
```
|
||||
|
||||
### Using Polar to test Lightning payments
|
||||
|
||||
- Install and run [Polar](https://lightningpolar.com/). Setup a small network of nodes.
|
||||
- Go to your store's General Settings and enable Lightning.
|
||||
- Build your connection string using the Connect infomation in the Polar app.
|
||||
|
||||
LND Connection string example:
|
||||
type=lnd-rest;server=https://127.0.0.1:8084/;macaroonfilepath="local path to admin.macaroon on your computer, without these quotes";allowinsecure=true
|
||||
|
||||
Now you can create a Lightning invoice on BTCPay Server regtest and make a payment through Polar.
|
||||
|
||||
PLEASE NOTE: You may get an exception break in Visual Studio. You must quickly click "Continue" in VS so the invoice is generated.
|
||||
Or, uncheck the box that says, "Break when this exceptiontype is thrown".
|
||||
|
||||
|
||||
### Using the test litecoin-cli
|
||||
|
||||
Same as bitcoin-cli, but with `.\docker-litecoin-cli.ps1` and `.\docker-litecoin-cli.sh` instead.
|
||||
|
|
|
@ -478,12 +478,21 @@ namespace BTCPayServer.Tests
|
|||
s.Driver.FindElement(By.Id("SelectedStore")).SendKeys(storeName);
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
s.Driver.FindElement(By.Id("DefaultView")).SendKeys("Cart");
|
||||
s.Driver.FindElement(By.CssSelector(".template-item:nth-of-type(1) .btn-primary")).Click();
|
||||
s.Driver.FindElement(By.Id("BuyButtonText")).SendKeys("Take my money");
|
||||
s.Driver.FindElement(By.Id("SaveItemChanges")).Click();
|
||||
s.Driver.FindElement(By.Id("ToggleRawEditor")).Click();
|
||||
|
||||
var template = s.Driver.FindElement(By.Id("Template")).GetAttribute("value");
|
||||
Assert.Contains("buyButtonText: Take my money", template);
|
||||
|
||||
s.Driver.FindElement(By.Id("SaveSettings")).Click();
|
||||
s.Driver.FindElement(By.Id("ViewApp")).Click();
|
||||
|
||||
var posBaseUrl = s.Driver.Url.Replace("/Cart", "");
|
||||
Assert.True(s.Driver.PageSource.Contains("Tea shop"), "Unable to create PoS");
|
||||
Assert.True(s.Driver.PageSource.Contains("Cart"), "PoS not showing correct default view");
|
||||
Assert.True(s.Driver.PageSource.Contains("Take my money"), "PoS not showing correct default view");
|
||||
|
||||
s.Driver.Url = posBaseUrl + "/static";
|
||||
Assert.False(s.Driver.PageSource.Contains("Cart"), "Static PoS not showing correct view");
|
||||
|
|
|
@ -17,6 +17,7 @@ namespace BTCPayServer.Models.AppViewModels
|
|||
public ItemPrice Price { get; set; }
|
||||
public string Title { get; set; }
|
||||
public bool Custom { get; set; }
|
||||
public string BuyButtonText { get; set; }
|
||||
public int? Inventory { get; set; } = null;
|
||||
public string[] PaymentMethods { get; set; }
|
||||
}
|
||||
|
|
|
@ -321,9 +321,9 @@ namespace BTCPayServer.Services.Apps
|
|||
Formatted = Currencies.FormatCurrency(cc.Value.Value, currency)
|
||||
}).Single(),
|
||||
Custom = c.GetDetailString("custom") == "true",
|
||||
BuyButtonText = c.GetDetailString("buyButtonText"),
|
||||
Inventory = string.IsNullOrEmpty(c.GetDetailString("inventory")) ? (int?)null : int.Parse(c.GetDetailString("inventory"), CultureInfo.InvariantCulture),
|
||||
PaymentMethods = c.GetDetailStringList("payment_methods")
|
||||
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
<button type="button" class="btn btn-secondary" v-on:click="editItem(-1)" id="btn-add">
|
||||
<i class="fa fa-plus fa-fw"></i> Add
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary ml-2" v-on:click="toggleTemplateElement()">
|
||||
<button type="button" class="btn btn-secondary ml-2" v-on:click="toggleTemplateElement()" id="ToggleRawEditor">
|
||||
Toggle raw editor
|
||||
</button>
|
||||
</div>
|
||||
|
@ -113,11 +113,17 @@
|
|||
<input type="text" required pattern="[^\*#]+" class="form-control mb-2" v-model="editingItem.id" ref="txtId"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="col">
|
||||
<label>Buy Button Text</label>
|
||||
<input type="text" id="BuyButtonText" class="form-control mb-2" v-model="editingItem.buyButtonText" ref="txtBuyButtonText"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal" v-on:click="clearEditingItem()">Close</button>
|
||||
<button type="button" class="btn btn-primary" v-on:click="saveEditingItem()">Save Changes</button>
|
||||
<button type="button" class="btn btn-primary" v-on:click="saveEditingItem()" id="SaveItemChanges">Save Changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -156,47 +162,44 @@ $(function() {
|
|||
},
|
||||
getImage: function(item){
|
||||
return {
|
||||
"background-image" : "url('"+(this.unEscapeKey(item.image) || "/img/img-placeholder.svg") +"')",
|
||||
"background-image" : "url('" + (this.unEscapeKey(item.image) || "/img/img-placeholder.svg") +"')",
|
||||
"opacity": item.image? 1: 0.5
|
||||
}
|
||||
},
|
||||
getInputElement : function(){ return $("#"+this.elementId); },
|
||||
getInputElement : function(){ return $("#" + this.elementId); },
|
||||
getModalElement : function(){ return $("#product-modal"); },
|
||||
loadYml: function(){
|
||||
var result = [];
|
||||
var template = this.getInputElement().val().trim();
|
||||
|
||||
var lines = [];
|
||||
var items = template.split("\n");
|
||||
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
if(items[i] === ""){
|
||||
if (items[i] === ""){
|
||||
continue;
|
||||
}
|
||||
if(items[i].startsWith(" ")){
|
||||
if (items[i].startsWith(" ")){
|
||||
lines[lines.length-1]+=items[i] + "\n";
|
||||
}else{
|
||||
|
||||
} else {
|
||||
lines.push(items[i] + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Split products from the template
|
||||
for (var kl in lines) {
|
||||
var line = lines[kl],
|
||||
product = line.split("\n"),
|
||||
id, price, title, description, image = null,
|
||||
custom, inventory=null, paymentMethods = [];
|
||||
|
||||
var goingThroughMethods = false;
|
||||
var line = lines[kl], product = line.split("\n"), goingThroughMethods = false,
|
||||
id = null, price = null, title = null, description = null, image = null,
|
||||
custom = null, buyButtonText = null, inventory = null, paymentMethods = [];
|
||||
|
||||
for (var kp in product) {
|
||||
var productProperty = product[kp].trim();
|
||||
|
||||
if (kp == 0) {
|
||||
id = productProperty.replace(":", "");
|
||||
}
|
||||
if(productProperty.startsWith("-") && goingThroughMethods){
|
||||
if (productProperty.startsWith("-") && goingThroughMethods) {
|
||||
paymentMethods.push(productProperty.substr(1));
|
||||
}else{
|
||||
} else {
|
||||
goingThroughMethods = false;
|
||||
}
|
||||
|
||||
|
@ -215,6 +218,9 @@ $(function() {
|
|||
if (productProperty.indexOf('custom:') !== -1) {
|
||||
custom = productProperty.replace('custom:', '').trim();
|
||||
}
|
||||
if (productProperty.indexOf('buyButtonText:') !== -1) {
|
||||
buyButtonText = productProperty.replace('buyButtonText:', '').trim();
|
||||
}
|
||||
if (productProperty.indexOf('inventory:') !== -1) {
|
||||
inventory = parseInt(productProperty.replace('inventory:', '').trim(),10);
|
||||
}
|
||||
|
@ -232,6 +238,7 @@ $(function() {
|
|||
image: image || null,
|
||||
description: description || '',
|
||||
custom: custom === "true",
|
||||
buyButtonText: buyButtonText,
|
||||
inventory: isNaN(inventory)? null: inventory,
|
||||
paymentMethods: paymentMethods
|
||||
});
|
||||
|
@ -250,6 +257,7 @@ $(function() {
|
|||
image = product.image,
|
||||
description = product.description,
|
||||
custom = product.custom,
|
||||
buyButtonText = product.buyButtonText,
|
||||
inventory = product.inventory,
|
||||
paymentMethods = product.paymentMethods;
|
||||
|
||||
|
@ -269,6 +277,9 @@ $(function() {
|
|||
if (custom != null) {
|
||||
template += ' custom: ' + custom + '\n';
|
||||
}
|
||||
if (buyButtonText != null && buyButtonText.length > 0) {
|
||||
template += ' buyButtonText: ' + buyButtonText + '\n';
|
||||
}
|
||||
if(paymentMethods != null && paymentMethods.length > 0){
|
||||
template+= ' payment_methods:\n';
|
||||
for (var method of paymentMethods){
|
||||
|
|
|
@ -292,10 +292,10 @@
|
|||
v-bind:class="{ 'btn-disabled': loading}"
|
||||
:disabled="!active || loading"
|
||||
type="submit">
|
||||
<div v-if="loading" class="spinner-grow spinner-grow-sm" role="status">
|
||||
<div v-if="loading" class="spinner-grow spinner-grow-sm" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
Continue
|
||||
{{perk.buyButtonText || 'Continue'}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -304,7 +304,7 @@
|
|||
|
||||
<span v-if="perk.inventory != null && perk.inventory > 0" class="text-center text-muted">{{perk.inventory}} left</span>
|
||||
<span v-if="perk.inventory != null && perk.inventory <= 0" class="text-center text-muted">Sold out</span>
|
||||
<span v-if="perk.sold" >{{perk.sold}} Contributor{{perk.sold > 1? "s": ""}}</span>
|
||||
<span v-if="perk.sold">{{perk.sold}} Contributor{{perk.sold > 1? "s": ""}}</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
</th>
|
||||
</tr>
|
||||
}
|
||||
@if (@Model.ShowDiscount)
|
||||
@if (Model.ShowDiscount)
|
||||
{
|
||||
<tr>
|
||||
<th colspan="5" class="border-top-0">
|
||||
|
@ -253,7 +253,7 @@
|
|||
</div>
|
||||
<div class="card-footer pt-0 bg-transparent border-0">
|
||||
|
||||
<span class="text-muted small">@Model.ButtonText.Replace("{0}",item.Price.Formatted).Replace("{Price}",item.Price.Formatted)</span>
|
||||
<span class="text-muted small">@((item.BuyButtonText ?? Model.ButtonText).Replace("{0}",item.Price.Formatted).Replace("{Price}",item.Price.Formatted))</span>
|
||||
@if (item.Inventory.HasValue)
|
||||
{
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
<div class="container d-flex h-100">
|
||||
<div class="justify-content-center align-self-center text-center mx-auto px-2 py-3 w-100" style="margin: auto;">
|
||||
@if (this.TempData.HasStatusMessage())
|
||||
@if (TempData.HasStatusMessage())
|
||||
{
|
||||
<partial name="_StatusMessage" />
|
||||
}
|
||||
|
@ -33,7 +33,6 @@
|
|||
{
|
||||
<p class="card-text">@System.Net.WebUtility.HtmlDecode(item.Description)</p>
|
||||
}
|
||||
|
||||
</div>
|
||||
<div class="card-footer bg-transparent border-0">
|
||||
@if (!item.Inventory.HasValue || item.Inventory.Value > 0)
|
||||
|
@ -48,9 +47,11 @@
|
|||
<span class="input-group-text">@Model.CurrencySymbol</span>
|
||||
</div>
|
||||
<input class="form-control" type="number" min="@item.Price.Value" step="@Model.Step" name="amount"
|
||||
value="@item.Price.Value" placeholder="Amount">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-primary" type="submit">@Model.CustomButtonText</button>
|
||||
value="@item.Price.Value" placeholder="Amount" style="flex: 2 0 80px;">
|
||||
<div class="input-group-append flex-fill">
|
||||
<button class="btn btn-primary text-nowrap flex-fill" type="submit">
|
||||
@(item.BuyButtonText ?? Model.CustomButtonText)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -59,7 +60,7 @@
|
|||
{
|
||||
<form method="post" asp-controller="AppsPublic" asp-action="ViewPointOfSale" asp-route-appId="@Model.AppId" asp-antiforgery="false">
|
||||
<button type="submit" name="choiceKey" class="js-add-cart btn btn-primary" value="@item.Id">
|
||||
@Model.ButtonText.Replace("{0}",item.Price.Formatted).Replace("{Price}",item.Price.Formatted)
|
||||
@((item.BuyButtonText ?? Model.ButtonText).Replace("{0}",item.Price.Formatted).Replace("{Price}",item.Price.Formatted))
|
||||
</button>
|
||||
</form>
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue