mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2024-11-19 18:11:36 +01:00
Add ability to accept tips in POS terminal (#2983)
* Add ability to accept tips in POS terminal * Add logic for showing and hiding sections specific to a POS app type * Fix issue with floating point error
This commit is contained in:
parent
05f99f3855
commit
fd27bd94e2
@ -52,51 +52,57 @@
|
||||
<span class="text-secondary">Choose the point of sale style for your customers.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-group" id="button-price-text">
|
||||
<label asp-for="ButtonText" class="form-label" data-required></label>
|
||||
<input asp-for="ButtonText" class="form-control" required />
|
||||
<span asp-validation-for="ButtonText" class="text-danger"></span>
|
||||
</div>
|
||||
<h4 class="mt-5 mb-4">Discounts</h4>
|
||||
<div class="form-check mb-4">
|
||||
<input asp-for="ShowDiscount" type="checkbox" class="form-check-input" />
|
||||
<label asp-for="ShowDiscount" class="form-check-label"></label>
|
||||
<span asp-validation-for="ShowDiscount" class="text-danger"></span>
|
||||
<div class="mt-2">
|
||||
<span class="text-secondary">Not recommended for customer self-checkout.</span>
|
||||
<section id="discounts" class="p-0">
|
||||
<h4 class="mt-5 mb-4">Discounts</h4>
|
||||
<div class="form-check mb-4">
|
||||
<input asp-for="ShowDiscount" type="checkbox" class="form-check-input" />
|
||||
<label asp-for="ShowDiscount" class="form-check-label"></label>
|
||||
<span asp-validation-for="ShowDiscount" class="text-danger"></span>
|
||||
<div class="mt-2">
|
||||
<span class="text-secondary">Not recommended for customer self-checkout.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h4 class="mt-5 mb-4">Custom Payments</h4>
|
||||
<div class="form-group mb-4 d-flex align-items-center">
|
||||
<input asp-for="ShowCustomAmount" type="checkbox" class="btcpay-toggle me-2" data-bs-toggle="collapse" data-bs-target="#CustomAmountSettings" aria-expanded="@Model.ShowCustomAmount" aria-controls="CustomAmountSettings"/>
|
||||
<label asp-for="ShowCustomAmount" class="form-label mb-0"></label>
|
||||
<span asp-validation-for="ShowCustomAmount" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="collapse @(Model.ShowCustomAmount ? "show" : "")" id="CustomAmountSettings">
|
||||
<div class="form-group mb-0 pb-3">
|
||||
<label asp-for="CustomButtonText" class="form-label" data-required></label>
|
||||
<input asp-for="CustomButtonText" class="form-control" required />
|
||||
<span asp-validation-for="CustomButtonText" class="text-danger"></span>
|
||||
</section>
|
||||
<section id="custom-payments" class="p-0">
|
||||
<h4 class="mt-5 mb-4">Custom Payments</h4>
|
||||
<div class="form-group mb-4 d-flex align-items-center">
|
||||
<input asp-for="ShowCustomAmount" type="checkbox" class="btcpay-toggle me-2" data-bs-toggle="collapse" data-bs-target="#CustomAmountSettings" aria-expanded="@Model.ShowCustomAmount" aria-controls="CustomAmountSettings"/>
|
||||
<label asp-for="ShowCustomAmount" class="form-label mb-0"></label>
|
||||
<span asp-validation-for="ShowCustomAmount" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<h4 class="mt-5 mb-4">Tips</h4>
|
||||
<div class="form-group mb-4 d-flex align-items-center">
|
||||
<input asp-for="EnableTips" type="checkbox" class="btcpay-toggle me-2" data-bs-toggle="collapse" data-bs-target="#CustomTipsSettings" aria-expanded="@Model.EnableTips" aria-controls="CustomTipsSettings" />
|
||||
<label asp-for="EnableTips" class="form-label mb-0"></label>
|
||||
<span asp-validation-for="EnableTips" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="collapse @(Model.EnableTips ? "show" : "")" id="CustomTipsSettings">
|
||||
<div class="form-group">
|
||||
<label asp-for="CustomTipText" class="form-label" data-required></label>
|
||||
<input asp-for="CustomTipText" class="form-control" required />
|
||||
<span asp-validation-for="CustomTipText" class="text-danger"></span>
|
||||
<div class="collapse @(Model.ShowCustomAmount ? "show" : "")" id="CustomAmountSettings">
|
||||
<div class="form-group mb-0 pb-3">
|
||||
<label asp-for="CustomButtonText" class="form-label" data-required></label>
|
||||
<input asp-for="CustomButtonText" class="form-control" required />
|
||||
<span asp-validation-for="CustomButtonText" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0 pb-3">
|
||||
<label asp-for="CustomTipPercentages" class="form-label"></label>
|
||||
<input asp-for="CustomTipPercentages" class="form-control" />
|
||||
<span asp-validation-for="CustomTipPercentages" class="text-danger"></span>
|
||||
</section>
|
||||
<section id="tips" class="p-0">
|
||||
<h4 class="mt-5 mb-4">Tips</h4>
|
||||
<div class="form-group mb-4 d-flex align-items-center">
|
||||
<input asp-for="EnableTips" type="checkbox" class="btcpay-toggle me-2" data-bs-toggle="collapse" data-bs-target="#CustomTipsSettings" aria-expanded="@Model.EnableTips" aria-controls="CustomTipsSettings" />
|
||||
<label asp-for="EnableTips" class="form-label mb-0"></label>
|
||||
<span asp-validation-for="EnableTips" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="collapse @(Model.EnableTips ? "show" : "")" id="CustomTipsSettings">
|
||||
<div class="form-group">
|
||||
<label asp-for="CustomTipText" class="form-label" data-required></label>
|
||||
<input asp-for="CustomTipText" class="form-control" required />
|
||||
<span asp-validation-for="CustomTipText" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group mb-0 pb-3">
|
||||
<label asp-for="CustomTipPercentages" class="form-label"></label>
|
||||
<input asp-for="CustomTipPercentages" class="form-control" />
|
||||
<span asp-validation-for="CustomTipPercentages" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<h4 class="mt-5 mb-4">Additional Options</h4>
|
||||
@ -303,6 +309,103 @@
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
<script>
|
||||
var posStyleSelector = document.getElementById('DefaultView');
|
||||
posStyleSelector.addEventListener('change', function(e) {
|
||||
handleStyleSelected(e.target.value);
|
||||
});
|
||||
|
||||
function handleStyleSelected(style) {
|
||||
switch (style) {
|
||||
case '0': // Item list only
|
||||
hideDiscountsSection();
|
||||
hideButtonPriceTextSection();
|
||||
showCustomPaymentAmountSection();
|
||||
hideTipsSection();
|
||||
break;
|
||||
case '1': // Item list and cart
|
||||
showDiscountsSection();
|
||||
showButtonPriceTextSection();
|
||||
showCustomPaymentAmountSection();
|
||||
showTipsSection();
|
||||
break;
|
||||
case '2': // Keypad only
|
||||
hideDiscountsSection();
|
||||
hideButtonPriceTextSection();
|
||||
hideCustomPaymentAmountSection();
|
||||
showTipsSection();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** Show/hide discounts section */
|
||||
|
||||
var discountsSection = document.getElementById('discounts');
|
||||
|
||||
function hideDiscountsSection() {
|
||||
hideElement(discountsSection);
|
||||
}
|
||||
|
||||
function showDiscountsSection() {
|
||||
showElement(discountsSection);
|
||||
}
|
||||
|
||||
/***************************************/
|
||||
|
||||
/** Show/hide button text section */
|
||||
|
||||
var buttonPriceTextSection = document.getElementById('button-price-text');
|
||||
|
||||
function hideButtonPriceTextSection() {
|
||||
hideElement(buttonPriceTextSection);
|
||||
}
|
||||
|
||||
function showButtonPriceTextSection() {
|
||||
showElement(buttonPriceTextSection);
|
||||
}
|
||||
|
||||
/***************************************/
|
||||
|
||||
/** Show/hide custom payments amount seciton */
|
||||
|
||||
var customPaymentAmountSection = document.getElementById('custom-payments');
|
||||
|
||||
function hideCustomPaymentAmountSection() {
|
||||
hideElement(customPaymentAmountSection);
|
||||
}
|
||||
|
||||
function showCustomPaymentAmountSection() {
|
||||
showElement(customPaymentAmountSection);
|
||||
}
|
||||
|
||||
/***************************************/
|
||||
|
||||
/** Show/hide tips seciton */
|
||||
|
||||
var tipsSection = document.getElementById('tips');
|
||||
|
||||
function hideTipsSection() {
|
||||
hideElement(tipsSection);
|
||||
}
|
||||
|
||||
function showTipsSection() {
|
||||
showElement(tipsSection);
|
||||
}
|
||||
|
||||
/***************************************/
|
||||
|
||||
function hideElement(el) {
|
||||
el.setAttribute('hidden', true);
|
||||
}
|
||||
|
||||
function showElement(el) {
|
||||
el.removeAttribute('hidden');
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
handleStyleSelected(posStyleSelector.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<bundle name="wwwroot/bundles/pos-admin-bundle.min.js" asp-append-version="true"></bundle>
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
<div class="l-pos-header bg-primary py-3 px-3">
|
||||
@if (!string.IsNullOrEmpty(Model.CustomLogoLink))
|
||||
{
|
||||
|
||||
<img src="@Model.CustomLogoLink" height="40"/>
|
||||
}
|
||||
else
|
||||
@ -12,19 +11,19 @@
|
||||
<h1 class="mb-0">@Model.Title</h1>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
<div ref="display" class="l-pos-display pt-5 pb-3 px-3"><div class="text-muted">{{srvModel.currencyCode}}</div><span ref="amount" v-bind:style="{fontSize: fontSize + 'px'}">{{ payTotal }}</span></div>
|
||||
<div class="l-pos-keypad">
|
||||
<template
|
||||
v-for="(key, index) in keys"
|
||||
:key="index">
|
||||
<div v-if="key !== ''" class="btn"
|
||||
v-bind:class="{ 'btn-primary' : (isNaN(key) === false) || key === '.', 'btn-dark' : isNaN(key) && key !== '.' }"
|
||||
<div v-if="key !== ''" class="btn"
|
||||
v-bind:class="{ 'btn-primary' : (isNaN(key) === false) || key === '.', 'btn-dark' : isNaN(key) && key !== '.' }"
|
||||
v-on:click="buttonClicked(key)">{{ key }}</div>
|
||||
<div v-else class="btn btn-empty"></div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="l-pos-controls mt-2">
|
||||
<div class="btn btn-outline-secondary btn-lg mb-0" v-on:click="clearTotal">Clear</div><button class="btn btn-primary btn-lg mb-0" type="submit"><b>Pay</b></button>
|
||||
</div>
|
||||
@ -32,6 +31,48 @@
|
||||
<input class="form-control" type="hidden" name="amount" v-model="payTotalNumeric">
|
||||
</form>
|
||||
|
||||
@if (Model.EnableTips)
|
||||
{
|
||||
<p class="pt-5 h5">@Model.CustomTipText</p>
|
||||
<div class="input-group mb-2">
|
||||
<span class="input-group-text"><i class="fa fa-money fa-fw"></i></span>
|
||||
<input
|
||||
class="js-cart-tip form-control form-control-lg"
|
||||
disabled
|
||||
type="number"
|
||||
min="0"
|
||||
step="@Model.Step"
|
||||
v-model="tipTotal"
|
||||
name="tip"
|
||||
placeholder="Tip in @(Model.CurrencyInfo.CurrencySymbol ?? Model.CurrencyCode)"
|
||||
>
|
||||
<a
|
||||
class="js-cart-tip-remove btn btn-lg btn-danger"
|
||||
href="#"
|
||||
v-on:click="removeTip"
|
||||
><i class="fa fa-times"></i></a>
|
||||
</div>
|
||||
<div class="row">
|
||||
@if (Model.CustomTipPercentages != null && Model.CustomTipPercentages.Length > 0)
|
||||
{
|
||||
@for (int i = 0; i < Model.CustomTipPercentages.Length; i++)
|
||||
{
|
||||
var percentage = Model.CustomTipPercentages[i];
|
||||
|
||||
<div class="@(Model.CustomTipPercentages.Length > 3 ? "col-4" : "col")">
|
||||
<button
|
||||
class="js-cart-tip-btn btn btn-lg btn-light w-100 border mb-2"
|
||||
data-tip="@percentage"
|
||||
v-on:click="tipClicked(@percentage)"
|
||||
>
|
||||
@percentage%
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="text-center mt-4 mb-3 py-2">
|
||||
<svg class="logo" viewBox="0 0 192 84" xmlns="http://www.w3.org/2000/svg"><g><path d="M5.206 83.433a4.86 4.86 0 01-4.859-4.861V5.431a4.86 4.86 0 119.719 0v73.141a4.861 4.861 0 01-4.86 4.861" fill="#CEDC21" class="logo-brand-light"/><path d="M5.209 83.433a4.862 4.862 0 01-2.086-9.253L32.43 60.274 2.323 38.093a4.861 4.861 0 015.766-7.826l36.647 26.999a4.864 4.864 0 01-.799 8.306L7.289 82.964a4.866 4.866 0 01-2.08.469" fill="#51B13E" class="logo-brand-medium"/><path d="M5.211 54.684a4.86 4.86 0 01-2.887-8.774L32.43 23.73 3.123 9.821a4.861 4.861 0 014.166-8.784l36.648 17.394a4.86 4.86 0 01.799 8.305l-36.647 27a4.844 4.844 0 01-2.878.948" fill="#CEDC21" class="logo-brand-light"/><path d="M10.066 31.725v20.553L24.01 42.006z" fill="#1E7A44" class="logo-brand-dark"/><path d="M10.066 5.431A4.861 4.861 0 005.206.57 4.86 4.86 0 00.347 5.431v61.165h9.72V5.431h-.001z" fill="#CEDC21" class="logo-brand-light"/><path d="M74.355 41.412c3.114.884 4.84 3.704 4.84 7.238 0 5.513-3.368 8.082-7.955 8.082H60.761V27.271h9.259c4.504 0 7.997 2.146 7.997 7.743 0 2.821-1.179 5.43-3.662 6.398m-4.293-.716c3.324 0 6.018-1.179 6.018-5.724 0-4.586-2.776-5.808-6.145-5.808h-7.197v11.531h7.324v.001zm1.052 14.099c3.366 0 6.06-1.768 6.06-6.145 0-4.713-3.072-6.144-6.901-6.144h-7.534v12.288h8.375v.001zM98.893 27.271v1.81h-8.122v27.651h-1.979V29.081h-8.123v-1.81zM112.738 26.85c5.01 0 9.554 2.524 10.987 8.543h-1.895c-1.348-4.923-5.303-6.732-9.134-6.732-6.944 0-10.605 5.681-10.605 13.341 0 8.08 3.661 13.256 10.646 13.256 4.125 0 7.828-1.85 9.26-7.279h1.895c-1.264 6.271-6.229 9.174-11.154 9.174-7.87 0-12.583-5.808-12.583-15.15 0-8.966 4.969-15.153 12.583-15.153M138.709 27.271c5.091 0 8.795 3.326 8.795 9.764 0 6.06-3.704 9.722-8.795 9.722h-7.746v9.976h-1.935V27.271h9.681zm0 17.549c3.745 0 6.816-2.397 6.816-7.827 0-5.429-2.947-7.869-6.816-7.869h-7.746V44.82h7.746zM147.841 56.732v-.255l11.741-29.29h.885l11.615 29.29v.255h-2.062l-3.322-8.501H153.27l-3.324 8.501h-2.105zm12.164-26.052l-6.059 15.697h12.078l-6.019-15.697zM189.551 27.271h2.104v.293l-9.176 16.92v12.248h-2.02V44.484l-9.216-16.961v-.252h2.147l3.997 7.492 4.043 7.786h.04l4.081-7.786z" class="logo-brand-text"/></g></svg>
|
||||
</div>
|
||||
|
@ -11,6 +11,8 @@ document.addEventListener("DOMContentLoaded",function (ev) {
|
||||
srvModel: window.srvModel,
|
||||
payTotal: '0',
|
||||
payTotalNumeric: 0,
|
||||
tipTotal: null,
|
||||
tipTotalNumeric: 0,
|
||||
fontSize: displayFontSize,
|
||||
defaultFontSize: displayFontSize,
|
||||
keys: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '0', 'C']
|
||||
@ -52,6 +54,8 @@ document.addEventListener("DOMContentLoaded",function (ev) {
|
||||
clearTotal: function() {
|
||||
this.payTotal = '0';
|
||||
this.payTotalNumeric = 0;
|
||||
this.tipTotal = null;
|
||||
this.tipTotalNumeric = 0;
|
||||
},
|
||||
buttonClicked: function(key) {
|
||||
var payTotal = this.payTotal;
|
||||
@ -70,17 +74,33 @@ document.addEventListener("DOMContentLoaded",function (ev) {
|
||||
}
|
||||
payTotal += key;
|
||||
|
||||
var divsibility = this.srvModel.currencyInfo.divisibility;
|
||||
var divisibility = this.srvModel.currencyInfo.divisibility;
|
||||
var decimalIndex = payTotal.indexOf('.')
|
||||
if (decimalIndex !== -1 && (payTotal.length - decimalIndex-1 > divsibility)) {
|
||||
if (decimalIndex !== -1 && (payTotal.length - decimalIndex - 1 > divisibility)) {
|
||||
payTotal = payTotal.replace(".", "");
|
||||
payTotal = payTotal.substr(0, payTotal.length - divsibility) + "." + payTotal.substr(payTotal.length - divsibility);
|
||||
payTotal = payTotal.substr(0, payTotal.length - divisibility) + "." + payTotal.substr(payTotal.length - divisibility);
|
||||
}
|
||||
}
|
||||
|
||||
this.payTotal = payTotal;
|
||||
this.payTotalNumeric = parseFloat(payTotal);
|
||||
}
|
||||
this.tipTotalNumeric = 0;
|
||||
this.tipTotal = null;
|
||||
},
|
||||
tipClicked: function(percentage) {
|
||||
this.payTotalNumeric -= this.tipTotalNumeric;
|
||||
this.tipTotalNumeric = parseFloat((this.payTotalNumeric * (percentage / 100)).toFixed(this.srvModel.currencyInfo.divisibility));
|
||||
this.payTotalNumeric = parseFloat((this.payTotalNumeric + this.tipTotalNumeric).toFixed(this.srvModel.currencyInfo.divisibility));
|
||||
|
||||
this.payTotal = this.payTotalNumeric.toString(10);
|
||||
this.tipTotal = this.tipTotalNumeric === 0 ? null : this.tipTotalNumeric.toFixed(this.srvModel.currencyInfo.divisibility);
|
||||
},
|
||||
removeTip: function() {
|
||||
this.payTotalNumeric -= this.tipTotalNumeric;
|
||||
this.payTotal = this.payTotalNumeric.toString(10);
|
||||
this.tipTotalNumeric = 0;
|
||||
this.tipTotal = null;
|
||||
},
|
||||
}
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user