Add option to show recommeded fee on checkout invoice

Address #1036
This commit is contained in:
Umar Bolatov
2019-10-07 21:06:12 -07:00
parent e68d76b56d
commit c908301b84
17 changed files with 70 additions and 7 deletions

View File

@@ -104,7 +104,6 @@ namespace BTCPayServer
public abstract class BTCPayNetworkBase public abstract class BTCPayNetworkBase
{ {
public string CryptoCode { get; internal set; } public string CryptoCode { get; internal set; }
public string BlockExplorerLink { get; internal set; } public string BlockExplorerLink { get; internal set; }
public string DisplayName { get; set; } public string DisplayName { get; set; }

View File

@@ -225,7 +225,6 @@ namespace BTCPayServer.Controllers
(1m + (changelly.AmountMarkupPercentage / 100m))) (1m + (changelly.AmountMarkupPercentage / 100m)))
: (decimal?)null; : (decimal?)null;
var paymentMethodHandler = _paymentMethodHandlerDictionary[paymentMethodId]; var paymentMethodHandler = _paymentMethodHandlerDictionary[paymentMethodId];
var model = new PaymentModel() var model = new PaymentModel()
{ {
@@ -244,6 +243,8 @@ namespace BTCPayServer.Controllers
OrderAmountFiat = OrderAmountFromInvoice(network.CryptoCode, invoice.ProductInformation), OrderAmountFiat = OrderAmountFromInvoice(network.CryptoCode, invoice.ProductInformation),
CustomerEmail = invoice.RefundMail, CustomerEmail = invoice.RefundMail,
RequiresRefundEmail = storeBlob.RequiresRefundEmail, RequiresRefundEmail = storeBlob.RequiresRefundEmail,
ShowRecommendedFee = storeBlob.ShowRecommendedFee,
FeeRate = paymentMethodDetails.GetFeeRate(),
ExpirationSeconds = Math.Max(0, (int)(invoice.ExpirationTime - DateTimeOffset.UtcNow).TotalSeconds), ExpirationSeconds = Math.Max(0, (int)(invoice.ExpirationTime - DateTimeOffset.UtcNow).TotalSeconds),
MaxTimeSeconds = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalSeconds, MaxTimeSeconds = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalSeconds,
MaxTimeMinutes = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalMinutes, MaxTimeMinutes = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalMinutes,

View File

@@ -363,6 +363,7 @@ namespace BTCPayServer.Controllers
vm.HtmlTitle = storeBlob.HtmlTitle; vm.HtmlTitle = storeBlob.HtmlTitle;
vm.SetLanguages(_LangService, storeBlob.DefaultLang); vm.SetLanguages(_LangService, storeBlob.DefaultLang);
vm.RequiresRefundEmail = storeBlob.RequiresRefundEmail; vm.RequiresRefundEmail = storeBlob.RequiresRefundEmail;
vm.ShowRecommendedFee = storeBlob.ShowRecommendedFee;
vm.OnChainMinValue = storeBlob.OnChainMinValue?.ToString() ?? ""; vm.OnChainMinValue = storeBlob.OnChainMinValue?.ToString() ?? "";
vm.LightningMaxValue = storeBlob.LightningMaxValue?.ToString() ?? ""; vm.LightningMaxValue = storeBlob.LightningMaxValue?.ToString() ?? "";
vm.LightningAmountInSatoshi = storeBlob.LightningAmountInSatoshi; vm.LightningAmountInSatoshi = storeBlob.LightningAmountInSatoshi;
@@ -421,6 +422,7 @@ namespace BTCPayServer.Controllers
blob.HtmlTitle = string.IsNullOrWhiteSpace(model.HtmlTitle) ? null : model.HtmlTitle; blob.HtmlTitle = string.IsNullOrWhiteSpace(model.HtmlTitle) ? null : model.HtmlTitle;
blob.DefaultLang = model.DefaultLang; blob.DefaultLang = model.DefaultLang;
blob.RequiresRefundEmail = model.RequiresRefundEmail; blob.RequiresRefundEmail = model.RequiresRefundEmail;
blob.ShowRecommendedFee = model.ShowRecommendedFee;
blob.OnChainMinValue = onchainMinValue; blob.OnChainMinValue = onchainMinValue;
blob.LightningMaxValue = lightningMaxValue; blob.LightningMaxValue = lightningMaxValue;
blob.LightningAmountInSatoshi = model.LightningAmountInSatoshi; blob.LightningAmountInSatoshi = model.LightningAmountInSatoshi;

View File

@@ -21,6 +21,7 @@ namespace BTCPayServer.Data
MonitoringExpiration = 1440; MonitoringExpiration = 1440;
PaymentTolerance = 0; PaymentTolerance = 0;
RequiresRefundEmail = true; RequiresRefundEmail = true;
ShowRecommendedFee = true;
} }
[Obsolete("Use NetworkFeeMode instead")] [Obsolete("Use NetworkFeeMode instead")]
@@ -39,6 +40,8 @@ namespace BTCPayServer.Data
public bool RequiresRefundEmail { get; set; } public bool RequiresRefundEmail { get; set; }
public bool ShowRecommendedFee { get; set; }
CurrencyPair[] _DefaultCurrencyPairs; CurrencyPair[] _DefaultCurrencyPairs;
[JsonProperty("defaultCurrencyPairs", ItemConverterType = typeof(CurrencyPairJsonConverter))] [JsonProperty("defaultCurrencyPairs", ItemConverterType = typeof(CurrencyPairJsonConverter))]
public CurrencyPair[] DefaultCurrencyPairs public CurrencyPair[] DefaultCurrencyPairs

