Compare commits
47 Commits
Author | SHA1 | Date | |
---|---|---|---|
59de852484 | |||
c9d5e9ac05 | |||
59ce3b5fc0 | |||
b71f9d0a08 | |||
55a4c3c08d | |||
c79db1dc99 | |||
a5bd27661b | |||
9a0d0a7124 | |||
454939adad | |||
37c02d2539 | |||
5f8407b4b1 | |||
abeb10cc8c | |||
4dca81403b | |||
6ba6a34df2 | |||
6d14fe9c30 | |||
c0c4637c77 | |||
752d34f603 | |||
0009ed0921 | |||
953de22233 | |||
1477aef39c | |||
2cca8f82e1 | |||
9094af025d | |||
0ba608bd1e | |||
67f5d497d5 | |||
3cc614c024 | |||
7c20bab1c5 | |||
a2a3f43fd0 | |||
9f17e3e1f8 | |||
c1a2fc22f4 | |||
f1f19369a3 | |||
e49f25af09 | |||
bc28571174 | |||
2beae1dcd3 | |||
39a0b1e29e | |||
0f603ffb0a | |||
8fe5835e09 | |||
5fed7a3a0c | |||
28ea694791 | |||
45b0991841 | |||
505d9904af | |||
da385f9295 | |||
214de77a41 | |||
7b2db3755e | |||
44791cc9f3 | |||
a14b94c96f | |||
db1cf5c2ce | |||
1a060a6c7b |
@ -121,3 +121,4 @@ bower_components
|
||||
output
|
||||
|
||||
.vs
|
||||
**/launchSettings.json
|
||||
|
@ -1,6 +1,6 @@
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Hosting;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.Tests.Mocks;
|
||||
@ -121,6 +121,11 @@ namespace BTCPayServer.Tests
|
||||
internal set;
|
||||
}
|
||||
|
||||
public T GetService<T>()
|
||||
{
|
||||
return _Host.Services.GetRequiredService<T>();
|
||||
}
|
||||
|
||||
public T GetController<T>(string userId = null) where T : Controller
|
||||
{
|
||||
var context = new DefaultHttpContext();
|
||||
|
@ -3,6 +3,8 @@
|
||||
The tests depends on having a proper environment running with Postgres, Bitcoind, NBxplorer configured.
|
||||
You can however use the `docker-compose.yml` of this folder to get it running.
|
||||
|
||||
This is running a bitcoind instance on regtest, a private bitcoin blockchain for testing on which you can generate blocks yourself.
|
||||
|
||||
```
|
||||
docker-compose up nbxplorer
|
||||
```
|
||||
@ -31,8 +33,14 @@ You can run the tests inside a container by running
|
||||
docker-compose run --rm tests
|
||||
```
|
||||
|
||||
The Bitcoin RPC server is exposed to the host, for example, you can send 0.23111090 BTC to mohu16LH66ptoWGEL1GtP6KHTBJYXMWhEf.
|
||||
## Send commands to bitcoind
|
||||
|
||||
You can call bitcoin-cli inside the container with `docker exec`, for example, if you want to send `0.23111090` to `mohu16LH66ptoWGEL1GtP6KHTBJYXMWhEf`:
|
||||
```
|
||||
bitcoin-cli -regtest -rpcport=43782 -rpcuser=ceiwHEbqWI83 -rpcpassword=DwubwWsoo3 sendtoaddress "mohu16LH66ptoWGEL1GtP6KHTBJYXMWhEf" 0.23111090
|
||||
docker exec -ti btcpayserver_dev_bitcoind bitcoin-cli -regtest -conf="/data/bitcoin.conf" -datadir="/data" sendtoaddress "mohu16LH66ptoWGEL1GtP6KHTBJYXMWhEf" 0.23111090
|
||||
```
|
||||
|
||||
If you are using Powershell:
|
||||
```
|
||||
.\docker-bitcoin-cli.ps1 sendtoaddress "mohu16LH66ptoWGEL1GtP6KHTBJYXMWhEf" 0.23111090
|
||||
```
|
@ -1,7 +1,7 @@
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Models.AccountViewModels;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
|
@ -9,13 +9,19 @@ using System.Threading;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Newtonsoft.Json;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using BTCPayServer.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using BTCPayServer.Authentication;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.EntityFrameworkCore.Extensions;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -209,15 +215,14 @@ namespace BTCPayServer.Tests
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
|
||||
var repo = tester.PayTester.GetService<InvoiceRepository>();
|
||||
var ctx = tester.PayTester.GetService<ApplicationDbContextFactory>().CreateContext();
|
||||
var textSearchResult = tester.PayTester.Runtime.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
StoreId = user.StoreId,
|
||||
TextSearch = invoice.OrderId
|
||||
}).GetAwaiter().GetResult();
|
||||
|
||||
Assert.Equal(1, textSearchResult.Length);
|
||||
|
||||
textSearchResult = tester.PayTester.Runtime.InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
StoreId = user.StoreId,
|
||||
@ -245,19 +250,36 @@ namespace BTCPayServer.Tests
|
||||
var rate = user.BitPay.GetRates();
|
||||
|
||||
var cashCow = tester.ExplorerNode;
|
||||
|
||||
var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
|
||||
var iii = ctx.AddressInvoices.ToArray();
|
||||
Assert.True(IsMapped(invoice, ctx));
|
||||
cashCow.SendToAddress(invoiceAddress, firstPayment);
|
||||
|
||||
var invoiceEntity = repo.GetInvoice(null, invoice.Id, true).GetAwaiter().GetResult();
|
||||
Assert.Equal(1, invoiceEntity.HistoricalAddresses.Length);
|
||||
Assert.Null(invoiceEntity.HistoricalAddresses[0].UnAssigned);
|
||||
|
||||
Money secondPayment = Money.Zero;
|
||||
|
||||
Eventually(() =>
|
||||
{
|
||||
tester.SimulateCallback(invoiceAddress);
|
||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal("paidPartial", localInvoice.Status);
|
||||
Assert.Equal("new", localInvoice.Status);
|
||||
Assert.Equal(firstPayment, localInvoice.BtcPaid);
|
||||
txFee = localInvoice.BtcDue - invoice.BtcDue;
|
||||
Assert.Equal("paidPartial", localInvoice.ExceptionStatus);
|
||||
Assert.NotEqual(localInvoice.BitcoinAddress, invoice.BitcoinAddress); //New address
|
||||
Assert.True(IsMapped(invoice, ctx));
|
||||
Assert.True(IsMapped(localInvoice, ctx));
|
||||
|
||||
invoiceEntity = repo.GetInvoice(null, invoice.Id, true).GetAwaiter().GetResult();
|
||||
var historical1 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.Address == invoice.BitcoinAddress.ToString());
|
||||
Assert.NotNull(historical1.UnAssigned);
|
||||
var historical2 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.Address == localInvoice.BitcoinAddress.ToString());
|
||||
Assert.Null(historical2.UnAssigned);
|
||||
invoiceAddress = BitcoinAddress.Create(localInvoice.BitcoinAddress, cashCow.Network);
|
||||
secondPayment = localInvoice.BtcDue;
|
||||
});
|
||||
|
||||
@ -270,6 +292,8 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal("paid", localInvoice.Status);
|
||||
Assert.Equal(firstPayment + secondPayment, localInvoice.BtcPaid);
|
||||
Assert.Equal(Money.Zero, localInvoice.BtcDue);
|
||||
Assert.Equal(localInvoice.BitcoinAddress, invoiceAddress.ToString()); //no new address generated
|
||||
Assert.True(IsMapped(localInvoice, ctx));
|
||||
Assert.Equal(false, (bool)((JValue)localInvoice.ExceptionStatus).Value);
|
||||
});
|
||||
|
||||
@ -310,7 +334,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
tester.SimulateCallback(invoiceAddress);
|
||||
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
|
||||
Assert.Equal("paidOver", localInvoice.Status);
|
||||
Assert.Equal("paid", localInvoice.Status);
|
||||
Assert.Equal(Money.Zero, localInvoice.BtcDue);
|
||||
Assert.Equal("paidOver", (string)((JValue)localInvoice.ExceptionStatus).Value);
|
||||
});
|
||||
@ -328,6 +352,27 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckRatesProvider()
|
||||
{
|
||||
var coinAverage = new CoinAverageRateProvider();
|
||||
var jpy = coinAverage.GetRateAsync("JPY").GetAwaiter().GetResult();
|
||||
var jpy2 = new BitpayRateProvider(new Bitpay(new Key(), new Uri("https://bitpay.com/"))).GetRateAsync("JPY").GetAwaiter().GetResult();
|
||||
|
||||
var cached = new CachedRateProvider(coinAverage, new MemoryCache(new MemoryCacheOptions() { ExpirationScanFrequency = TimeSpan.FromSeconds(1.0) }));
|
||||
cached.CacheSpan = TimeSpan.FromSeconds(10);
|
||||
var a = cached.GetRateAsync("JPY").GetAwaiter().GetResult();
|
||||
var b = cached.GetRateAsync("JPY").GetAwaiter().GetResult();
|
||||
//Manually check that cache get hit after 10 sec
|
||||
var c = cached.GetRateAsync("JPY").GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private static bool IsMapped(Invoice invoice, ApplicationDbContext ctx)
|
||||
{
|
||||
var h = BitcoinAddress.Create(invoice.BitcoinAddress).ScriptPubKey.Hash.ToString();
|
||||
return ctx.AddressInvoices.FirstOrDefault(i => i.InvoiceDataId == invoice.Id && i.Address == h) != null;
|
||||
}
|
||||
|
||||
private void Eventually(Action act)
|
||||
{
|
||||
CancellationTokenSource cts = new CancellationTokenSource(10000);
|
||||
|
50
BTCPayServer.Tests/UnitTestPeusa.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitpayClient;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Xunit;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
// Helper class for testing functionality and generating data needed during coding/debuging
|
||||
public class UnitTestPeusa
|
||||
{
|
||||
// Unit test that generates temorary checkout Bitpay page
|
||||
// https://forkbitpay.slack.com/archives/C7M093Z55/p1508293682000217
|
||||
[Fact]
|
||||
public void BitpayCheckout()
|
||||
{
|
||||
var key = new Key(Encoders.Hex.DecodeData("7b70a06f35562873e3dcb46005ed0fe78e1991ad906e56adaaafa40ba861e056"));
|
||||
var url = new Uri("https://test.bitpay.com/");
|
||||
var btcpay = new Bitpay(key, url);
|
||||
var invoice = btcpay.CreateInvoice(new Invoice()
|
||||
{
|
||||
|
||||
Price = 5.0,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "cdfd8a5f-6928-4c3b-ba9b-ddf438029e73",
|
||||
ItemDesc = "Hello from the otherside"
|
||||
}, Facade.Merchant);
|
||||
|
||||
// go to invoice.Url
|
||||
Console.WriteLine(invoice.Url);
|
||||
}
|
||||
|
||||
// Generating Extended public key to use on http://localhost:14142/stores/{storeId}
|
||||
[Fact]
|
||||
public void GeneratePubkey()
|
||||
{
|
||||
var network = Network.RegTest;
|
||||
|
||||
ExtKey masterKey = new ExtKey();
|
||||
Console.WriteLine("Master key : " + masterKey.ToString(network));
|
||||
ExtPubKey masterPubKey = masterKey.Neuter();
|
||||
|
||||
ExtPubKey pubkey = masterPubKey.Derive(0);
|
||||
Console.WriteLine("PubKey " + 0 + " : " + pubkey.ToString(network));
|
||||
}
|
||||
}
|
||||
}
|
1
BTCPayServer.Tests/docker-bitcoin-cli.ps1
Normal file
@ -0,0 +1 @@
|
||||
docker exec -ti btcpayserver_dev_bitcoind bitcoin-cli -regtest -conf="/data/bitcoin.conf" -datadir="/data" $args
|
@ -39,14 +39,13 @@ services:
|
||||
- postgres
|
||||
|
||||
bitcoind:
|
||||
container_name: btcpayserver_dev_bitcoind
|
||||
image: nicolasdorier/docker-bitcoin:0.15.0.1
|
||||
ports:
|
||||
- "43782:43782"
|
||||
- "39388:39388"
|
||||
environment:
|
||||
BITCOIN_EXTRA_ARGS: "regtest=1\nrpcport=43782\nport=39388\nwhitelist=0.0.0.0/0"
|
||||
BITCOIN_RPC_USER: ceiwHEbqWI83
|
||||
BITCOIN_RPC_PASSWORD: DwubwWsoo3
|
||||
BITCOIN_EXTRA_ARGS: "rpcuser=ceiwHEbqWI83\nrpcpassword=DwubwWsoo3\nregtest=1\nrpcport=43782\nport=39388\nwhitelist=0.0.0.0/0"
|
||||
expose:
|
||||
- "43782"
|
||||
- "39388"
|
||||
|
@ -167,6 +167,8 @@ namespace BTCPayServer.Authentication
|
||||
|
||||
private PairingCodeEntity CreatePairingCodeEntity(PairingCodeData data)
|
||||
{
|
||||
if(data == null)
|
||||
return null;
|
||||
return new PairingCodeEntity()
|
||||
{
|
||||
Facade = data.Facade,
|
||||
|
@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<Version>1.0.0.16</Version>
|
||||
<Version>1.0.0.25</Version>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="Build\dockerfiles\**" />
|
||||
@ -22,7 +22,7 @@
|
||||
<PackageReference Include="Hangfire.PostgreSql" Version="1.4.8.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
|
||||
<PackageReference Include="NBitcoin" Version="4.0.0.38" />
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.11" />
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.12" />
|
||||
<PackageReference Include="DBreeze" Version="1.87.0" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="1.0.0.17" />
|
||||
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.1" />
|
||||
@ -46,7 +46,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="wwwroot\img\bitcoin-symbol.svg" />
|
||||
<None Include="wwwroot\js\core.js" />
|
||||
<None Include="wwwroot\js\creative.js" />
|
||||
<None Include="wwwroot\js\creative.min.js" />
|
||||
|
@ -13,7 +13,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
|
||||
namespace BTCPayServer.Configuration
|
||||
|
@ -56,6 +56,8 @@ namespace BTCPayServer.Controllers
|
||||
throw new BitpayHttpException(400, "'id' property is required, alternatively, use BitId");
|
||||
|
||||
pairingEntity = await _TokenRepository.GetPairingAsync(request.PairingCode);
|
||||
if(pairingEntity == null)
|
||||
throw new BitpayHttpException(404, "The specified pairingCode is not found");
|
||||
pairingEntity.SIN = sin;
|
||||
|
||||
if(string.IsNullOrEmpty(pairingEntity.Label) && !string.IsNullOrEmpty(request.Label))
|
||||
|
@ -1,5 +1,5 @@
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
@ -10,7 +10,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using BTCPayServer.Services.Stores;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@ -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")]
|
||||
@ -104,37 +125,47 @@ namespace BTCPayServer.Controllers
|
||||
id = invoiceId;
|
||||
////
|
||||
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
if(invoice == null)
|
||||
var model = await GetInvoiceModel(invoiceId);
|
||||
if (model == null)
|
||||
return NotFound();
|
||||
|
||||
return View(nameof(Checkout), model);
|
||||
}
|
||||
|
||||
private async Task<PaymentModel> GetInvoiceModel(string invoiceId)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
if (invoice == null)
|
||||
return null;
|
||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||
var dto = invoice.EntityToDTO();
|
||||
|
||||
var model = new PaymentModel()
|
||||
{
|
||||
ServerUrl = HttpContext.Request.GetAbsoluteRoot(),
|
||||
OrderId = invoice.OrderId,
|
||||
InvoiceId = invoice.Id,
|
||||
BTCAddress = invoice.DepositAddress.ToString(),
|
||||
BTCAmount = (invoice.GetTotalCryptoDue() - invoice.TxFee).ToString(),
|
||||
BTCTotalDue = invoice.GetTotalCryptoDue().ToString(),
|
||||
BTCDue = invoice.GetCryptoDue().ToString(),
|
||||
BtcAddress = invoice.DepositAddress.ToString(),
|
||||
BtcAmount = (invoice.GetTotalCryptoDue() - invoice.TxFee).ToString(),
|
||||
BtcTotalDue = invoice.GetTotalCryptoDue().ToString(),
|
||||
BtcDue = invoice.GetCryptoDue().ToString(),
|
||||
CustomerEmail = invoice.RefundMail,
|
||||
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(),
|
||||
RedirectUrl = invoice.RedirectURL,
|
||||
Rate = invoice.Rate.ToString("C", GetCurrencyProvider(invoice.ProductInformation.Currency)),
|
||||
MerchantRefLink = invoice.RedirectURL ?? "/",
|
||||
StoreName = store.StoreName,
|
||||
TxFees = invoice.TxFee.ToString(),
|
||||
InvoiceBitcoinUrl = dto.PaymentUrls.BIP72,
|
||||
TxCount = invoice.GetTxCount(),
|
||||
BTCPaid = invoice.GetTotalPaid().ToString(),
|
||||
BtcPaid = invoice.GetTotalPaid().ToString(),
|
||||
Status = invoice.Status
|
||||
};
|
||||
|
||||
var expiration = TimeSpan.FromSeconds((double)model.ExpirationSeconds);
|
||||
var expiration = TimeSpan.FromSeconds(model.ExpirationSeconds);
|
||||
model.TimeLeft = PrettyPrint(expiration);
|
||||
return View(nameof(Checkout), model);
|
||||
return model;
|
||||
}
|
||||
|
||||
private string PrettyPrint(TimeSpan expiration)
|
||||
@ -152,10 +183,10 @@ namespace BTCPayServer.Controllers
|
||||
[Route("i/{invoiceId}/status")]
|
||||
public async Task<IActionResult> GetStatus(string invoiceId)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
if(invoice == null)
|
||||
var model = await GetInvoiceModel(invoiceId);
|
||||
if(model == null)
|
||||
return NotFound();
|
||||
return Content(invoice.Status);
|
||||
return Json(model);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
|
@ -29,7 +29,7 @@ using BTCPayServer.Services;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.RegularExpressions;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using BTCPayServer.Validations;
|
||||
@ -75,8 +75,9 @@ namespace BTCPayServer.Controllers
|
||||
_FeeProvider = feeProvider ?? throw new ArgumentNullException(nameof(feeProvider));
|
||||
}
|
||||
|
||||
internal async Task<DataWrapper<InvoiceResponse>> CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl)
|
||||
internal async Task<DataWrapper<InvoiceResponse>> CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl, double expiryMinutes = 15, double monitoringMinutes = 60)
|
||||
{
|
||||
//TODO: expiryMinutes (time before a new invoice can become paid) and monitoringMinutes (time before a paid invoice becomes invalid) should be configurable at store level
|
||||
var derivationStrategy = store.DerivationStrategy;
|
||||
var entity = new InvoiceEntity
|
||||
{
|
||||
@ -87,7 +88,9 @@ namespace BTCPayServer.Controllers
|
||||
if(notificationUri == null || (notificationUri.Scheme != "http" && notificationUri.Scheme != "https")) //TODO: Filer non routable addresses ?
|
||||
notificationUri = null;
|
||||
EmailAddressAttribute emailValidator = new EmailAddressAttribute();
|
||||
entity.ExpirationTime = entity.InvoiceTime + TimeSpan.FromMinutes(15.0);
|
||||
entity.ExpirationTime = entity.InvoiceTime.AddMinutes(expiryMinutes);
|
||||
entity.MonitoringExpiration = entity.InvoiceTime.AddMinutes(monitoringMinutes);
|
||||
entity.OrderId = invoice.OrderId;
|
||||
entity.ServerUrl = serverUrl;
|
||||
entity.FullNotifications = invoice.FullNotifications;
|
||||
entity.NotificationURL = notificationUri?.AbsoluteUri;
|
||||
@ -103,19 +106,31 @@ namespace BTCPayServer.Controllers
|
||||
entity.ProductInformation = Map<Invoice, ProductInformation>(invoice);
|
||||
entity.RedirectURL = invoice.RedirectURL ?? store.StoreWebsite;
|
||||
entity.Status = "new";
|
||||
entity.SpeedPolicy = store.SpeedPolicy;
|
||||
entity.TxFee = (await _FeeProvider.GetFeeRateAsync()).GetFee(100); // assume price for 100 bytes
|
||||
entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy);
|
||||
entity.TxFee = store.GetStoreBlob(_Network).NetworkFeeDisabled ? Money.Zero : (await _FeeProvider.GetFeeRateAsync()).GetFee(100); // assume price for 100 bytes
|
||||
entity.Rate = (double)await _RateProvider.GetRateAsync(invoice.Currency);
|
||||
entity.PosData = invoice.PosData;
|
||||
entity.DepositAddress = await _Wallet.ReserveAddressAsync(ParseDerivationStrategy(derivationStrategy));
|
||||
|
||||
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity);
|
||||
await _Wallet.MapAsync(entity.DepositAddress.ScriptPubKey, entity.Id);
|
||||
await _Watcher.WatchAsync(entity.Id);
|
||||
var resp = entity.EntityToDTO();
|
||||
return new DataWrapper<InvoiceResponse>(resp) { Facade = "pos/invoice" };
|
||||
}
|
||||
|
||||
private SpeedPolicy ParseSpeedPolicy(string transactionSpeed, SpeedPolicy defaultPolicy)
|
||||
{
|
||||
if(transactionSpeed == null)
|
||||
return defaultPolicy;
|
||||
var mappings = new Dictionary<string, SpeedPolicy>();
|
||||
mappings.Add("low", SpeedPolicy.LowSpeed);
|
||||
mappings.Add("medium", SpeedPolicy.MediumSpeed);
|
||||
mappings.Add("high", SpeedPolicy.HighSpeed);
|
||||
if(!mappings.TryGetValue(transactionSpeed, out SpeedPolicy policy))
|
||||
policy = defaultPolicy;
|
||||
return policy;
|
||||
}
|
||||
|
||||
private void FillBuyerInfo(Buyer buyer, BuyerInformation buyerInformation)
|
||||
{
|
||||
if(buyer == null)
|
||||
|
@ -108,6 +108,34 @@ namespace BTCPayServer.Controllers
|
||||
return View(result);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{storeId}/delete")]
|
||||
public async Task<IActionResult> DeleteStore(string storeId)
|
||||
{
|
||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||
if(store == null)
|
||||
return NotFound();
|
||||
return View("Confirm", new ConfirmModel()
|
||||
{
|
||||
Title = "Delete store " + store.StoreName,
|
||||
Description = "This store will still be accessible to users sharing it",
|
||||
Action = "Delete"
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("{storeId}/delete")]
|
||||
public async Task<IActionResult> DeleteStorePost(string storeId)
|
||||
{
|
||||
var userId = GetUserId();
|
||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||
if(store == null)
|
||||
return NotFound();
|
||||
await _Repo.RemoveStore(storeId, userId);
|
||||
StatusMessage = "Store removed successfully";
|
||||
return RedirectToAction(nameof(ListStores));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{storeId}")]
|
||||
public async Task<IActionResult> UpdateStore(string storeId)
|
||||
@ -119,6 +147,7 @@ namespace BTCPayServer.Controllers
|
||||
var vm = new StoreViewModel();
|
||||
vm.StoreName = store.StoreName;
|
||||
vm.StoreWebsite = store.StoreWebsite;
|
||||
vm.NetworkFee = !store.GetStoreBlob(_Network).NetworkFeeDisabled;
|
||||
vm.SpeedPolicy = store.SpeedPolicy;
|
||||
vm.DerivationScheme = store.DerivationStrategy;
|
||||
vm.StatusMessage = StatusMessage;
|
||||
@ -173,6 +202,14 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
if(store.GetStoreBlob(_Network).NetworkFeeDisabled != !model.NetworkFee)
|
||||
{
|
||||
var blob = store.GetStoreBlob(_Network);
|
||||
blob.NetworkFeeDisabled = !model.NetworkFee;
|
||||
store.SetStoreBlob(blob, _Network);
|
||||
needUpdate = true;
|
||||
}
|
||||
|
||||
if(needUpdate)
|
||||
{
|
||||
await _Repo.UpdateStore(store);
|
||||
|
@ -21,5 +21,10 @@ namespace BTCPayServer.Data
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DateTimeOffset? CreatedTime
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,11 @@ namespace BTCPayServer.Data
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DbSet<HistoricalAddressInvoiceData> HistoricalAddressInvoices
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DbSet<PendingInvoiceData> PendingInvoices
|
||||
{
|
||||
get; set;
|
||||
@ -118,6 +123,13 @@ namespace BTCPayServer.Data
|
||||
b.HasIndex(o => o.SIN);
|
||||
b.HasIndex(o => o.StoreDataId);
|
||||
});
|
||||
|
||||
builder.Entity<HistoricalAddressInvoiceData>()
|
||||
.HasKey(o => new
|
||||
{
|
||||
o.InvoiceDataId,
|
||||
o.Address
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
30
BTCPayServer/Data/HistoricalAddressInvoiceData.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
public class HistoricalAddressInvoiceData
|
||||
{
|
||||
public string InvoiceDataId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string Address
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DateTimeOffset Assigned
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DateTimeOffset? UnAssigned
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
@ -37,6 +37,11 @@ namespace BTCPayServer.Data
|
||||
get; set;
|
||||
}
|
||||
|
||||
public List<HistoricalAddressInvoiceData> HistoricalAddressInvoices
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public byte[] Blob
|
||||
{
|
||||
get; set;
|
||||
|
@ -1,9 +1,12 @@
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
@ -51,5 +54,28 @@ namespace BTCPayServer.Data
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public byte[] StoreBlob
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public StoreBlob GetStoreBlob(Network network)
|
||||
{
|
||||
return StoreBlob == null ? new StoreBlob() : new Serializer(network).ToObject<StoreBlob>(Encoding.UTF8.GetString(StoreBlob));
|
||||
}
|
||||
|
||||
public void SetStoreBlob(StoreBlob storeBlob, Network network)
|
||||
{
|
||||
StoreBlob = Encoding.UTF8.GetBytes(new Serializer(network).ToString(storeBlob));
|
||||
}
|
||||
}
|
||||
|
||||
public class StoreBlob
|
||||
{
|
||||
public bool NetworkFeeDisabled
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
using BTCPayServer.Authentication;
|
||||
using BTCPayServer.Configuration;
|
||||
using Microsoft.AspNetCore.Html;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
@ -44,5 +47,12 @@ namespace BTCPayServer
|
||||
return throws ? throw new UnauthorizedAccessException("no-bitid") : (BitIdentity)null;
|
||||
return (BitIdentity)controller.User.Identity;
|
||||
}
|
||||
|
||||
private static JsonSerializerSettings jsonSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };
|
||||
public static string ToJson(this object o)
|
||||
{
|
||||
var res = JsonConvert.SerializeObject(o, Formatting.None, jsonSettings);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ using NBXplorer;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Fees;
|
||||
@ -33,6 +33,7 @@ using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using BTCPayServer.Authentication;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace BTCPayServer.Hosting
|
||||
{
|
||||
@ -112,6 +113,7 @@ namespace BTCPayServer.Hosting
|
||||
runtime.Configure(o.GetRequiredService<BTCPayServerOptions>());
|
||||
return runtime;
|
||||
});
|
||||
services.AddSingleton<BTCPayServerEnvironment>();
|
||||
services.TryAddSingleton<TokenRepository>();
|
||||
services.TryAddSingleton(o => o.GetRequiredService<BTCPayServerRuntime>().InvoiceRepository);
|
||||
services.TryAddSingleton<Network>(o => o.GetRequiredService<BTCPayServerOptions>().Network);
|
||||
@ -137,7 +139,10 @@ namespace BTCPayServer.Hosting
|
||||
else
|
||||
return new Bitpay(new Key(), new Uri("https://test.bitpay.com/"));
|
||||
});
|
||||
services.TryAddSingleton<IRateProvider, BitpayRateProvider>();
|
||||
services.TryAddSingleton<IRateProvider>(o =>
|
||||
{
|
||||
return new CachedRateProvider(new CoinAverageRateProvider(), o.GetRequiredService<IMemoryCache>()) { CacheSpan = TimeSpan.FromMinutes(1.0) };
|
||||
});
|
||||
services.TryAddSingleton<InvoiceWatcher>();
|
||||
services.TryAddSingleton<InvoiceNotificationManager>();
|
||||
services.TryAddSingleton<IHostedService>(o => o.GetRequiredService<InvoiceWatcher>());
|
||||
|
@ -67,7 +67,7 @@ namespace BTCPayServer.Hosting
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.ConfigureBTCPayServer(Configuration);
|
||||
|
||||
services.AddMemoryCache();
|
||||
services.AddIdentity<ApplicationUser, IdentityRole>()
|
||||
.AddEntityFrameworkStores<ApplicationDbContext>()
|
||||
.AddDefaultTokenProviders();
|
||||
|
@ -1,6 +1,6 @@
|
||||
// <auto-generated />
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
|
@ -1,6 +1,6 @@
|
||||
// <auto-generated />
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
|
@ -1,6 +1,6 @@
|
||||
// <auto-generated />
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
|
@ -1,6 +1,6 @@
|
||||
// <auto-generated />
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
|
@ -1,6 +1,6 @@
|
||||
// <auto-generated />
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
|
452
BTCPayServer/Migrations/20171023101754_StoreBlob.Designer.cs
generated
Normal file
@ -0,0 +1,452 @@
|
||||
// <auto-generated />
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
using Microsoft.EntityFrameworkCore.Storage.Internal;
|
||||
using System;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20171023101754_StoreBlob")]
|
||||
partial class StoreBlob
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.0.0-rtm-26452");
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Address")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.HasKey("Address");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
b.ToTable("AddressInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<DateTimeOffset>("Created");
|
||||
|
||||
b.Property<string>("CustomerEmail");
|
||||
|
||||
b.Property<string>("ExceptionStatus");
|
||||
|
||||
b.Property<string>("ItemCode");
|
||||
|
||||
b.Property<string>("OrderId");
|
||||
|
||||
b.Property<string>("Status");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("Invoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PairedSINData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Facade");
|
||||
|
||||
b.Property<string>("Label");
|
||||
|
||||
b.Property<DateTimeOffset>("PairingTime");
|
||||
|
||||
b.Property<string>("SIN");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SIN");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("PairedSINData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PairingCodeData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTime>("DateCreated");
|
||||
|
||||
b.Property<DateTimeOffset>("Expiration");
|
||||
|
||||
b.Property<string>("Facade");
|
||||
|
||||
b.Property<string>("Label");
|
||||
|
||||
b.Property<string>("SIN");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.Property<string>("TokenValue");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PairingCodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
b.ToTable("Payments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PendingInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PendingInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
b.ToTable("RefundAddresses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.SettingData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Value");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoreData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("DerivationStrategy");
|
||||
|
||||
b.Property<int>("SpeedPolicy");
|
||||
|
||||
b.Property<byte[]>("StoreBlob");
|
||||
|
||||
b.Property<byte[]>("StoreCertificate");
|
||||
|
||||
b.Property<string>("StoreName");
|
||||
|
||||
b.Property<string>("StoreWebsite");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Stores");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.UserStore", b =>
|
||||
{
|
||||
b.Property<string>("ApplicationUserId");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.Property<string>("Role");
|
||||
|
||||
b.HasKey("ApplicationUserId", "StoreDataId");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("UserStore");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Models.ApplicationUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("AccessFailedCount");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken();
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<bool>("EmailConfirmed");
|
||||
|
||||
b.Property<bool>("LockoutEnabled");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("PasswordHash");
|
||||
|
||||
b.Property<string>("PhoneNumber");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed");
|
||||
|
||||
b.Property<bool>("RequiresEmailConfirmation");
|
||||
|
||||
b.Property<string>("SecurityStamp");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasName("UserNameIndex");
|
||||
|
||||
b.ToTable("AspNetUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken();
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasName("RoleNameIndex");
|
||||
|
||||
b.ToTable("AspNetRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ClaimType");
|
||||
|
||||
b.Property<string>("ClaimValue");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ClaimType");
|
||||
|
||||
b.Property<string>("ClaimValue");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider");
|
||||
|
||||
b.Property<string>("ProviderKey");
|
||||
|
||||
b.Property<string>("ProviderDisplayName");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.Property<string>("RoleId");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.Property<string>("LoginProvider");
|
||||
|
||||
b.Property<string>("Name");
|
||||
|
||||
b.Property<string>("Value");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany()
|
||||
.HasForeignKey("InvoiceDataId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany()
|
||||
.HasForeignKey("StoreDataId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("Payments")
|
||||
.HasForeignKey("InvoiceDataId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("RefundAddresses")
|
||||
.HasForeignKey("InvoiceDataId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.UserStore", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("UserStores")
|
||||
.HasForeignKey("ApplicationUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany("UserStores")
|
||||
.HasForeignKey("StoreDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
24
BTCPayServer/Migrations/20171023101754_StoreBlob.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
public partial class StoreBlob : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<byte[]>(
|
||||
name: "StoreBlob",
|
||||
table: "Stores",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "StoreBlob",
|
||||
table: "Stores");
|
||||
}
|
||||
}
|
||||
}
|
477
BTCPayServer/Migrations/20171024163354_RenewUsedAddresses.Designer.cs
generated
Normal file
@ -0,0 +1,477 @@
|
||||
// <auto-generated />
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
using Microsoft.EntityFrameworkCore.Storage.Internal;
|
||||
using System;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20171024163354_RenewUsedAddresses")]
|
||||
partial class RenewUsedAddresses
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.0.0-rtm-26452");
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Address")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTimeOffset?>("CreatedTime");
|
||||
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.HasKey("Address");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
b.ToTable("AddressInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.Property<string>("Address");
|
||||
|
||||
b.Property<DateTimeOffset>("Assigned");
|
||||
|
||||
b.Property<DateTimeOffset?>("UnAssigned");
|
||||
|
||||
b.HasKey("InvoiceDataId", "Address");
|
||||
|
||||
b.ToTable("HistoricalAddressInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<DateTimeOffset>("Created");
|
||||
|
||||
b.Property<string>("CustomerEmail");
|
||||
|
||||
b.Property<string>("ExceptionStatus");
|
||||
|
||||
b.Property<string>("ItemCode");
|
||||
|
||||
b.Property<string>("OrderId");
|
||||
|
||||
b.Property<string>("Status");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("Invoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PairedSINData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Facade");
|
||||
|
||||
b.Property<string>("Label");
|
||||
|
||||
b.Property<DateTimeOffset>("PairingTime");
|
||||
|
||||
b.Property<string>("SIN");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SIN");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("PairedSINData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PairingCodeData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTime>("DateCreated");
|
||||
|
||||
b.Property<DateTimeOffset>("Expiration");
|
||||
|
||||
b.Property<string>("Facade");
|
||||
|
||||
b.Property<string>("Label");
|
||||
|
||||
b.Property<string>("SIN");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.Property<string>("TokenValue");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PairingCodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
b.ToTable("Payments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PendingInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PendingInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
b.ToTable("RefundAddresses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.SettingData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Value");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoreData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("DerivationStrategy");
|
||||
|
||||
b.Property<int>("SpeedPolicy");
|
||||
|
||||
b.Property<byte[]>("StoreBlob");
|
||||
|
||||
b.Property<byte[]>("StoreCertificate");
|
||||
|
||||
b.Property<string>("StoreName");
|
||||
|
||||
b.Property<string>("StoreWebsite");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Stores");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.UserStore", b =>
|
||||
{
|
||||
b.Property<string>("ApplicationUserId");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.Property<string>("Role");
|
||||
|
||||
b.HasKey("ApplicationUserId", "StoreDataId");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("UserStore");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Models.ApplicationUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("AccessFailedCount");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken();
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<bool>("EmailConfirmed");
|
||||
|
||||
b.Property<bool>("LockoutEnabled");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("PasswordHash");
|
||||
|
||||
b.Property<string>("PhoneNumber");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed");
|
||||
|
||||
b.Property<bool>("RequiresEmailConfirmation");
|
||||
|
||||
b.Property<string>("SecurityStamp");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasName("UserNameIndex");
|
||||
|
||||
b.ToTable("AspNetUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken();
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasName("RoleNameIndex");
|
||||
|
||||
b.ToTable("AspNetRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ClaimType");
|
||||
|
||||
b.Property<string>("ClaimValue");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ClaimType");
|
||||
|
||||
b.Property<string>("ClaimValue");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider");
|
||||
|
||||
b.Property<string>("ProviderKey");
|
||||
|
||||
b.Property<string>("ProviderDisplayName");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.Property<string>("RoleId");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.Property<string>("LoginProvider");
|
||||
|
||||
b.Property<string>("Name");
|
||||
|
||||
b.Property<string>("Value");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany()
|
||||
.HasForeignKey("InvoiceDataId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData")
|
||||
.WithMany("HistoricalAddressInvoices")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany()
|
||||
.HasForeignKey("StoreDataId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("Payments")
|
||||
.HasForeignKey("InvoiceDataId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("RefundAddresses")
|
||||
.HasForeignKey("InvoiceDataId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.UserStore", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("UserStores")
|
||||
.HasForeignKey("ApplicationUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany("UserStores")
|
||||
.HasForeignKey("StoreDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
47
BTCPayServer/Migrations/20171024163354_RenewUsedAddresses.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
public partial class RenewUsedAddresses : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTimeOffset>(
|
||||
name: "CreatedTime",
|
||||
table: "AddressInvoices",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "HistoricalAddressInvoices",
|
||||
columns: table => new
|
||||
{
|
||||
InvoiceDataId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Address = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Assigned = table.Column<DateTimeOffset>(nullable: false),
|
||||
UnAssigned = table.Column<DateTimeOffset>(nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_HistoricalAddressInvoices", x => new { x.InvoiceDataId, x.Address });
|
||||
table.ForeignKey(
|
||||
name: "FK_HistoricalAddressInvoices_Invoices_InvoiceDataId",
|
||||
column: x => x.InvoiceDataId,
|
||||
principalTable: "Invoices",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "HistoricalAddressInvoices");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CreatedTime",
|
||||
table: "AddressInvoices");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
// <auto-generated />
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
@ -25,6 +25,8 @@ namespace BTCPayServer.Migrations
|
||||
b.Property<string>("Address")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTimeOffset?>("CreatedTime");
|
||||
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.HasKey("Address");
|
||||
@ -34,6 +36,21 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("AddressInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.Property<string>("Address");
|
||||
|
||||
b.Property<DateTimeOffset>("Assigned");
|
||||
|
||||
b.Property<DateTimeOffset?>("UnAssigned");
|
||||
|
||||
b.HasKey("InvoiceDataId", "Address");
|
||||
|
||||
b.ToTable("HistoricalAddressInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -173,6 +190,8 @@ namespace BTCPayServer.Migrations
|
||||
|
||||
b.Property<int>("SpeedPolicy");
|
||||
|
||||
b.Property<byte[]>("StoreBlob");
|
||||
|
||||
b.Property<byte[]>("StoreCertificate");
|
||||
|
||||
b.Property<string>("StoreName");
|
||||
@ -365,6 +384,14 @@ namespace BTCPayServer.Migrations
|
||||
.HasForeignKey("InvoiceDataId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData")
|
||||
.WithMany("HistoricalAddressInvoices")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
|
23
BTCPayServer/Models/ConfirmModel.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Models
|
||||
{
|
||||
public class ConfirmModel
|
||||
{
|
||||
public string Title
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string Description
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string Action
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Models.InvoicingModels
|
||||
|
@ -5,103 +5,31 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Models.InvoicingModels
|
||||
{
|
||||
public class PaymentModel
|
||||
{
|
||||
public string InvoiceId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public class PaymentModel
|
||||
{
|
||||
public string ServerUrl { get; set; }
|
||||
public string InvoiceId { get; set; }
|
||||
public string BtcAddress { get; set; }
|
||||
public string BtcDue { get; set; }
|
||||
public string CustomerEmail { get; set; }
|
||||
public int ExpirationSeconds { get; set; }
|
||||
public string Status { get; set; }
|
||||
public string MerchantRefLink { get; set; }
|
||||
public int MaxTimeSeconds { get; set; }
|
||||
|
||||
public string OrderId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string BTCAddress
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
// These properties are not used in client side code
|
||||
public string StoreName { get; set; }
|
||||
public string ItemDesc { get; set; }
|
||||
public string TimeLeft { get; set; }
|
||||
public string Rate { get; set; }
|
||||
public string BtcAmount { get; set; }
|
||||
public string TxFees { get; set; }
|
||||
public string InvoiceBitcoinUrl { get; set; }
|
||||
public string BtcTotalDue { get; set; }
|
||||
public int TxCount { get; set; }
|
||||
public string BtcPaid { get; set; }
|
||||
public string StoreEmail { get; set; }
|
||||
|
||||
public string BTCDue
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string CustomerEmail
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public int ExpirationSeconds
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public int MaxTimeSeconds
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string TimeLeft
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string RedirectUrl
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
|
||||
public string StoreName
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string ItemDesc
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string Rate
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string BTCAmount
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string TxFees
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string InvoiceBitcoinUrl
|
||||
{
|
||||
get;
|
||||
internal set;
|
||||
}
|
||||
public string BTCTotalDue
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public int TxCount
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string BTCPaid
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string StoreEmail
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string Status
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string OrderId { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
using BTCPayServer.Servcices.Invoices;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Validations;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -21,6 +21,7 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
|
||||
[Url]
|
||||
[Display(Name = "Store Website")]
|
||||
[MaxLength(500)]
|
||||
public string StoreWebsite
|
||||
{
|
||||
get;
|
||||
@ -39,6 +40,12 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
get; set;
|
||||
}
|
||||
|
||||
[Display(Name = "Add network fee to invoice (vary with mining fees)")]
|
||||
public bool NetworkFee
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public List<(string KeyPath, string Address)> AddressSamples
|
||||
{
|
||||
get; set;
|
||||
|
@ -1,23 +1,26 @@
|
||||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:14139/",
|
||||
"sslPort": 0
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:14139/",
|
||||
"sslPort": 0
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"Default": {
|
||||
"commandName": "Project"
|
||||
},
|
||||
"Docker-Regtest": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"BTCPAY_EXPLORERURL": "http://127.0.0.1:32838/",
|
||||
"BTCPAY_NETWORK": "regtest",
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver"
|
||||
},
|
||||
"applicationUrl": "http://localhost:14142/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"Docker-Regtest": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"BTCPAY_EXPLORERURL": "http://127.0.0.1:32838/",
|
||||
"BTCPAY_NETWORK": "regtest",
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver"
|
||||
},
|
||||
"applicationUrl": "http://localhost:14142/"
|
||||
}
|
||||
}
|
||||
}
|
46
BTCPayServer/Services/BTCPayServerEnvironment.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text;
|
||||
|
||||
namespace BTCPayServer.Services
|
||||
{
|
||||
public class BTCPayServerEnvironment
|
||||
{
|
||||
public BTCPayServerEnvironment(IHostingEnvironment env)
|
||||
{
|
||||
Version = typeof(BTCPayServerEnvironment).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyFileVersionAttribute>().Version;
|
||||
#if DEBUG
|
||||
Build = "Debug";
|
||||
#else
|
||||
Build = "Release";
|
||||
#endif
|
||||
Environment = env;
|
||||
}
|
||||
public IHostingEnvironment Environment
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string Version
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string Build
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder txt = new StringBuilder();
|
||||
txt.Append($"@Copyright BTCPayServer v{Version}");
|
||||
if(!Environment.IsProduction() || Build.Equals("Release", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
txt.Append($" Environment: {Environment.EnvironmentName} Build: {Build}");
|
||||
}
|
||||
return txt.ToString();
|
||||
}
|
||||
}
|
||||
}
|
@ -8,8 +8,9 @@ using BTCPayServer.Models;
|
||||
using NBitpayClient;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NBitcoin.DataEncoders;
|
||||
using BTCPayServer.Data;
|
||||
|
||||
namespace BTCPayServer.Servcices.Invoices
|
||||
namespace BTCPayServer.Services.Invoices
|
||||
{
|
||||
public class BuyerInformation
|
||||
{
|
||||
@ -248,6 +249,16 @@ namespace BTCPayServer.Servcices.Invoices
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public DateTimeOffset? MonitoringExpiration
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public HistoricalAddressInvoiceData[] HistoricalAddresses
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public bool IsExpired()
|
||||
{
|
||||
@ -268,7 +279,7 @@ namespace BTCPayServer.Servcices.Invoices
|
||||
ExpirationTime = ExpirationTime,
|
||||
BTCPrice = Money.Coins((decimal)(1.0 / Rate)).ToString(),
|
||||
Status = Status,
|
||||
Url = ServerUrl.WithTrailingSlash() + "invoice?id=" + Id,
|
||||
Url = ServerUrl.WithTrailingSlash() + "invoice?id=" + Id,
|
||||
Currency = ProductInformation.Currency,
|
||||
Flags = new Flags() { Refundable = Refundable }
|
||||
};
|
||||
|
@ -16,7 +16,7 @@ using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace BTCPayServer.Servcices.Invoices
|
||||
namespace BTCPayServer.Services.Invoices
|
||||
{
|
||||
public class InvoiceNotificationManager
|
||||
{
|
||||
|
@ -17,7 +17,7 @@ using BTCPayServer.Data;
|
||||
using System.Globalization;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
|
||||
namespace BTCPayServer.Servcices.Invoices
|
||||
namespace BTCPayServer.Services.Invoices
|
||||
{
|
||||
public class InvoiceRepository
|
||||
{
|
||||
@ -54,8 +54,6 @@ namespace BTCPayServer.Servcices.Invoices
|
||||
_ContextFactory = contextFactory;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public async Task AddPendingInvoice(string invoiceId)
|
||||
{
|
||||
using(var ctx = _ContextFactory.CreateContext())
|
||||
@ -79,6 +77,14 @@ namespace BTCPayServer.Servcices.Invoices
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> GetInvoiceIdFromScriptPubKey(Script scriptPubKey)
|
||||
{
|
||||
using(var db = _ContextFactory.CreateContext())
|
||||
{
|
||||
var result = await db.AddressInvoices.FindAsync(scriptPubKey.Hash.ToString());
|
||||
return result?.InvoiceDataId;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string[]> GetPendingInvoices()
|
||||
{
|
||||
@ -122,11 +128,26 @@ namespace BTCPayServer.Servcices.Invoices
|
||||
ItemCode = invoice.ProductInformation.ItemCode,
|
||||
CustomerEmail = invoice.RefundMail
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
context.AddressInvoices.Add(new AddressInvoiceData()
|
||||
{
|
||||
Address = invoice.DepositAddress.ScriptPubKey.Hash.ToString(),
|
||||
InvoiceDataId = invoice.Id,
|
||||
CreatedTime = DateTimeOffset.UtcNow,
|
||||
});
|
||||
|
||||
context.HistoricalAddressInvoices.Add(new HistoricalAddressInvoiceData()
|
||||
{
|
||||
InvoiceDataId = invoice.Id,
|
||||
Address = invoice.DepositAddress.ToString(),
|
||||
Assigned = DateTimeOffset.UtcNow
|
||||
});
|
||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
AddToTextSearch(invoice.Id,
|
||||
invoice.Id,
|
||||
invoice.DepositAddress.ToString(),
|
||||
invoice.InvoiceTime.ToString(CultureInfo.InvariantCulture),
|
||||
invoice.ProductInformation.Price.ToString(CultureInfo.InvariantCulture),
|
||||
invoice.GetTotalCryptoDue().ToString(),
|
||||
@ -139,6 +160,63 @@ namespace BTCPayServer.Servcices.Invoices
|
||||
return invoice;
|
||||
}
|
||||
|
||||
public async Task<bool> NewAddress(string invoiceId, BitcoinAddress bitcoinAddress)
|
||||
{
|
||||
using(var context = _ContextFactory.CreateContext())
|
||||
{
|
||||
var invoice = await context.Invoices.FirstOrDefaultAsync(i => i.Id == invoiceId);
|
||||
if(invoice == null)
|
||||
return false;
|
||||
var invoiceEntity = ToObject<InvoiceEntity>(invoice.Blob);
|
||||
var old = invoiceEntity.DepositAddress;
|
||||
invoiceEntity.DepositAddress = bitcoinAddress;
|
||||
invoice.Blob = ToBytes(invoiceEntity);
|
||||
if(old != null)
|
||||
{
|
||||
MarkUnassigned(invoiceId, old, context);
|
||||
}
|
||||
context.AddressInvoices.Add(new AddressInvoiceData() { Address = bitcoinAddress.ScriptPubKey.Hash.ToString(), InvoiceDataId = invoiceId, CreatedTime = DateTimeOffset.UtcNow });
|
||||
context.HistoricalAddressInvoices.Add(new HistoricalAddressInvoiceData()
|
||||
{
|
||||
InvoiceDataId = invoiceId,
|
||||
Address = bitcoinAddress.ToString(),
|
||||
Assigned = DateTimeOffset.UtcNow
|
||||
});
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
AddToTextSearch(invoice.Id, bitcoinAddress.ToString());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void MarkUnassigned(string invoiceId, BitcoinAddress old, ApplicationDbContext context)
|
||||
{
|
||||
var historical = new HistoricalAddressInvoiceData();
|
||||
historical.InvoiceDataId = invoiceId;
|
||||
historical.Address = old.ToString();
|
||||
historical.UnAssigned = DateTimeOffset.UtcNow;
|
||||
context.Attach(historical);
|
||||
context.Entry(historical).Property(o => o.UnAssigned).IsModified = true;
|
||||
}
|
||||
|
||||
public async Task UnaffectAddress(string invoiceId)
|
||||
{
|
||||
using(var context = _ContextFactory.CreateContext())
|
||||
{
|
||||
var invoiceData = await context.FindAsync<InvoiceData>(invoiceId).ConfigureAwait(false);
|
||||
if(invoiceData == null)
|
||||
return;
|
||||
var invoiceEntity = ToObject<InvoiceEntity>(invoiceData.Blob);
|
||||
if(invoiceEntity.DepositAddress == null)
|
||||
return;
|
||||
MarkUnassigned(invoiceId, invoiceEntity.DepositAddress, context);
|
||||
try
|
||||
{
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
catch(DbUpdateException) { } //Possibly, it was unassigned before
|
||||
}
|
||||
}
|
||||
|
||||
private string[] SearchInvoice(string searchTerms)
|
||||
{
|
||||
@ -173,7 +251,7 @@ namespace BTCPayServer.Servcices.Invoices
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<InvoiceEntity> GetInvoice(string storeId, string id)
|
||||
public async Task<InvoiceEntity> GetInvoice(string storeId, string id, bool includeHistoricalAddresses = false)
|
||||
{
|
||||
using(var context = _ContextFactory.CreateContext())
|
||||
{
|
||||
@ -181,8 +259,10 @@ namespace BTCPayServer.Servcices.Invoices
|
||||
context
|
||||
.Invoices
|
||||
.Include(o => o.Payments)
|
||||
.Include(o => o.RefundAddresses)
|
||||
.Where(i => i.Id == id);
|
||||
.Include(o => o.RefundAddresses);
|
||||
if(includeHistoricalAddresses)
|
||||
query = query.Include(o => o.HistoricalAddressInvoices);
|
||||
query = query.Where(i => i.Id == id);
|
||||
|
||||
if(storeId != null)
|
||||
query = query.Where(i => i.StoreDataId == storeId);
|
||||
@ -203,6 +283,10 @@ namespace BTCPayServer.Servcices.Invoices
|
||||
entity.Status = invoice.Status;
|
||||
entity.RefundMail = invoice.CustomerEmail;
|
||||
entity.Refundable = invoice.RefundAddresses.Count != 0;
|
||||
if(invoice.HistoricalAddressInvoices != null)
|
||||
{
|
||||
entity.HistoricalAddresses = invoice.HistoricalAddressInvoices.ToArray();
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ using System.Collections.Concurrent;
|
||||
using Hangfire;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
|
||||
namespace BTCPayServer.Servcices.Invoices
|
||||
namespace BTCPayServer.Services.Invoices
|
||||
{
|
||||
public class InvoiceWatcher : IHostedService
|
||||
{
|
||||
@ -45,7 +45,7 @@ namespace BTCPayServer.Servcices.Invoices
|
||||
|
||||
public async Task NotifyReceived(Script scriptPubKey)
|
||||
{
|
||||
var invoice = await _Wallet.GetInvoiceId(scriptPubKey);
|
||||
var invoice = await _InvoiceRepository.GetInvoiceIdFromScriptPubKey(scriptPubKey);
|
||||
if(invoice != null)
|
||||
_WatchRequests.Add(invoice);
|
||||
}
|
||||
@ -80,7 +80,9 @@ namespace BTCPayServer.Servcices.Invoices
|
||||
Logs.PayServer.LogInformation($"Invoice {invoice.Id}: {stateBefore} => {invoice.Status}");
|
||||
}
|
||||
|
||||
if(invoice.Status == "complete" || invoice.Status == "invalid")
|
||||
var expirationMonitoring = invoice.MonitoringExpiration.HasValue ? invoice.MonitoringExpiration.Value : invoice.InvoiceTime + TimeSpan.FromMinutes(60);
|
||||
if(invoice.Status == "complete" ||
|
||||
((invoice.Status == "invalid" || invoice.Status == "expired") && expirationMonitoring < DateTimeOffset.UtcNow))
|
||||
{
|
||||
if(await _InvoiceRepository.RemovePendingInvoice(invoice.Id).ConfigureAwait(false))
|
||||
Logs.PayServer.LogInformation("Stopped watching invoice " + invoiceId);
|
||||
@ -106,110 +108,128 @@ namespace BTCPayServer.Servcices.Invoices
|
||||
private async Task<(bool NeedSave, UTXOChanges Changes)> UpdateInvoice(UTXOChanges changes, InvoiceEntity invoice)
|
||||
{
|
||||
bool needSave = false;
|
||||
//Fetch unknown payments
|
||||
var strategy = _DerivationFactory.Parse(invoice.DerivationStrategy);
|
||||
changes = await _ExplorerClient.SyncAsync(strategy, changes, !LongPollingMode, _Cts.Token).ConfigureAwait(false);
|
||||
|
||||
if(invoice.Status != "invalid" && invoice.ExpirationTime < DateTimeOffset.UtcNow && (invoice.Status == "new" || invoice.Status == "paidPartial"))
|
||||
var utxos = changes.Confirmed.UTXOs.Concat(changes.Unconfirmed.UTXOs).ToArray();
|
||||
var invoiceIds = utxos.Select(u => _InvoiceRepository.GetInvoiceIdFromScriptPubKey(u.Output.ScriptPubKey)).ToArray();
|
||||
utxos =
|
||||
utxos
|
||||
.Where((u, i) => invoiceIds[i].GetAwaiter().GetResult() == invoice.Id)
|
||||
.ToArray();
|
||||
|
||||
List<Coin> receivedCoins = new List<Coin>();
|
||||
foreach(var received in utxos)
|
||||
if(received.Output.ScriptPubKey == invoice.DepositAddress.ScriptPubKey)
|
||||
receivedCoins.Add(new Coin(received.Outpoint, received.Output));
|
||||
|
||||
var alreadyAccounted = new HashSet<OutPoint>(invoice.Payments.Select(p => p.Outpoint));
|
||||
BitcoinAddress generatedAddress = null;
|
||||
bool dirtyAddress = false;
|
||||
foreach(var coin in receivedCoins.Where(c => !alreadyAccounted.Contains(c.Outpoint)))
|
||||
{
|
||||
var payment = await _InvoiceRepository.AddPayment(invoice.Id, coin).ConfigureAwait(false);
|
||||
invoice.Payments.Add(payment);
|
||||
if(coin.ScriptPubKey == invoice.DepositAddress.ScriptPubKey && generatedAddress == null)
|
||||
{
|
||||
dirtyAddress = true;
|
||||
}
|
||||
}
|
||||
//////
|
||||
|
||||
if(invoice.Status == "new" && invoice.ExpirationTime < DateTimeOffset.UtcNow)
|
||||
{
|
||||
needSave = true;
|
||||
invoice.Status = "invalid";
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
invoice.Status = "expired";
|
||||
}
|
||||
|
||||
if(invoice.Status == "invalid" || invoice.Status == "new" || invoice.Status == "paidPartial")
|
||||
if(invoice.Status == "new" || invoice.Status == "expired")
|
||||
{
|
||||
var strategy = _DerivationFactory.Parse(invoice.DerivationStrategy);
|
||||
changes = await _ExplorerClient.SyncAsync(strategy, changes, !LongPollingMode, _Cts.Token).ConfigureAwait(false);
|
||||
|
||||
var utxos = changes.Confirmed.UTXOs.Concat(changes.Unconfirmed.UTXOs).ToArray();
|
||||
var invoiceIds = utxos.Select(u => _Wallet.GetInvoiceId(u.Output.ScriptPubKey)).ToArray();
|
||||
utxos =
|
||||
utxos
|
||||
.Where((u, i) => invoiceIds[i].GetAwaiter().GetResult() == invoice.Id)
|
||||
.ToArray();
|
||||
|
||||
List<Coin> receivedCoins = new List<Coin>();
|
||||
foreach(var received in utxos)
|
||||
if(received.Output.ScriptPubKey == invoice.DepositAddress.ScriptPubKey)
|
||||
receivedCoins.Add(new Coin(received.Outpoint, received.Output));
|
||||
|
||||
var alreadyAccounted = new HashSet<OutPoint>(invoice.Payments.Select(p => p.Outpoint));
|
||||
foreach(var coin in receivedCoins.Where(c => !alreadyAccounted.Contains(c.Outpoint)))
|
||||
var totalPaid = invoice.Payments.Select(p => p.Output.Value).Sum();
|
||||
if(totalPaid >= invoice.GetTotalCryptoDue())
|
||||
{
|
||||
var payment = await _InvoiceRepository.AddPayment(invoice.Id, coin).ConfigureAwait(false);
|
||||
invoice.Payments.Add(payment);
|
||||
if(invoice.Status == "new")
|
||||
{
|
||||
invoice.Status = "paidPartial";
|
||||
invoice.Status = "paid";
|
||||
if(invoice.FullNotifications)
|
||||
{
|
||||
_NotificationManager.Notify(invoice);
|
||||
}
|
||||
invoice.ExceptionStatus = null;
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
needSave = true;
|
||||
}
|
||||
else if(invoice.Status == "expired")
|
||||
{
|
||||
invoice.ExceptionStatus = "paidLate";
|
||||
needSave = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(invoice.Status == "paidPartial")
|
||||
{
|
||||
var totalPaid = invoice.Payments.Select(p => p.Output.Value).Sum();
|
||||
if(totalPaid == invoice.GetTotalCryptoDue())
|
||||
if(totalPaid > invoice.GetTotalCryptoDue() && invoice.ExceptionStatus != "paidOver")
|
||||
{
|
||||
invoice.Status = "paid";
|
||||
if(invoice.FullNotifications)
|
||||
{
|
||||
_NotificationManager.Notify(invoice);
|
||||
}
|
||||
invoice.ExceptionStatus = null;
|
||||
needSave = true;
|
||||
}
|
||||
|
||||
if(totalPaid > invoice.GetTotalCryptoDue())
|
||||
{
|
||||
invoice.Status = "paidOver";
|
||||
invoice.ExceptionStatus = "paidOver";
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
needSave = true;
|
||||
}
|
||||
|
||||
if(totalPaid < invoice.GetTotalCryptoDue() && invoice.ExceptionStatus == null)
|
||||
if(totalPaid < invoice.GetTotalCryptoDue() && invoice.Payments.Count != 0 && invoice.ExceptionStatus != "paidPartial")
|
||||
{
|
||||
Logs.PayServer.LogInformation("Paid to " + invoice.DepositAddress);
|
||||
invoice.ExceptionStatus = "paidPartial";
|
||||
needSave = true;
|
||||
if(dirtyAddress)
|
||||
{
|
||||
var address = await _Wallet.ReserveAddressAsync(_DerivationFactory.Parse(invoice.DerivationStrategy));
|
||||
Logs.PayServer.LogInformation("Generate new " + address);
|
||||
await _InvoiceRepository.NewAddress(invoice.Id, address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(invoice.Status == "paid" || invoice.Status == "paidOver")
|
||||
if(invoice.Status == "paid")
|
||||
{
|
||||
var getTransactions = invoice.Payments.Select(o => o.Outpoint.Hash).Select(o => _ExplorerClient.GetTransactionAsync(o, _Cts.Token)).ToArray();
|
||||
await Task.WhenAll(getTransactions).ConfigureAwait(false);
|
||||
var transactions = getTransactions.Select(c => c.GetAwaiter().GetResult()).ToArray();
|
||||
if(!invoice.MonitoringExpiration.HasValue || invoice.MonitoringExpiration > DateTimeOffset.UtcNow)
|
||||
{
|
||||
var transactions = await GetPaymentsWithTransaction(invoice);
|
||||
if(invoice.SpeedPolicy == SpeedPolicy.HighSpeed)
|
||||
{
|
||||
transactions = transactions.Where(t => !t.Transaction.Transaction.RBF);
|
||||
}
|
||||
else if(invoice.SpeedPolicy == SpeedPolicy.MediumSpeed)
|
||||
{
|
||||
transactions = transactions.Where(t => t.Transaction.Confirmations >= 1);
|
||||
}
|
||||
else if(invoice.SpeedPolicy == SpeedPolicy.LowSpeed)
|
||||
{
|
||||
transactions = transactions.Where(t => t.Transaction.Confirmations >= 6);
|
||||
}
|
||||
|
||||
bool confirmed = false;
|
||||
var minConf = transactions.Select(t => t.Confirmations).Min();
|
||||
if(invoice.SpeedPolicy == SpeedPolicy.HighSpeed)
|
||||
{
|
||||
if(minConf > 0)
|
||||
confirmed = true;
|
||||
else
|
||||
confirmed = !transactions.Any(t => t.Transaction.RBF);
|
||||
var totalConfirmed = transactions.Select(t => t.Payment.Output.Value).Sum();
|
||||
if(totalConfirmed >= invoice.GetTotalCryptoDue())
|
||||
{
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
invoice.Status = "confirmed";
|
||||
_NotificationManager.Notify(invoice);
|
||||
needSave = true;
|
||||
}
|
||||
}
|
||||
else if(invoice.SpeedPolicy == SpeedPolicy.MediumSpeed)
|
||||
else
|
||||
{
|
||||
confirmed = minConf >= 1;
|
||||
}
|
||||
else if(invoice.SpeedPolicy == SpeedPolicy.LowSpeed)
|
||||
{
|
||||
confirmed = minConf >= 6;
|
||||
}
|
||||
|
||||
if(confirmed)
|
||||
{
|
||||
invoice.Status = "confirmed";
|
||||
_NotificationManager.Notify(invoice);
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
invoice.Status = "invalid";
|
||||
needSave = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(invoice.Status == "confirmed")
|
||||
{
|
||||
var getTransactions = invoice.Payments.Select(o => o.Outpoint.Hash).Select(o => _ExplorerClient.GetTransactionAsync(o, _Cts.Token)).ToArray();
|
||||
await Task.WhenAll(getTransactions).ConfigureAwait(false);
|
||||
var transactions = getTransactions.Select(c => c.GetAwaiter().GetResult()).ToArray();
|
||||
var minConf = transactions.Select(t => t.Confirmations).Min();
|
||||
if(minConf >= 6)
|
||||
var transactions = await GetPaymentsWithTransaction(invoice);
|
||||
transactions = transactions.Where(t => t.Transaction.Confirmations >= 6);
|
||||
var totalConfirmed = transactions.Select(t => t.Payment.Output.Value).Sum();
|
||||
if(totalConfirmed >= invoice.GetTotalCryptoDue())
|
||||
{
|
||||
invoice.Status = "complete";
|
||||
if(invoice.FullNotifications)
|
||||
@ -221,6 +241,15 @@ namespace BTCPayServer.Servcices.Invoices
|
||||
return (needSave, changes);
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<(PaymentEntity Payment, TransactionResult Transaction)>> GetPaymentsWithTransaction(InvoiceEntity invoice)
|
||||
{
|
||||
var getPayments = invoice.Payments
|
||||
.Select(async o => (Payment: o, Transaction: await _ExplorerClient.GetTransactionAsync(o.Outpoint.Hash, _Cts.Token)))
|
||||
.ToArray();
|
||||
await Task.WhenAll(getPayments).ConfigureAwait(false);
|
||||
var transactions = getPayments.Select(c => (Payment: c.Result.Payment, Transaction: c.Result.Transaction));
|
||||
return transactions;
|
||||
}
|
||||
|
||||
TimeSpan _PollInterval;
|
||||
public TimeSpan PollInterval
|
||||
|
54
BTCPayServer/Services/Rates/CachedRateProvider.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
public class CachedRateProvider : IRateProvider
|
||||
{
|
||||
private IRateProvider _Inner;
|
||||
private IMemoryCache _MemoryCache;
|
||||
|
||||
public CachedRateProvider(IRateProvider inner, IMemoryCache memoryCache)
|
||||
{
|
||||
if(inner == null)
|
||||
throw new ArgumentNullException(nameof(inner));
|
||||
if(memoryCache == null)
|
||||
throw new ArgumentNullException(nameof(memoryCache));
|
||||
this._Inner = inner;
|
||||
this._MemoryCache = memoryCache;
|
||||
}
|
||||
|
||||
public TimeSpan CacheSpan
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = TimeSpan.FromMinutes(1.0);
|
||||
|
||||
public Task<decimal> GetRateAsync(string currency)
|
||||
{
|
||||
return _MemoryCache.GetOrCreateAsync("CURR_" + currency, (ICacheEntry entry) =>
|
||||
{
|
||||
entry.AbsoluteExpiration = DateTimeOffset.UtcNow + CacheSpan;
|
||||
return _Inner.GetRateAsync(currency);
|
||||
});
|
||||
}
|
||||
|
||||
private bool TryGetFromCache(string key, out object obj)
|
||||
{
|
||||
obj = _MemoryCache.Get(key);
|
||||
return obj != null;
|
||||
}
|
||||
|
||||
public Task<ICollection<Rate>> GetRatesAsync()
|
||||
{
|
||||
return _MemoryCache.GetOrCreateAsync("GLOBAL_RATES", (ICacheEntry entry) =>
|
||||
{
|
||||
entry.AbsoluteExpiration = DateTimeOffset.UtcNow + CacheSpan;
|
||||
return _Inner.GetRatesAsync();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
116
BTCPayServer/Services/Rates/CoinAverageRateProvider.cs
Normal file
@ -0,0 +1,116 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
public class CoinAverageException : Exception
|
||||
{
|
||||
public CoinAverageException(string message) : base(message)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
public class CoinAverageRateProvider : IRateProvider
|
||||
{
|
||||
public class RatesJson
|
||||
{
|
||||
public class RateJson
|
||||
{
|
||||
public string Code
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public decimal Rate
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty("rates")]
|
||||
public JObject RatesInternal
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
[JsonIgnore]
|
||||
public List<RateJson> Rates
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public Dictionary<string, decimal> RatesByCurrency
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public decimal GetRate(string currency)
|
||||
{
|
||||
if(!RatesByCurrency.TryGetValue(currency.ToUpperInvariant(), out decimal currUSD))
|
||||
throw new RateUnavailableException(currency);
|
||||
|
||||
if(!RatesByCurrency.TryGetValue("BTC", out decimal btcUSD))
|
||||
throw new RateUnavailableException(currency);
|
||||
|
||||
return currUSD / btcUSD;
|
||||
}
|
||||
public void CalculateDictionary()
|
||||
{
|
||||
RatesByCurrency = new Dictionary<string, decimal>();
|
||||
Rates = new List<RateJson>();
|
||||
foreach(var rate in RatesInternal.OfType<JProperty>())
|
||||
{
|
||||
var rateJson = new RateJson();
|
||||
rateJson.Code = rate.Name;
|
||||
rateJson.Rate = rate.Value["rate"].Value<decimal>();
|
||||
RatesByCurrency.Add(rate.Name, rateJson.Rate);
|
||||
Rates.Add(rateJson);
|
||||
}
|
||||
}
|
||||
}
|
||||
static HttpClient _Client = new HttpClient();
|
||||
|
||||
public string Market
|
||||
{
|
||||
get; set;
|
||||
} = "global";
|
||||
public async Task<decimal> GetRateAsync(string currency)
|
||||
{
|
||||
RatesJson rates = await GetRatesCore();
|
||||
return rates.GetRate(currency);
|
||||
}
|
||||
|
||||
private async Task<RatesJson> GetRatesCore()
|
||||
{
|
||||
var resp = await _Client.GetAsync("https://apiv2.bitcoinaverage.com/constants/exchangerates/" + Market);
|
||||
using(resp)
|
||||
{
|
||||
|
||||
if((int)resp.StatusCode == 401)
|
||||
throw new CoinAverageException("Unauthorized access to the API");
|
||||
if((int)resp.StatusCode == 429)
|
||||
throw new CoinAverageException("Exceed API limits");
|
||||
if((int)resp.StatusCode == 403)
|
||||
throw new CoinAverageException("Unauthorized access to the API, premium plan needed");
|
||||
resp.EnsureSuccessStatusCode();
|
||||
var rates = JsonConvert.DeserializeObject<RatesJson>(await resp.Content.ReadAsStringAsync());
|
||||
rates.CalculateDictionary();
|
||||
return rates;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ICollection<Rate>> GetRatesAsync()
|
||||
{
|
||||
RatesJson rates = await GetRatesCore();
|
||||
return rates.Rates.Select(o => new Rate()
|
||||
{
|
||||
Currency = o.Code,
|
||||
Value = rates.GetRate(o.Code)
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
}
|
@ -83,6 +83,18 @@ namespace BTCPayServer.Services.Stores
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RemoveStore(string storeId, string userId)
|
||||
{
|
||||
using(var ctx = _ContextFactory.CreateContext())
|
||||
{
|
||||
var storeUser = await ctx.UserStore.FirstOrDefaultAsync(o => o.StoreDataId == storeId && o.ApplicationUserId == userId);
|
||||
if(storeUser == null)
|
||||
return;
|
||||
ctx.UserStore.Remove(storeUser);
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateStore(StoreData store)
|
||||
{
|
||||
using(var ctx = _ContextFactory.CreateContext())
|
||||
|
@ -39,28 +39,6 @@ namespace BTCPayServer.Services.Wallets
|
||||
await _Client.TrackAsync(derivationStrategy);
|
||||
}
|
||||
|
||||
public async Task<string> GetInvoiceId(Script scriptPubKey)
|
||||
{
|
||||
using(var db = _DBFactory.CreateContext())
|
||||
{
|
||||
var result = await db.AddressInvoices.FindAsync(scriptPubKey.Hash.ToString());
|
||||
return result?.InvoiceDataId;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task MapAsync(Script address, string invoiceId)
|
||||
{
|
||||
using(var db = _DBFactory.CreateContext())
|
||||
{
|
||||
db.AddressInvoices.Add(new AddressInvoiceData()
|
||||
{
|
||||
Address = address.Hash.ToString(),
|
||||
InvoiceDataId = invoiceId
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] ToBytes<T>(T obj)
|
||||
{
|
||||
return ZipUtils.Zip(_Serializer.ToString(obj));
|
||||
|
@ -1,4 +1,5 @@
|
||||
@model PaymentModel
|
||||
@inject System.Text.Encodings.Web.JavaScriptEncoder jsEncoder;
|
||||
@{
|
||||
Layout = null;
|
||||
ViewData["Title"] = "Payment";
|
||||
@ -27,22 +28,7 @@
|
||||
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 invoiceId = "@Model.InvoiceId";
|
||||
var btcAddress = "@Model.BTCAddress";
|
||||
var btcDue = "@Model.BTCDue"; //must be a string
|
||||
var customerEmail = "@Model.CustomerEmail"; // Place holder
|
||||
var expirationTime = @Model.ExpirationSeconds; // Can be calculted server-side, or fixed
|
||||
var isArchieved = false; // Preferably Bool
|
||||
var status = "@Model.Status"; // Tx listener
|
||||
var merchantRefLink = "@Model.RedirectUrl"; // Merchant link to redect the user
|
||||
var merchantName = "@Model.StoreName";
|
||||
var merchantDesc = "@Model.ItemDesc";
|
||||
var btcRate = "@Model.Rate";
|
||||
var btcAmount = "@Model.BTCAmount";
|
||||
var itemAmount = 1;
|
||||
var txFees = "@Model.TxFees";
|
||||
var noMock = true;
|
||||
var maxTime = @Model.MaxTimeSeconds;
|
||||
var srvModel = JSON.parse('@jsEncoder.Encode(Model.ToJson())');
|
||||
</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/1.7.1/clipboard.min.js"></script>
|
||||
<script src="~/js/core.js" type="text/javascript" defer="defer"></script>
|
||||
@ -56,7 +42,7 @@
|
||||
<noscript>
|
||||
<center style="padding: 2em">
|
||||
<h2>Javascript is currently disabled in your browser.</h2>
|
||||
<h5>Please enable Javascript from 'https://bitpay.com/' and refresh this page for the best experience.</h5>
|
||||
<h5>Please enable Javascript and refresh this page for the best experience.</h5>
|
||||
|
||||
<p>Alternatively, click below to continue to our HTML-only invoice.</p>
|
||||
|
||||
@ -128,9 +114,7 @@
|
||||
<!---->
|
||||
<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">
|
||||
<span>@Model.BtcTotalDue</span>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="single-item-order__right__ex-rate">
|
||||
@ -145,7 +129,7 @@
|
||||
<!---->
|
||||
<div class="line-items__item">
|
||||
<div class="line-items__item__label" i18n="">Payment Amount</div>
|
||||
<div class="line-items__item__value">@Model.BTCAmount BTC</div>
|
||||
<div class="line-items__item__value">@Model.BtcAmount BTC</div>
|
||||
</div>
|
||||
<div class="line-items__item">
|
||||
<div class="line-items__item__label">
|
||||
@ -157,11 +141,11 @@
|
||||
<div class="line-items__item__label">
|
||||
<span i18n="">Already Paid</span>
|
||||
</div>
|
||||
<div class="line-items__item__value" i18n="">-@Model.BTCPaid BTC</div>
|
||||
<div class="line-items__item__value" i18n="">-@Model.BtcPaid BTC</div>
|
||||
</div>
|
||||
<div class="line-items__item line-items__item--total">
|
||||
<div class="line-items__item__label" i18n="">Due </div>
|
||||
<div class="line-items__item__value">@Model.BTCDue BTC</div>
|
||||
<div class="line-items__item__value">@Model.BtcDue BTC</div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
@ -180,20 +164,20 @@
|
||||
<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="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"></div>
|
||||
</div>
|
||||
@ -300,7 +284,7 @@
|
||||
</div>
|
||||
<div class="bp-view confirm-bitcoin-address-view" id="confirm-refund-address">
|
||||
<form class="manual__step-one refund-address-form ng-untouched ng-pristine ng-valid" novalidate="" style="padding-top: 1.6rem;">
|
||||
<div><img src="~/img/mail.svg"></div>
|
||||
<div><img src="~/imlegacy/mail.svg"></div>
|
||||
<div class="manual__step-one__header">
|
||||
<span i18n="">Please confirm your address</span>
|
||||
</div>
|
||||
@ -316,7 +300,7 @@
|
||||
</span>
|
||||
<div class="success-text">
|
||||
<!---->
|
||||
<img src="~/img/circle-check.svg">
|
||||
<img src="~/imlegacy/circle-check.svg">
|
||||
<!---->
|
||||
<div i18n="">Email resent</div>
|
||||
<!---->
|
||||
@ -348,7 +332,7 @@
|
||||
<bp-refund-address name="refundAddress" ngmodel="" class="ng-untouched ng-pristine ng-invalid">
|
||||
<div class="bp-refund-address">
|
||||
<div class="bitcoin-logo">
|
||||
<div><img src="~/imgs/bitcoin-symbol.svg"></div>
|
||||
<div><img src="~/imlegacy/bitcoin-symbol.svg"></div>
|
||||
</div>
|
||||
<input class="bp-input {'not-empty': addressValue.length > 0} ng-untouched ng-pristine ng-valid" id="refund-address-input" name="refundAddress" ngclass="{'not-empty': addressValue.length > 0}">
|
||||
</div>
|
||||
@ -380,7 +364,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>
|
||||
@ -397,9 +381,9 @@
|
||||
<div class="manual-box__address__value copy-cursor" ngxclipboard="">
|
||||
<div class="manual-box__address__wrapper">
|
||||
<div class="manual-box__address__wrapper__logo">
|
||||
<img src="~/img/bitcoin-symbol.svg">
|
||||
<img src="~/imlegacy/bitcoin-symbol.svg">
|
||||
</div>
|
||||
<div class="manual-box__address__wrapper__value">@Model.BTCAddress</div>
|
||||
<div class="manual-box__address__wrapper__value">@Model.BtcAddress</div>
|
||||
</div>
|
||||
<div class="copied-label" style="top: 5px;">
|
||||
<span i18n="">Copied</span>
|
||||
@ -418,7 +402,7 @@
|
||||
<div class="status-icon__wrapper">
|
||||
<div class="inner-wrapper">
|
||||
<div class="status-icon__wrapper__icon">
|
||||
<img src="~/img/checkmark.svg">
|
||||
<img src="~/imlegacy/checkmark.svg">
|
||||
</div>
|
||||
<div class="status-icon__wrapper__outline"></div>
|
||||
</div>
|
||||
@ -427,6 +411,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>
|
||||
<!---->
|
||||
@ -438,7 +427,7 @@
|
||||
<div class="bp-view" id="refund-pending">
|
||||
<div class="status-block">
|
||||
<div class="pending-block" style="position: relative; padding-bottom: 1.6rem;">
|
||||
<img src="~/img/refund-pending.svg">
|
||||
<img src="~/imlegacy/refund-pending.svg">
|
||||
<div class="pending-block__header" i18n="">Processing Refund</div>
|
||||
<span>
|
||||
<!---->
|
||||
@ -464,7 +453,7 @@
|
||||
<div class="manual-box__address__label label" i18n="">Will Be Refunded To</div>
|
||||
<div class="manual-box__address__wrapper">
|
||||
<div class="manual-box__address__wrapper__logo">
|
||||
<img src="~/img/bitcoin-symbol.svg">
|
||||
<img src="~/imlegacy/bitcoin-symbol.svg">
|
||||
</div>
|
||||
<div class="manual-box__address__wrapper__value">
|
||||
</div>
|
||||
@ -489,13 +478,13 @@
|
||||
<div class="timeline">
|
||||
<div class="timeline__item">
|
||||
<div class="timeline__item__icon timeline__item__icon--complete">
|
||||
<img src="~/img/checkmark-small.svg">
|
||||
<img src="~/imlegacy/checkmark-small.svg">
|
||||
</div>
|
||||
<div class="timeline__item__name" i18n="">Transaction created</div>
|
||||
</div>
|
||||
<div class="timeline__item">
|
||||
<div class="timeline__item__icon timeline__item__icon--pending">
|
||||
<img src="~/img/pending.svg">
|
||||
<img src="~/imlegacy/pending.svg">
|
||||
</div>
|
||||
<div class="timeline__item__name">
|
||||
<span i18n="">Transaction confirming — funds have not yet moved</span>
|
||||
@ -539,20 +528,20 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="action-button" style="margin-top: 20px;">
|
||||
<a href="/invoices" class="action-button" style="margin-top: 20px;">
|
||||
<bp-done-text>
|
||||
<!---->
|
||||
<!---->
|
||||
<span i18n="" class="i18n-return-to-merchant">Return to @Model.StoreName</span>
|
||||
</bp-done-text>
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
<div class="bp-view expired" id="archived">
|
||||
<div class="expired-icon">
|
||||
<img src="~/img/archived.svg">
|
||||
<img src="~/imlegacy/archived.svg">
|
||||
</div>
|
||||
<div class="archived__message">
|
||||
<div class="archived__message__header">
|
||||
@ -571,7 +560,7 @@
|
||||
<div class="status-icon__wrapper">
|
||||
<div class="inner-wrapper">
|
||||
<div class="status-icon__wrapper__icon">
|
||||
<img src="~/img/checkmark.svg">
|
||||
<img src="~/imlegacy/checkmark.svg">
|
||||
</div>
|
||||
<div class="status-icon__wrapper__outline" style="height: 117px; width: 117px;"></div>
|
||||
</div>
|
||||
@ -606,7 +595,7 @@
|
||||
<div class="manual-box__address__label label" i18n="">Refunded To</div>
|
||||
<div class="manual-box__address__wrapper">
|
||||
<div class="manual-box__address__wrapper__logo">
|
||||
<img src="~/img/bitcoin-symbol.svg">
|
||||
<img src="~/imlegacy/bitcoin-symbol.svg">
|
||||
</div>
|
||||
<div class="manual-box__address__wrapper__value">
|
||||
</div>
|
||||
|
@ -3,6 +3,15 @@
|
||||
ViewData["Title"] = "Invoice " + Model.Id;
|
||||
}
|
||||
|
||||
<style type="text/css">
|
||||
.overflowbox {
|
||||
max-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
|
||||
@ -86,7 +95,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Payment Url</th>
|
||||
<td><a href="@Model.PaymentUrl">@Model.PaymentUrl</a></td>
|
||||
<td class="overflowbox"><a href="@Model.PaymentUrl">@Model.PaymentUrl</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
23
BTCPayServer/Views/Shared/Confirm.cshtml
Normal file
@ -0,0 +1,23 @@
|
||||
@model ConfirmModel
|
||||
@{
|
||||
Layout = "_Layout.cshtml";
|
||||
}
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<h2 class="section-heading">@Model.Title</h2>
|
||||
<hr class="primary">
|
||||
<p>@Model.Description</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<form method="post">
|
||||
<button type="submit" class="btn btn-info btn-danger" title="Continue">@Model.Action</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
@ -1,6 +1,7 @@
|
||||
@inject SignInManager<ApplicationUser> SignInManager
|
||||
@inject UserManager<ApplicationUser> UserManager
|
||||
@inject RoleManager<IdentityRole> RoleManager
|
||||
@inject BTCPayServer.Services.BTCPayServerEnvironment env
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
@ -75,7 +76,9 @@
|
||||
</div>
|
||||
</nav>
|
||||
@RenderBody()
|
||||
|
||||
<footer class="bg-dark">
|
||||
<div class="container" style="text-align:right;font-size:10px; ">@env.ToString()</div>
|
||||
</footer>
|
||||
<!-- Bootstrap core JavaScript -->
|
||||
<script src="~/vendor/jquery/jquery.min.js"></script>
|
||||
<script src="~/vendor/popper/popper.min.js"></script>
|
||||
|
@ -42,7 +42,7 @@
|
||||
<a href="@store.WebSite">@store.WebSite</a>
|
||||
}</td>
|
||||
<td>@store.Balance</td>
|
||||
<td><a asp-action="UpdateStore" asp-route-storeId="@store.Id">Settings</a></td>
|
||||
<td><a asp-action="UpdateStore" asp-route-storeId="@store.Id">Settings</a> - <a asp-action="DeleteStore" asp-route-storeId="@store.Id">Remove</a></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
|
@ -26,6 +26,10 @@
|
||||
<input asp-for="StoreWebsite" class="form-control" />
|
||||
<span asp-validation-for="StoreWebsite" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="NetworkFee"></label>
|
||||
<input asp-for="NetworkFee" type="checkbox" class="form-check" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="SpeedPolicy"></label>
|
||||
<select asp-for="SpeedPolicy" class="form-control">
|
||||
|
@ -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 {
|
||||
|
28
BTCPayServer/wwwroot/imlegacy/archived.svg
Normal file
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="143px" height="143px" viewBox="0 0 143 143" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 3.7.1 (28215) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Group 2</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Invoice-Archived" transform="translate(-340.000000, -179.000000)">
|
||||
<g id="Invoice" transform="translate(236.000000, 87.000000)">
|
||||
<g id="Group-2" transform="translate(105.000000, 93.000000)">
|
||||
<g id="archived">
|
||||
<g id="ios-checkmark-outline" stroke="#9B9B9B">
|
||||
<path d="M70.5,141 C109.436075,141 141,109.436075 141,70.5 C141,31.5639251 109.436075,0 70.5,0 C31.5639251,0 0,31.5639251 0,70.5 C0,109.436075 31.5639251,141 70.5,141 Z" id="Oval-1"></path>
|
||||
</g>
|
||||
<g id="ui-24px-outline-2_archive" opacity="0.6" transform="translate(43.000000, 38.000000)" stroke="#343434">
|
||||
<g id="Group" transform="translate(0.473684, 0.478261)">
|
||||
<rect id="Rectangle-path" x="0" y="23.6521739" width="53.0526316" height="41.3913043"></rect>
|
||||
<path d="M5.89473684,11.826087 L47.1578947,11.826087" id="Shape"></path>
|
||||
<path d="M14.7368421,0 L38.3157895,0" id="Shape"></path>
|
||||
<polyline id="Shape" points="38.3157895 38.4347826 38.3157895 44.3478261 14.7368421 44.3478261 14.7368421 38.4347826"></polyline>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
14
BTCPayServer/wwwroot/imlegacy/checkmark-small.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="12px" height="8px" viewBox="0 0 12 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 40.1 (33804) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Line</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Scan---Design-Exploration-(Modal)" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="square">
|
||||
<g id="Payment-Success-(Confirming)" transform="translate(-299.000000, -401.000000)" stroke="#FFFFFF">
|
||||
<g id="Group-2" transform="translate(293.000000, 391.000000)">
|
||||
<polyline id="Line" points="7 14.150352 10.0039526 17.1543046 17 10"></polyline>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 807 B |
16
BTCPayServer/wwwroot/imlegacy/checkmark.svg
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="64px" height="49px" viewBox="0 0 64 49" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 3.7 (28169) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Shape</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Overpaid-(Email-receipt-sent)" transform="translate(-385.000000, -270.000000)" stroke="#F5F5F7" stroke-width="4" fill="#12E5B6">
|
||||
<g id="Invoice" transform="translate(236.000000, 86.000000)">
|
||||
<g id="ios-checkmark-outline" transform="translate(118.000000, 150.000000)">
|
||||
<path d="M86.5963111,36.9873548 L52.1800773,71.6517825 L39.1636812,58.6353864 L34.2549556,63.5441121 L49.6981374,78.9872939 C50.3875651,79.6767216 51.3251869,80.2282638 52.1525002,80.2282638 C52.9798135,80.2282638 53.8898582,79.6767216 54.5792859,79.014871 L91.4498825,41.9512346 L86.5963111,36.9873548 L86.5963111,36.9873548 Z" id="Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
17
BTCPayServer/wwwroot/imlegacy/circle-check.svg
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="21px" height="21px" viewBox="0 0 21 21" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 3.7 (28169) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>check</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Overpaid-(Email-receipt-sent)" transform="translate(-384.000000, -628.000000)">
|
||||
<g id="Group-2" transform="translate(384.000000, 628.000000)">
|
||||
<g id="check">
|
||||
<path d="M10.5,21 C16.2989899,21 21,16.2989899 21,10.5 C21,4.70101013 16.2989899,0 10.5,0 C4.70101013,0 0,4.70101013 0,10.5 C0,16.2989899 4.70101013,21 10.5,21 Z" id="Oval-1" fill="#12E5B6"></path>
|
||||
<path d="M15.5429276,6.63875598 L9.3656549,12.8605763 L7.02937868,10.5243001 L6.14832536,11.4053534 L8.92017851,14.1772066 C9.04392195,14.30095 9.21221303,14.3999448 9.36070517,14.3999448 C9.5091973,14.3999448 9.67253865,14.30095 9.79628209,14.1821563 L16.4140815,7.52970878 L15.5429276,6.63875598 L15.5429276,6.63875598 Z" id="Shape" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
21
BTCPayServer/wwwroot/imlegacy/mail.svg
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="99px" height="68px" viewBox="0 0 99 68" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>586581DD-76A4-41B9-B37A-F862632CDCE3</title>
|
||||
<desc>Created with sketchtool.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Check-email" transform="translate(-675.000000, -223.000000)">
|
||||
<g id="Group-2" transform="translate(0.000000, -32.000000)">
|
||||
<g id="Group-5" transform="translate(527.000000, 196.000000)">
|
||||
<g id="Group-3" transform="translate(149.000000, 59.000000)">
|
||||
<path d="M89.6850586,7.62011719 L3.31494141,7.62011719 C1.49853516,7.62011719 0,9.07324219 0,10.9350586 L0,64.0649414 C0,65.9267578 1.49853516,67.3798828 3.31494141,67.3798828 L89.6850586,67.3798828 C91.5014648,67.3798828 93,65.9267578 93,64.0649414 L93,10.9350586 C93,9.07324219 91.5014648,7.62011719 89.6850586,7.62011719 L89.6850586,7.62011719 Z M86.0522461,10.9350586 L48.8613281,48.1259766 C48.2255859,48.7617188 47.362793,49.125 46.5,49.125 C45.637207,49.125 44.7744141,48.7617188 44.0932617,48.1259766 L6.90234375,10.9350586 L86.0522461,10.9350586 Z M89.6850586,63.065918 L64.1191406,37.5 L89.6850586,11.934082 L89.6850586,63.065918 Z M3.31494141,11.9794922 L28.8354492,37.5 L3.31494141,63.0205078 L3.31494141,11.9794922 Z M6.90234375,64.0649414 L31.1513672,39.7705078 L41.8227539,50.4873047 C43.1396484,51.7587891 44.8198242,52.3945312 46.4545898,52.3945312 C48.1347656,52.3945312 49.8149414,51.7587891 51.0864258,50.4873047 L61.8032227,39.7705078 L86.0522461,64.0649414 L6.90234375,64.0649414 Z" id="mail" stroke="#FFFFFF" fill="#4B62CA"></path>
|
||||
<circle id="Oval-36" fill="#647CE8" cx="88" cy="10" r="10"></circle>
|
||||
<text id="1" font-family="ProximaNova-Regular, Proxima Nova" font-size="11" font-weight="normal" fill="#FFFFFF">
|
||||
<tspan x="85.6355" y="14">1</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
23
BTCPayServer/wwwroot/imlegacy/pending.svg
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="13px" height="13px" viewBox="0 0 13 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 40.1 (33804) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>arrows-24px-outline-1_refresh-69</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Scan---Design-Exploration-(Modal)" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Payment-Success-(Confirming)" transform="translate(-299.000000, -454.000000)" stroke="#D0D0D0">
|
||||
<g id="Group-2" transform="translate(293.000000, 391.000000)">
|
||||
<g id="Group-3" transform="translate(0.000000, 57.000000)">
|
||||
<g id="arrows-24px-outline-1_refresh-69" transform="translate(7.000000, 7.000000)">
|
||||
<g id="Group">
|
||||
<path d="M0,5.5 C0,2.6125 2.475,0.25 5.5,0.25 C7.645,0.25 9.515,1.405 10.395,3.1375" id="Shape"></path>
|
||||
<path d="M11,5.5 C11,8.3875 8.525,10.75 5.5,10.75 C3.355,10.75 1.485,9.595 0.605,7.8625" id="Shape"></path>
|
||||
<polyline id="Shape" stroke-linecap="square" points="10.89 0.0925 10.45 3.19 7.15 2.77"></polyline>
|
||||
<polyline id="Shape" stroke-linecap="square" points="0.11 10.9075 0.55 7.81 3.85 8.23"></polyline>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
19
BTCPayServer/wwwroot/imlegacy/refund-pending.svg
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="110px" height="110px" viewBox="0 0 110 110" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 3.8.3 (29802) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>bitcoin-topup</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Screens" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Overpaid---Immediate--Copy-2" transform="translate(-352.000000, -236.000000)">
|
||||
<g id="bitcoin-topup" transform="translate(353.000000, 237.000000)">
|
||||
<g id="Group" transform="translate(0.341463, 0.341463)" stroke="#50E3C2" stroke-width="2">
|
||||
<path d="M107.317073,53.6585366 C107.317073,83.1707317 83.1707317,107.317073 53.6585366,107.317073 C24.1463415,107.317073 0,83.1707317 0,53.6585366 C0,24.1463415 24.1463415,0 53.6585366,0 C74.8536585,0 93.3658537,12.3414634 101.95122,30.3170732" id="Shape"></path>
|
||||
<polyline id="Shape" points="104.634146 10.7317073 101.95122 30.0487805 85.8536585 27.902439"></polyline>
|
||||
</g>
|
||||
<g id="top-up" transform="translate(20.463415, 17.489966)"></g>
|
||||
<path d="M51.906321,29.2490103 C51.906321,28.6339821 52.4042009,28.1653893 52.9899419,28.1653893 C53.575683,28.1653893 54.0735629,28.6339821 54.0735629,29.2490103 L54.0735629,79.5641667 L51.906321,79.5641667 L51.906321,29.2490103 Z M41.8022878,54.9337554 L25.6065477,54.9337554 L33.7190613,33.3491976 L41.8022878,54.9337554 Z M67.9849129,83.8693635 C67.9849129,81.4971122 66.0812545,79.5641667 63.7090032,79.5641667 L56.2115177,79.5641667 L56.2115177,38.8258765 C62.5375211,35.1649949 64.9683465,34.7549762 71.0307664,38.0644131 L61.6296225,63.1341302 C61.6003354,63.2512784 61.5710484,63.3684266 61.5710484,63.5148619 C61.5710484,63.6612971 61.6296225,63.8077324 61.6881966,63.9541677 C62.3325117,67.9372068 66.8134308,71.0123473 72.2608226,71.0123473 C77.7375014,71.0123473 82.2184205,67.9372068 82.8627356,63.9541677 C82.9505968,63.8077324 82.9798839,63.6612971 82.9798839,63.5148619 C82.9798839,63.3684266 82.9505968,63.2512784 82.8920227,63.1341302 L82.9213097,63.1341302 L73.8130364,38.8258765 L73.3151565,36.7464957 C65.1733558,31.8262709 61.6589095,33.2320494 56.2115177,36.2193288 L56.2115177,29.2490103 C56.2115177,27.4625 54.7764521,26.0274345 52.9899419,26.0274345 C51.379154,26.0274345 50.0612367,27.2282036 49.8269402,28.7511304 C44.3502614,25.9981474 41.333695,24.0652019 32.6354403,29.2490103 L32.2547086,31.1526687 L23.0585741,55.6366447 L23.0878612,55.6366447 C23.0292871,55.7537929 23,55.8709411 23,56.0173764 C23,56.1638116 23.0292871,56.3102469 23.1171482,56.4566822 C23.7614634,60.4397213 28.2423824,63.5148619 33.7190613,63.5148619 C39.1664531,63.5148619 43.6766592,60.4397213 44.3209743,56.4566822 C44.3795484,56.3102469 44.4088355,56.1638116 44.4088355,56.0173764 C44.4088355,55.8709411 44.3795484,55.7537929 44.3502614,55.6366447 L34.9491175,30.5376406 C42.0072972,26.6717496 44.115965,28.4582598 49.7683661,31.2991039 L49.7683661,79.5641667 L42.2708807,79.5641667 C39.8986294,79.5641667 37.994971,81.4971122 37.994971,83.8693635 L37.994971,86.0073183 L67.9849129,86.0073183 L67.9849129,83.8693635 Z M72.2608226,40.8466831 L80.3733362,62.4312409 L64.177596,62.4312409 L72.2608226,40.8466831 Z M33.7190613,61.376907 C29.7945962,61.376907 26.4851593,59.5611098 25.4601124,57.0717103 L41.9487231,57.0717103 C40.9236762,59.5611098 37.6435263,61.376907 33.7190613,61.376907 L33.7190613,61.376907 Z M72.2608226,68.8743925 C68.3363575,68.8743925 65.0562076,67.0585952 64.0311608,64.5691958 L80.5197714,64.5691958 C79.4947246,67.0585952 76.1852876,68.8743925 72.2608226,68.8743925 L72.2608226,68.8743925 Z M63.7090032,81.7314086 C64.8804853,81.7314086 65.846958,82.6685943 65.846958,83.8693635 L40.1329258,83.8693635 C40.1329258,82.6685943 41.0993985,81.7314086 42.2708807,81.7314086 L63.7090032,81.7314086 Z" id="scales" fill="#D3D3D3"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.0 KiB |
@ -33,12 +33,12 @@
|
||||
var display = $(".timer-row__time-left"); // Timer container
|
||||
|
||||
// check if the Document expired
|
||||
if (expirationTime > 0) {
|
||||
if (srvModel.expirationSeconds > 0) {
|
||||
|
||||
progressStart(maxTime); // Progress bar
|
||||
startTimer(expirationTime, display); // Timer
|
||||
progressStart(srvModel.maxTimeSeconds); // Progress bar
|
||||
startTimer(srvModel.expirationSeconds, display); // Timer
|
||||
|
||||
if (!validateEmail(customerEmail))
|
||||
if (!validateEmail(srvModel.customerEmail))
|
||||
emailForm(); // Email form Display
|
||||
else
|
||||
hideEmailForm();
|
||||
@ -48,9 +48,9 @@ if (expirationTime > 0) {
|
||||
function hideEmailForm() {
|
||||
$("[role=document]").removeClass("enter-purchaser-email");
|
||||
$("#emailAddressView").removeClass("active");
|
||||
$("placeholder-refundEmail").html(customerEmail);
|
||||
$("placeholder-refundEmail").html(srvModel.customerEmail);
|
||||
// to generate a QR-Code : $(<selector>).qrcode("1Dut19quHiJrXEwfmig4hB8RyLss5aTRTC");
|
||||
$('.qr-codes').qrcode(btcAddress);
|
||||
$('.qr-codes').qrcode(srvModel.btcAddress);
|
||||
|
||||
// Remove Email mode
|
||||
$(".modal-dialog").removeClass("enter-purchaser-email");
|
||||
@ -67,14 +67,14 @@ function emailForm() {
|
||||
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
|
||||
customerEmail = emailAddress;
|
||||
srvModel.customerEmail = emailAddress;
|
||||
|
||||
var path = "i/" + invoiceId + "/UpdateCustomer";
|
||||
var path = srvModel.serverUrl + "/i/" + srvModel.invoiceId + "/UpdateCustomer";
|
||||
|
||||
$.ajax({
|
||||
url: path,
|
||||
type: "POST",
|
||||
data: JSON.stringify({ Email: customerEmail }),
|
||||
data: JSON.stringify({ Email: srvModel.customerEmail }),
|
||||
contentType: "application/json; charset=utf-8"
|
||||
}).done(function () {
|
||||
hideEmailForm();
|
||||
@ -94,7 +94,7 @@ function emailForm() {
|
||||
}
|
||||
|
||||
// Copy Tab Info
|
||||
$("#copy .manual__step-two__instructions span").html("To complete your payment, please send " + btcDue + " BTC to the address below.");
|
||||
$("#copy .manual__step-two__instructions span").html("To complete your payment, please send " + srvModel.btcDue + " BTC to the address below.");
|
||||
|
||||
|
||||
/* =============== Even listeners =============== */
|
||||
@ -155,22 +155,32 @@ $("#copy-tab").click(function () {
|
||||
// Should connect using webhook ?
|
||||
// If notification received
|
||||
|
||||
var oldStatus = status;
|
||||
updateState(status);
|
||||
var oldStat = srvModel.status;
|
||||
onDataCallback(srvModel);
|
||||
|
||||
function updateState(status) {
|
||||
if (oldStatus != status)
|
||||
{
|
||||
oldStatus = status;
|
||||
window.parent.postMessage({ "invoiceId": invoiceId, "status": status }, "*");
|
||||
function onDataCallback(jsonData) {
|
||||
var newStatus = jsonData.status;
|
||||
|
||||
if (oldStat != newStatus) {
|
||||
oldStat = newStatus;
|
||||
window.parent.postMessage({ "invoiceId": srvModel.invoiceId, "status": newStatus }, "*");
|
||||
}
|
||||
if (status == "complete" ||
|
||||
status == "paidOver" ||
|
||||
status == "confirmed" ||
|
||||
status =="paid") {
|
||||
if (newStatus == "complete" ||
|
||||
newStatus == "confirmed" ||
|
||||
newStatus == "paid") {
|
||||
if ($(".modal-dialog").hasClass("expired")) {
|
||||
$(".modal-dialog").removeClass("expired");
|
||||
}
|
||||
|
||||
if (srvModel.merchantRefLink != "") {
|
||||
$(".action-button").click(function () {
|
||||
window.location.href = srvModel.merchantRefLink;
|
||||
});
|
||||
}
|
||||
else {
|
||||
$(".action-button").hide();
|
||||
}
|
||||
|
||||
$(".modal-dialog").addClass("paid");
|
||||
|
||||
if ($("#scan").hasClass("active")) {
|
||||
@ -181,7 +191,7 @@ function updateState(status) {
|
||||
$("#paid").addClass("active");
|
||||
}
|
||||
|
||||
if (status == "invalid") {
|
||||
if (newStatus == "expired" || newStatus == "invalid") { //TODO: different state if the invoice is invalid (failed to confirm after timeout)
|
||||
$(".timer-row").removeClass("expiring-soon");
|
||||
$(".timer-row__message span").html("Invoice expired.");
|
||||
$(".timer-row__spinner").html("");
|
||||
@ -192,17 +202,15 @@ function updateState(status) {
|
||||
}
|
||||
|
||||
var watcher = setInterval(function () {
|
||||
var path = "i/" + invoiceId + "/status";
|
||||
var path = srvModel.serverUrl + "/i/" + srvModel.invoiceId + "/status";
|
||||
$.ajax({
|
||||
url: path,
|
||||
type: "GET"
|
||||
}).done(function (data) {
|
||||
status = data;
|
||||
updateState(status);
|
||||
})
|
||||
.fail(function (jqXHR, textStatus, errorThrown) {
|
||||
onDataCallback(data);
|
||||
}).fail(function (jqXHR, textStatus, errorThrown) {
|
||||
|
||||
});
|
||||
});
|
||||
}, 2000);
|
||||
|
||||
$(".menu__item").click(function () {
|
||||
@ -213,11 +221,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,}))$/;
|
||||
@ -249,11 +252,11 @@ function startTimer(duration, display) {
|
||||
}
|
||||
|
||||
// Progress bar
|
||||
function progressStart(maxTime) {
|
||||
function progressStart(timerMax) {
|
||||
var end = new Date(); // Setup Time Variable, should come from server
|
||||
end.setSeconds(end.getSeconds() + expirationTime);
|
||||
maxTime *= 1000; // Usually 15 minutes = 9000 second= 900000 ms
|
||||
var timeoutVal = Math.floor(maxTime / 100); // Timeout calc
|
||||
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) {
|
||||
@ -264,7 +267,7 @@ function progressStart(maxTime) {
|
||||
|
||||
var now = new Date();
|
||||
var timeDiff = end.getTime() - now.getTime();
|
||||
var perc = 100 - Math.round((timeDiff / maxTime) * 100);
|
||||
var perc = 100 - Math.round((timeDiff / timerMax) * 100);
|
||||
|
||||
if (perc === 75 && (status == "paidPartial" || status == "new")) {
|
||||
$(".timer-row").addClass("expiring-soon");
|
||||
@ -276,7 +279,7 @@ function progressStart(maxTime) {
|
||||
setTimeout(animateUpdate, timeoutVal);
|
||||
}
|
||||
if (perc >= 100 && status == "expired") {
|
||||
updateState(status);
|
||||
onDataCallback(status);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -307,4 +310,5 @@ $(document).keypress(
|
||||
if (event.which === '13') {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -14,3 +14,9 @@ If you:
|
||||
* Want features BitPay is not willing to consider (Multi-sig + SegWit support soon)
|
||||
|
||||
Then head out to the [documentation](https://github.com/btcpayserver/btcpayserver-doc), this project is for you.
|
||||
|
||||
If you want to help development, go to [Local Development](https://github.com/btcpayserver/btcpayserver-doc/blob/master/Local-Development.md).
|
||||
|
||||
## Additional resources
|
||||
|
||||
[Introduction of BTCPay on youtube](https://www.youtube.com/watch?v=npFMOu6tTpA)
|
@ -1,2 +0,0 @@
|
||||
docker-compose -f docker-compose.regtest.yml down
|
||||
docker-compose -f docker-compose.regtest.yml up --force-recreate --build
|