Compare commits

...

32 Commits

Author SHA1 Message Date
ed4430ae7d bump 2018-03-07 07:49:46 -05:00
9a0e4e35d9 Merge pull request #56 from lepipele/dev-lepi
Tweaking Checkout page so that it works properly in IE
2018-03-07 07:48:52 -05:00
5715dd2058 Disabling AJAX caching that messes up checkout in IE
Ref: https://stackoverflow.com/questions/4303829/how-to-prevent-a-jquery-ajax-request-from-caching-in-internet-explorer
2018-03-06 22:04:03 -06:00
da4c132f9d Adding Vue.js binding attributes 2018-03-06 22:02:34 -06:00
303a617f9e Improve invoice.cshtml display if offchain payment is present 2018-03-06 16:37:25 -05:00
3116ec9cb8 Fix bitcoin logo for internet explorer, update nbxplorer 2018-03-06 10:41:41 -05:00
b3f4eab075 fix doc 2018-03-06 09:40:21 -05:00
c98593b47b add dependencies to README 2018-03-06 09:35:20 -05:00
2c49d61682 shebang and fix line ending 2018-03-06 09:23:08 -05:00
21f5c94cff update readme 2018-03-06 09:18:00 -05:00
834ba4afab chmod +x scripts 2018-03-06 09:15:09 -05:00
d690cd6b7b Add build and run scripts 2018-03-06 09:14:45 -05:00
937bd07daa add doc 2018-03-05 10:58:27 -05:00
559b822111 fix broken expiration screen on checkout 2018-03-03 20:33:52 -05:00
7afb4c6b11 bump 2018-03-03 15:08:11 -05:00
1c98a3a33d Rename currency selection with "Pay With" 2018-03-03 15:07:34 -05:00
3320eb284e Merge branch 'lepipele-dev-lepi' 2018-03-03 15:06:58 -05:00
919fb60558 Hiding currency selection when invoice paid 2018-03-03 00:52:49 -06:00
7796589105 Extracting public methods in core.js 2018-03-03 00:41:52 -06:00
de6d3198ff Bundling JS and CSS files for Checkout.cshtml
Now we'll finally have versioning so when those JS/CSS files update, clients will properly request new bundle
2018-03-03 00:32:51 -06:00
f1e971d047 Refactoring core.js in preparation for bundling
Moving Vue registration to body for quick update of page
Removing defer dependancy for core.js
2018-03-03 00:32:04 -06:00
acd98aad32 Showing loader for better UX when switching currencies 2018-03-03 00:11:08 -06:00
b0c810398c Moving currency selection to order details
This way state transitions of form are now properly preserved
2018-03-02 23:49:51 -06:00
03a0044745 Currency selection moved to top of the form 2018-03-02 23:42:17 -06:00
15684efdce Update README.md 2018-03-02 15:04:00 -05:00
b67a962d12 Make sure the txrelayfee is correctly set 2018-03-02 14:16:16 -05:00
339cedadf7 Save a call to nbxplorer.GetStatus, update NBXplorer 2018-03-02 14:03:47 -05:00
e19d730fb7 Merge pull request #54 from practicalswift/typos
Fix typos
2018-03-03 02:43:16 +09:00
649497e54f Fix typos 2018-03-01 15:11:30 +01:00
40eee0924a bump 2018-03-01 11:48:55 +09:00
bbf5fb3c30 show port if failing to connect to lightning node 2018-03-01 10:31:01 +09:00
346cdf2431 show block gap if lightning node is not synched 2018-02-28 23:12:09 +09:00
28 changed files with 539 additions and 356 deletions

17
.gitattributes vendored Normal file
View File

@ -0,0 +1,17 @@
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto
# Explicitly declare text files you want to always be normalized and converted
# to native line endings on checkout.
*.c text
*.h text
# Declare files that will always have CRLF line endings on checkout.
*.sln text eol=crlf
# Declare files that will always have CRLF line endings on checkout.
*.sh text eol=lf
# Denote all files that are truly binary and should not be modified.
*.png binary
*.jpg binary

View File

@ -40,7 +40,7 @@ services:
- lightning-charged
nbxplorer:
image: nicolasdorier/nbxplorer:1.0.1.16
image: nicolasdorier/nbxplorer:1.0.1.19
ports:
- "32838:32838"
expose:

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<Version>1.0.1.40</Version>
<Version>1.0.1.45</Version>
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
</PropertyGroup>
<ItemGroup>
@ -31,10 +31,10 @@
<PackageReference Include="Meziantou.AspNetCore.BundleTagHelpers" Version="1.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
<PackageReference Include="Microsoft.NetCore.Analyzers" Version="2.6.0" />
<PackageReference Include="NBitcoin" Version="4.0.0.56" />
<PackageReference Include="NBitcoin" Version="4.0.0.60" />
<PackageReference Include="NBitpayClient" Version="1.0.0.18" />
<PackageReference Include="DBreeze" Version="1.87.0" />
<PackageReference Include="NBXplorer.Client" Version="1.0.1.9" />
<PackageReference Include="NBXplorer.Client" Version="1.0.1.14" />
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.1" />
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.2" />
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.13" />

View File

