mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-03 17:36:59 +01:00
Support Topup Invoices in Apps
This commit is contained in:
parent
9df4429fc2
commit
7d2aa28e1f
12 changed files with 129 additions and 89 deletions
|
@ -114,7 +114,7 @@ namespace BTCPayServer.Controllers
|
||||||
[DomainMappingConstraint(AppType.PointOfSale)]
|
[DomainMappingConstraint(AppType.PointOfSale)]
|
||||||
public async Task<IActionResult> ViewPointOfSale(string appId,
|
public async Task<IActionResult> ViewPointOfSale(string appId,
|
||||||
PosViewType viewType,
|
PosViewType viewType,
|
||||||
[ModelBinder(typeof(InvariantDecimalModelBinder))] decimal amount,
|
[ModelBinder(typeof(InvariantDecimalModelBinder))] decimal? amount,
|
||||||
string email,
|
string email,
|
||||||
string orderId,
|
string orderId,
|
||||||
string notificationUrl,
|
string notificationUrl,
|
||||||
|
@ -136,7 +136,7 @@ namespace BTCPayServer.Controllers
|
||||||
return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId, viewType = viewType });
|
return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId, viewType = viewType });
|
||||||
}
|
}
|
||||||
string title = null;
|
string title = null;
|
||||||
var price = 0.0m;
|
decimal? price = null;
|
||||||
Dictionary<string, InvoiceSupportedTransactionCurrency> paymentMethods = null;
|
Dictionary<string, InvoiceSupportedTransactionCurrency> paymentMethods = null;
|
||||||
ViewPointOfSaleViewModel.Item choice = null;
|
ViewPointOfSaleViewModel.Item choice = null;
|
||||||
if (!string.IsNullOrEmpty(choiceKey))
|
if (!string.IsNullOrEmpty(choiceKey))
|
||||||
|
@ -146,9 +146,17 @@ namespace BTCPayServer.Controllers
|
||||||
if (choice == null)
|
if (choice == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
title = choice.Title;
|
title = choice.Title;
|
||||||
price = choice.Price.Value;
|
if (choice.Custom == "topup")
|
||||||
if (amount > price)
|
{
|
||||||
price = amount;
|
price = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
price = choice.Price.Value;
|
||||||
|
if (amount > price)
|
||||||
|
price = amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (choice.Inventory.HasValue)
|
if (choice.Inventory.HasValue)
|
||||||
{
|
{
|
||||||
|
@ -277,10 +285,7 @@ namespace BTCPayServer.Controllers
|
||||||
[DomainMappingConstraintAttribute(AppType.Crowdfund)]
|
[DomainMappingConstraintAttribute(AppType.Crowdfund)]
|
||||||
public async Task<IActionResult> ContributeToCrowdfund(string appId, ContributeToCrowdfund request, CancellationToken cancellationToken)
|
public async Task<IActionResult> ContributeToCrowdfund(string appId, ContributeToCrowdfund request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (request.Amount <= 0)
|
|
||||||
{
|
|
||||||
return NotFound("Please provide an amount greater than 0");
|
|
||||||
}
|
|
||||||
var app = await _AppService.GetApp(appId, AppType.Crowdfund, true);
|
var app = await _AppService.GetApp(appId, AppType.Crowdfund, true);
|
||||||
|
|
||||||
if (app == null)
|
if (app == null)
|
||||||
|
@ -307,7 +312,7 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
var store = await _AppService.GetStore(app);
|
var store = await _AppService.GetStore(app);
|
||||||
var title = settings.Title;
|
var title = settings.Title;
|
||||||
var price = request.Amount;
|
decimal? price = request.Amount;
|
||||||
Dictionary<string, InvoiceSupportedTransactionCurrency> paymentMethods = null;
|
Dictionary<string, InvoiceSupportedTransactionCurrency> paymentMethods = null;
|
||||||
ViewPointOfSaleViewModel.Item choice = null;
|
ViewPointOfSaleViewModel.Item choice = null;
|
||||||
if (!string.IsNullOrEmpty(request.ChoiceKey))
|
if (!string.IsNullOrEmpty(request.ChoiceKey))
|
||||||
|
@ -317,11 +322,17 @@ namespace BTCPayServer.Controllers
|
||||||
if (choice == null)
|
if (choice == null)
|
||||||
return NotFound("Incorrect option provided");
|
return NotFound("Incorrect option provided");
|
||||||
title = choice.Title;
|
title = choice.Title;
|
||||||
price = choice.Price.Value;
|
|
||||||
if (request.Amount > price)
|
|
||||||
price = request.Amount;
|
|
||||||
|
|
||||||
|
|
||||||
|
if (choice.Custom == "topup")
|
||||||
|
{
|
||||||
|
price = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
price = choice.Price.Value;
|
||||||
|
if (request.Amount > price)
|
||||||
|
price = request.Amount;
|
||||||
|
}
|
||||||
if (choice.Inventory.HasValue)
|
if (choice.Inventory.HasValue)
|
||||||
{
|
{
|
||||||
if (choice.Inventory <= 0)
|
if (choice.Inventory <= 0)
|
||||||
|
@ -329,14 +340,21 @@ namespace BTCPayServer.Controllers
|
||||||
return NotFound("Option was out of stock");
|
return NotFound("Option was out of stock");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (choice?.PaymentMethods?.Any() is true)
|
if (choice?.PaymentMethods?.Any() is true)
|
||||||
{
|
{
|
||||||
paymentMethods = choice?.PaymentMethods.ToDictionary(s => s,
|
paymentMethods = choice?.PaymentMethods.ToDictionary(s => s,
|
||||||
s => new InvoiceSupportedTransactionCurrency() { Enabled = true });
|
s => new InvoiceSupportedTransactionCurrency() { Enabled = true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (request.Amount < 0)
|
||||||
|
{
|
||||||
|
return NotFound("Please provide an amount greater than 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
price = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!isAdmin && (settings.EnforceTargetAmount && info.TargetAmount.HasValue && price >
|
if (!isAdmin && (settings.EnforceTargetAmount && info.TargetAmount.HasValue && price >
|
||||||
(info.TargetAmount - (info.Info.CurrentAmount + info.Info.CurrentPendingAmount))))
|
(info.TargetAmount - (info.Info.CurrentAmount + info.Info.CurrentPendingAmount))))
|
||||||
|
|
|
@ -83,7 +83,7 @@ namespace BTCPayServer.Models.AppViewModels
|
||||||
public class ContributeToCrowdfund
|
public class ContributeToCrowdfund
|
||||||
{
|
{
|
||||||
public ViewCrowdfundViewModel ViewCrowdfundViewModel { get; set; }
|
public ViewCrowdfundViewModel ViewCrowdfundViewModel { get; set; }
|
||||||
[Required] public decimal Amount { get; set; }
|
[Required] public decimal? Amount { get; set; }
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
public string ChoiceKey { get; set; }
|
public string ChoiceKey { get; set; }
|
||||||
public bool RedirectToCheckout { get; set; }
|
public bool RedirectToCheckout { get; set; }
|
||||||
|
|
|
@ -17,7 +17,7 @@ namespace BTCPayServer.Models.AppViewModels
|
||||||
public string Image { get; set; }
|
public string Image { get; set; }
|
||||||
public ItemPrice Price { get; set; }
|
public ItemPrice Price { get; set; }
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
public bool Custom { get; set; }
|
public string Custom { get; set; }
|
||||||
public string BuyButtonText { get; set; }
|
public string BuyButtonText { get; set; }
|
||||||
public int? Inventory { get; set; } = null;
|
public int? Inventory { get; set; } = null;
|
||||||
public string[] PaymentMethods { get; set; }
|
public string[] PaymentMethods { get; set; }
|
||||||
|
|
|
@ -289,7 +289,8 @@ namespace BTCPayServer.Services.Apps
|
||||||
{
|
{
|
||||||
var itemNode = new YamlMappingNode();
|
var itemNode = new YamlMappingNode();
|
||||||
itemNode.Add("title", new YamlScalarNode(item.Title));
|
itemNode.Add("title", new YamlScalarNode(item.Title));
|
||||||
itemNode.Add("price", new YamlScalarNode(item.Price.Value.ToStringInvariant()));
|
if(item.Custom!= "topup")
|
||||||
|
itemNode.Add("price", new YamlScalarNode(item.Price.Value.ToStringInvariant()));
|
||||||
if (!string.IsNullOrEmpty(item.Description))
|
if (!string.IsNullOrEmpty(item.Description))
|
||||||
{
|
{
|
||||||
itemNode.Add("description", new YamlScalarNode(item.Description)
|
itemNode.Add("description", new YamlScalarNode(item.Description)
|
||||||
|
@ -341,13 +342,16 @@ namespace BTCPayServer.Services.Apps
|
||||||
Id = c.Key,
|
Id = c.Key,
|
||||||
Image = c.GetDetailString("image"),
|
Image = c.GetDetailString("image"),
|
||||||
Title = c.GetDetailString("title") ?? c.Key,
|
Title = c.GetDetailString("title") ?? c.Key,
|
||||||
Price = c.GetDetail("price")
|
Custom = c.GetDetailString("custom"),
|
||||||
.Select(cc => new ViewPointOfSaleViewModel.Item.ItemPrice()
|
Price =
|
||||||
{
|
c.GetDetailString("custom") == "topup"
|
||||||
Value = decimal.Parse(cc.Value.Value, CultureInfo.InvariantCulture),
|
? null
|
||||||
Formatted = Currencies.FormatCurrency(cc.Value.Value, currency)
|
: c.GetDetail("price")
|
||||||
}).Single(),
|
.Select(cc => new ViewPointOfSaleViewModel.Item.ItemPrice()
|
||||||
Custom = c.GetDetailString("custom") == "true",
|
{
|
||||||
|
Value = decimal.Parse(cc.Value.Value, CultureInfo.InvariantCulture),
|
||||||
|
Formatted = Currencies.FormatCurrency(cc.Value.Value, currency)
|
||||||
|
}).Single(),
|
||||||
BuyButtonText = c.GetDetailString("buyButtonText"),
|
BuyButtonText = c.GetDetailString("buyButtonText"),
|
||||||
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"),
|
PaymentMethods = c.GetDetailStringList("payment_methods"),
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
{
|
{
|
||||||
foreach (var error in errors.Errors)
|
foreach (var error in errors.Errors)
|
||||||
{
|
{
|
||||||
<br />
|
<br/>
|
||||||
<span class="text-danger">@error.ErrorMessage</span>
|
<span class="text-danger">@error.ErrorMessage</span>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!items || items.length === 0" class="col-12 text-center">
|
<div v-if="!items || items.length === 0" class="col-12 text-center">
|
||||||
No items.<br />
|
No items.<br/>
|
||||||
<button type="button" class="btn btn-link" v-on:click="editItem(-1)" id="btn-add-first">
|
<button type="button" class="btn btn-link" v-on:click="editItem(-1)" id="btn-add-first">
|
||||||
Add your first item
|
Add your first item
|
||||||
</button>
|
</button>
|
||||||
|
@ -46,7 +46,7 @@
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title" v-if="editingItem">{{editingItem.index>=0? "Edit" : "Create"}} item</h5>
|
<h5 class="modal-title" v-if="editingItem">{{editingItem.index>=0? "Edit" : "Create"}} item</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" ref="close">
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" ref="close">
|
||||||
<vc:icon symbol="close" />
|
<vc:icon symbol="close"/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body" v-if="editingItem">
|
<div class="modal-body" v-if="editingItem">
|
||||||
|
@ -55,9 +55,15 @@
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<label class="form-label" data-required>Title</label>
|
<label class="form-label" data-required>Title</label>
|
||||||
<input type="text" required pattern="[^\*#]+" class="form-control mb-2" v-model="editingItem.title" autofocus ref="txtTitle" />
|
<input type="text" required pattern="[^\*#]+" class="form-control mb-2" v-model="editingItem.title" autofocus ref="txtTitle"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-3">
|
<div class="col-sm-3">
|
||||||
|
<label class="form-label">Price</label>
|
||||||
|
<select class="form-select" v-model="editingItem.custom">
|
||||||
|
<option v-for="option in customPriceOptions" :value="option.value">{{option.text}}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-3" v-show="editingItem.custom !== 'topup'">
|
||||||
<label class="form-label" data-required>Price</label>
|
<label class="form-label" data-required>Price</label>
|
||||||
<input class="form-control mb-2"
|
<input class="form-control mb-2"
|
||||||
inputmode="numeric"
|
inputmode="numeric"
|
||||||
|
@ -66,18 +72,13 @@
|
||||||
min="0"
|
min="0"
|
||||||
type="number"
|
type="number"
|
||||||
required
|
required
|
||||||
v-model="editingItem.price" ref="txtPrice" />
|
v-model="editingItem.price" ref="txtPrice"/>
|
||||||
</div>
|
|
||||||
<div class="col-sm-3">
|
|
||||||
<label class="form-label">Custom price</label>
|
|
||||||
<select class="form-select" v-model="editingItem.custom">
|
|
||||||
<option v-for="option in customPriceOptions" :value="option.value">{{option.text}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Image</label>
|
<label class="form-label">Image</label>
|
||||||
<input type="text" class="form-control mb-2" pattern="[^\*#]+" v-model="editingItem.image" ref="txtImage" />
|
<input type="text" class="form-control mb-2" pattern="[^\*#]+" v-model="editingItem.image" ref="txtImage"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Description</label>
|
<label class="form-label">Description</label>
|
||||||
|
@ -85,18 +86,18 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Inventory (leave blank to not use inventory feature)</label>
|
<label class="form-label">Inventory (leave blank to not use inventory feature)</label>
|
||||||
<input type="number" min="0" step="1" class="form-control mb-2" v-model="editingItem.inventory" ref="txtInventory" />
|
<input type="number" min="0" step="1" class="form-control mb-2" v-model="editingItem.inventory" ref="txtInventory"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Id (leave blank to generate from title)</label>
|
<label class="form-label">Id (leave blank to generate from title)</label>
|
||||||
<input type="text" required pattern="[^\*#]+" class="form-control mb-2" v-model="editingItem.id" ref="txtId" />
|
<input type="text" required pattern="[^\*#]+" class="form-control mb-2" v-model="editingItem.id" ref="txtId"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Buy Button Text</label>
|
<label class="form-label">Buy Button Text</label>
|
||||||
<input type="text" id="BuyButtonText" class="form-control mb-2" v-model="editingItem.buyButtonText" ref="txtBuyButtonText" />
|
<input type="text" id="BuyButtonText" class="form-control mb-2" v-model="editingItem.buyButtonText" ref="txtBuyButtonText"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group d-flex align-items-center">
|
<div class="form-group d-flex align-items-center">
|
||||||
<input type="checkbox" id="Disabled" class="btcpay-toggle me-2" v-model="editingItem.disabled" />
|
<input type="checkbox" id="Disabled" class="btcpay-toggle me-2" v-model="editingItem.disabled"/>
|
||||||
<label class="form-label mb-0">Disabled</label>
|
<label class="form-label mb-0">Disabled</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -120,8 +121,9 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||||
items: [],
|
items: [],
|
||||||
editingItem: null,
|
editingItem: null,
|
||||||
customPriceOptions: [
|
customPriceOptions: [
|
||||||
{ text: 'No', value: false },
|
{ text: 'Fixed', value: false },
|
||||||
{ text: 'Yes', value: true },
|
{ text: 'Minimum', value: true },
|
||||||
|
{ text: 'Topup', value: 'topup' },
|
||||||
],
|
],
|
||||||
elementId: "@Model.templateId"
|
elementId: "@Model.templateId"
|
||||||
},
|
},
|
||||||
|
@ -222,7 +224,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (price != null || title != null) {
|
if (title != null) {
|
||||||
// Add product to the list
|
// Add product to the list
|
||||||
result.push({
|
result.push({
|
||||||
id: id,
|
id: id,
|
||||||
|
@ -230,7 +232,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||||
price: price,
|
price: price,
|
||||||
image: image || null,
|
image: image || null,
|
||||||
description: description || '',
|
description: description || '',
|
||||||
custom: custom === "true",
|
custom: custom === "topup"? "topup": custom === "true",
|
||||||
buyButtonText: buyButtonText,
|
buyButtonText: buyButtonText,
|
||||||
inventory: isNaN(inventory)? null: inventory,
|
inventory: isNaN(inventory)? null: inventory,
|
||||||
paymentMethods: paymentMethods,
|
paymentMethods: paymentMethods,
|
||||||
|
@ -241,13 +243,13 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||||
this.items = result;
|
this.items = result;
|
||||||
},
|
},
|
||||||
toYml: function(){
|
toYml: function(){
|
||||||
var template = '';
|
let template = '';
|
||||||
// Construct template from the product list
|
// Construct template from the product list
|
||||||
for (var key in this.items) {
|
for (const key in this.items) {
|
||||||
var product = this.items[key],
|
const product = this.items[key],
|
||||||
id = product.id,
|
id = product.id,
|
||||||
title = product.title,
|
title = product.title,
|
||||||
price = product.price? product.price : 0,
|
price = product.custom === 'topup'? null : product.price??0,
|
||||||
image = product.image,
|
image = product.image,
|
||||||
description = product.description,
|
description = product.description,
|
||||||
custom = product.custom,
|
custom = product.custom,
|
||||||
|
@ -255,36 +257,36 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||||
inventory = product.inventory,
|
inventory = product.inventory,
|
||||||
paymentMethods = product.paymentMethods,
|
paymentMethods = product.paymentMethods,
|
||||||
disabled = product.disabled;
|
disabled = product.disabled;
|
||||||
|
let itemTemplate = id+":\n";
|
||||||
template += id + ':\n' +
|
itemTemplate += ( product.custom === 'topup'? '' : (' price: ' + parseFloat(price).noExponents() + '\n'));
|
||||||
' price: ' + parseFloat(price).noExponents() + '\n' +
|
itemTemplate+= ' title: ' + title + '\n';
|
||||||
' title: ' + title + '\n';
|
|
||||||
|
|
||||||
if (description) {
|
if (description) {
|
||||||
template += ' description: "' + description.replaceAll("\n", "<br/>").replaceAll('"', '\\"') + '"\n';
|
itemTemplate += ' description: "' + description.replaceAll("\n", "<br/>").replaceAll('"', '\\"') + '"\n';
|
||||||
}
|
}
|
||||||
if (image) {
|
if (image) {
|
||||||
template += ' image: ' + image + '\n';
|
itemTemplate += ' image: ' + image + '\n';
|
||||||
}
|
}
|
||||||
if (inventory) {
|
if (inventory) {
|
||||||
template += ' inventory: ' + inventory + '\n';
|
itemTemplate += ' inventory: ' + inventory + '\n';
|
||||||
}
|
}
|
||||||
if (custom != null) {
|
if (custom != null) {
|
||||||
template += ' custom: ' + custom + '\n';
|
itemTemplate += ' custom: ' + (custom === "topup"? '"topup"': custom) + '\n';
|
||||||
}
|
}
|
||||||
if (buyButtonText != null && buyButtonText.length > 0) {
|
if (buyButtonText != null && buyButtonText.length > 0) {
|
||||||
template += ' buyButtonText: ' + buyButtonText + '\n';
|
itemTemplate += ' buyButtonText: ' + buyButtonText + '\n';
|
||||||
}
|
}
|
||||||
if (disabled != null) {
|
if (disabled != null) {
|
||||||
template += ' disabled: ' + disabled.toString() + '\n';
|
itemTemplate += ' disabled: ' + disabled.toString() + '\n';
|
||||||
}
|
}
|
||||||
if(paymentMethods != null && paymentMethods.length > 0){
|
if(paymentMethods != null && paymentMethods.length > 0){
|
||||||
template+= ' payment_methods:\n';
|
itemTemplate+= ' payment_methods:\n';
|
||||||
for (var method of paymentMethods){
|
for (var method of paymentMethods){
|
||||||
template+= ' - '+method+'\n';
|
itemTemplate+= ' - '+method+'\n';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
template += '\n';
|
itemTemplate += '\n';
|
||||||
|
template+=itemTemplate;
|
||||||
}
|
}
|
||||||
this.getInputElement().val(template);
|
this.getInputElement().val(template);
|
||||||
},
|
},
|
||||||
|
@ -337,7 +339,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||||
this.errors.push("Image cannot start with \"- \"");
|
this.errors.push("Image cannot start with \"- \"");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.$refs.txtPrice.checkValidity()) {
|
if (this.editingItem.custom !== "topup" && !this.$refs.txtPrice.checkValidity()) {
|
||||||
this.errors.push("Price must be a valid number");
|
this.errors.push("Price must be a valid number");
|
||||||
}
|
}
|
||||||
if (!this.$refs.txtTitle.checkValidity()) {
|
if (!this.$refs.txtTitle.checkValidity()) {
|
||||||
|
|
|
@ -19,26 +19,29 @@
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="card-title d-flex align-items-center justify-content-between mb-1">
|
<div class="card-title d-flex align-items-center justify-content-between mb-1">
|
||||||
<label class="h5 d-flex align-items-center">
|
<label class="h5 d-flex align-items-center">
|
||||||
@if (vm.Started && !vm.Ended && (item.Price.Value > 0 || item.Custom))
|
@if (vm.Started && !vm.Ended )
|
||||||
{
|
{
|
||||||
<input type="radio" asp-for="ChoiceKey" value="@item.Id" class="form-check-input mt-0 me-2"/>
|
<input type="radio" asp-for="ChoiceKey" value="@item.Id" class="form-check-input mt-0 me-2"/>
|
||||||
}
|
}
|
||||||
@(string.IsNullOrEmpty(item.Title) ? item.Id : item.Title)
|
@(string.IsNullOrEmpty(item.Title) ? item.Id : item.Title)
|
||||||
</label>
|
</label>
|
||||||
<span class="text-muted">
|
<span class="text-muted">
|
||||||
@if (item.Price.Value > 0)
|
@if (item.Price?.Value > 0)
|
||||||
{
|
{
|
||||||
<span>@item.Price.Value</span>
|
<span>@item.Price.Value</span>
|
||||||
<span>@vm.TargetCurrency</span>
|
<span>@vm.TargetCurrency</span>
|
||||||
|
|
||||||
if (item.Custom)
|
if (item.Custom == "true")
|
||||||
{
|
{
|
||||||
@Safe.Raw("or more")
|
@Safe.Raw("or more")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (item.Custom)
|
else if (item.Custom == "topup" || item.Custom == "true" )
|
||||||
{
|
{
|
||||||
@Safe.Raw("Any amount")
|
@Safe.Raw("Any amount")
|
||||||
|
}else if (item.Custom == "false")
|
||||||
|
{
|
||||||
|
@Safe.Raw("Free")
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -230,7 +230,17 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer bg-transparent border-0 pt-0 pb-3">
|
<div class="card-footer bg-transparent border-0 pt-0 pb-3">
|
||||||
|
|
||||||
<span class="text-muted small">@((item.BuyButtonText ?? Model.ButtonText).Replace("{0}",item.Price.Formatted).Replace("{Price}",item.Price.Formatted))</span>
|
<span class="text-muted small">
|
||||||
|
@{
|
||||||
|
var buttonText = string.IsNullOrEmpty(item.BuyButtonText) ? item.Custom != "false" ? Model.CustomButtonText : Model.ButtonText : item.BuyButtonText;
|
||||||
|
if (item.Custom != "topup")
|
||||||
|
{
|
||||||
|
buttonText = buttonText.Replace("{0}",item.Price.Formatted)
|
||||||
|
?.Replace("{Price}",item.Price.Formatted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Safe.Raw(buttonText)
|
||||||
|
</span>
|
||||||
@if (item.Inventory.HasValue)
|
@if (item.Inventory.HasValue)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
|
@ -19,13 +19,13 @@
|
||||||
@for (int x = 0; x < Model.Items.Length; x++)
|
@for (int x = 0; x < Model.Items.Length; x++)
|
||||||
{
|
{
|
||||||
var item = Model.Items[x];
|
var item = Model.Items[x];
|
||||||
var buttonText = (string.IsNullOrEmpty(item.BuyButtonText) ?
|
var buttonText = string.IsNullOrEmpty(item.BuyButtonText) ? item.Custom != "false" ? Model.CustomButtonText : Model.ButtonText : item.BuyButtonText;
|
||||||
item.Custom?
|
if (item.Custom != "topup")
|
||||||
Model.CustomButtonText :
|
{
|
||||||
Model.ButtonText
|
buttonText = buttonText.Replace("{0}",item.Price.Formatted)
|
||||||
: item.BuyButtonText)
|
?.Replace("{Price}",item.Price.Formatted);
|
||||||
.Replace("{0}",item.Price.Formatted)
|
}
|
||||||
.Replace("{Price}",item.Price.Formatted);
|
|
||||||
<div class="card px-0" data-id="@x">
|
<div class="card px-0" data-id="@x">
|
||||||
@if (!String.IsNullOrWhiteSpace(item.Image))
|
@if (!String.IsNullOrWhiteSpace(item.Image))
|
||||||
{
|
{
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
<div class="card-footer bg-transparent border-0 pb-3">
|
<div class="card-footer bg-transparent border-0 pb-3">
|
||||||
@if (!item.Inventory.HasValue || item.Inventory.Value > 0)
|
@if (!item.Inventory.HasValue || item.Inventory.Value > 0)
|
||||||
{
|
{
|
||||||
@if (item.Custom)
|
@if (item.Custom == "true")
|
||||||
{
|
{
|
||||||
<form method="post" asp-controller="AppsPublic" asp-action="ViewPointOfSale" asp-route-appId="@Model.AppId" asp-antiforgery="false" data-buy>
|
<form method="post" asp-controller="AppsPublic" asp-action="ViewPointOfSale" asp-route-appId="@Model.AppId" asp-antiforgery="false" data-buy>
|
||||||
<input type="hidden" name="choicekey" value="@item.Id"/>
|
<input type="hidden" name="choicekey" value="@item.Id"/>
|
||||||
|
|
|
@ -326,12 +326,12 @@
|
||||||
<div class="card-title d-flex justify-content-between">
|
<div class="card-title d-flex justify-content-between">
|
||||||
<span class="h5">{{perk.title? perk.title : perk.id}}</span>
|
<span class="h5">{{perk.title? perk.title : perk.id}}</span>
|
||||||
<span class="text-muted">
|
<span class="text-muted">
|
||||||
<template v-if="perk.price.value">
|
<template v-if="perk.price && perk.price.value">
|
||||||
{{perk.price.value.noExponents()}}
|
{{perk.price.value.noExponents()}}
|
||||||
{{targetCurrency}}
|
{{targetCurrency}}
|
||||||
<template v-if="perk.custom">or more</template>
|
<template v-if="perk.custom === 'true'">or more</template>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="!perk.price.value && perk.custom">
|
<template v-else-if="perk.custom === 'topup' || (!perk.price.value && perk.custom === 'true')">
|
||||||
Any amount
|
Any amount
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
|
@ -340,8 +340,9 @@
|
||||||
|
|
||||||
<div class="input-group" style="max-width:500px;" v-if="expanded" :id="'perk-form'+ perk.id">
|
<div class="input-group" style="max-width:500px;" v-if="expanded" :id="'perk-form'+ perk.id">
|
||||||
<input
|
<input
|
||||||
|
v-if="perk.custom !== 'topup'"
|
||||||
:disabled="!active"
|
:disabled="!active"
|
||||||
:readonly="!perk.custom"
|
:readonly="perk.custom !== 'true'"
|
||||||
class="form-control hide-number-spin"
|
class="form-control hide-number-spin"
|
||||||
type="number"
|
type="number"
|
||||||
v-model="amount"
|
v-model="amount"
|
||||||
|
@ -349,7 +350,7 @@
|
||||||
step="any"
|
step="any"
|
||||||
placeholder="Contribution Amount"
|
placeholder="Contribution Amount"
|
||||||
required>
|
required>
|
||||||
<span class="input-group-text">{{targetCurrency}}</span>
|
<span class="input-group-text" v-if="perk.custom !== 'topup'">{{targetCurrency}}</span>
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary d-flex align-items-center"
|
class="btn btn-primary d-flex align-items-center"
|
||||||
v-bind:class="{ 'btn-disabled': loading}"
|
v-bind:class="{ 'btn-disabled': loading}"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
@model PaymentModel
|
@model PaymentModel
|
||||||
<div>
|
<div>
|
||||||
<p>To complete payment, please send <b>@Model.BtcDue @Model.CryptoCode</b> to <b style="word-break: break-word;">@Model.BtcAddress</b></p>
|
<p>To complete payment, please send <b>@Safe.Raw(Model.IsUnsetTopUp? "any amount of": Model.BtcDue) @Model.CryptoCode</b> to <b style="word-break: break-word;">@Model.BtcAddress</b></p>
|
||||||
<p>Time remaining: @Model.TimeLeft</p>
|
<p>Time remaining: @Model.TimeLeft</p>
|
||||||
<p>
|
<p>
|
||||||
<a href="@Model.InvoiceBitcoinUrl" style="word-break: break-word;" rel="noreferrer noopener">@Model.InvoiceBitcoinUrl</a>
|
<a href="@Model.InvoiceBitcoinUrl" style="word-break: break-word;" rel="noreferrer noopener">@Model.InvoiceBitcoinUrl</a>
|
||||||
|
|
|
@ -112,7 +112,7 @@ Cart.prototype.getTotalProducts = function() {
|
||||||
typeof this.content[key] != 'undefined' &&
|
typeof this.content[key] != 'undefined' &&
|
||||||
!this.content[key].disabled
|
!this.content[key].disabled
|
||||||
) {
|
) {
|
||||||
var price = this.toCents(this.content[key].price.value);
|
var price = this.toCents(this.content[key].price?.value??0);
|
||||||
amount += (this.content[key].count * price);
|
amount += (this.content[key].count * price);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -439,7 +439,7 @@ Cart.prototype.listItems = function() {
|
||||||
'title': this.escape(item.title),
|
'title': this.escape(item.title),
|
||||||
'count': this.escape(item.count),
|
'count': this.escape(item.count),
|
||||||
'inventory': this.escape(item.inventory < 0? 99999: item.inventory),
|
'inventory': this.escape(item.inventory < 0? 99999: item.inventory),
|
||||||
'price': this.escape(item.price.formatted)
|
'price': this.escape(item.price?.formatted??0)
|
||||||
});
|
});
|
||||||
list.push($(tableTemplate));
|
list.push($(tableTemplate));
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ addLoadEvent(function (ev) {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
canExpand: function(){
|
canExpand: function(){
|
||||||
return !this.expanded && this.active && (this.perk.price.value || this.perk.custom) && (this.perk.inventory==null || this.perk.inventory > 0)
|
return !this.expanded && this.active && (this.perk.custom=="topup" || this.perk.price.value || this.perk.custom == "true") && (this.perk.inventory==null || this.perk.inventory > 0)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -58,18 +58,20 @@ addLoadEvent(function (ev) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setAmount: function (amount) {
|
setAmount: function (amount) {
|
||||||
this.amount = (amount || 0).noExponents();
|
this.amount = this.perk.custom == "topup"? null : (amount || 0).noExponents();
|
||||||
this.expanded = false;
|
this.expanded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
this.setAmount(this.perk.price.value);
|
this.setAmount(this.perk.price?.value);
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
perk: function (newValue, oldValue) {
|
perk: function (newValue, oldValue) {
|
||||||
if (newValue.price.value != oldValue.price.value) {
|
if(newValue.custom === "topup"){
|
||||||
|
this.setAmount();
|
||||||
|
}else if (newValue.price.value != oldValue.price.value) {
|
||||||
this.setAmount(newValue.price.value);
|
this.setAmount(newValue.price.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue