Merge pull request #2299 from dennisreimann/pos-item-button-text

PoS: Custom buy button text per product
This commit is contained in:
Nicolas Dorier 2021-02-26 11:45:44 +09:00 committed by GitHub
commit 6d9b93a407
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 68 additions and 31 deletions

View file

@ -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.

View file

@ -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");

View file

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

View file

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

View file

@ -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){

View file

@ -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>

View file

@ -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)
{

View file

@ -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>
}