@ -68,11 +68,11 @@ namespace BTCPayServer.Controllers
{
var cryptoInfo = dto.CryptoInfo.First(o => o.GetpaymentMethodId() == data.GetId());
var accounting = data.Calculate();
var paymentNetwork = _NetworkProvider.GetNetwork(data.GetId().CryptoCode);
var paymentMethodId = data.GetId();
var cryptoPayment = new InvoiceDetailsModel.CryptoPayment();
cryptoPayment.CryptoCode = paymentNetwork.CryptoCode;
cryptoPayment.Due = accounting.Due.ToString() + $" {paymentNetwork.CryptoCode}";
cryptoPayment.Paid = accounting.CryptoPaid.ToString() + $" {paymentNetwork.CryptoCode}";
cryptoPayment.PaymentMethod = ToString(paymentMethodId);
cryptoPayment.Due = accounting.Due.ToString() + $" {paymentMethodId.CryptoCode}";
cryptoPayment.Paid = accounting.CryptoPaid.ToString() + $" {paymentMethodId.CryptoCode}";
var onchainMethod = data.GetPaymentMethodDetails() as Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod;
if(onchainMethod != null)
@ -86,13 +86,13 @@ namespace BTCPayServer.Controllers
var payments = invoice
.GetPayments()
.Where(p => p.GetpaymentMethodId().PaymentType == PaymentTypes.BTCLike)
.Where(p => p.GetPaymentMethodId().PaymentType == PaymentTypes.BTCLike)
.Select(async payment =>
{
var paymentData = (Payments.Bitcoin.BitcoinLikePaymentData)payment.GetCryptoPaymentData();
var m = new InvoiceDetailsModel.Payment();
var paymentNetwork = _NetworkProvider.GetNetwork(payment.GetCryptoCode());
m.CryptoCode = payment.GetCryptoCode();
m.PaymentMethod = ToString(payment.GetPaymentMethodId());
m.DepositAddress = paymentData.Output.ScriptPubKey.GetDestinationAddress(paymentNetwork.NBitcoinNetwork);
int confirmationCount = 0;
@ -121,12 +121,32 @@ namespace BTCPayServer.Controllers
})
.ToArray();
await Task.WhenAll(payments);
model.Addresses = invoice.HistoricalAddresses;
model.Addresses = invoice.HistoricalAddresses.Select(h=> new InvoiceDetailsModel.AddressModel
{
Destination = h.GetAddress(),
PaymentMethod = ToString(h.GetPaymentMethodId()),
Current = !h.UnAssigned.HasValue
}).ToArray();
model.Payments = payments.Select(p => p.GetAwaiter().GetResult()).ToList();
model.StatusMessage = StatusMessage;
return View(model);
}
private string ToString(PaymentMethodId paymentMethodId)
{
var type = paymentMethodId.PaymentType.ToString();
switch (paymentMethodId.PaymentType)
{
case PaymentTypes.BTCLike:
type = "On-Chain";
break;
case PaymentTypes.LightningLike:
type = "Off-Chain";
break;
}
return $"{paymentMethodId.CryptoCode} ({type})";
}
[HttpGet]
[Route("i/{invoiceId}")]
[Route("i/{invoiceId}/{paymentMethodId}")]
@ -220,7 +240,7 @@ namespace BTCPayServer.Controllers
.ToList()
};
var isMultiCurrency = invoice.GetPayments().Select(p=>p.GetpaymentMethodId()).Concat(new[] { paymentMethod.GetId() }).Distinct().Count() > 1;
var isMultiCurrency = invoice.GetPayments().Select(p=>p.GetPaymentMethodId()).Concat(new[] { paymentMethod.GetId() }).Distinct().Count() > 1;
if (isMultiCurrency)
model.NetworkFeeDescription = $"{accounting.NetworkFee} {network.CryptoCode}";

View File

@ -368,7 +368,7 @@ namespace BTCPayServer.Controllers
if (!user.TwoFactorEnabled)
{
throw new ApplicationException($"Unexpected error occured disabling 2FA for user with ID '{user.Id}'.");
throw new ApplicationException($"Unexpected error occurred disabling 2FA for user with ID '{user.Id}'.");
}
return View(nameof(Disable2fa));
@ -387,7 +387,7 @@ namespace BTCPayServer.Controllers
var disable2faResult = await _userManager.SetTwoFactorEnabledAsync(user, false);
if (!disable2faResult.Succeeded)
{
throw new ApplicationException($"Unexpected error occured disabling 2FA for user with ID '{user.Id}'.");
throw new ApplicationException($"Unexpected error occurred disabling 2FA for user with ID '{user.Id}'.");
}
_logger.LogInformation("User with ID {UserId} has disabled 2fa.", user.Id);

View File