View File

@@ -38,6 +38,8 @@ namespace BTCPayServer.Models.InvoicingModels
public string BtcDue { get; set; } public string BtcDue { get; set; }
public string CustomerEmail { get; set; } public string CustomerEmail { get; set; }
public bool RequiresRefundEmail { get; set; } public bool RequiresRefundEmail { get; set; }
public bool ShowRecommendedFee { get; set; }
public decimal FeeRate { get; set; }
public int ExpirationSeconds { get; set; } public int ExpirationSeconds { get; set; }
public string Status { get; set; } public string Status { get; set; }
public string MerchantRefLink { get; set; } public string MerchantRefLink { get; set; }

View File

@@ -39,6 +39,9 @@ namespace BTCPayServer.Models.StoreViewModels
[Display(Name = "Requires a refund email")] [Display(Name = "Requires a refund email")]
public bool RequiresRefundEmail { get; set; } public bool RequiresRefundEmail { get; set; }
[Display(Name = "Show recommended fee")]
public bool ShowRecommendedFee { get; set; }
[Display(Name = "Do not propose on chain payment if the value of the invoice is below...")] [Display(Name = "Do not propose on chain payment if the value of the invoice is below...")]
[MaxLength(20)] [MaxLength(20)]
public string OnChainMinValue { get; set; } public string OnChainMinValue { get; set; }

View File

