Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
44791cc9f3 | |||
a14b94c96f | |||
db1cf5c2ce | |||
1a060a6c7b | |||
ff719fbe2d | |||
94e9ab7f67 | |||
06a96e8b77 | |||
d43c3dc968 | |||
e3c5efa929 | |||
8db9d93d23 | |||
517bb94b8b | |||
c804f55d82 | |||
c96a25c9b9 |
@ -19,6 +19,12 @@ Once you want to stop
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
If you want to stop, and remove all existing data
|
||||
|
||||
```
|
||||
docker-compose down -v
|
||||
```
|
||||
|
||||
You can run the tests inside a container by running
|
||||
|
||||
```
|
||||
|
@ -118,7 +118,7 @@ namespace BTCPayServer.Tests
|
||||
var acc = tester.NewAccount();
|
||||
acc.Register();
|
||||
acc.CreateStore();
|
||||
|
||||
|
||||
var controller = tester.PayTester.GetController<StoresController>(acc.UserId);
|
||||
var token = (RedirectToActionResult)controller.CreateToken(acc.StoreId, new Models.StoreViewModels.CreateTokenViewModel()
|
||||
{
|
||||
|
@ -58,7 +58,13 @@ namespace BTCPayServer.Authentication
|
||||
|
||||
public async Task<string> CreatePairingCodeAsync()
|
||||
{
|
||||
string pairingCodeId = Encoders.Base58.EncodeData(RandomUtils.GetBytes(6));
|
||||
string pairingCodeId = null;
|
||||
while(true)
|
||||
{
|
||||
pairingCodeId = Encoders.Base58.EncodeData(RandomUtils.GetBytes(6));
|
||||
if(pairingCodeId.Length == 7) // woocommerce plugin check for exactly 7 digits
|
||||
break;
|
||||
}
|
||||
using(var ctx = _Factory.CreateContext())
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
|
@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<Version>1.0.0.13</Version>
|
||||
<Version>1.0.0.17</Version>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="Build\dockerfiles\**" />
|
||||
|
@ -58,6 +58,12 @@ namespace BTCPayServer.Controllers
|
||||
pairingEntity = await _TokenRepository.GetPairingAsync(request.PairingCode);
|
||||
pairingEntity.SIN = sin;
|
||||
|
||||
if(string.IsNullOrEmpty(pairingEntity.Label) && !string.IsNullOrEmpty(request.Label))
|
||||
{
|
||||
pairingEntity.Label = request.Label;
|
||||
await _TokenRepository.UpdatePairingCode(pairingEntity);
|
||||
}
|
||||
|
||||
var result = await _TokenRepository.PairWithSINAsync(request.PairingCode, sin);
|
||||
if(result != PairingResult.Complete && result != PairingResult.Partial)
|
||||
throw new BitpayHttpException(400, $"Error while pairing ({result})");
|
||||
|
@ -92,6 +92,27 @@ namespace BTCPayServer.Controllers
|
||||
return View(model);
|
||||
}
|
||||
|
||||
|
||||
static Dictionary<string, CultureInfo> _CurrencyProviders = new Dictionary<string, CultureInfo>();
|
||||
private IFormatProvider GetCurrencyProvider(string currency)
|
||||
{
|
||||
lock(_CurrencyProviders)
|
||||
{
|
||||
if(_CurrencyProviders.Count == 0)
|
||||
{
|
||||
foreach(var culture in CultureInfo.GetCultures(CultureTypes.AllCultures).Where(c => !c.IsNeutralCulture))
|
||||
{
|
||||
try
|
||||
{
|
||||
_CurrencyProviders.TryAdd(new RegionInfo(culture.LCID).ISOCurrencySymbol, culture);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
return _CurrencyProviders.TryGet(currency);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("i/{invoiceId}")]
|
||||
[Route("invoice")]
|
||||
@ -112,6 +133,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
var model = new PaymentModel()
|
||||
{
|
||||
ServerUrl = HttpContext.Request.GetAbsoluteRoot(),
|
||||
OrderId = invoice.OrderId,
|
||||
InvoiceId = invoice.Id,
|
||||
BTCAddress = invoice.DepositAddress.ToString(),
|
||||
@ -122,7 +144,7 @@ namespace BTCPayServer.Controllers
|
||||
ExpirationSeconds = Math.Max(0, (int)(invoice.ExpirationTime - DateTimeOffset.UtcNow).TotalSeconds),
|
||||
MaxTimeSeconds = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalSeconds,
|
||||
ItemDesc = invoice.ProductInformation.ItemDesc,
|
||||
Rate = invoice.Rate.ToString(),
|
||||
Rate = invoice.Rate.ToString("C", GetCurrencyProvider(invoice.ProductInformation.Currency)),
|
||||
RedirectUrl = invoice.RedirectURL,
|
||||
StoreName = store.StoreName,
|
||||
TxFees = invoice.TxFee.ToString(),
|
||||
|
@ -1,4 +1,5 @@
|
||||
using BTCPayServer.Authentication;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Services.Stores;
|
||||
@ -8,6 +9,7 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
@ -21,6 +23,7 @@ namespace BTCPayServer.Controllers
|
||||
[Route("stores")]
|
||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||
[Authorize(Policy = "CanAccessStore")]
|
||||
[AutoValidateAntiforgeryToken]
|
||||
public class StoresController : Controller
|
||||
{
|
||||
public StoresController(
|
||||
@ -123,7 +126,6 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[Route("{storeId}")]
|
||||
public async Task<IActionResult> UpdateStore(string storeId, StoreViewModel model, string command)
|
||||
{
|
||||
@ -220,7 +222,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[Route("/api-tokens")]
|
||||
[Route("{storeId}/Tokens/Create")]
|
||||
public async Task<IActionResult> CreateToken(string storeId, CreateTokenViewModel model)
|
||||
{
|
||||
@ -228,6 +230,17 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
model.Label = model.Label ?? String.Empty;
|
||||
if(storeId == null) // Permissions are not checked by Policy if the storeId is not passed by url
|
||||
{
|
||||
storeId = model.StoreId;
|
||||
var userId = GetUserId();
|
||||
if(userId == null)
|
||||
return Unauthorized();
|
||||
var store = await _Repo.FindStore(storeId, userId);
|
||||
if(store == null)
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var tokenRequest = new TokenRequest()
|
||||
{
|
||||
@ -262,16 +275,29 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("/api-tokens")]
|
||||
[Route("{storeId}/Tokens/Create")]
|
||||
public IActionResult CreateToken()
|
||||
public async Task<IActionResult> CreateToken(string storeId)
|
||||
{
|
||||
var userId = GetUserId();
|
||||
if(string.IsNullOrWhiteSpace(userId))
|
||||
return Unauthorized();
|
||||
var model = new CreateTokenViewModel();
|
||||
model.Facade = "merchant";
|
||||
ViewBag.HidePublicKey = storeId == null;
|
||||
ViewBag.ShowStores = storeId == null;
|
||||
ViewBag.ShowMenu = storeId != null;
|
||||
model.StoreId = storeId;
|
||||
if(storeId == null)
|
||||
{
|
||||
model.Stores = new SelectList(await _Repo.GetStoresByUserId(userId), nameof(StoreData.Id), nameof(StoreData.StoreName), storeId);
|
||||
}
|
||||
|
||||
return View(model);
|
||||
}
|
||||
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[Route("{storeId}/Tokens/Delete")]
|
||||
public async Task<IActionResult> DeleteToken(string storeId, string tokenId)
|
||||
{
|
||||
@ -316,7 +342,6 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[Route("api-access-request")]
|
||||
public async Task<IActionResult> Pair(string pairingCode, string selectedStore)
|
||||
{
|
||||
|
@ -69,12 +69,16 @@ namespace BTCPayServer.Hosting
|
||||
object storeId = null;
|
||||
if(!((Microsoft.AspNetCore.Mvc.ActionContext)context.Resource).RouteData.Values.TryGetValue("storeId", out storeId))
|
||||
context.Succeed(requirement);
|
||||
else
|
||||
else if(storeId != null)
|
||||
{
|
||||
var store = await _StoreRepository.FindStore((string)storeId, _UserManager.GetUserId(((Microsoft.AspNetCore.Mvc.ActionContext)context.Resource).HttpContext.User));
|
||||
if(store != null)
|
||||
if(requirement.Role == null || requirement.Role == store.Role)
|
||||
context.Succeed(requirement);
|
||||
var user = _UserManager.GetUserId(((Microsoft.AspNetCore.Mvc.ActionContext)context.Resource).HttpContext.User);
|
||||
if(user != null)
|
||||
{
|
||||
var store = await _StoreRepository.FindStore((string)storeId, user);
|
||||
if(store != null)
|
||||
if(requirement.Role == null || requirement.Role == store.Role)
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,10 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string ServerUrl
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string OrderId
|
||||
{
|
||||
get; set;
|
||||
|
@ -1,4 +1,5 @@
|
||||
using BTCPayServer.Validations;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
@ -14,7 +15,7 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
[Required]
|
||||
|
||||
public string Label
|
||||
{
|
||||
get; set;
|
||||
@ -25,6 +26,17 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[Required]
|
||||
public string StoreId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public SelectList Stores
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
public class TokenViewModel
|
||||
{
|
||||
|
@ -27,6 +27,8 @@
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.qrcode/1.0/jquery.qrcode.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
var serverUrl = "@Model.ServerUrl";
|
||||
var invoiceId = "@Model.InvoiceId";
|
||||
var btcAddress = "@Model.BTCAddress";
|
||||
var btcDue = "@Model.BTCDue"; //must be a string
|
||||
var customerEmail = "@Model.CustomerEmail"; // Place holder
|
||||
@ -128,8 +130,6 @@
|
||||
<div class="single-item-order__right">
|
||||
<div class="single-item-order__right__btc-price clickable" id="buyerTotalBtcAmount">
|
||||
<span>@Model.BTCTotalDue</span>
|
||||
<!---->
|
||||
<img class="single-item-order__right__btc-price__chevron" src="~/img/chevron.svg">
|
||||
</div>
|
||||
<!---->
|
||||
<div class="single-item-order__right__ex-rate">
|
||||
@ -178,23 +178,23 @@
|
||||
<div adjust-height="" class="payment-box">
|
||||
<div class="bp-view payment scan" id="scan" style="opacity: 1;">
|
||||
<div class="payment__scan">
|
||||
<div class="payment__details__instruction__open-wallet hidden-sm-up">
|
||||
<!---->
|
||||
<a class="payment__details__instruction__open-wallet__btn action-button action-button--secondary">
|
||||
<span i18n="">Show QR code</span>
|
||||
<img class="m-qr-code-icon" src="~/img/qr-code.svg">
|
||||
</a>
|
||||
<div class="m-qr-code-container hidden-sm-up hide">
|
||||
<p class="m-qr-code-header" i18n="">
|
||||
Hide QR code
|
||||
<img class="m-qr-code-expand" src="~/img/chevron.svg">
|
||||
</p>
|
||||
@*<div class="payment__details__instruction__open-wallet hidden-sm-up">
|
||||
<!---->
|
||||
<div class="qr-codes"></div>
|
||||
</div>
|
||||
</div>
|
||||
<a class="payment__details__instruction__open-wallet__btn action-button action-button--secondary">
|
||||
<span i18n="">Show QR code</span>
|
||||
<img class="m-qr-code-icon" src="~/img/qr-code.svg">
|
||||
</a>
|
||||
<div class="m-qr-code-container hidden-sm-up hide">
|
||||
<p class="m-qr-code-header" i18n="">
|
||||
Hide QR code
|
||||
<img class="m-qr-code-expand" src="~/img/chevron.svg">
|
||||
</p>
|
||||
<!---->
|
||||
<div class="qr-codes"></div>
|
||||
</div>
|
||||
</div>*@
|
||||
<!---->
|
||||
<div class="qr-codes hidden-xs-down"></div>
|
||||
<div class="qr-codes"></div>
|
||||
</div>
|
||||
<div class="payment__details__instruction__open-wallet">
|
||||
<a class="payment__details__instruction__open-wallet__btn action-button" href="@Model.InvoiceBitcoinUrl">
|
||||
@ -379,7 +379,7 @@
|
||||
<div class="manual-box__amount__label label" i18n="">Amount</div>
|
||||
<!---->
|
||||
<div class="manual-box__amount__value copy-cursor" ngxclipboard="">
|
||||
<span>@Model.BTCDue BTC</span>
|
||||
<span>@Model.BTCDue</span> BTC
|
||||
<div class="copied-label">
|
||||
<span i18n="">Copied</span>
|
||||
</div>
|
||||
@ -426,6 +426,11 @@
|
||||
<!---->
|
||||
<div class="success-message" i18n="">This invoice has been paid.</div>
|
||||
<!---->
|
||||
<button class="action-button" style="margin-top: 0px;">
|
||||
<bp-done-text>
|
||||
<span i18n="" class="i18n-return-to-merchant">Return to @Model.StoreName</span>
|
||||
</bp-done-text>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
|
@ -1,5 +1,6 @@
|
||||
@{
|
||||
Layout = "/Views/Shared/_Layout.cshtml";
|
||||
ViewBag.ShowMenu = ViewBag.ShowMenu ?? true;
|
||||
}
|
||||
|
||||
|
||||
@ -15,7 +16,10 @@
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
@await Html.PartialAsync("_Nav")
|
||||
@if(ViewBag.ShowMenu)
|
||||
{
|
||||
@await Html.PartialAsync("_Nav")
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
@RenderBody()
|
||||
|
@ -3,24 +3,33 @@
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
ViewData["Title"] = "Create a new token";
|
||||
ViewData.AddActivePage(StoreNavPages.Tokens);
|
||||
ViewBag.HidePublicKey = ViewBag.HidePublicKey ?? false;
|
||||
ViewBag.ShowStores = ViewBag.ShowStores ?? false;
|
||||
}
|
||||
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<form asp-action="CreateToken" method="post">
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
<label asp-for="Label"></label>
|
||||
@if(ViewBag.HidePublicKey)
|
||||
{
|
||||
<small class="text-muted">optional</small>
|
||||
}
|
||||
<input asp-for="Label" class="form-control" />
|
||||
<span asp-validation-for="Label" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="PublicKey"></label>
|
||||
<small class="text-muted">Keep empty for server-initiated pairing</small>
|
||||
<input asp-for="PublicKey" class="form-control" />
|
||||
<span asp-validation-for="PublicKey" class="text-danger"></span>
|
||||
</div>
|
||||
@if(!ViewBag.HidePublicKey)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="PublicKey"></label>
|
||||
<small class="text-muted">Keep empty for server-initiated pairing</small>
|
||||
<input asp-for="PublicKey" class="form-control" />
|
||||
<span asp-validation-for="PublicKey" class="text-danger"></span>
|
||||
</div>
|
||||
}
|
||||
<div class="form-group">
|
||||
<label asp-for="Facade"></label>
|
||||
<select asp-for="Facade" class="form-control">
|
||||
@ -29,6 +38,19 @@
|
||||
</select>
|
||||
<span asp-validation-for="Facade" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
@if(ViewBag.ShowStores)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="StoreId" class="control-label"></label>
|
||||
<select asp-for="StoreId" asp-items="Model.Stores" class="form-control"></select>
|
||||
<span asp-validation-for="StoreId" class="text-danger"></span>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<input type="hidden" asp-for="StoreId" />
|
||||
}
|
||||
<div class="form-group">
|
||||
<input type="submit" value="Request pairing" class="btn btn-default" />
|
||||
</div>
|
||||
|
@ -8461,7 +8461,6 @@ strong {
|
||||
.single-item-order__right__ex-rate {
|
||||
font-style: italic;
|
||||
font-size: 11px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.single-item-order__right__btc-price {
|
||||
|
@ -69,7 +69,7 @@ function emailForm() {
|
||||
// Push the email to a server, once the reception is confirmed move on
|
||||
customerEmail = emailAddress;
|
||||
|
||||
var path = window.location.pathname + "/UpdateCustomer";
|
||||
var path = serverUrl + "/i/" + invoiceId + "/UpdateCustomer";
|
||||
|
||||
$.ajax({
|
||||
url: path,
|
||||
@ -155,9 +155,15 @@ $("#copy-tab").click(function () {
|
||||
// Should connect using webhook ?
|
||||
// If notification received
|
||||
|
||||
var oldStatus = status;
|
||||
updateState(status);
|
||||
|
||||
function updateState(status) {
|
||||
if (oldStatus != status)
|
||||
{
|
||||
oldStatus = status;
|
||||
window.parent.postMessage({ "invoiceId": invoiceId, "status": status }, "*");
|
||||
}
|
||||
if (status == "complete" ||
|
||||
status == "paidOver" ||
|
||||
status == "confirmed" ||
|
||||
@ -165,6 +171,17 @@ function updateState(status) {
|
||||
if ($(".modal-dialog").hasClass("expired")) {
|
||||
$(".modal-dialog").removeClass("expired");
|
||||
}
|
||||
|
||||
if (merchantRefLink != "") {
|
||||
$(".action-button").click(function () {
|
||||
window.location.href = merchantRefLink;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
$(".action-button").hide();
|
||||
}
|
||||
|
||||
$(".modal-dialog").addClass("paid");
|
||||
|
||||
if ($("#scan").hasClass("active")) {
|
||||
@ -186,7 +203,7 @@ function updateState(status) {
|
||||
}
|
||||
|
||||
var watcher = setInterval(function () {
|
||||
var path = window.location.pathname + "/status";
|
||||
var path = serverUrl + "/i/" + invoiceId + "/status";
|
||||
$.ajax({
|
||||
url: path,
|
||||
type: "GET"
|
||||
@ -207,11 +224,6 @@ $(".menu__item").click(function () {
|
||||
// function to load contents in different language should go there
|
||||
});
|
||||
|
||||
// Redirect
|
||||
$("#expired .action-button").click(function () {
|
||||
window.location.href = merchantRefLink;
|
||||
});
|
||||
|
||||
// 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,}))$/;
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26730.3
|
||||
VisualStudioVersion = 15.0.26730.16
|
||||
MinimumVisualStudioVersion = 15.0.26124.0
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BTCPayServer", "BTCPayServer\BTCPayServer.csproj", "{949A0870-8D8C-4DE5-8845-DDD560489177}"
|
||||
EndProject
|
||||
|
Reference in New Issue
Block a user