@ -197,7 +197,7 @@ namespace BTCPayServer.Controllers
{
subsctractFeesValue = bool.Parse(substractFees);
}
catch { throw new FormatException("Invalid value for substract fees"); }
catch { throw new FormatException("Invalid value for subtract fees"); }
}
if (command == "test")
{
@ -224,17 +224,20 @@ namespace BTCPayServer.Controllers
if (command == "sendtoaddress")
{
if(!_Dashboard.IsFullySynched(network.CryptoCode, out var summary))
throw new Exception($"{network.CryptoCode}: not started or fully synched");
var strategy = GetDirectDerivationStrategy(store, network);
var strategyBase = GetDerivationStrategy(store, network);
var wallet = _WalletProvider.GetWallet(network);
var change = wallet.GetChangeAddressAsync(strategyBase);
var unspentCoins = await wallet.GetUnspentCoins(strategyBase);
var changeAddress = await change;
var transaction = await hw.SendToAddress(strategy, unspentCoins, network,
new[] { (destinationAddress as IDestination, amountBTC, subsctractFeesValue) },
feeRateValue,
changeAddress.Item1,
changeAddress.Item2);
changeAddress.Item2, summary.Status.BitcoinStatus.MinRelayTxFee);
try
{
var broadcastResult = await wallet.BroadcastTransactionsAsync(new List<Transaction>() { transaction });

View File

@ -1,6 +1,7 @@
using BTCPayServer.Authentication;
using BTCPayServer.Configuration;
using BTCPayServer.Data;
using BTCPayServer.HostedServices;
using BTCPayServer.Models;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Services;
@ -31,6 +32,7 @@ namespace BTCPayServer.Controllers
public partial class StoresController : Controller
{
public StoresController(
NBXplorerDashboard dashboard,
IServiceProvider serviceProvider,
BTCPayServerOptions btcpayServerOptions,
BTCPayServerEnvironment btcpayEnv,
@ -45,6 +47,7 @@ namespace BTCPayServer.Controllers
IFeeProviderFactory feeRateProvider,
IHostingEnvironment env)
{
_Dashboard = dashboard;
_Repo = repo;
_TokenRepository = tokenRepo;
_UserManager = userManager;
@ -59,6 +62,7 @@ namespace BTCPayServer.Controllers
_BtcpayServerOptions = btcpayServerOptions;
_BTCPayEnv = btcpayEnv;
}
NBXplorerDashboard _Dashboard;
BTCPayServerOptions _BtcpayServerOptions;
BTCPayServerEnvironment _BTCPayEnv;
IServiceProvider _ServiceProvider;
@ -516,7 +520,7 @@ namespace BTCPayServer.Controllers
var pairingResult = await _TokenRepository.PairWithStoreAsync(pairingCode, store.Id);
if (pairingResult == PairingResult.Complete || pairingResult == PairingResult.Partial)
{
StatusMessage = "Pairing is successfull";
StatusMessage = "Pairing is successful";
if (pairingResult == PairingResult.Partial)
StatusMessage = "Server initiated pairing code: " + pairingCode;
return RedirectToAction(nameof(ListTokens), new

View File

@ -27,9 +27,10 @@ namespace BTCPayServer.Data
public string CryptoCode { get; set; }
#pragma warning disable CS0618
public string GetCryptoCode()
public Payments.PaymentMethodId GetPaymentMethodId()
{
return string.IsNullOrEmpty(CryptoCode) ? "BTC" : CryptoCode;
return string.IsNullOrEmpty(CryptoCode) ? new Payments.PaymentMethodId("BTC", Payments.PaymentTypes.BTCLike)
: Payments.PaymentMethodId.Parse(CryptoCode);
}
public string GetAddress()
{

View File

@ -77,7 +77,7 @@ namespace BTCPayServer
public bool IsAvailable(string cryptoCode)
{
return _Clients.ContainsKey(cryptoCode) && _Dashboard.IsFullySynched(cryptoCode);
return _Clients.ContainsKey(cryptoCode) && _Dashboard.IsFullySynched(cryptoCode, out var unused);
}
public BTCPayNetwork GetNetwork(string cryptoCode)

View File

@ -41,9 +41,11 @@ namespace BTCPayServer.HostedServices
return _Summaries.All(s => s.Value.Status != null && s.Value.Status.IsFullySynched);
}
public bool IsFullySynched(string cryptoCode)
public bool IsFullySynched(string cryptoCode, out NBXplorerSummary summary)
{
return _Summaries.Any(s => s.Key.Equals(cryptoCode, StringComparison.OrdinalIgnoreCase) && s.Value.Status != null && s.Value.Status.IsFullySynched);
return _Summaries.TryGetValue(cryptoCode, out summary) &&
summary.Status != null &&
summary.Status.IsFullySynched;
}
public IEnumerable<NBXplorerSummary> GetAll()

View File

@ -12,16 +12,22 @@ namespace BTCPayServer.Models.InvoicingModels
{
public class CryptoPayment
{
public string CryptoCode { get; set; }
public string PaymentMethod { get; set; }
public string Due { get; set; }
public string Paid { get; set; }
public string Address { get; internal set; }
public string Rate { get; internal set; }
public string PaymentUrl { get; internal set; }
}
public class AddressModel
{
public string PaymentMethod { get; set; }
public string Destination { get; set; }
public bool Current { get; set; }
}
public class Payment
{
public string CryptoCode { get; set; }
public string PaymentMethod { get; set; }
public string Confirmations
{
get; set;
@ -126,7 +132,7 @@ namespace BTCPayServer.Models.InvoicingModels
get;
internal set;
}
public HistoricalAddressInvoiceData[] Addresses { get; set; }
public AddressModel[] Addresses { get; set; }
public DateTimeOffset MonitoringDate { get; internal set; }
public List<Data.InvoiceEventData> Events { get; internal set; }
}

View File

@ -16,7 +16,7 @@ namespace BTCPayServer
/// there is functionality to allow adding or removing more than a single
/// value at once.
///
/// The MultiValueDictionary can also be viewed as a IReadOnlyDictionary&lt;TKey,IReadOnlyCollection&lt;TValue&gt;t&gt;
/// The MultiValueDictionary can also be viewed as an IReadOnlyDictionary&lt;TKey,IReadOnlyCollection&lt;TValue&gt;t&gt;
/// where the <see cref="IReadOnlyCollection{TValue}" /> is abstracted from the view of the programmer.
///
/// For a read-only MultiValueDictionary.

View File

@ -213,7 +213,7 @@ namespace BTCPayServer.Payments.Bitcoin
IEnumerable<BitcoinLikePaymentData> GetAllBitcoinPaymentData(InvoiceEntity invoice)
{
return invoice.GetPayments()
.Where(p => p.GetpaymentMethodId().PaymentType == PaymentTypes.BTCLike)
.Where(p => p.GetPaymentMethodId().PaymentType == PaymentTypes.BTCLike)
.Select(p => (BitcoinLikePaymentData)p.GetCryptoPaymentData());
}
@ -227,7 +227,7 @@ namespace BTCPayServer.Payments.Bitcoin
var conflicts = GetConflicts(transactions.Select(t => t.Value));
foreach (var payment in invoice.GetPayments(wallet.Network))
{
if (payment.GetpaymentMethodId().PaymentType != PaymentTypes.BTCLike)
if (payment.GetPaymentMethodId().PaymentType != PaymentTypes.BTCLike)
continue;
var paymentData = (BitcoinLikePaymentData)payment.GetCryptoPaymentData();
if (!transactions.TryGetValue(paymentData.Outpoint.Hash, out TransactionResult tx))
@ -279,7 +279,7 @@ namespace BTCPayServer.Payments.Bitcoin
}
else
{
// Take the most recent (bitcoin node would not forward a conflict without a successfull RBF)
// Take the most recent (bitcoin node would not forward a conflict without a successful RBF)
_Winner = Transactions
.OrderByDescending(t => t.Value.Timestamp)
.First()

View File

@ -5,6 +5,7 @@ using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.HostedServices;
using BTCPayServer.Payments.Lightning.CLightning;
using BTCPayServer.Services.Invoices;
@ -12,10 +13,10 @@ namespace BTCPayServer.Payments.Lightning
{
public class LightningLikePaymentHandler : PaymentMethodHandlerBase<LightningSupportedPaymentMethod>
{
ExplorerClientProvider _ExplorerClientProvider;
public LightningLikePaymentHandler(ExplorerClientProvider explorerClientProvider)
NBXplorerDashboard _Dashboard;
public LightningLikePaymentHandler(NBXplorerDashboard dashboard)
{
_ExplorerClientProvider = explorerClientProvider;
_Dashboard = dashboard;
}
public override async Task<IPaymentMethodDetails> CreatePaymentMethodDetails(LightningSupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod, BTCPayNetwork network)
{
@ -47,13 +48,12 @@ namespace BTCPayServer.Payments.Lightning
public async Task Test(LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network)
{
if (!_ExplorerClientProvider.IsAvailable(network))
if (!_Dashboard.IsFullySynched(network.CryptoCode, out var summary))
throw new Exception($"Full node not available");
var explorerClient = _ExplorerClientProvider.GetExplorerClient(network);
var cts = new CancellationTokenSource(5000);
var client = GetClient(supportedPaymentMethod, network);
var status = explorerClient.GetStatusAsync();
GetInfoResponse info = null;
try
{
@ -73,9 +73,10 @@ namespace BTCPayServer.Payments.Lightning
throw new Exception($"Lightning node network {info.Network}, but expected is {network.CLightningNetworkName}");
}
if (Math.Abs(info.BlockHeight - (await status).ChainHeight) > 10)
var blocksGap = Math.Abs(info.BlockHeight - summary.Status.ChainHeight);
if (blocksGap > 10)
{
throw new Exception($"The lightning node is not synched");
throw new Exception($"The lightning is not synched ({blocksGap} blocks)");
}
try
@ -84,7 +85,7 @@ namespace BTCPayServer.Payments.Lightning
}
catch (Exception ex)
{
throw new Exception($"Error while connecting to the lightning node via {address} ({ex.Message})");
throw new Exception($"Error while connecting to the lightning node via {address}:{port} ({ex.Message})");
}
}

View File

@ -152,7 +152,8 @@ namespace BTCPayServer.Services
(IDestination destination, Money amount, bool substractFees)[] send,
FeeRate feeRate,
IDestination changeAddress,
KeyPath changeKeyPath)
KeyPath changeKeyPath,
FeeRate minTxRelayFee)
{
if (strategy == null)
throw new ArgumentNullException(nameof(strategy));
@ -185,6 +186,7 @@ namespace BTCPayServer.Services
}
TransactionBuilder builder = new TransactionBuilder();
builder.StandardTransactionPolicy.MinRelayTxFee = minTxRelayFee;
builder.AddCoins(coins.Select(c=>c.Coin).ToArray());
foreach (var element in send)

View File

@ -655,7 +655,7 @@ namespace BTCPayServer.Services.Invoices
.OrderBy(p => p.ReceivedTime)
.Select(_ =>
{
var txFee = _.GetValue(paymentMethods, GetId(), paymentMethods[_.GetpaymentMethodId()].GetTxFee());
var txFee = _.GetValue(paymentMethods, GetId(), paymentMethods[_.GetPaymentMethodId()].GetTxFee());
paid += _.GetValue(paymentMethods, GetId());
if (!paidEnough)
{
@ -663,7 +663,7 @@ namespace BTCPayServer.Services.Invoices
paidTxFee += txFee;
}
paidEnough |= paid >= RoundUp(totalDue, 8);
if (GetId() == _.GetpaymentMethodId())
if (GetId() == _.GetPaymentMethodId())
{
cryptoPaid += _.GetCryptoPaymentData().GetValue();
txRequired++;
@ -766,7 +766,7 @@ namespace BTCPayServer.Services.Invoices
paymentData.Legacy = true;
return paymentData;
}
if (GetpaymentMethodId().PaymentType == PaymentTypes.BTCLike)
if (GetPaymentMethodId().PaymentType == PaymentTypes.BTCLike)
{
var paymentData = JsonConvert.DeserializeObject<Payments.Bitcoin.BitcoinLikePaymentData>(CryptoPaymentData);
// legacy
@ -774,7 +774,7 @@ namespace BTCPayServer.Services.Invoices
paymentData.Outpoint = Outpoint;
return paymentData;
}
if(GetpaymentMethodId().PaymentType== PaymentTypes.LightningLike)
if(GetPaymentMethodId().PaymentType== PaymentTypes.LightningLike)
{
return JsonConvert.DeserializeObject<Payments.Lightning.LightningLikePaymentData>(CryptoPaymentData);
}
@ -802,7 +802,7 @@ namespace BTCPayServer.Services.Invoices
{
value = value ?? this.GetCryptoPaymentData().GetValue();
var to = paymentMethodId;
var from = this.GetpaymentMethodId();
var from = this.GetPaymentMethodId();
if (to == from)
return decimal.Round(value.Value, 8);
var fromRate = paymentMethods[from].Rate;
@ -813,7 +813,7 @@ namespace BTCPayServer.Services.Invoices
return otherCurrencyValue;
}
public PaymentMethodId GetpaymentMethodId()
public PaymentMethodId GetPaymentMethodId()
{
#pragma warning disable CS0618 // Type or member is obsolete
return new PaymentMethodId(CryptoCode ?? "BTC", string.IsNullOrEmpty(CryptoPaymentDataType) ? PaymentTypes.BTCLike : Enum.Parse<PaymentTypes>(CryptoPaymentDataType));

View File

@ -1,4 +1,5 @@
@model PaymentModel
@addTagHelper *, Meziantou.AspNetCore.BundleTagHelpers
@model PaymentModel
@{
Layout = null;
ViewData["Title"] = "Payment";
@ -12,18 +13,13 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>BTCPay Invoice</title>
<link href="~/vendor/font-awesome/css/font-awesome.css" rel="stylesheet" />
<link href="~/css/css.css" rel="stylesheet" type="text/css">
<link href="~/css/normalizer.css" rel="stylesheet" type="text/css">
<bundle name="wwwroot/bundles/checkout-bundle.min.css" />
<script type="text/javascript">
@Model.ToJSVariableModel("srvModel")
</script>
<script src="~/vendor/clipboard.js/clipboard.js"></script>
<script src="~/vendor/jquery/jquery.js"></script>
<script src="~/js/vue.min.js" type="text/javascript"></script>
<script src="~/js/vue-qrcode.js" type="text/javascript"></script>
<script src="~/js/core.js" type="text/javascript" defer="defer"></script>
<bundle name="wwwroot/bundles/checkout-bundle.min.js" />
</head>
<body style="background: #E4E4E4">
<noscript>
@ -72,7 +68,7 @@
<bp-spinner>
<svg xml:space="preserve" style="enable-background:new 0 0 50 50;" version="1.1" viewBox="0 0 50 50" x="0px" xmlns="http://www.w3.org/2000/svg" y="0px">
<path d="M11.1,29.6c-0.5-1.5-0.8-3-0.8-4.6c0-8.1,6.6-14.7,14.7-14.7S39.7,16.9,39.7,25c0,1.6-0.3,3.2-0.8,4.6l6.1,2c0.7-2.1,1.1-4.3,1.1-6.6c0-11.7-9.5-21.2-21.2-21.2S3.8,13.3,3.8,25c0,2.3,0.4,4.5,1.1,6.6L11.1,29.6z"></path>
</svg>
</svg>
</bp-spinner>
</div>
<div class="timer-row__message">
@ -88,6 +84,26 @@
</div>
</div>
<div class="order-details">
@if (Model.AvailableCryptos.Count > 1)
{
<div class="currency-selection">
<div class="single-item-order__left">
<div style="font-weight: 600;">
Pay with
</div>
</div>
<div class="single-item-order__right">
<div>
@foreach (var crypto in Model.AvailableCryptos)
{
<a href="@crypto.Link" onclick="return changeCurrency('@crypto.PaymentMethodId');">
<img style="height:32px; margin-left:5px;" alt="@crypto.PaymentMethodId" src="@crypto.CryptoImage" />
</a>
}
</div>
</div>
</div>
}
<!---->
<div class="single-item-order buyerTotalLine">
<div class="single-item-order__left">
@ -153,8 +169,9 @@
<div adjust-height="" class="payment-box">
<div class="bp-view payment scan" id="scan">
<div class="payment__scan">
<img :src="srvModel.cryptoImage" style="position: absolute; height:64px; width:64px; left:118px; top:96px;" />
<qrcode :val="srvModel.invoiceBitcoinUrlQR" :size="256" bg-color="#f5f5f7" fg-color="#000" />
<img v-bind:src="srvModel.cryptoImage" style="position: absolute; height:64px; width:64px; left:118px; top:96px;" />
<qrcode v-bind:val="srvModel.invoiceBitcoinUrlQR" v-bind:size="256" bg-color="#f5f5f7" fg-color="#000">
</qrcode>
</div>
<div class="payment__details__instruction__open-wallet">
<a class="payment__details__instruction__open-wallet__btn action-button" v-bind:href="srvModel.invoiceBitcoinUrl">
@ -163,6 +180,13 @@
</a>
</div>
</div>
<div class="payment__spinner">
<bp-spinner>
<svg xml:space="preserve" style="enable-background:new 0 0 50 50;" version="1.1" viewBox="0 0 50 50" x="0px" xmlns="http://www.w3.org/2000/svg" y="0px">
<path d="M11.1,29.6c-0.5-1.5-0.8-3-0.8-4.6c0-8.1,6.6-14.7,14.7-14.7S39.7,16.9,39.7,25c0,1.6-0.3,3.2-0.8,4.6l6.1,2c0.7-2.1,1.1-4.3,1.1-6.6c0-11.7-9.5-21.2-21.2-21.2S3.8,13.3,3.8,25c0,2.3,0.4,4.5,1.1,6.6L11.1,29.6z"></path>
</svg>
</bp-spinner>
</div>
<div class="bp-view payment manual-flow enter-contact-email active" id="emailAddressView">
<form class="manual__step-one refund-address-form contact-email-form" id="emailAddressForm" name="emailAddressForm" novalidate="">
<div class="manual__step-one__header">
@ -600,23 +624,27 @@
</div>
</div>
</div>
<div class="footer">
<div class="footer__item no-hover" style="opacity: 1; padding-left: 0; max-height: 21px;">
@if(Model.AvailableCryptos.Count > 1)
{
<div style="text-align:center">Accepted here</div>
<div style="text-align:center">
@foreach(var crypto in Model.AvailableCryptos)
{
<a style="text-decoration:none;" href="@crypto.Link" onclick="srvModel.paymentMethodId='@crypto.PaymentMethodId'; fetchStatus(); return false;"><img style="height:32px; margin-right:5px; margin-left:5px;" alt="@crypto.PaymentMethodId" src="@crypto.CryptoImage" /></a>
}
</div>
}
</div>
</div>
</div>
</div>
</div>
</invoice>
<script type="text/javascript">
// TODO: Move all logic from core.js to Vue controller
Vue.config.ignoredElements = [
'line-items',
'low-fee-timeline',
// Ignoring custom HTML5 elements, eg: bp-spinner
/^bp-/
];
var checkoutCtrl = new Vue({
el: '#checkoutCtrl',
components: {
qrcode: VueQr
},
data: {
srvModel: srvModel
}
});
</script>
</body>
</html>

View File

@ -153,7 +153,7 @@
<table class="table">
<thead class="thead-inverse">
<tr>
<th>Crypto</th>
<th style="white-space:nowrap;">Payment method</th>
<th>Rate</th>
<th>Paid</th>
<th>Due</th>
@ -164,7 +164,7 @@
@foreach(var payment in Model.CryptoPayments)
{
<tr>
<td>@payment.CryptoCode</td>
<td>@payment.PaymentMethod</td>
<td>@payment.Rate</td>
<td>@payment.Paid</td>
<td>@payment.Due</td>
@ -181,8 +181,7 @@
<table class="table">
<thead class="thead-inverse">
<tr>
<th>Crypto</th>
<th>Date</th>
<th style="white-space:nowrap;">Payment method</th>
<th>Deposit address</th>
<th>Transaction Id</th>
<th>Confirmations</th>
@ -193,8 +192,7 @@
@foreach(var payment in Model.Payments)
{
<tr>
<td>@payment.CryptoCode</td>
<td>@payment.ReceivedTime</td>
<td>@payment.PaymentMethod</td>
<td>@payment.DepositAddress</td>
<td><a href="@payment.TransactionLink" target="_blank">@payment.TransactionId</a></td>
<td>@payment.Confirmations</td>
@ -211,7 +209,7 @@
<table class="table">
<thead class="thead-inverse">
<tr>
<th>Crypto</th>
<th style="white-space:nowrap;">Payment method</th>
<th>Address</th>
<th>Current</th>
</tr>
@ -220,11 +218,11 @@
@foreach(var address in Model.Addresses)
{
<tr>
<td>@address.GetCryptoCode()</td>
<td>@address.GetAddress()</td>
<td>@(!address.UnAssigned.HasValue)</td>
<td style="width:100px;">@address.PaymentMethod</td>
<td style="max-width:100px;overflow:hidden;">@address.Destination</td>
<td>@address.Current.ToString()</td>
</tr>
}
}
</tbody>
</table>
</div>

View File

@ -58,7 +58,7 @@
</p>
</div>
<div class="form-group">
<label>Substract fees from amount</label>
<label>Subtract fees from amount</label>
<input id="substract-checkbox" name="SubstractFees" class="form-check" type="checkbox" />
</div>
<button id="confirm-button" name="command" type="submit" class="btn btn-success">Confirm</button>

View File

@ -27,5 +27,23 @@
"wwwroot/vendor/jquery-validate/jquery.validate.js",
"wwwroot/vendor/jquery-validate-unobtrusive/jquery.validate.unobtrusive.js"
]
},
{
"outputFileName": "wwwroot/bundles/checkout-bundle.min.css",
"inputFiles": [
"wwwroot/vendor/font-awesome/css/font-awesome.css",
"wwwroot/css/css.css",
"wwwroot/css/normalizer.css"
]
},
{
"outputFileName": "wwwroot/bundles/checkout-bundle.min.js",
"inputFiles": [
"wwwroot/vendor/clipboard.js/clipboard.js",
"wwwroot/vendor/jquery/jquery.js",
"wwwroot/js/vue.min.js",
"wwwroot/js/vue-qrcode.js",
"wwwroot/js/core.js"
]
}
]

View File

@ -8411,6 +8411,17 @@ strong {
float: right;
}
.currency-selection {
border-bottom: 1px solid #E9E9E9;
position: relative;
padding: 4px 15px;
display: flex;
font-weight: 300;
color: #565D6E;
letter-spacing: .45px;
background: #fff;
}
.single-item-order {
position: relative;
padding: 15px;
@ -10487,6 +10498,7 @@ All mobile class names should be prefixed by m- */
min-height: 575px;
}
.paid .currency-selection,
.expired .order-details,
.archived .order-details {
display: none;
@ -10881,6 +10893,20 @@ bp-spinner {
display: flex;
}
.payment__spinner {
margin-top: 140px;
display: none;
}
.payment__spinner > bp-spinner > svg {
margin: auto auto;
height: 60px;
width: 60px;
fill: gray;
animation: spin 0.55s linear infinite;
opacity: .85;
}
bp-refund-address.ng-valid .bitcoin-logo {
opacity: 1;
margin-left: 0;

View File

@ -1,6 +1,10 @@
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
<g transform="translate(0.00630876,-0.00301984) scale(0.25,0.25)">
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="64" width="64" version="1.1"
x="0px" y="0px"
viewBox="0 0 64 64"
xml:space="preserve"
xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
<g transform="translate(0.00630876,-0.00301984)">
<path fill="#f7931a" d="m63.033,39.744c-4.274,17.143-21.637,27.576-38.782,23.301-17.138-4.274-27.571-21.638-23.295-38.78,4.272-17.145,21.635-27.579,38.775-23.305,17.144,4.274,27.576,21.64,23.302,38.784z"/>
<path fill="#FFF" d="m46.103,27.444c0.637-4.258-2.605-6.547-7.038-8.074l1.438-5.768-3.511-0.875-1.4,5.616c-0.923-0.23-1.871-0.447-2.813-0.662l1.41-5.653-3.509-0.875-1.439,5.766c-0.764-0.174-1.514-0.346-2.242-0.527l0.004-0.018-4.842-1.209-0.934,3.75s2.605,0.597,2.55,0.634c1.422,0.355,1.679,1.296,1.636,2.042l-1.638,6.571c0.098,0.025,0.225,0.061,0.365,0.117-0.117-0.029-0.242-0.061-0.371-0.092l-2.296,9.205c-0.174,0.432-0.615,1.08-1.609,0.834,0.035,0.051-2.552-0.637-2.552-0.637l-1.743,4.019,4.569,1.139c0.85,0.213,1.683,0.436,2.503,0.646l-1.453,5.834,3.507,0.875,1.439-5.772c0.958,0.26,1.888,0.5,2.798,0.726l-1.434,5.745,3.511,0.875,1.453-5.823c5.987,1.133,10.489,0.676,12.384-4.739,1.527-4.36-0.076-6.875-3.226-8.515,2.294-0.529,4.022-2.038,4.483-5.155zm-8.022,11.249c-1.085,4.36-8.426,2.003-10.806,1.412l1.928-7.729c2.38,0.594,10.012,1.77,8.878,6.317zm1.086-11.312c-0.99,3.966-7.1,1.951-9.082,1.457l1.748-7.01c1.982,0.494,8.365,1.416,7.334,5.553z"/>
</g>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,156 +1,4 @@
/* TAF
- Version mobile
- Réparer le décallage par timer
- Preparer les variables de l'API
- Gestion des differents evenements en fonction du status de l'invoice
- sécuriser les CDN
*/
// TODO: Vue controller... complete migrate to it for binding, animations can stay in jQuery
Vue.config.ignoredElements = [
'line-items',
'low-fee-timeline',
// Ignoring custom HTML5 elements, eg: bp-spinner
/^bp-/
];
var checkoutCtrl = new Vue({
el: '#checkoutCtrl',
components: {
qrcode: VueQr
},
data: {
srvModel: srvModel
}
});
var display = $(".timer-row__time-left"); // Timer container
// check if the Document expired
if (srvModel.expirationSeconds > 0) {
progressStart(srvModel.maxTimeSeconds); // Progress bar
startTimer(srvModel.expirationSeconds, display); // Timer
if (!validateEmail(srvModel.customerEmail))
emailForm(); // Email form Display
else
hideEmailForm();
}
function hideEmailForm() {
$("[role=document]").removeClass("enter-purchaser-email");
$("#emailAddressView").removeClass("active");
$("placeholder-refundEmail").html(srvModel.customerEmail);
// Remove Email mode
$(".modal-dialog").removeClass("enter-purchaser-email");
$("#scan").addClass("active");
}
// Email Form
// Setup Email mode
function emailForm() {
$(".modal-dialog").addClass("enter-purchaser-email");
$("#emailAddressForm .action-button").click(function () {
var emailAddress = $("#emailAddressFormInput").val();
if (validateEmail(emailAddress)) {
$("#emailAddressForm .input-wrapper bp-loading-button .action-button").addClass("loading");
// Push the email to a server, once the reception is confirmed move on
srvModel.customerEmail = emailAddress;
var path = srvModel.serverUrl + "/i/" + srvModel.invoiceId + "/UpdateCustomer";
$.ajax({
url: path,
type: "POST",
data: JSON.stringify({ Email: srvModel.customerEmail }),
contentType: "application/json; charset=utf-8"
}).done(function () {
hideEmailForm();
})
.fail(function (jqXHR, textStatus, errorThrown) {
})
.always(function () {
$("#emailAddressForm .input-wrapper bp-loading-button .action-button").removeClass("loading");
});
} else {
$("#emailAddressForm").addClass("ng-touched ng-dirty ng-submitted ng-invalid");
}
});
}
/* =============== Even listeners =============== */
// Email
$("#emailAddressFormInput").change(function () {
if ($("#emailAddressForm").hasClass("ng-submitted")) {
$("#emailAddressForm").removeClass("ng-submitted");
}
});
// Scan/Copy Transitions
// Scan Tab
$("#scan-tab").click(function () {
if (!$(this).is(".active")) {
$(this).addClass("active");
}
if ($("#copy-tab").is(".active")) {
$("#copy-tab").removeClass("active");
}
$(".payment-tabs__slider").removeClass("slide-right");
if (!$("#scan").is(".active")) {
$("#copy").hide();
$("#copy").removeClass("active");
$("#scan").show();
$("#scan").addClass("active");
}
});
// Main Copy tab
$("#copy-tab").click(function () {
if (!$(this).is(".active")) {
$(this).addClass("active");
}
if ($("#scan-tab").is(".active")) {
$("#scan-tab").removeClass("active");
}
if (!$(".payment-tabs__slider").is("slide-right")) {
$(".payment-tabs__slider").addClass("slide-right");
}
if (!$("#copy").is(".active")) {
$("#copy").show();
$("#copy").addClass("active");
$("#scan").hide();
$("#scan").removeClass("active");
}
});
// Payment received
// Should connect using webhook ?
// If notification received
onDataCallback(srvModel);
// public methods
function onDataCallback(jsonData) {
var newStatus = jsonData.status;
@ -187,21 +35,44 @@ function onDataCallback(jsonData) {
$("#emailAddressView").removeClass("active");
$(".modal-dialog").addClass("expired");
$("#expired").addClass("active");
if ($("#scan").hasClass("active")) {
$("#scan").removeClass("active");
} else if ($("#copy").hasClass("active")) {
$("#copy").removeClass("active");
}
}
if (checkoutCtrl.srvModel.status !== newStatus) {
window.parent.postMessage({ "invoiceId": srvModel.invoiceId, "status": newStatus }, "*");
}
// restoring qr code view only when currency is switched
if (jsonData.paymentMethodId == srvModel.paymentMethodId) {
$("#scan").show();
$(".payment__spinner").hide();
}
// updating ui
checkoutCtrl.srvModel = jsonData;
}
function changeCurrency(currency) {
if (srvModel.paymentMethodId != currency) {
$("#scan").hide();
$(".payment__spinner").show();
srvModel.paymentMethodId = currency;
fetchStatus();
}
return false;
}
function fetchStatus() {
var path = srvModel.serverUrl + "/i/" + srvModel.invoiceId + "/" + srvModel.paymentMethodId + "/status";
$.ajax({
url: path,
type: "GET"
type: "GET",
cache: false
}).done(function (data) {
onDataCallback(data);
}).fail(function (jqXHR, textStatus, errorThrown) {
@ -209,123 +80,261 @@ function fetchStatus() {
});
}
var supportsWebSockets = 'WebSocket' in window && window.WebSocket.CLOSING === 2;
if (supportsWebSockets) {
var path = srvModel.serverUrl + "/i/" + srvModel.invoiceId + "/status/ws";
path = path.replace("https://", "wss://");
path = path.replace("http://", "ws://");
try {
var socket = new WebSocket(path);
socket.onmessage = function (e) {
fetchStatus();
};
}
catch (e) {
console.error("Error while connecting to websocket for invoice notifications");
}
}
// private methods
$(document).ready(function () {
// initialize
onDataCallback(srvModel);
var watcher = setInterval(function () {
fetchStatus();
}, 2000);
/* TAF
- Version mobile
- Réparer le décallage par timer
- Preparer les variables de l'API
- Gestion des differents evenements en fonction du status de l'invoice
- sécuriser les CDN
*/
var display = $(".timer-row__time-left"); // Timer container
// check if the Document expired
if (srvModel.expirationSeconds > 0) {
progressStart(srvModel.maxTimeSeconds); // Progress bar
startTimer(srvModel.expirationSeconds, display); // Timer
if (!validateEmail(srvModel.customerEmail))
emailForm(); // Email form Display
else
hideEmailForm();
}
function hideEmailForm() {
$("[role=document]").removeClass("enter-purchaser-email");
$("#emailAddressView").removeClass("active");
$("placeholder-refundEmail").html(srvModel.customerEmail);
// Remove Email mode
$(".modal-dialog").removeClass("enter-purchaser-email");
$("#scan").addClass("active");
}
// Email Form
// Setup Email mode
function emailForm() {
$(".modal-dialog").addClass("enter-purchaser-email");
$("#emailAddressForm .action-button").click(function () {
var emailAddress = $("#emailAddressFormInput").val();
if (validateEmail(emailAddress)) {
$("#emailAddressForm .input-wrapper bp-loading-button .action-button").addClass("loading");
// Push the email to a server, once the reception is confirmed move on
srvModel.customerEmail = emailAddress;
var path = srvModel.serverUrl + "/i/" + srvModel.invoiceId + "/UpdateCustomer";
$.ajax({
url: path,
type: "POST",
data: JSON.stringify({ Email: srvModel.customerEmail }),
contentType: "application/json; charset=utf-8"
}).done(function () {
hideEmailForm();
})
.fail(function (jqXHR, textStatus, errorThrown) {
})
.always(function () {
$("#emailAddressForm .input-wrapper bp-loading-button .action-button").removeClass("loading");
});
} else {
$("#emailAddressForm").addClass("ng-touched ng-dirty ng-submitted ng-invalid");
}
});
}
/* =============== Even listeners =============== */
// Email
$("#emailAddressFormInput").change(function () {
if ($("#emailAddressForm").hasClass("ng-submitted")) {
$("#emailAddressForm").removeClass("ng-submitted");
}
});
// Scan/Copy Transitions
// Scan Tab
$("#scan-tab").click(function () {
if (!$(this).is(".active")) {
$(this).addClass("active");
}
if ($("#copy-tab").is(".active")) {
$("#copy-tab").removeClass("active");
}
$(".payment-tabs__slider").removeClass("slide-right");
if (!$("#scan").is(".active")) {
$("#copy").hide();
$("#copy").removeClass("active");
$("#scan").show();
$("#scan").addClass("active");
}
});
// Main Copy tab
$("#copy-tab").click(function () {
if (!$(this).is(".active")) {
$(this).addClass("active");
}
if ($("#scan-tab").is(".active")) {
$("#scan-tab").removeClass("active");
}
if (!$(".payment-tabs__slider").is("slide-right")) {
$(".payment-tabs__slider").addClass("slide-right");
}
if (!$("#copy").is(".active")) {
$("#copy").show();
$("#copy").addClass("active");
$("#scan").hide();
$("#scan").removeClass("active");
}
});
// Payment received
// Should connect using webhook ?
// If notification received
var supportsWebSockets = 'WebSocket' in window && window.WebSocket.CLOSING === 2;
if (supportsWebSockets) {
var path = srvModel.serverUrl + "/i/" + srvModel.invoiceId + "/status/ws";
path = path.replace("https://", "wss://");
path = path.replace("http://", "ws://");
try {
var socket = new WebSocket(path);
socket.onmessage = function (e) {
fetchStatus();
};
}
catch (e) {
console.error("Error while connecting to websocket for invoice notifications");
}
}
var watcher = setInterval(function () {
fetchStatus();
}, 2000);
$(".menu__item").click(function () {
$(".menu__scroll .menu__item").removeClass("selected");
$(this).addClass("selected");
language();
$(".selector span").text($(".selected").text());
// function to load contents in different language should go there
});
// Validate Email address
function validateEmail(email) {
var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(email);
}
// Expand Line-Items
$(".buyerTotalLine").click(function () {
$("line-items").toggleClass("expanded");
$(".buyerTotalLine").toggleClass("expanded");
$(".single-item-order__right__btc-price__chevron").toggleClass("expanded");
});
// Timer Countdown
function startTimer(duration, display) {
var timer = duration, minutes, seconds;
var timeout = setInterval(function () {
minutes = parseInt(timer / 60, 10);
seconds = parseInt(timer % 60, 10);
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
display.text(minutes + ":" + seconds);
if (--timer < 0) {
clearInterval(timeout);
}
}, 1000);
}
// Progress bar
function progressStart(timerMax) {
var end = new Date(); // Setup Time Variable, should come from server
end.setSeconds(end.getSeconds() + srvModel.expirationSeconds);
timerMax *= 1000; // Usually 15 minutes = 9000 second= 900000 ms
var timeoutVal = Math.floor(timerMax / 100); // Timeout calc
animateUpdate(); //Launch it
function updateProgress(percentage) {
$('.timer-row__progress-bar').css("width", percentage + "%");
}
function animateUpdate() {
var now = new Date();
var timeDiff = end.getTime() - now.getTime();
var perc = 100 - Math.round(timeDiff / timerMax * 100);
if (perc === 75 && (status === "paidPartial" || status === "new")) {
$(".timer-row").addClass("expiring-soon");
$(".timer-row__message span").html("Invoice expiring soon ...");
updateProgress(perc);
}
if (perc <= 100) {
updateProgress(perc);
setTimeout(animateUpdate, timeoutVal);
}
if (perc >= 100 && status === "expired") {
onDataCallback(status);
}
}
}
// Manual Copy
// Amount
var copyAmount = new Clipboard('.manual-box__amount__value', {
target: function () {
var $el = $(".manual-box__amount__value");
$el.removeClass("copy-cursor").addClass("copied");
setTimeout(function () { $el.removeClass("copied").addClass("copy-cursor"); }, 500);
return document.querySelector('.manual-box__amount__value span');
}
});
// Address
var copyAddress = new Clipboard('.manual-box__address__value', {
target: function () {
var $elm = $(".manual-box__address__value");
$elm.removeClass("copy-cursor").addClass("copied");
setTimeout(function () { $elm.removeClass("copied").addClass("copy-cursor"); }, 500);
return document.querySelector('.manual-box__address__value .manual-box__address__wrapper .manual-box__address__wrapper__value');
}
});
// Disable enter key
$(document).keypress(
function (event) {
if (event.which === '13') {
event.preventDefault();
}
}
);
$(".menu__item").click(function () {
$(".menu__scroll .menu__item").removeClass("selected");
$(this).addClass("selected");
language();
$(".selector span").text($(".selected").text());
// function to load contents in different language should go there
});
// Validate Email address
function validateEmail(email) {
var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(email);
}
// Expand Line-Items
$(".buyerTotalLine").click(function () {
$("line-items").toggleClass("expanded");
$(".buyerTotalLine").toggleClass("expanded");
$(".single-item-order__right__btc-price__chevron").toggleClass("expanded");
});
// Timer Countdown
function startTimer(duration, display) {
var timer = duration, minutes, seconds;
var timeout = setInterval(function () {
minutes = parseInt(timer / 60, 10);
seconds = parseInt(timer % 60, 10);
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
display.text(minutes + ":" + seconds);
if (--timer < 0) {
clearInterval(timeout);
}
}, 1000);
}
// Progress bar
function progressStart(timerMax) {
var end = new Date(); // Setup Time Variable, should come from server
end.setSeconds(end.getSeconds() + srvModel.expirationSeconds);
timerMax *= 1000; // Usually 15 minutes = 9000 second= 900000 ms
var timeoutVal = Math.floor(timerMax / 100); // Timeout calc
animateUpdate(); //Launch it
function updateProgress(percentage) {
$('.timer-row__progress-bar').css("width", percentage + "%");
}
function animateUpdate() {
var now = new Date();
var timeDiff = end.getTime() - now.getTime();
var perc = 100 - Math.round(timeDiff / timerMax * 100);
if (perc === 75 && (status === "paidPartial" || status === "new")) {
$(".timer-row").addClass("expiring-soon");
$(".timer-row__message span").html("Invoice expiring soon ...");
updateProgress(perc);
}
if (perc <= 100) {
updateProgress(perc);
setTimeout(animateUpdate, timeoutVal);
}
if (perc >= 100 && status === "expired") {
onDataCallback(status);
}
}
}
// Manual Copy
// Amount
var copyAmount = new Clipboard('.manual-box__amount__value', {
target: function () {
var $el = $(".manual-box__amount__value");
$el.removeClass("copy-cursor").addClass("copied");
setTimeout(function () { $el.removeClass("copied").addClass("copy-cursor"); }, 500);
return document.querySelector('.manual-box__amount__value span');
}
});
// Address
var copyAddress = new Clipboard('.manual-box__address__value', {
target: function () {
var $elm = $(".manual-box__address__value");
$elm.removeClass("copy-cursor").addClass("copied");
setTimeout(function () { $elm.removeClass("copied").addClass("copy-cursor"); }, 500);
return document.querySelector('.manual-box__address__value .manual-box__address__wrapper .manual-box__address__wrapper__value');
}
});
// Disable enter key
$(document).keypress(
function (event) {
if (event.which === '13') {
event.preventDefault();
}
}
);

View File

@ -23,3 +23,39 @@ This solution is for you if:
## Documentation
Please check out our [complete documentation](https://github.com/btcpayserver/btcpayserver-doc) for more details.
You can also checkout [The Merchants Guide to accepting Bitcoin directly with no intermediates through BTCPay](https://www.reddit.com/r/Bitcoin/comments/81h1oy/the_merchants_guide_to_accepting_bitcoin_directly/).
## How to build
While the documentation advise using docker-compose, you may want to build yourself outside of development purpose.
First install .NET Core SDK as specified by [Microsoft website](https://www.microsoft.com/net/download).
On Powershell:
```
.\build.ps1
```
On linux:
```
./build.sh
```
## How to run
Use the `run` scripts to run BTCPayServer, this example show how to print the available command line arguments of BTCPayServer.
On Powershell:
```
.\run.ps1 --help
```
On linux:
```
./run.sh --help
```
## Other dependencies
For more information see the documentation [How to deploy a BTCPay server instance](https://github.com/btcpayserver/btcpayserver-doc/blob/master/Deployment.md).

1
build.ps1 Executable file
View File

@ -0,0 +1 @@
dotnet build -c Release .\BTCPayServer\BTCPayServer.csproj

3
build.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
dotnet build -c Release BTCPayServer/BTCPayServer.csproj

1
run.ps1 Executable file
View File

@ -0,0 +1 @@
dotnet run --no-launch-profile --no-build -c Release -p .\BTCPayServer\BTCPayServer.csproj -- $args

3
run.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
dotnet run --no-launch-profile --no-build -c Release -p "BTCPayServer/BTCPayServer.csproj" -- "$@"