@@ -21,6 +21,11 @@ namespace BTCPayServer.Payments.Bitcoin
{ {
return NextNetworkFee.ToDecimal(MoneyUnit.BTC); return NextNetworkFee.ToDecimal(MoneyUnit.BTC);
} }
public decimal GetFeeRate() {
return FeeRate.SatoshiPerByte;
}
public void SetPaymentDestination(string newPaymentDestination) public void SetPaymentDestination(string newPaymentDestination)
{ {
DepositAddress = newPaymentDestination; DepositAddress = newPaymentDestination;

View File

@@ -23,6 +23,11 @@ namespace BTCPayServer.Payments
/// <returns></returns> /// <returns></returns>
decimal GetNextNetworkFee(); decimal GetNextNetworkFee();
/// <summary> /// <summary>
/// Returns recommended fee rate for a transaction
/// </summary>
/// <returns></returns>
decimal GetFeeRate();
/// <summary>
/// Change the payment destination (internal plumbing) /// Change the payment destination (internal plumbing)
/// </summary> /// </summary>
/// <param name="newPaymentDestination"></param> /// <param name="newPaymentDestination"></param>

View File

@@ -26,6 +26,11 @@ namespace BTCPayServer.Payments.Lightning
{ {
return 0.0m; return 0.0m;
} }
public decimal GetFeeRate() {
return 0.0m;
}
public void SetPaymentDestination(string newPaymentDestination) public void SetPaymentDestination(string newPaymentDestination)
{ {
BOLT11 = newPaymentDestination; BOLT11 = newPaymentDestination;

View File

@@ -18,6 +18,11 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments
{ {
return NextNetworkFee; return NextNetworkFee;
} }
public decimal GetFeeRate() {
return 0.0m;
}
public void SetPaymentDestination(string newPaymentDestination) public void SetPaymentDestination(string newPaymentDestination)
{ {
DepositAddress = newPaymentDestination; DepositAddress = newPaymentDestination;

View File

@@ -153,7 +153,7 @@
</component> </component>
</div> </div>
<div class="payment-box"> <div class="payment-box" v-bind:class="{ 'payment-box--with-recommended-fee': showRecommendedFee && !showEmailForm }">
<div class="bp-view payment manual-flow enter-contact-email" id="emailAddressView" v-bind:class="{ 'active': showEmailForm}"> <div class="bp-view payment manual-flow enter-contact-email" id="emailAddressView" v-bind:class="{ 'active': showEmailForm}">
<form class="manual__step-one refund-address-form contact-email-form" id="emailAddressForm" name="emailAddressForm" novalidate="" v-on:submit.prevent="onEmailSubmit"> <form class="manual__step-one refund-address-form contact-email-form" id="emailAddressForm" name="emailAddressForm" novalidate="" v-on:submit.prevent="onEmailSubmit">
<div class="manual__step-one__header"> <div class="manual__step-one__header">

View File

@@ -185,6 +185,9 @@
showEmailForm: function(){ showEmailForm: function(){
return this.srvModel.requiresRefundEmail && (!this.srvModel.customerEmail || !this.validateEmail(this.srvModel.customerEmail)) && !this.invoiceUnpayable && !this.invoicePaid; return this.srvModel.requiresRefundEmail && (!this.srvModel.customerEmail || !this.validateEmail(this.srvModel.customerEmail)) && !this.invoiceUnpayable && !this.invoicePaid;
}, },
showRecommendedFee: function(){
return this.srvModel.showRecommendedFee && this.srvModel.feeRate != 0;
},
invoiceUnpayable: function(){ invoiceUnpayable: function(){
return ["expired", "invalid"].indexOf(this.srvModel.status) >= 0; return ["expired", "invalid"].indexOf(this.srvModel.status) >= 0;
}, },

View File

@@ -167,7 +167,13 @@
</center> </center>
</nav> </nav>
</div> </div>
} }
@if (Model.ShowRecommendedFee) {
<div id="recommended-fee" class="recommended-fee" v-bind:class="{ hide: !srvModel.feeRate }">
<span>{{$t("Recommended_Fee", srvModel)}}</span>
</div>
}
</div> </div>
</script> </script>

View File

@@ -65,9 +65,14 @@
</div> </div>
<div class="form-check"> <div class="form-check">
<input asp-for="RedirectAutomatically" type="checkbox" class="form-check-input" /> <input asp-for="RedirectAutomatically" type="checkbox" class="form-check-input" />
<label asp-for="RedirectAutomatically" class="form-check-label" ></label> <label asp-for="RedirectAutomatically" class="form-check-label"></label>
<span asp-validation-for="RedirectAutomatically" class="text-danger"></span> <span asp-validation-for="RedirectAutomatically" class="text-danger"></span>
</div> </div>
<div class="form-check">
<input asp-for="ShowRecommendedFee" type="checkbox" class="form-check-input" />
<label asp-for="ShowRecommendedFee" class="form-check-label"></label>
<p class="form-text text-muted">Fee will be shown for BTC and LTC onchain payments only.</p>
</div>
</div> </div>
<button name="command" type="submit" class="btn btn-primary" value="Save">Save</button> <button name="command" type="submit" class="btn btn-primary" value="Save">Save</button>
</form> </form>

View File

@@ -9015,6 +9015,10 @@ strong {
min-height: 393px; min-height: 393px;
} }
.payment-box--with-recommended-fee {
min-height: 430px;
}
.paypro { .paypro {
padding: 10px; padding: 10px;
} }
@@ -9248,6 +9252,20 @@ strong {
padding-bottom: 40px; padding-bottom: 40px;
} }
.recommended-fee {
box-sizing: border-box;
color: #818EA9;
font-size: 14px;
font-weight: 300;
position: absolute;
top: calc(100% - 40px);
left: 50%;
padding: 0 16px;
text-align: center;
transform: translateX(-50%);
width: 100%;
}
.success-message { .success-message {
color: rgba(111, 111, 111, 0.9); color: rgba(111, 111, 111, 0.9);
text-align: center; text-align: center;

View File

@@ -47,5 +47,6 @@
"Pay with CoinSwitch": "Pay with CoinSwitch", "Pay with CoinSwitch": "Pay with CoinSwitch",
"Pay with Changelly": "Pay with Changelly", "Pay with Changelly": "Pay with Changelly",
"Close": "Close", "Close": "Close",
"NotPaid_ExtraTransaction": "The invoice hasn't been paid in full. Please send another transaction to cover amount Due." "NotPaid_ExtraTransaction": "The invoice hasn't been paid in full. Please send another transaction to cover amount Due.",
"Recommended_Fee": "Recommended fee: {{feeRate}} sat/byte"
} }