mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-22 06:21:44 +01:00
Create Metadata property for InvoiceEntity, migrate all data without logic there
This commit is contained in:
parent
8dea7df82a
commit
b2ff041ec0
22 changed files with 463 additions and 259 deletions
|
@ -2,6 +2,7 @@ using System;
|
||||||
using BTCPayServer.JsonConverters;
|
using BTCPayServer.JsonConverters;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Converters;
|
using Newtonsoft.Json.Converters;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace BTCPayServer.Client.Models
|
namespace BTCPayServer.Client.Models
|
||||||
{
|
{
|
||||||
|
@ -10,8 +11,7 @@ namespace BTCPayServer.Client.Models
|
||||||
[JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))]
|
[JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))]
|
||||||
public decimal Amount { get; set; }
|
public decimal Amount { get; set; }
|
||||||
public string Currency { get; set; }
|
public string Currency { get; set; }
|
||||||
public string Metadata { get; set; }
|
public JObject Metadata { get; set; }
|
||||||
public string CustomerEmail { get; set; }
|
|
||||||
public CheckoutOptions Checkout { get; set; } = new CheckoutOptions();
|
public CheckoutOptions Checkout { get; set; } = new CheckoutOptions();
|
||||||
|
|
||||||
public class CheckoutOptions
|
public class CheckoutOptions
|
||||||
|
|
|
@ -17,9 +17,6 @@
|
||||||
<PropertyGroup Condition="'$(CI_TESTS)' == 'true'">
|
<PropertyGroup Condition="'$(CI_TESTS)' == 'true'">
|
||||||
<DefineConstants>$(DefineConstants);SHORT_TIMEOUT</DefineConstants>
|
<DefineConstants>$(DefineConstants);SHORT_TIMEOUT</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup>
|
|
||||||
<DefineConstants>$(DefineConstants);ALTCOINS</DefineConstants>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
||||||
|
|
|
@ -10,6 +10,7 @@ using BTCPayServer.Controllers;
|
||||||
using BTCPayServer.Events;
|
using BTCPayServer.Events;
|
||||||
using BTCPayServer.JsonConverters;
|
using BTCPayServer.JsonConverters;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
using BTCPayServer.Tests.Logging;
|
using BTCPayServer.Tests.Logging;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
@ -17,6 +18,7 @@ using NBitcoin;
|
||||||
using NBitpayClient;
|
using NBitpayClient;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
using NUglify.Helpers;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
using CreateApplicationUserRequest = BTCPayServer.Client.Models.CreateApplicationUserRequest;
|
using CreateApplicationUserRequest = BTCPayServer.Client.Models.CreateApplicationUserRequest;
|
||||||
|
@ -732,7 +734,74 @@ namespace BTCPayServer.Tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact(Timeout = TestTimeout)]
|
||||||
|
[Trait("Integration", "Integration")]
|
||||||
|
public async Task InvoiceLegacyTests()
|
||||||
|
{
|
||||||
|
using (var tester = ServerTester.Create())
|
||||||
|
{
|
||||||
|
await tester.StartAsync();
|
||||||
|
var user = tester.NewAccount();
|
||||||
|
await user.GrantAccessAsync();
|
||||||
|
user.RegisterDerivationScheme("BTC");
|
||||||
|
var client = await user.CreateClient(Policies.Unrestricted);
|
||||||
|
var oldBitpay = user.BitPay;
|
||||||
|
|
||||||
|
Logs.Tester.LogInformation("Let's create an invoice with bitpay API");
|
||||||
|
var oldInvoice = await oldBitpay.CreateInvoiceAsync(new Invoice()
|
||||||
|
{
|
||||||
|
Currency = "BTC",
|
||||||
|
Price = 1000.19392922m,
|
||||||
|
BuyerAddress1 = "blah",
|
||||||
|
Buyer = new Buyer()
|
||||||
|
{
|
||||||
|
Address2 = "blah2"
|
||||||
|
},
|
||||||
|
ItemCode = "code",
|
||||||
|
ItemDesc = "desc",
|
||||||
|
OrderId = "orderId",
|
||||||
|
PosData = "posData"
|
||||||
|
});
|
||||||
|
|
||||||
|
async Task<Client.Models.InvoiceData> AssertInvoiceMetadata()
|
||||||
|
{
|
||||||
|
Logs.Tester.LogInformation("Let's check if we can get invoice in the new format with the metadata");
|
||||||
|
var newInvoice = await client.GetInvoice(user.StoreId, oldInvoice.Id);
|
||||||
|
Assert.Equal("posData", newInvoice.Metadata["posData"].Value<string>());
|
||||||
|
Assert.Equal("code", newInvoice.Metadata["itemCode"].Value<string>());
|
||||||
|
Assert.Equal("desc", newInvoice.Metadata["itemDesc"].Value<string>());
|
||||||
|
Assert.Equal("orderId", newInvoice.Metadata["orderId"].Value<string>());
|
||||||
|
Assert.False(newInvoice.Metadata["physical"].Value<bool>());
|
||||||
|
Assert.Null(newInvoice.Metadata["buyerCountry"]);
|
||||||
|
Assert.Equal(1000.19392922m, newInvoice.Amount);
|
||||||
|
Assert.Equal("BTC", newInvoice.Currency);
|
||||||
|
return newInvoice;
|
||||||
|
}
|
||||||
|
|
||||||
|
await AssertInvoiceMetadata();
|
||||||
|
|
||||||
|
Logs.Tester.LogInformation("Let's hack the Bitpay created invoice to be just like before this update. (Invoice V1)");
|
||||||
|
var invoiceV1 = "{\r\n \"version\": 1,\r\n \"id\": \"" + oldInvoice.Id + "\",\r\n \"storeId\": \"" + user.StoreId + "\",\r\n \"orderId\": \"orderId\",\r\n \"speedPolicy\": 1,\r\n \"rate\": 1.0,\r\n \"invoiceTime\": 1598329634,\r\n \"expirationTime\": 1598330534,\r\n \"depositAddress\": \"mm83rVs8ZnZok1SkRBmXiwQSiPFgTgCKpD\",\r\n \"productInformation\": {\r\n \"itemDesc\": \"desc\",\r\n \"itemCode\": \"code\",\r\n \"physical\": false,\r\n \"price\": 1000.19392922,\r\n \"currency\": \"BTC\"\r\n },\r\n \"buyerInformation\": {\r\n \"buyerName\": null,\r\n \"buyerEmail\": null,\r\n \"buyerCountry\": null,\r\n \"buyerZip\": null,\r\n \"buyerState\": null,\r\n \"buyerCity\": null,\r\n \"buyerAddress2\": \"blah2\",\r\n \"buyerAddress1\": \"blah\",\r\n \"buyerPhone\": null\r\n },\r\n \"posData\": \"posData\",\r\n \"internalTags\": [],\r\n \"derivationStrategy\": null,\r\n \"derivationStrategies\": \"{\\\"BTC\\\":{\\\"signingKey\\\":\\\"tpubDD1AW2ruUxSsDa55NQYtNt7DQw9bqXx4K7r2aScySmjxHtsCZoxFTN3qCMcKLxgsRDMGSwk9qj1fBfi8jqSLenwyYkhDrmgaxQuvuKrTHEf\\\",\\\"source\\\":\\\"NBXplorer\\\",\\\"accountDerivation\\\":\\\"tpubDD1AW2ruUxSsDa55NQYtNt7DQw9bqXx4K7r2aScySmjxHtsCZoxFTN3qCMcKLxgsRDMGSwk9qj1fBfi8jqSLenwyYkhDrmgaxQuvuKrTHEf-[legacy]\\\",\\\"accountOriginal\\\":null,\\\"accountKeySettings\\\":[{\\\"rootFingerprint\\\":\\\"54d5044d\\\",\\\"accountKeyPath\\\":\\\"44'/1'/0'\\\",\\\"accountKey\\\":\\\"tpubDD1AW2ruUxSsDa55NQYtNt7DQw9bqXx4K7r2aScySmjxHtsCZoxFTN3qCMcKLxgsRDMGSwk9qj1fBfi8jqSLenwyYkhDrmgaxQuvuKrTHEf\\\"}],\\\"label\\\":null}}\",\r\n \"status\": \"new\",\r\n \"exceptionStatus\": \"\",\r\n \"payments\": [],\r\n \"refundable\": false,\r\n \"refundMail\": null,\r\n \"redirectURL\": null,\r\n \"redirectAutomatically\": false,\r\n \"txFee\": 0,\r\n \"fullNotifications\": false,\r\n \"notificationEmail\": null,\r\n \"notificationURL\": null,\r\n \"serverUrl\": \"http://127.0.0.1:8001\",\r\n \"cryptoData\": {\r\n \"BTC\": {\r\n \"rate\": 1.0,\r\n \"paymentMethod\": {\r\n \"networkFeeMode\": 0,\r\n \"networkFeeRate\": 100.0,\r\n \"payjoinEnabled\": false\r\n },\r\n \"feeRate\": 100.0,\r\n \"txFee\": 0,\r\n \"depositAddress\": \"mm83rVs8ZnZok1SkRBmXiwQSiPFgTgCKpD\"\r\n }\r\n },\r\n \"monitoringExpiration\": 1598416934,\r\n \"historicalAddresses\": null,\r\n \"availableAddressHashes\": null,\r\n \"extendedNotifications\": false,\r\n \"events\": null,\r\n \"paymentTolerance\": 0.0,\r\n \"archived\": false\r\n}";
|
||||||
|
var db = tester.PayTester.GetService<Data.ApplicationDbContextFactory>();
|
||||||
|
using var ctx = db.CreateContext();
|
||||||
|
var dbInvoice = await ctx.Invoices.FindAsync(oldInvoice.Id);
|
||||||
|
dbInvoice.Blob = ZipUtils.Zip(invoiceV1);
|
||||||
|
await ctx.SaveChangesAsync();
|
||||||
|
var newInvoice = await AssertInvoiceMetadata();
|
||||||
|
|
||||||
|
Logs.Tester.LogInformation("Now, let's create an invoice with the new API but with the same metadata as Bitpay");
|
||||||
|
newInvoice.Metadata.Add("lol", "lol");
|
||||||
|
newInvoice = await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest()
|
||||||
|
{
|
||||||
|
Metadata = newInvoice.Metadata,
|
||||||
|
Amount = 1000.19392922m,
|
||||||
|
Currency = "BTC"
|
||||||
|
});
|
||||||
|
oldInvoice = await oldBitpay.GetInvoiceAsync(newInvoice.Id);
|
||||||
|
await AssertInvoiceMetadata();
|
||||||
|
Assert.Equal("lol", newInvoice.Metadata["lol"].Value<string>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact(Timeout = TestTimeout)]
|
[Fact(Timeout = TestTimeout)]
|
||||||
[Trait("Integration", "Integration")]
|
[Trait("Integration", "Integration")]
|
||||||
|
@ -750,10 +819,10 @@ namespace BTCPayServer.Tests
|
||||||
//create
|
//create
|
||||||
|
|
||||||
//validation errors
|
//validation errors
|
||||||
await AssertValidationError(new[] { nameof(CreateInvoiceRequest.Currency), nameof(CreateInvoiceRequest.Amount), $"{nameof(CreateInvoiceRequest.Checkout)}.{nameof(CreateInvoiceRequest.Checkout.PaymentTolerance)}", $"{nameof(CreateInvoiceRequest.Checkout)}.{nameof(CreateInvoiceRequest.Checkout.PaymentMethods)}[0]" }, async () =>
|
await AssertValidationError(new[] { nameof(CreateInvoiceRequest.Currency), nameof(CreateInvoiceRequest.Amount), $"{nameof(CreateInvoiceRequest.Checkout)}.{nameof(CreateInvoiceRequest.Checkout.PaymentTolerance)}", $"{nameof(CreateInvoiceRequest.Checkout)}.{nameof(CreateInvoiceRequest.Checkout.PaymentMethods)}[0]" }, async () =>
|
||||||
{
|
{
|
||||||
await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest() { Amount = -1, Checkout = new CreateInvoiceRequest.CheckoutOptions(){ PaymentTolerance = -2, PaymentMethods = new []{"jasaas_sdsad"}}});
|
await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest() { Amount = -1, Checkout = new CreateInvoiceRequest.CheckoutOptions() { PaymentTolerance = -2, PaymentMethods = new[] { "jasaas_sdsad" } } });
|
||||||
});
|
});
|
||||||
|
|
||||||
await AssertHttpError(403, async () =>
|
await AssertHttpError(403, async () =>
|
||||||
{
|
{
|
||||||
|
@ -762,7 +831,7 @@ namespace BTCPayServer.Tests
|
||||||
});
|
});
|
||||||
await user.RegisterDerivationSchemeAsync("BTC");
|
await user.RegisterDerivationSchemeAsync("BTC");
|
||||||
var newInvoice = await client.CreateInvoice(user.StoreId,
|
var newInvoice = await client.CreateInvoice(user.StoreId,
|
||||||
new CreateInvoiceRequest() { Currency = "USD", Amount = 1, Metadata = "{\"itemCode\": \"testitem\"}"});
|
new CreateInvoiceRequest() { Currency = "USD", Amount = 1, Metadata = JObject.Parse("{\"itemCode\": \"testitem\"}") });
|
||||||
|
|
||||||
//list
|
//list
|
||||||
var invoices = await viewOnly.GetInvoices(user.StoreId);
|
var invoices = await viewOnly.GetInvoices(user.StoreId);
|
||||||
|
@ -788,15 +857,7 @@ namespace BTCPayServer.Tests
|
||||||
Email = "j@g.com"
|
Email = "j@g.com"
|
||||||
});
|
});
|
||||||
invoice = await viewOnly.GetInvoice(user.StoreId, newInvoice.Id);
|
invoice = await viewOnly.GetInvoice(user.StoreId, newInvoice.Id);
|
||||||
Assert.Equal("j@g.com", invoice.CustomerEmail);
|
|
||||||
|
|
||||||
await AssertValidationError(new[] { nameof(AddCustomerEmailRequest.Email) }, async () =>
|
|
||||||
{
|
|
||||||
await client.AddCustomerEmailToInvoice(user.StoreId, invoice.Id, new AddCustomerEmailRequest()
|
|
||||||
{
|
|
||||||
Email = "j@g2.com",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
await AssertValidationError(new[] { nameof(MarkInvoiceStatusRequest.Status) }, async () =>
|
await AssertValidationError(new[] { nameof(MarkInvoiceStatusRequest.Status) }, async () =>
|
||||||
{
|
{
|
||||||
await client.MarkInvoiceStatus(user.StoreId, invoice.Id, new MarkInvoiceStatusRequest()
|
await client.MarkInvoiceStatus(user.StoreId, invoice.Id, new MarkInvoiceStatusRequest()
|
||||||
|
@ -818,7 +879,7 @@ namespace BTCPayServer.Tests
|
||||||
|
|
||||||
//unarchive
|
//unarchive
|
||||||
await client.UnarchiveInvoice(user.StoreId, invoice.Id);
|
await client.UnarchiveInvoice(user.StoreId, invoice.Id);
|
||||||
Assert.NotNull(await client.GetInvoice(user.StoreId,invoice.Id));
|
Assert.NotNull(await client.GetInvoice(user.StoreId, invoice.Id));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -855,10 +916,11 @@ namespace BTCPayServer.Tests
|
||||||
Assert.Throws<JsonSerializationException>(() =>
|
Assert.Throws<JsonSerializationException>(() =>
|
||||||
{
|
{
|
||||||
jsonConverter.ReadJson(Get("null"), typeof(decimal), null, null);
|
jsonConverter.ReadJson(Get("null"), typeof(decimal), null, null);
|
||||||
});Assert.Throws<JsonSerializationException>(() =>
|
|
||||||
{
|
|
||||||
jsonConverter.ReadJson(Get("null"), typeof(double), null, null);
|
|
||||||
});
|
});
|
||||||
|
Assert.Throws<JsonSerializationException>(() =>
|
||||||
|
{
|
||||||
|
jsonConverter.ReadJson(Get("null"), typeof(double), null, null);
|
||||||
|
});
|
||||||
Assert.Equal(1.2m, jsonConverter.ReadJson(Get(stringJson), typeof(decimal), null, null));
|
Assert.Equal(1.2m, jsonConverter.ReadJson(Get(stringJson), typeof(decimal), null, null));
|
||||||
Assert.Equal(1.2m, jsonConverter.ReadJson(Get(stringJson), typeof(decimal?), null, null));
|
Assert.Equal(1.2m, jsonConverter.ReadJson(Get(stringJson), typeof(decimal?), null, null));
|
||||||
Assert.Equal(1.2, jsonConverter.ReadJson(Get(stringJson), typeof(double), null, null));
|
Assert.Equal(1.2, jsonConverter.ReadJson(Get(stringJson), typeof(double), null, null));
|
||||||
|
|
|
@ -53,7 +53,6 @@ using Newtonsoft.Json.Schema;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
using Xunit.Sdk;
|
using Xunit.Sdk;
|
||||||
using ProductInformation = BTCPayServer.Services.Invoices.ProductInformation;
|
|
||||||
using RatesViewModel = BTCPayServer.Models.StoreViewModels.RatesViewModel;
|
using RatesViewModel = BTCPayServer.Models.StoreViewModels.RatesViewModel;
|
||||||
|
|
||||||
namespace BTCPayServer.Tests
|
namespace BTCPayServer.Tests
|
||||||
|
@ -326,7 +325,7 @@ namespace BTCPayServer.Tests
|
||||||
Assert.True(Torrc.TryParse(input, out torrc));
|
Assert.True(Torrc.TryParse(input, out torrc));
|
||||||
Assert.Equal(expected, torrc.ToString());
|
Assert.Equal(expected, torrc.ToString());
|
||||||
}
|
}
|
||||||
|
#if ALTCOINS
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Fast", "Fast")]
|
[Trait("Fast", "Fast")]
|
||||||
public void CanCalculateCryptoDue()
|
public void CanCalculateCryptoDue()
|
||||||
|
@ -347,7 +346,7 @@ namespace BTCPayServer.Tests
|
||||||
Rate = 5000,
|
Rate = 5000,
|
||||||
NextNetworkFee = Money.Coins(0.1m)
|
NextNetworkFee = Money.Coins(0.1m)
|
||||||
});
|
});
|
||||||
entity.ProductInformation = new ProductInformation() { Price = 5000 };
|
entity.Price = 5000;
|
||||||
|
|
||||||
var paymentMethod = entity.GetPaymentMethods().TryGet("BTC", PaymentTypes.BTCLike);
|
var paymentMethod = entity.GetPaymentMethods().TryGet("BTC", PaymentTypes.BTCLike);
|
||||||
var accounting = paymentMethod.Calculate();
|
var accounting = paymentMethod.Calculate();
|
||||||
|
@ -397,7 +396,7 @@ namespace BTCPayServer.Tests
|
||||||
|
|
||||||
entity = new InvoiceEntity();
|
entity = new InvoiceEntity();
|
||||||
entity.Networks = networkProvider;
|
entity.Networks = networkProvider;
|
||||||
entity.ProductInformation = new ProductInformation() { Price = 5000 };
|
entity.Price = 5000;
|
||||||
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary();
|
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary();
|
||||||
paymentMethods.Add(
|
paymentMethods.Add(
|
||||||
new PaymentMethod() { CryptoCode = "BTC", Rate = 1000, NextNetworkFee = Money.Coins(0.1m) });
|
new PaymentMethod() { CryptoCode = "BTC", Rate = 1000, NextNetworkFee = Money.Coins(0.1m) });
|
||||||
|
@ -491,7 +490,7 @@ namespace BTCPayServer.Tests
|
||||||
Assert.Equal(accounting.Paid, accounting.TotalDue);
|
Assert.Equal(accounting.Paid, accounting.TotalDue);
|
||||||
#pragma warning restore CS0618
|
#pragma warning restore CS0618
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Integration", "Integration")]
|
[Trait("Integration", "Integration")]
|
||||||
public async Task CanUseTestWebsiteUI()
|
public async Task CanUseTestWebsiteUI()
|
||||||
|
@ -546,7 +545,7 @@ namespace BTCPayServer.Tests
|
||||||
Rate = 5000,
|
Rate = 5000,
|
||||||
NextNetworkFee = Money.Coins(0.1m)
|
NextNetworkFee = Money.Coins(0.1m)
|
||||||
});
|
});
|
||||||
entity.ProductInformation = new ProductInformation() { Price = 5000 };
|
entity.Price = 5000;
|
||||||
entity.PaymentTolerance = 0;
|
entity.PaymentTolerance = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -175,7 +175,7 @@ namespace BTCPayServer.Controllers
|
||||||
var store = await _AppService.GetStore(app);
|
var store = await _AppService.GetStore(app);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var invoice = await _InvoiceController.CreateInvoiceCore(new CreateInvoiceRequest()
|
var invoice = await _InvoiceController.CreateInvoiceCore(new BitpayCreateInvoiceRequest()
|
||||||
{
|
{
|
||||||
ItemCode = choice?.Id,
|
ItemCode = choice?.Id,
|
||||||
ItemDesc = title,
|
ItemDesc = title,
|
||||||
|
@ -317,7 +317,7 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var invoice = await _InvoiceController.CreateInvoiceCore(new CreateInvoiceRequest()
|
var invoice = await _InvoiceController.CreateInvoiceCore(new BitpayCreateInvoiceRequest()
|
||||||
{
|
{
|
||||||
OrderId = AppService.GetCrowdfundOrderId(appId),
|
OrderId = AppService.GetCrowdfundOrderId(appId),
|
||||||
Currency = settings.TargetCurrency,
|
Currency = settings.TargetCurrency,
|
||||||
|
|
|
@ -11,6 +11,7 @@ using BTCPayServer.Validation;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Cors;
|
using Microsoft.AspNetCore.Cors;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using NBitpayClient;
|
using NBitpayClient;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
@ -48,7 +49,8 @@ namespace BTCPayServer.Controllers.GreenField
|
||||||
var invoices =
|
var invoices =
|
||||||
await _invoiceRepository.GetInvoices(new InvoiceQuery()
|
await _invoiceRepository.GetInvoices(new InvoiceQuery()
|
||||||
{
|
{
|
||||||
StoreId = new[] {store.Id}, IncludeArchived = includeArchived
|
StoreId = new[] { store.Id },
|
||||||
|
IncludeArchived = includeArchived
|
||||||
});
|
});
|
||||||
|
|
||||||
return Ok(invoices.Select(ToModel));
|
return Ok(invoices.Select(ToModel));
|
||||||
|
@ -123,13 +125,6 @@ namespace BTCPayServer.Controllers.GreenField
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(request.CustomerEmail) &&
|
|
||||||
!EmailValidator.IsEmail(request.CustomerEmail))
|
|
||||||
{
|
|
||||||
request.AddModelError(invoiceRequest => invoiceRequest.CustomerEmail, "Invalid email address",
|
|
||||||
this);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.Checkout.ExpirationTime != null && request.Checkout.ExpirationTime < DateTime.Now)
|
if (request.Checkout.ExpirationTime != null && request.Checkout.ExpirationTime < DateTime.Now)
|
||||||
{
|
{
|
||||||
request.AddModelError(invoiceRequest => invoiceRequest.Checkout.ExpirationTime,
|
request.AddModelError(invoiceRequest => invoiceRequest.Checkout.ExpirationTime,
|
||||||
|
@ -211,7 +206,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||||
request.AddModelError(invoiceRequest => invoiceRequest.Email, "Invalid email address",
|
request.AddModelError(invoiceRequest => invoiceRequest.Email, "Invalid email address",
|
||||||
this);
|
this);
|
||||||
}
|
}
|
||||||
else if (!string.IsNullOrEmpty(invoice.BuyerInformation.BuyerEmail))
|
else if (!string.IsNullOrEmpty(invoice.Metadata.BuyerEmail))
|
||||||
{
|
{
|
||||||
request.AddModelError(invoiceRequest => invoiceRequest.Email, "Email address already set",
|
request.AddModelError(invoiceRequest => invoiceRequest.Email, "Email address already set",
|
||||||
this);
|
this);
|
||||||
|
@ -220,7 +215,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||||
if (!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
return this.CreateValidationError(ModelState);
|
return this.CreateValidationError(ModelState);
|
||||||
|
|
||||||
await _invoiceRepository.UpdateInvoice(invoice.Id, new UpdateCustomerModel() {Email = request.Email});
|
await _invoiceRepository.UpdateInvoice(invoice.Id, new UpdateCustomerModel() { Email = request.Email });
|
||||||
|
|
||||||
return await GetInvoice(storeId, invoiceId);
|
return await GetInvoice(storeId, invoiceId);
|
||||||
}
|
}
|
||||||
|
@ -260,13 +255,12 @@ namespace BTCPayServer.Controllers.GreenField
|
||||||
{
|
{
|
||||||
return new InvoiceData()
|
return new InvoiceData()
|
||||||
{
|
{
|
||||||
Amount = entity.ProductInformation.Price,
|
Amount = entity.Price,
|
||||||
Id = entity.Id,
|
Id = entity.Id,
|
||||||
Status = entity.Status,
|
Status = entity.Status,
|
||||||
AdditionalStatus = entity.ExceptionStatus,
|
AdditionalStatus = entity.ExceptionStatus,
|
||||||
Currency = entity.ProductInformation.Currency,
|
Currency = entity.Currency,
|
||||||
Metadata = entity.PosData,
|
Metadata = entity.Metadata.ToJObject(),
|
||||||
CustomerEmail = entity.RefundMail ?? entity.BuyerInformation.BuyerEmail,
|
|
||||||
Checkout = new CreateInvoiceRequest.CheckoutOptions()
|
Checkout = new CreateInvoiceRequest.CheckoutOptions()
|
||||||
{
|
{
|
||||||
ExpirationTime = entity.ExpirationTime,
|
ExpirationTime = entity.ExpirationTime,
|
||||||
|
@ -324,31 +318,27 @@ namespace BTCPayServer.Controllers.GreenField
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private Models.CreateInvoiceRequest FromModel(CreateInvoiceRequest entity)
|
private Models.BitpayCreateInvoiceRequest FromModel(CreateInvoiceRequest entity)
|
||||||
{
|
{
|
||||||
Buyer buyer = null;
|
InvoiceMetadata invoiceMetadata = null;
|
||||||
ProductInformation pi = null;
|
if (entity.Metadata != null)
|
||||||
JToken? orderId = null;
|
|
||||||
if (!string.IsNullOrEmpty(entity.Metadata) && entity.Metadata.StartsWith('{'))
|
|
||||||
{
|
{
|
||||||
//metadata was provided and is json. Let's try and match props
|
invoiceMetadata = entity.Metadata.ToObject<InvoiceMetadata>();
|
||||||
try
|
|
||||||
{
|
|
||||||
buyer = JsonConvert.DeserializeObject<Buyer>(entity.Metadata);
|
|
||||||
pi = JsonConvert.DeserializeObject<ProductInformation>(entity.Metadata);
|
|
||||||
JObject.Parse(entity.Metadata).TryGetValue("orderid", StringComparison.InvariantCultureIgnoreCase,
|
|
||||||
out orderId);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return new Models.BitpayCreateInvoiceRequest()
|
||||||
return new Models.CreateInvoiceRequest()
|
|
||||||
{
|
{
|
||||||
Buyer = buyer,
|
Buyer = invoiceMetadata == null ? null : new Buyer()
|
||||||
BuyerEmail = entity.CustomerEmail,
|
{
|
||||||
|
Address1 = invoiceMetadata.BuyerAddress1,
|
||||||
|
Address2 = invoiceMetadata.BuyerAddress2,
|
||||||
|
City = invoiceMetadata.BuyerCity,
|
||||||
|
country = invoiceMetadata.BuyerCountry,
|
||||||
|
email = invoiceMetadata.BuyerEmail,
|
||||||
|
Name = invoiceMetadata.BuyerName,
|
||||||
|
phone = invoiceMetadata.BuyerPhone,
|
||||||
|
State = invoiceMetadata.BuyerState,
|
||||||
|
zip = invoiceMetadata.BuyerZip,
|
||||||
|
},
|
||||||
Currency = entity.Currency,
|
Currency = entity.Currency,
|
||||||
Price = entity.Amount,
|
Price = entity.Amount,
|
||||||
Refundable = true,
|
Refundable = true,
|
||||||
|
@ -360,12 +350,13 @@ namespace BTCPayServer.Controllers.GreenField
|
||||||
TransactionSpeed = entity.Checkout.SpeedPolicy?.ToString(),
|
TransactionSpeed = entity.Checkout.SpeedPolicy?.ToString(),
|
||||||
PaymentCurrencies = entity.Checkout.PaymentMethods,
|
PaymentCurrencies = entity.Checkout.PaymentMethods,
|
||||||
NotificationURL = entity.Checkout.RedirectUri,
|
NotificationURL = entity.Checkout.RedirectUri,
|
||||||
PosData = entity.Metadata,
|
PosData = invoiceMetadata?.PosData,
|
||||||
Physical = pi?.Physical ?? false,
|
Physical = invoiceMetadata?.Physical ?? false,
|
||||||
ItemCode = pi?.ItemCode,
|
ItemCode = invoiceMetadata?.ItemCode,
|
||||||
ItemDesc = pi?.ItemDesc,
|
ItemDesc = invoiceMetadata?.ItemDesc,
|
||||||
TaxIncluded = pi?.TaxIncluded,
|
TaxIncluded = invoiceMetadata?.TaxIncluded,
|
||||||
OrderId = orderId?.ToString()
|
OrderId = invoiceMetadata?.OrderId,
|
||||||
|
Metadata = entity.Metadata
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ namespace BTCPayServer.Controllers
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("invoices")]
|
[Route("invoices")]
|
||||||
[MediaTypeConstraint("application/json")]
|
[MediaTypeConstraint("application/json")]
|
||||||
public async Task<DataWrapper<InvoiceResponse>> CreateInvoice([FromBody] CreateInvoiceRequest invoice, CancellationToken cancellationToken)
|
public async Task<DataWrapper<InvoiceResponse>> CreateInvoice([FromBody] BitpayCreateInvoiceRequest invoice, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (invoice == null)
|
if (invoice == null)
|
||||||
throw new BitpayHttpException(400, "Invalid invoice");
|
throw new BitpayHttpException(400, "Invalid invoice");
|
||||||
|
|
|
@ -30,8 +30,7 @@ using NBitcoin;
|
||||||
using NBitpayClient;
|
using NBitpayClient;
|
||||||
using NBXplorer;
|
using NBXplorer;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using CreateInvoiceRequest = BTCPayServer.Models.CreateInvoiceRequest;
|
using BitpayCreateInvoiceRequest = BTCPayServer.Models.BitpayCreateInvoiceRequest;
|
||||||
using ProductInformation = BTCPayServer.Services.Invoices.ProductInformation;
|
|
||||||
using StoreData = BTCPayServer.Data.StoreData;
|
using StoreData = BTCPayServer.Data.StoreData;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
|
@ -54,7 +53,6 @@ namespace BTCPayServer.Controllers
|
||||||
if (invoice == null)
|
if (invoice == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
var prodInfo = invoice.ProductInformation;
|
|
||||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||||
var model = new InvoiceDetailsModel()
|
var model = new InvoiceDetailsModel()
|
||||||
{
|
{
|
||||||
|
@ -70,16 +68,14 @@ namespace BTCPayServer.Controllers
|
||||||
CreatedDate = invoice.InvoiceTime,
|
CreatedDate = invoice.InvoiceTime,
|
||||||
ExpirationDate = invoice.ExpirationTime,
|
ExpirationDate = invoice.ExpirationTime,
|
||||||
MonitoringDate = invoice.MonitoringExpiration,
|
MonitoringDate = invoice.MonitoringExpiration,
|
||||||
OrderId = invoice.OrderId,
|
Fiat = _CurrencyNameTable.DisplayFormatCurrency(invoice.Price, invoice.Currency),
|
||||||
BuyerInformation = invoice.BuyerInformation,
|
TaxIncluded = _CurrencyNameTable.DisplayFormatCurrency(invoice.Metadata.TaxIncluded ?? 0.0m, invoice.Currency),
|
||||||
Fiat = _CurrencyNameTable.DisplayFormatCurrency(prodInfo.Price, prodInfo.Currency),
|
|
||||||
TaxIncluded = _CurrencyNameTable.DisplayFormatCurrency(prodInfo.TaxIncluded, prodInfo.Currency),
|
|
||||||
NotificationUrl = invoice.NotificationURL?.AbsoluteUri,
|
NotificationUrl = invoice.NotificationURL?.AbsoluteUri,
|
||||||
RedirectUrl = invoice.RedirectURL?.AbsoluteUri,
|
RedirectUrl = invoice.RedirectURL?.AbsoluteUri,
|
||||||
ProductInformation = invoice.ProductInformation,
|
TypedMetadata = invoice.Metadata,
|
||||||
StatusException = invoice.ExceptionStatus,
|
StatusException = invoice.ExceptionStatus,
|
||||||
Events = invoice.Events,
|
Events = invoice.Events,
|
||||||
PosData = PosDataParser.ParsePosData(invoice.PosData),
|
PosData = PosDataParser.ParsePosData(invoice.Metadata.PosData),
|
||||||
Archived = invoice.Archived,
|
Archived = invoice.Archived,
|
||||||
CanRefund = CanRefund(invoice.GetInvoiceState()),
|
CanRefund = CanRefund(invoice.GetInvoiceState()),
|
||||||
};
|
};
|
||||||
|
@ -179,7 +175,7 @@ namespace BTCPayServer.Controllers
|
||||||
if (!CanRefund(invoice.GetInvoiceState()))
|
if (!CanRefund(invoice.GetInvoiceState()))
|
||||||
return NotFound();
|
return NotFound();
|
||||||
var paymentMethodId = new PaymentMethodId(model.SelectedPaymentMethod, PaymentTypes.BTCLike);
|
var paymentMethodId = new PaymentMethodId(model.SelectedPaymentMethod, PaymentTypes.BTCLike);
|
||||||
var cdCurrency = _CurrencyNameTable.GetCurrencyData(invoice.ProductInformation.Currency, true);
|
var cdCurrency = _CurrencyNameTable.GetCurrencyData(invoice.Currency, true);
|
||||||
var paymentMethodDivisibility = _CurrencyNameTable.GetCurrencyData(paymentMethodId.CryptoCode, false)?.Divisibility ?? 8;
|
var paymentMethodDivisibility = _CurrencyNameTable.GetCurrencyData(paymentMethodId.CryptoCode, false)?.Divisibility ?? 8;
|
||||||
if (model.SelectedRefundOption is null)
|
if (model.SelectedRefundOption is null)
|
||||||
{
|
{
|
||||||
|
@ -189,7 +185,7 @@ namespace BTCPayServer.Controllers
|
||||||
model.CryptoAmountThen = Math.Round(paidCurrency / paymentMethod.Rate, paymentMethodDivisibility);
|
model.CryptoAmountThen = Math.Round(paidCurrency / paymentMethod.Rate, paymentMethodDivisibility);
|
||||||
model.RateThenText = _CurrencyNameTable.DisplayFormatCurrency(model.CryptoAmountThen, paymentMethodId.CryptoCode, true);
|
model.RateThenText = _CurrencyNameTable.DisplayFormatCurrency(model.CryptoAmountThen, paymentMethodId.CryptoCode, true);
|
||||||
var rules = store.GetStoreBlob().GetRateRules(_NetworkProvider);
|
var rules = store.GetStoreBlob().GetRateRules(_NetworkProvider);
|
||||||
var rateResult = await _RateProvider.FetchRate(new Rating.CurrencyPair(paymentMethodId.CryptoCode, invoice.ProductInformation.Currency), rules, cancellationToken);
|
var rateResult = await _RateProvider.FetchRate(new Rating.CurrencyPair(paymentMethodId.CryptoCode, invoice.Currency), rules, cancellationToken);
|
||||||
//TODO: What if fetching rate failed?
|
//TODO: What if fetching rate failed?
|
||||||
if (rateResult.BidAsk is null)
|
if (rateResult.BidAsk is null)
|
||||||
{
|
{
|
||||||
|
@ -199,7 +195,7 @@ namespace BTCPayServer.Controllers
|
||||||
model.CryptoAmountNow = Math.Round(paidCurrency / rateResult.BidAsk.Bid, paymentMethodDivisibility);
|
model.CryptoAmountNow = Math.Round(paidCurrency / rateResult.BidAsk.Bid, paymentMethodDivisibility);
|
||||||
model.CurrentRateText = _CurrencyNameTable.DisplayFormatCurrency(model.CryptoAmountNow, paymentMethodId.CryptoCode, true);
|
model.CurrentRateText = _CurrencyNameTable.DisplayFormatCurrency(model.CryptoAmountNow, paymentMethodId.CryptoCode, true);
|
||||||
model.FiatAmount = paidCurrency;
|
model.FiatAmount = paidCurrency;
|
||||||
model.FiatText = _CurrencyNameTable.DisplayFormatCurrency(model.FiatAmount, invoice.ProductInformation.Currency, true);
|
model.FiatText = _CurrencyNameTable.DisplayFormatCurrency(model.FiatAmount, invoice.Currency, true);
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -219,7 +215,7 @@ namespace BTCPayServer.Controllers
|
||||||
createPullPayment.Amount = model.CryptoAmountNow;
|
createPullPayment.Amount = model.CryptoAmountNow;
|
||||||
break;
|
break;
|
||||||
case "Fiat":
|
case "Fiat":
|
||||||
createPullPayment.Currency = invoice.ProductInformation.Currency;
|
createPullPayment.Currency = invoice.Currency;
|
||||||
createPullPayment.Amount = model.FiatAmount;
|
createPullPayment.Amount = model.FiatAmount;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -405,7 +401,7 @@ namespace BTCPayServer.Controllers
|
||||||
var dto = invoice.EntityToDTO();
|
var dto = invoice.EntityToDTO();
|
||||||
var cryptoInfo = dto.CryptoInfo.First(o => o.GetpaymentMethodId() == paymentMethodId);
|
var cryptoInfo = dto.CryptoInfo.First(o => o.GetpaymentMethodId() == paymentMethodId);
|
||||||
var storeBlob = store.GetStoreBlob();
|
var storeBlob = store.GetStoreBlob();
|
||||||
var currency = invoice.ProductInformation.Currency;
|
var currency = invoice.Currency;
|
||||||
var accounting = paymentMethod.Calculate();
|
var accounting = paymentMethod.Calculate();
|
||||||
|
|
||||||
ChangellySettings changelly = (storeBlob.ChangellySettings != null && storeBlob.ChangellySettings.Enabled &&
|
ChangellySettings changelly = (storeBlob.ChangellySettings != null && storeBlob.ChangellySettings.Enabled &&
|
||||||
|
@ -427,12 +423,11 @@ namespace BTCPayServer.Controllers
|
||||||
var paymentMethodHandler = _paymentMethodHandlerDictionary[paymentMethodId];
|
var paymentMethodHandler = _paymentMethodHandlerDictionary[paymentMethodId];
|
||||||
|
|
||||||
var divisibility = _CurrencyNameTable.GetNumberFormatInfo(paymentMethod.GetId().CryptoCode, false)?.CurrencyDecimalDigits;
|
var divisibility = _CurrencyNameTable.GetNumberFormatInfo(paymentMethod.GetId().CryptoCode, false)?.CurrencyDecimalDigits;
|
||||||
|
|
||||||
var model = new PaymentModel()
|
var model = new PaymentModel()
|
||||||
{
|
{
|
||||||
CryptoCode = network.CryptoCode,
|
CryptoCode = network.CryptoCode,
|
||||||
RootPath = this.Request.PathBase.Value.WithTrailingSlash(),
|
RootPath = this.Request.PathBase.Value.WithTrailingSlash(),
|
||||||
OrderId = invoice.OrderId,
|
OrderId = invoice.Metadata.OrderId,
|
||||||
InvoiceId = invoice.Id,
|
InvoiceId = invoice.Id,
|
||||||
DefaultLang = storeBlob.DefaultLang ?? "en",
|
DefaultLang = storeBlob.DefaultLang ?? "en",
|
||||||
CustomCSSLink = storeBlob.CustomCSS,
|
CustomCSSLink = storeBlob.CustomCSS,
|
||||||
|
@ -442,7 +437,7 @@ namespace BTCPayServer.Controllers
|
||||||
BtcAddress = paymentMethodDetails.GetPaymentDestination(),
|
BtcAddress = paymentMethodDetails.GetPaymentDestination(),
|
||||||
BtcDue = accounting.Due.ShowMoney(divisibility),
|
BtcDue = accounting.Due.ShowMoney(divisibility),
|
||||||
OrderAmount = (accounting.TotalDue - accounting.NetworkFee).ShowMoney(divisibility),
|
OrderAmount = (accounting.TotalDue - accounting.NetworkFee).ShowMoney(divisibility),
|
||||||
OrderAmountFiat = OrderAmountFromInvoice(network.CryptoCode, invoice.ProductInformation),
|
OrderAmountFiat = OrderAmountFromInvoice(network.CryptoCode, invoice),
|
||||||
CustomerEmail = invoice.RefundMail,
|
CustomerEmail = invoice.RefundMail,
|
||||||
RequiresRefundEmail = storeBlob.RequiresRefundEmail,
|
RequiresRefundEmail = storeBlob.RequiresRefundEmail,
|
||||||
ShowRecommendedFee = storeBlob.ShowRecommendedFee,
|
ShowRecommendedFee = storeBlob.ShowRecommendedFee,
|
||||||
|
@ -450,7 +445,7 @@ namespace BTCPayServer.Controllers
|
||||||
ExpirationSeconds = Math.Max(0, (int)(invoice.ExpirationTime - DateTimeOffset.UtcNow).TotalSeconds),
|
ExpirationSeconds = Math.Max(0, (int)(invoice.ExpirationTime - DateTimeOffset.UtcNow).TotalSeconds),
|
||||||
MaxTimeSeconds = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalSeconds,
|
MaxTimeSeconds = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalSeconds,
|
||||||
MaxTimeMinutes = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalMinutes,
|
MaxTimeMinutes = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalMinutes,
|
||||||
ItemDesc = invoice.ProductInformation.ItemDesc,
|
ItemDesc = invoice.Metadata.ItemDesc,
|
||||||
Rate = ExchangeRate(paymentMethod),
|
Rate = ExchangeRate(paymentMethod),
|
||||||
MerchantRefLink = invoice.RedirectURL?.AbsoluteUri ?? "/",
|
MerchantRefLink = invoice.RedirectURL?.AbsoluteUri ?? "/",
|
||||||
RedirectAutomatically = invoice.RedirectAutomatically,
|
RedirectAutomatically = invoice.RedirectAutomatically,
|
||||||
|
@ -500,7 +495,7 @@ namespace BTCPayServer.Controllers
|
||||||
paymentMethodHandler.PreparePaymentModel(model, dto, storeBlob);
|
paymentMethodHandler.PreparePaymentModel(model, dto, storeBlob);
|
||||||
if (model.IsLightning && storeBlob.LightningAmountInSatoshi && model.CryptoCode == "Sats")
|
if (model.IsLightning && storeBlob.LightningAmountInSatoshi && model.CryptoCode == "Sats")
|
||||||
{
|
{
|
||||||
model.Rate = _CurrencyNameTable.DisplayFormatCurrency(paymentMethod.Rate / 100_000_000, paymentMethod.ParentEntity.ProductInformation.Currency);
|
model.Rate = _CurrencyNameTable.DisplayFormatCurrency(paymentMethod.Rate / 100_000_000, paymentMethod.ParentEntity.Currency);
|
||||||
}
|
}
|
||||||
model.UISettings = paymentMethodHandler.GetCheckoutUISettings();
|
model.UISettings = paymentMethodHandler.GetCheckoutUISettings();
|
||||||
model.PaymentMethodId = paymentMethodId.ToString();
|
model.PaymentMethodId = paymentMethodId.ToString();
|
||||||
|
@ -509,17 +504,17 @@ namespace BTCPayServer.Controllers
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string OrderAmountFromInvoice(string cryptoCode, ProductInformation productInformation)
|
private string OrderAmountFromInvoice(string cryptoCode, InvoiceEntity invoiceEntity)
|
||||||
{
|
{
|
||||||
// if invoice source currency is the same as currently display currency, no need for "order amount from invoice"
|
// if invoice source currency is the same as currently display currency, no need for "order amount from invoice"
|
||||||
if (cryptoCode == productInformation.Currency)
|
if (cryptoCode == invoiceEntity.Currency)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return _CurrencyNameTable.DisplayFormatCurrency(productInformation.Price, productInformation.Currency);
|
return _CurrencyNameTable.DisplayFormatCurrency(invoiceEntity.Price, invoiceEntity.Currency);
|
||||||
}
|
}
|
||||||
private string ExchangeRate(PaymentMethod paymentMethod)
|
private string ExchangeRate(PaymentMethod paymentMethod)
|
||||||
{
|
{
|
||||||
string currency = paymentMethod.ParentEntity.ProductInformation.Currency;
|
string currency = paymentMethod.ParentEntity.Currency;
|
||||||
return _CurrencyNameTable.DisplayFormatCurrency(paymentMethod.Rate, currency);
|
return _CurrencyNameTable.DisplayFormatCurrency(paymentMethod.Rate, currency);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -628,9 +623,9 @@ namespace BTCPayServer.Controllers
|
||||||
ShowCheckout = invoice.Status == InvoiceStatus.New,
|
ShowCheckout = invoice.Status == InvoiceStatus.New,
|
||||||
Date = invoice.InvoiceTime,
|
Date = invoice.InvoiceTime,
|
||||||
InvoiceId = invoice.Id,
|
InvoiceId = invoice.Id,
|
||||||
OrderId = invoice.OrderId ?? string.Empty,
|
OrderId = invoice.Metadata.OrderId ?? string.Empty,
|
||||||
RedirectUrl = invoice.RedirectURL?.AbsoluteUri ?? string.Empty,
|
RedirectUrl = invoice.RedirectURL?.AbsoluteUri ?? string.Empty,
|
||||||
AmountCurrency = _CurrencyNameTable.DisplayFormatCurrency(invoice.ProductInformation.Price, invoice.ProductInformation.Currency),
|
AmountCurrency = _CurrencyNameTable.DisplayFormatCurrency(invoice.Price, invoice.Currency),
|
||||||
CanMarkInvalid = state.CanMarkInvalid(),
|
CanMarkInvalid = state.CanMarkInvalid(),
|
||||||
CanMarkComplete = state.CanMarkComplete(),
|
CanMarkComplete = state.CanMarkComplete(),
|
||||||
Details = InvoicePopulatePayments(invoice),
|
Details = InvoicePopulatePayments(invoice),
|
||||||
|
@ -732,7 +727,7 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = await CreateInvoiceCore(new CreateInvoiceRequest()
|
var result = await CreateInvoiceCore(new BitpayCreateInvoiceRequest()
|
||||||
{
|
{
|
||||||
Price = model.Amount.Value,
|
Price = model.Amount.Value,
|
||||||
Currency = model.Currency,
|
Currency = model.Currency,
|
||||||
|
|
|
@ -23,9 +23,7 @@ using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using NBitpayClient;
|
using NBitpayClient;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using BuyerInformation = BTCPayServer.Services.Invoices.BuyerInformation;
|
using BitpayCreateInvoiceRequest = BTCPayServer.Models.BitpayCreateInvoiceRequest;
|
||||||
using CreateInvoiceRequest = BTCPayServer.Models.CreateInvoiceRequest;
|
|
||||||
using ProductInformation = BTCPayServer.Services.Invoices.ProductInformation;
|
|
||||||
using StoreData = BTCPayServer.Data.StoreData;
|
using StoreData = BTCPayServer.Data.StoreData;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
|
@ -74,7 +72,7 @@ namespace BTCPayServer.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal async Task<DataWrapper<InvoiceResponse>> CreateInvoiceCore(CreateInvoiceRequest invoice,
|
internal async Task<DataWrapper<InvoiceResponse>> CreateInvoiceCore(BitpayCreateInvoiceRequest invoice,
|
||||||
StoreData store, string serverUrl, List<string> additionalTags = null,
|
StoreData store, string serverUrl, List<string> additionalTags = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
@ -83,7 +81,7 @@ namespace BTCPayServer.Controllers
|
||||||
return new DataWrapper<InvoiceResponse>(resp) {Facade = "pos/invoice"};
|
return new DataWrapper<InvoiceResponse>(resp) {Facade = "pos/invoice"};
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<InvoiceEntity> CreateInvoiceCoreRaw(CreateInvoiceRequest invoice, StoreData store, string serverUrl, List<string> additionalTags = null, CancellationToken cancellationToken = default)
|
internal async Task<InvoiceEntity> CreateInvoiceCoreRaw(BitpayCreateInvoiceRequest invoice, StoreData store, string serverUrl, List<string> additionalTags = null, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
invoice.Currency = invoice.Currency?.ToUpperInvariant() ?? "USD";
|
invoice.Currency = invoice.Currency?.ToUpperInvariant() ?? "USD";
|
||||||
InvoiceLogs logs = new InvoiceLogs();
|
InvoiceLogs logs = new InvoiceLogs();
|
||||||
|
@ -99,23 +97,21 @@ namespace BTCPayServer.Controllers
|
||||||
throw new BitpayHttpException(400, "The expirationTime is set too soon");
|
throw new BitpayHttpException(400, "The expirationTime is set too soon");
|
||||||
}
|
}
|
||||||
entity.MonitoringExpiration = entity.ExpirationTime + TimeSpan.FromMinutes(storeBlob.MonitoringExpiration);
|
entity.MonitoringExpiration = entity.ExpirationTime + TimeSpan.FromMinutes(storeBlob.MonitoringExpiration);
|
||||||
entity.OrderId = invoice.OrderId;
|
entity.Metadata.OrderId = invoice.OrderId;
|
||||||
entity.ServerUrl = serverUrl;
|
entity.ServerUrl = serverUrl;
|
||||||
entity.FullNotifications = invoice.FullNotifications || invoice.ExtendedNotifications;
|
entity.FullNotifications = invoice.FullNotifications || invoice.ExtendedNotifications;
|
||||||
entity.ExtendedNotifications = invoice.ExtendedNotifications;
|
entity.ExtendedNotifications = invoice.ExtendedNotifications;
|
||||||
entity.NotificationURLTemplate = invoice.NotificationURL;
|
entity.NotificationURLTemplate = invoice.NotificationURL;
|
||||||
entity.NotificationEmail = invoice.NotificationEmail;
|
entity.NotificationEmail = invoice.NotificationEmail;
|
||||||
entity.BuyerInformation = Map<CreateInvoiceRequest, BuyerInformation>(invoice);
|
|
||||||
entity.PaymentTolerance = storeBlob.PaymentTolerance;
|
entity.PaymentTolerance = storeBlob.PaymentTolerance;
|
||||||
if (additionalTags != null)
|
if (additionalTags != null)
|
||||||
entity.InternalTags.AddRange(additionalTags);
|
entity.InternalTags.AddRange(additionalTags);
|
||||||
//Another way of passing buyer info to support
|
FillBuyerInfo(invoice, entity);
|
||||||
FillBuyerInfo(invoice.Buyer, entity.BuyerInformation);
|
if (entity.Metadata.BuyerEmail != null)
|
||||||
if (entity?.BuyerInformation?.BuyerEmail != null)
|
|
||||||
{
|
{
|
||||||
if (!EmailValidator.IsEmail(entity.BuyerInformation.BuyerEmail))
|
if (!EmailValidator.IsEmail(entity.Metadata.BuyerEmail))
|
||||||
throw new BitpayHttpException(400, "Invalid email");
|
throw new BitpayHttpException(400, "Invalid email");
|
||||||
entity.RefundMail = entity.BuyerInformation.BuyerEmail;
|
entity.RefundMail = entity.Metadata.BuyerEmail;
|
||||||
}
|
}
|
||||||
|
|
||||||
var taxIncluded = invoice.TaxIncluded.HasValue ? invoice.TaxIncluded.Value : 0m;
|
var taxIncluded = invoice.TaxIncluded.HasValue ? invoice.TaxIncluded.Value : 0m;
|
||||||
|
@ -132,8 +128,23 @@ namespace BTCPayServer.Controllers
|
||||||
invoice.TaxIncluded = Math.Max(0.0m, taxIncluded);
|
invoice.TaxIncluded = Math.Max(0.0m, taxIncluded);
|
||||||
invoice.TaxIncluded = Math.Min(taxIncluded, invoice.Price);
|
invoice.TaxIncluded = Math.Min(taxIncluded, invoice.Price);
|
||||||
|
|
||||||
entity.ProductInformation = Map<CreateInvoiceRequest, ProductInformation>(invoice);
|
entity.Metadata.ItemCode = invoice.ItemCode;
|
||||||
|
entity.Metadata.ItemDesc = invoice.ItemDesc;
|
||||||
|
entity.Metadata.Physical = invoice.Physical;
|
||||||
|
entity.Metadata.TaxIncluded = invoice.TaxIncluded;
|
||||||
|
entity.Currency = invoice.Currency;
|
||||||
|
entity.Price = invoice.Price;
|
||||||
|
|
||||||
|
if (invoice.Metadata != null)
|
||||||
|
{
|
||||||
|
var currentMetadata = entity.Metadata.ToJObject();
|
||||||
|
foreach (var prop in invoice.Metadata.Properties())
|
||||||
|
{
|
||||||
|
if (!currentMetadata.ContainsKey(prop.Name))
|
||||||
|
currentMetadata.Add(prop.Name, prop.Value);
|
||||||
|
}
|
||||||
|
entity.Metadata = InvoiceMetadata.FromJObject(currentMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
entity.RedirectURLTemplate = invoice.RedirectURL ?? store.StoreWebsite;
|
entity.RedirectURLTemplate = invoice.RedirectURL ?? store.StoreWebsite;
|
||||||
|
|
||||||
|
@ -220,8 +231,7 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
entity.SetSupportedPaymentMethods(supported);
|
entity.SetSupportedPaymentMethods(supported);
|
||||||
entity.SetPaymentMethods(paymentMethods);
|
entity.SetPaymentMethods(paymentMethods);
|
||||||
entity.PosData = invoice.PosData;
|
entity.Metadata.PosData = invoice.PosData;
|
||||||
|
|
||||||
foreach (var app in await getAppsTaggingStore)
|
foreach (var app in await getAppsTaggingStore)
|
||||||
{
|
{
|
||||||
entity.InternalTags.Add(AppService.GetAppInternalTag(app.Id));
|
entity.InternalTags.Add(AppService.GetAppInternalTag(app.Id));
|
||||||
|
@ -273,7 +283,7 @@ namespace BTCPayServer.Controllers
|
||||||
var logPrefix = $"{supportedPaymentMethod.PaymentId.ToPrettyString()}:";
|
var logPrefix = $"{supportedPaymentMethod.PaymentId.ToPrettyString()}:";
|
||||||
var storeBlob = store.GetStoreBlob();
|
var storeBlob = store.GetStoreBlob();
|
||||||
var preparePayment = handler.PreparePayment(supportedPaymentMethod, store, network);
|
var preparePayment = handler.PreparePayment(supportedPaymentMethod, store, network);
|
||||||
var rate = await fetchingByCurrencyPair[new CurrencyPair(network.CryptoCode, entity.ProductInformation.Currency)];
|
var rate = await fetchingByCurrencyPair[new CurrencyPair(network.CryptoCode, entity.Currency)];
|
||||||
if (rate.BidAsk == null)
|
if (rate.BidAsk == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
|
@ -337,24 +347,30 @@ namespace BTCPayServer.Controllers
|
||||||
return policy;
|
return policy;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FillBuyerInfo(Buyer buyer, BuyerInformation buyerInformation)
|
private void FillBuyerInfo(BitpayCreateInvoiceRequest req, InvoiceEntity invoiceEntity)
|
||||||
{
|
{
|
||||||
|
var buyerInformation = invoiceEntity.Metadata;
|
||||||
|
buyerInformation.BuyerAddress1 = req.BuyerAddress1;
|
||||||
|
buyerInformation.BuyerAddress2 = req.BuyerAddress2;
|
||||||
|
buyerInformation.BuyerCity = req.BuyerCity;
|
||||||
|
buyerInformation.BuyerCountry = req.BuyerCountry;
|
||||||
|
buyerInformation.BuyerEmail = req.BuyerEmail;
|
||||||
|
buyerInformation.BuyerName = req.BuyerName;
|
||||||
|
buyerInformation.BuyerPhone = req.BuyerPhone;
|
||||||
|
buyerInformation.BuyerState = req.BuyerState;
|
||||||
|
buyerInformation.BuyerZip = req.BuyerZip;
|
||||||
|
var buyer = req.Buyer;
|
||||||
if (buyer == null)
|
if (buyer == null)
|
||||||
return;
|
return;
|
||||||
buyerInformation.BuyerAddress1 = buyerInformation.BuyerAddress1 ?? buyer.Address1;
|
buyerInformation.BuyerAddress1 ??= buyer.Address1;
|
||||||
buyerInformation.BuyerAddress2 = buyerInformation.BuyerAddress2 ?? buyer.Address2;
|
buyerInformation.BuyerAddress2 ??= buyer.Address2;
|
||||||
buyerInformation.BuyerCity = buyerInformation.BuyerCity ?? buyer.City;
|
buyerInformation.BuyerCity ??= buyer.City;
|
||||||
buyerInformation.BuyerCountry = buyerInformation.BuyerCountry ?? buyer.country;
|
buyerInformation.BuyerCountry ??= buyer.country;
|
||||||
buyerInformation.BuyerEmail = buyerInformation.BuyerEmail ?? buyer.email;
|
buyerInformation.BuyerEmail ??= buyer.email;
|
||||||
buyerInformation.BuyerName = buyerInformation.BuyerName ?? buyer.Name;
|
buyerInformation.BuyerName ??= buyer.Name;
|
||||||
buyerInformation.BuyerPhone = buyerInformation.BuyerPhone ?? buyer.phone;
|
buyerInformation.BuyerPhone ??= buyer.phone;
|
||||||
buyerInformation.BuyerState = buyerInformation.BuyerState ?? buyer.State;
|
buyerInformation.BuyerState ??= buyer.State;
|
||||||
buyerInformation.BuyerZip = buyerInformation.BuyerZip ?? buyer.zip;
|
buyerInformation.BuyerZip ??= buyer.zip;
|
||||||
}
|
|
||||||
|
|
||||||
private TDest Map<TFrom, TDest>(TFrom data)
|
|
||||||
{
|
|
||||||
return JsonConvert.DeserializeObject<TDest>(JsonConvert.SerializeObject(data));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using CreateInvoiceRequest = BTCPayServer.Models.CreateInvoiceRequest;
|
using BitpayCreateInvoiceRequest = BTCPayServer.Models.BitpayCreateInvoiceRequest;
|
||||||
using PaymentRequestData = BTCPayServer.Data.PaymentRequestData;
|
using PaymentRequestData = BTCPayServer.Data.PaymentRequestData;
|
||||||
using StoreData = BTCPayServer.Data.StoreData;
|
using StoreData = BTCPayServer.Data.StoreData;
|
||||||
|
|
||||||
|
@ -260,7 +260,7 @@ namespace BTCPayServer.Controllers
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var redirectUrl = _linkGenerator.PaymentRequestLink(id, Request.Scheme, Request.Host, Request.PathBase);
|
var redirectUrl = _linkGenerator.PaymentRequestLink(id, Request.Scheme, Request.Host, Request.PathBase);
|
||||||
var newInvoiceId = (await _InvoiceController.CreateInvoiceCore(new CreateInvoiceRequest()
|
var newInvoiceId = (await _InvoiceController.CreateInvoiceCore(new BitpayCreateInvoiceRequest()
|
||||||
{
|
{
|
||||||
OrderId = $"{PaymentRequestRepository.GetOrderIdForPaymentRequest(id)}",
|
OrderId = $"{PaymentRequestRepository.GetOrderIdForPaymentRequest(id)}",
|
||||||
Currency = blob.Currency,
|
Currency = blob.Currency,
|
||||||
|
|
|
@ -57,7 +57,7 @@ namespace BTCPayServer.Controllers
|
||||||
DataWrapper<InvoiceResponse> invoice = null;
|
DataWrapper<InvoiceResponse> invoice = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
invoice = await _InvoiceController.CreateInvoiceCore(new CreateInvoiceRequest()
|
invoice = await _InvoiceController.CreateInvoiceCore(new BitpayCreateInvoiceRequest()
|
||||||
{
|
{
|
||||||
Price = model.Price,
|
Price = model.Price,
|
||||||
Currency = model.Currency,
|
Currency = model.Currency,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using BTCPayServer.Services.Invoices;
|
using BTCPayServer.Services.Invoices;
|
||||||
|
using Microsoft.AspNetCore.Authorization.Infrastructure;
|
||||||
|
|
||||||
namespace BTCPayServer.Data
|
namespace BTCPayServer.Data
|
||||||
{
|
{
|
||||||
|
@ -8,6 +9,17 @@ namespace BTCPayServer.Data
|
||||||
{
|
{
|
||||||
var entity = NBitcoin.JsonConverters.Serializer.ToObject<InvoiceEntity>(ZipUtils.Unzip(invoiceData.Blob), null);
|
var entity = NBitcoin.JsonConverters.Serializer.ToObject<InvoiceEntity>(ZipUtils.Unzip(invoiceData.Blob), null);
|
||||||
entity.Networks = networks;
|
entity.Networks = networks;
|
||||||
|
if (entity.Metadata is null)
|
||||||
|
{
|
||||||
|
if (entity.Version < InvoiceEntity.GreenfieldInvoices_Version)
|
||||||
|
{
|
||||||
|
entity.MigrateLegacyInvoice();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
entity.Metadata = new InvoiceMetadata();
|
||||||
|
}
|
||||||
|
}
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
public static InvoiceState GetInvoiceState(this InvoiceData invoiceData)
|
public static InvoiceState GetInvoiceState(this InvoiceData invoiceData)
|
||||||
|
|
|
@ -104,9 +104,8 @@ namespace BTCPayServer.HostedServices
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((!string.IsNullOrEmpty(invoiceEvent.Invoice.Metadata.ItemCode) ||
|
||||||
if ((!string.IsNullOrEmpty(invoiceEvent.Invoice.ProductInformation.ItemCode) ||
|
AppService.TryParsePosCartItems(invoiceEvent.Invoice.Metadata.PosData, out cartItems)))
|
||||||
AppService.TryParsePosCartItems(invoiceEvent.Invoice.PosData, out cartItems)))
|
|
||||||
{
|
{
|
||||||
var appIds = AppService.GetAppInternalTags(invoiceEvent.Invoice);
|
var appIds = AppService.GetAppInternalTags(invoiceEvent.Invoice);
|
||||||
|
|
||||||
|
@ -116,9 +115,9 @@ namespace BTCPayServer.HostedServices
|
||||||
}
|
}
|
||||||
|
|
||||||
var items = cartItems ?? new Dictionary<string, int>();
|
var items = cartItems ?? new Dictionary<string, int>();
|
||||||
if (!string.IsNullOrEmpty(invoiceEvent.Invoice.ProductInformation.ItemCode))
|
if (!string.IsNullOrEmpty(invoiceEvent.Invoice.Metadata.ItemCode))
|
||||||
{
|
{
|
||||||
items.TryAdd(invoiceEvent.Invoice.ProductInformation.ItemCode, 1);
|
items.TryAdd(invoiceEvent.Invoice.Metadata.ItemCode, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
_eventAggregator.Publish(new UpdateAppInventory()
|
_eventAggregator.Publish(new UpdateAppInventory()
|
||||||
|
|
|
@ -2,10 +2,11 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NBitpayClient;
|
using NBitpayClient;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace BTCPayServer.Models
|
namespace BTCPayServer.Models
|
||||||
{
|
{
|
||||||
public class CreateInvoiceRequest
|
public class BitpayCreateInvoiceRequest
|
||||||
{
|
{
|
||||||
[JsonProperty(PropertyName = "buyer")]
|
[JsonProperty(PropertyName = "buyer")]
|
||||||
public Buyer Buyer { get; set; }
|
public Buyer Buyer { get; set; }
|
||||||
|
@ -81,5 +82,6 @@ namespace BTCPayServer.Models
|
||||||
//Bitpay compatibility: create invoice in btcpay uses this instead of supportedTransactionCurrencies
|
//Bitpay compatibility: create invoice in btcpay uses this instead of supportedTransactionCurrencies
|
||||||
[JsonProperty(PropertyName = "paymentCurrencies", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
[JsonProperty(PropertyName = "paymentCurrencies", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||||
public IEnumerable<string> PaymentCurrencies { get; set; }
|
public IEnumerable<string> PaymentCurrencies { get; set; }
|
||||||
|
public JObject Metadata { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6,8 +6,6 @@ using BTCPayServer.Payments.Bitcoin;
|
||||||
using BTCPayServer.Services.Invoices;
|
using BTCPayServer.Services.Invoices;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using BuyerInformation = BTCPayServer.Services.Invoices.BuyerInformation;
|
|
||||||
using ProductInformation = BTCPayServer.Services.Invoices.ProductInformation;
|
|
||||||
|
|
||||||
namespace BTCPayServer.Models.InvoicingModels
|
namespace BTCPayServer.Models.InvoicingModels
|
||||||
{
|
{
|
||||||
|
@ -76,22 +74,12 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string OrderId
|
|
||||||
{
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
public string RefundEmail
|
public string RefundEmail
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public string TaxIncluded { get; set; }
|
public string TaxIncluded { get; set; }
|
||||||
public BuyerInformation BuyerInformation
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string TransactionSpeed { get; set; }
|
public string TransactionSpeed { get; set; }
|
||||||
public object StoreName
|
public object StoreName
|
||||||
|
@ -116,11 +104,7 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public ProductInformation ProductInformation
|
public InvoiceMetadata TypedMetadata { get; set; }
|
||||||
{
|
|
||||||
get;
|
|
||||||
internal set;
|
|
||||||
}
|
|
||||||
public AddressModel[] Addresses { get; set; }
|
public AddressModel[] Addresses { get; set; }
|
||||||
public DateTimeOffset MonitoringDate { get; internal set; }
|
public DateTimeOffset MonitoringDate { get; internal set; }
|
||||||
public List<Data.InvoiceEventData> Events { get; internal set; }
|
public List<Data.InvoiceEventData> Events { get; internal set; }
|
||||||
|
|
|
@ -101,9 +101,9 @@ namespace BTCPayServer.PaymentRequest
|
||||||
Invoices = invoices.Select(entity => new ViewPaymentRequestViewModel.PaymentRequestInvoice()
|
Invoices = invoices.Select(entity => new ViewPaymentRequestViewModel.PaymentRequestInvoice()
|
||||||
{
|
{
|
||||||
Id = entity.Id,
|
Id = entity.Id,
|
||||||
Amount = entity.ProductInformation.Price,
|
Amount = entity.Price,
|
||||||
AmountFormatted = _currencies.FormatCurrency(entity.ProductInformation.Price, blob.Currency),
|
AmountFormatted = _currencies.FormatCurrency(entity.Price, blob.Currency),
|
||||||
Currency = entity.ProductInformation.Currency,
|
Currency = entity.Currency,
|
||||||
ExpiryDate = entity.ExpirationTime.DateTime,
|
ExpiryDate = entity.ExpirationTime.DateTime,
|
||||||
Status = entity.GetInvoiceState().ToString(),
|
Status = entity.GetInvoiceState().ToString(),
|
||||||
Payments = entity
|
Payments = entity
|
||||||
|
|
|
@ -48,7 +48,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||||
var storeBlob = store.GetStoreBlob();
|
var storeBlob = store.GetStoreBlob();
|
||||||
var test = GetNodeInfo(paymentMethod.PreferOnion, supportedPaymentMethod, network);
|
var test = GetNodeInfo(paymentMethod.PreferOnion, supportedPaymentMethod, network);
|
||||||
var invoice = paymentMethod.ParentEntity;
|
var invoice = paymentMethod.ParentEntity;
|
||||||
var due = Extensions.RoundUp(invoice.ProductInformation.Price / paymentMethod.Rate, network.Divisibility);
|
var due = Extensions.RoundUp(invoice.Price / paymentMethod.Rate, network.Divisibility);
|
||||||
var client = _lightningClientFactory.Create(supportedPaymentMethod.GetLightningUrl(), network);
|
var client = _lightningClientFactory.Create(supportedPaymentMethod.GetLightningUrl(), network);
|
||||||
var expiry = invoice.ExpirationTime - DateTimeOffset.UtcNow;
|
var expiry = invoice.ExpirationTime - DateTimeOffset.UtcNow;
|
||||||
if (expiry < TimeSpan.Zero)
|
if (expiry < TimeSpan.Zero)
|
||||||
|
@ -58,8 +58,8 @@ namespace BTCPayServer.Payments.Lightning
|
||||||
|
|
||||||
string description = storeBlob.LightningDescriptionTemplate;
|
string description = storeBlob.LightningDescriptionTemplate;
|
||||||
description = description.Replace("{StoreName}", store.StoreName ?? "", StringComparison.OrdinalIgnoreCase)
|
description = description.Replace("{StoreName}", store.StoreName ?? "", StringComparison.OrdinalIgnoreCase)
|
||||||
.Replace("{ItemDescription}", invoice.ProductInformation.ItemDesc ?? "", StringComparison.OrdinalIgnoreCase)
|
.Replace("{ItemDescription}", invoice.Metadata.ItemDesc ?? "", StringComparison.OrdinalIgnoreCase)
|
||||||
.Replace("{OrderId}", invoice.OrderId ?? "", StringComparison.OrdinalIgnoreCase);
|
.Replace("{OrderId}", invoice.Metadata.OrderId ?? "", StringComparison.OrdinalIgnoreCase);
|
||||||
using (var cts = new CancellationTokenSource(LIGHTNING_TIMEOUT))
|
using (var cts = new CancellationTokenSource(LIGHTNING_TIMEOUT))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
@ -97,8 +97,8 @@ namespace BTCPayServer.Services.Apps
|
||||||
var currentPayments = GetContributionsByPaymentMethodId(settings.TargetCurrency, completeInvoices, !settings.EnforceTargetAmount);
|
var currentPayments = GetContributionsByPaymentMethodId(settings.TargetCurrency, completeInvoices, !settings.EnforceTargetAmount);
|
||||||
|
|
||||||
var perkCount = paidInvoices
|
var perkCount = paidInvoices
|
||||||
.Where(entity => !string.IsNullOrEmpty(entity.ProductInformation.ItemCode))
|
.Where(entity => !string.IsNullOrEmpty(entity.Metadata.ItemCode))
|
||||||
.GroupBy(entity => entity.ProductInformation.ItemCode)
|
.GroupBy(entity => entity.Metadata.ItemCode)
|
||||||
.ToDictionary(entities => entities.Key, entities => entities.Count());
|
.ToDictionary(entities => entities.Key, entities => entities.Count());
|
||||||
|
|
||||||
var perks = Parse(settings.PerksTemplate, settings.TargetCurrency);
|
var perks = Parse(settings.PerksTemplate, settings.TargetCurrency);
|
||||||
|
@ -331,12 +331,12 @@ namespace BTCPayServer.Services.Apps
|
||||||
public Contributions GetContributionsByPaymentMethodId(string currency, InvoiceEntity[] invoices, bool softcap)
|
public Contributions GetContributionsByPaymentMethodId(string currency, InvoiceEntity[] invoices, bool softcap)
|
||||||
{
|
{
|
||||||
var contributions = invoices
|
var contributions = invoices
|
||||||
.Where(p => p.ProductInformation.Currency.Equals(currency, StringComparison.OrdinalIgnoreCase))
|
.Where(p => p.Currency.Equals(currency, StringComparison.OrdinalIgnoreCase))
|
||||||
.SelectMany(p =>
|
.SelectMany(p =>
|
||||||
{
|
{
|
||||||
var contribution = new Contribution();
|
var contribution = new Contribution();
|
||||||
contribution.PaymentMethodId = new PaymentMethodId(p.ProductInformation.Currency, PaymentTypes.BTCLike);
|
contribution.PaymentMethodId = new PaymentMethodId(p.Currency, PaymentTypes.BTCLike);
|
||||||
contribution.CurrencyValue = p.ProductInformation.Price;
|
contribution.CurrencyValue = p.Price;
|
||||||
contribution.Value = contribution.CurrencyValue;
|
contribution.Value = contribution.CurrencyValue;
|
||||||
|
|
||||||
// For hardcap, we count newly created invoices as part of the contributions
|
// For hardcap, we count newly created invoices as part of the contributions
|
||||||
|
|
|
@ -56,9 +56,8 @@ namespace BTCPayServer.Services.Invoices.Export
|
||||||
private IEnumerable<ExportInvoiceHolder> convertFromDb(InvoiceEntity invoice)
|
private IEnumerable<ExportInvoiceHolder> convertFromDb(InvoiceEntity invoice)
|
||||||
{
|
{
|
||||||
var exportList = new List<ExportInvoiceHolder>();
|
var exportList = new List<ExportInvoiceHolder>();
|
||||||
var currency = Currencies.GetNumberFormatInfo(invoice.ProductInformation.Currency, true);
|
var currency = Currencies.GetNumberFormatInfo(invoice.Currency, true);
|
||||||
|
var invoiceDue = invoice.Price;
|
||||||
var invoiceDue = invoice.ProductInformation.Price;
|
|
||||||
// in this first version we are only exporting invoices that were paid
|
// in this first version we are only exporting invoices that were paid
|
||||||
foreach (var payment in invoice.GetPayments())
|
foreach (var payment in invoice.GetPayments())
|
||||||
{
|
{
|
||||||
|
@ -88,7 +87,7 @@ namespace BTCPayServer.Services.Invoices.Export
|
||||||
// while looking just at export you could sum Paid and assume merchant "received payments"
|
// while looking just at export you could sum Paid and assume merchant "received payments"
|
||||||
NetworkFee = payment.NetworkFee.ToString(CultureInfo.InvariantCulture),
|
NetworkFee = payment.NetworkFee.ToString(CultureInfo.InvariantCulture),
|
||||||
InvoiceDue = Math.Round(invoiceDue, currency.NumberDecimalDigits),
|
InvoiceDue = Math.Round(invoiceDue, currency.NumberDecimalDigits),
|
||||||
OrderId = invoice.OrderId,
|
OrderId = invoice.Metadata.OrderId ?? string.Empty,
|
||||||
StoreId = invoice.StoreId,
|
StoreId = invoice.StoreId,
|
||||||
InvoiceId = invoice.Id,
|
InvoiceId = invoice.Id,
|
||||||
InvoiceCreatedDate = invoice.InvoiceTime.UtcDateTime,
|
InvoiceCreatedDate = invoice.InvoiceTime.UtcDateTime,
|
||||||
|
@ -99,11 +98,11 @@ namespace BTCPayServer.Services.Invoices.Export
|
||||||
InvoiceStatus = invoice.StatusString,
|
InvoiceStatus = invoice.StatusString,
|
||||||
InvoiceExceptionStatus = invoice.ExceptionStatusString,
|
InvoiceExceptionStatus = invoice.ExceptionStatusString,
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
InvoiceItemCode = invoice.ProductInformation.ItemCode,
|
InvoiceItemCode = invoice.Metadata.ItemCode,
|
||||||
InvoiceItemDesc = invoice.ProductInformation.ItemDesc,
|
InvoiceItemDesc = invoice.Metadata.ItemDesc,
|
||||||
InvoicePrice = invoice.ProductInformation.Price,
|
InvoicePrice = invoice.Price,
|
||||||
InvoiceCurrency = invoice.ProductInformation.Currency,
|
InvoiceCurrency = invoice.Currency,
|
||||||
BuyerEmail = invoice.BuyerInformation?.BuyerEmail
|
BuyerEmail = invoice.Metadata.BuyerEmail
|
||||||
};
|
};
|
||||||
|
|
||||||
exportList.Add(target);
|
exportList.Add(target);
|
||||||
|
|
|
@ -2,6 +2,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Amazon.Runtime.Internal.Util;
|
||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.JsonConverters;
|
using BTCPayServer.JsonConverters;
|
||||||
|
@ -9,17 +10,31 @@ using BTCPayServer.Models;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
using BTCPayServer.Payments.Bitcoin;
|
using BTCPayServer.Payments.Bitcoin;
|
||||||
using Microsoft.AspNetCore.Http.Extensions;
|
using Microsoft.AspNetCore.Http.Extensions;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using NBitcoin.DataEncoders;
|
using NBitcoin.DataEncoders;
|
||||||
using NBitpayClient;
|
using NBitpayClient;
|
||||||
using NBXplorer;
|
using NBXplorer;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Newtonsoft.Json.Serialization;
|
||||||
|
using YamlDotNet.Core.Tokens;
|
||||||
|
using YamlDotNet.Serialization.NamingConventions;
|
||||||
|
|
||||||
namespace BTCPayServer.Services.Invoices
|
namespace BTCPayServer.Services.Invoices
|
||||||
{
|
{
|
||||||
public class BuyerInformation
|
public class InvoiceMetadata
|
||||||
{
|
{
|
||||||
|
public static readonly JsonSerializer MetadataSerializer;
|
||||||
|
static InvoiceMetadata()
|
||||||
|
{
|
||||||
|
var seria = new JsonSerializer();
|
||||||
|
seria.DefaultValueHandling = DefaultValueHandling.Ignore;
|
||||||
|
seria.FloatParseHandling = FloatParseHandling.Decimal;
|
||||||
|
seria.ContractResolver = new CamelCasePropertyNamesContractResolver();
|
||||||
|
MetadataSerializer = seria;
|
||||||
|
}
|
||||||
|
public string OrderId { get; set; }
|
||||||
[JsonProperty(PropertyName = "buyerName")]
|
[JsonProperty(PropertyName = "buyerName")]
|
||||||
public string BuyerName
|
public string BuyerName
|
||||||
{
|
{
|
||||||
|
@ -66,10 +81,7 @@ namespace BTCPayServer.Services.Invoices
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public class ProductInformation
|
|
||||||
{
|
|
||||||
[JsonProperty(PropertyName = "itemDesc")]
|
[JsonProperty(PropertyName = "itemDesc")]
|
||||||
public string ItemDesc
|
public string ItemDesc
|
||||||
{
|
{
|
||||||
|
@ -81,35 +93,122 @@ namespace BTCPayServer.Services.Invoices
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
[JsonProperty(PropertyName = "physical")]
|
[JsonProperty(PropertyName = "physical")]
|
||||||
public bool Physical
|
public bool? Physical
|
||||||
{
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "price")]
|
|
||||||
public decimal Price
|
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "taxIncluded", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
[JsonProperty(PropertyName = "taxIncluded", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||||
public decimal TaxIncluded
|
public decimal? TaxIncluded
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
public string PosData { get; set; }
|
||||||
|
[JsonExtensionData]
|
||||||
|
public IDictionary<string, JToken> AdditionalData { get; set; }
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "currency")]
|
public static InvoiceMetadata FromJObject(JObject jObject)
|
||||||
public string Currency
|
|
||||||
{
|
{
|
||||||
get; set;
|
return jObject.ToObject<InvoiceMetadata>(MetadataSerializer);
|
||||||
|
}
|
||||||
|
public JObject ToJObject()
|
||||||
|
{
|
||||||
|
return JObject.FromObject(this, MetadataSerializer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class InvoiceEntity
|
public class InvoiceEntity
|
||||||
{
|
{
|
||||||
|
class BuyerInformation
|
||||||
|
{
|
||||||
|
[JsonProperty(PropertyName = "buyerName")]
|
||||||
|
public string BuyerName
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
[JsonProperty(PropertyName = "buyerEmail")]
|
||||||
|
public string BuyerEmail
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
[JsonProperty(PropertyName = "buyerCountry")]
|
||||||
|
public string BuyerCountry
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
[JsonProperty(PropertyName = "buyerZip")]
|
||||||
|
public string BuyerZip
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
[JsonProperty(PropertyName = "buyerState")]
|
||||||
|
public string BuyerState
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
[JsonProperty(PropertyName = "buyerCity")]
|
||||||
|
public string BuyerCity
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
[JsonProperty(PropertyName = "buyerAddress2")]
|
||||||
|
public string BuyerAddress2
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
[JsonProperty(PropertyName = "buyerAddress1")]
|
||||||
|
public string BuyerAddress1
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonProperty(PropertyName = "buyerPhone")]
|
||||||
|
public string BuyerPhone
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class ProductInformation
|
||||||
|
{
|
||||||
|
[JsonProperty(PropertyName = "itemDesc")]
|
||||||
|
public string ItemDesc
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
[JsonProperty(PropertyName = "itemCode")]
|
||||||
|
public string ItemCode
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
[JsonProperty(PropertyName = "physical")]
|
||||||
|
public bool Physical
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonProperty(PropertyName = "price")]
|
||||||
|
public decimal Price
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonProperty(PropertyName = "taxIncluded", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||||
|
public decimal TaxIncluded
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonProperty(PropertyName = "currency")]
|
||||||
|
public string Currency
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
}
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public BTCPayNetworkProvider Networks { get; set; }
|
public BTCPayNetworkProvider Networks { get; set; }
|
||||||
public const int InternalTagSupport_Version = 1;
|
public const int InternalTagSupport_Version = 1;
|
||||||
public const int Lastest_Version = 1;
|
public const int GreenfieldInvoices_Version = 2;
|
||||||
|
public const int Lastest_Version = 2;
|
||||||
public int Version { get; set; }
|
public int Version { get; set; }
|
||||||
public string Id
|
public string Id
|
||||||
{
|
{
|
||||||
|
@ -120,11 +219,6 @@ namespace BTCPayServer.Services.Invoices
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string OrderId
|
|
||||||
{
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SpeedPolicy SpeedPolicy
|
public SpeedPolicy SpeedPolicy
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
|
@ -148,20 +242,20 @@ namespace BTCPayServer.Services.Invoices
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public ProductInformation ProductInformation
|
|
||||||
{
|
public InvoiceMetadata Metadata
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
public BuyerInformation BuyerInformation
|
|
||||||
{
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
public string PosData
|
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public decimal Price { get; set; }
|
||||||
|
public string Currency { get; set; }
|
||||||
|
|
||||||
|
[JsonExtensionData]
|
||||||
|
public IDictionary<string, JToken> AdditionalData { get; set; }
|
||||||
|
|
||||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||||
public HashSet<string> InternalTags { get; set; } = new HashSet<string>();
|
public HashSet<string> InternalTags { get; set; } = new HashSet<string>();
|
||||||
|
|
||||||
|
@ -300,7 +394,7 @@ namespace BTCPayServer.Services.Invoices
|
||||||
|
|
||||||
private Uri FillPlaceholdersUri(string v)
|
private Uri FillPlaceholdersUri(string v)
|
||||||
{
|
{
|
||||||
var uriStr = (v ?? string.Empty).Replace("{OrderId}", System.Web.HttpUtility.UrlEncode(OrderId) ?? "", StringComparison.OrdinalIgnoreCase)
|
var uriStr = (v ?? string.Empty).Replace("{OrderId}", System.Web.HttpUtility.UrlEncode(Metadata.OrderId) ?? "", StringComparison.OrdinalIgnoreCase)
|
||||||
.Replace("{InvoiceId}", System.Web.HttpUtility.UrlEncode(Id) ?? "", StringComparison.OrdinalIgnoreCase);
|
.Replace("{InvoiceId}", System.Web.HttpUtility.UrlEncode(Id) ?? "", StringComparison.OrdinalIgnoreCase);
|
||||||
if (Uri.TryCreate(uriStr, UriKind.Absolute, out var uri) && (uri.Scheme == "http" || uri.Scheme == "https"))
|
if (Uri.TryCreate(uriStr, UriKind.Absolute, out var uri) && (uri.Scheme == "http" || uri.Scheme == "https"))
|
||||||
return uri;
|
return uri;
|
||||||
|
@ -383,8 +477,8 @@ namespace BTCPayServer.Services.Invoices
|
||||||
{
|
{
|
||||||
Id = Id,
|
Id = Id,
|
||||||
StoreId = StoreId,
|
StoreId = StoreId,
|
||||||
OrderId = OrderId,
|
OrderId = Metadata.OrderId,
|
||||||
PosData = PosData,
|
PosData = Metadata.PosData,
|
||||||
CurrentTime = DateTimeOffset.UtcNow,
|
CurrentTime = DateTimeOffset.UtcNow,
|
||||||
InvoiceTime = InvoiceTime,
|
InvoiceTime = InvoiceTime,
|
||||||
ExpirationTime = ExpirationTime,
|
ExpirationTime = ExpirationTime,
|
||||||
|
@ -392,7 +486,7 @@ namespace BTCPayServer.Services.Invoices
|
||||||
Status = StatusString,
|
Status = StatusString,
|
||||||
ExceptionStatus = ExceptionStatus == InvoiceExceptionStatus.None ? new JValue(false) : new JValue(ExceptionStatusString),
|
ExceptionStatus = ExceptionStatus == InvoiceExceptionStatus.None ? new JValue(false) : new JValue(ExceptionStatusString),
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
Currency = ProductInformation.Currency,
|
Currency = Currency,
|
||||||
Flags = new Flags() { Refundable = Refundable },
|
Flags = new Flags() { Refundable = Refundable },
|
||||||
PaymentSubtotals = new Dictionary<string, decimal>(),
|
PaymentSubtotals = new Dictionary<string, decimal>(),
|
||||||
PaymentTotals = new Dictionary<string, decimal>(),
|
PaymentTotals = new Dictionary<string, decimal>(),
|
||||||
|
@ -415,7 +509,7 @@ namespace BTCPayServer.Services.Invoices
|
||||||
var address = details?.GetPaymentDestination();
|
var address = details?.GetPaymentDestination();
|
||||||
var exrates = new Dictionary<string, decimal>
|
var exrates = new Dictionary<string, decimal>
|
||||||
{
|
{
|
||||||
{ ProductInformation.Currency, cryptoInfo.Rate }
|
{ Currency, cryptoInfo.Rate }
|
||||||
};
|
};
|
||||||
|
|
||||||
cryptoInfo.CryptoCode = cryptoCode;
|
cryptoInfo.CryptoCode = cryptoCode;
|
||||||
|
@ -502,29 +596,27 @@ namespace BTCPayServer.Services.Invoices
|
||||||
|
|
||||||
//dto.AmountPaid dto.MinerFees & dto.TransactionCurrency are not supported by btcpayserver as we have multi currency payment support per invoice
|
//dto.AmountPaid dto.MinerFees & dto.TransactionCurrency are not supported by btcpayserver as we have multi currency payment support per invoice
|
||||||
|
|
||||||
Populate(ProductInformation, dto);
|
dto.ItemCode = Metadata.ItemCode;
|
||||||
|
dto.ItemDesc = Metadata.ItemDesc;
|
||||||
|
dto.TaxIncluded = Metadata.TaxIncluded ?? 0m;
|
||||||
|
dto.Price = Price;
|
||||||
|
dto.Currency = Currency;
|
||||||
dto.Buyer = new JObject();
|
dto.Buyer = new JObject();
|
||||||
dto.Buyer.Add(new JProperty("name", BuyerInformation.BuyerName));
|
dto.Buyer.Add(new JProperty("name", Metadata.BuyerName));
|
||||||
dto.Buyer.Add(new JProperty("address1", BuyerInformation.BuyerAddress1));
|
dto.Buyer.Add(new JProperty("address1", Metadata.BuyerAddress1));
|
||||||
dto.Buyer.Add(new JProperty("address2", BuyerInformation.BuyerAddress2));
|
dto.Buyer.Add(new JProperty("address2", Metadata.BuyerAddress2));
|
||||||
dto.Buyer.Add(new JProperty("locality", BuyerInformation.BuyerCity));
|
dto.Buyer.Add(new JProperty("locality", Metadata.BuyerCity));
|
||||||
dto.Buyer.Add(new JProperty("region", BuyerInformation.BuyerState));
|
dto.Buyer.Add(new JProperty("region", Metadata.BuyerState));
|
||||||
dto.Buyer.Add(new JProperty("postalCode", BuyerInformation.BuyerZip));
|
dto.Buyer.Add(new JProperty("postalCode", Metadata.BuyerZip));
|
||||||
dto.Buyer.Add(new JProperty("country", BuyerInformation.BuyerCountry));
|
dto.Buyer.Add(new JProperty("country", Metadata.BuyerCountry));
|
||||||
dto.Buyer.Add(new JProperty("phone", BuyerInformation.BuyerPhone));
|
dto.Buyer.Add(new JProperty("phone", Metadata.BuyerPhone));
|
||||||
dto.Buyer.Add(new JProperty("email", string.IsNullOrWhiteSpace(BuyerInformation.BuyerEmail) ? RefundMail : BuyerInformation.BuyerEmail));
|
dto.Buyer.Add(new JProperty("email", string.IsNullOrWhiteSpace(Metadata.BuyerEmail) ? RefundMail : Metadata.BuyerEmail));
|
||||||
|
|
||||||
dto.Token = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16)); //No idea what it is useful for
|
dto.Token = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16)); //No idea what it is useful for
|
||||||
dto.Guid = Guid.NewGuid().ToString();
|
dto.Guid = Guid.NewGuid().ToString();
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Populate<TFrom, TDest>(TFrom from, TDest dest)
|
|
||||||
{
|
|
||||||
var str = JsonConvert.SerializeObject(from);
|
|
||||||
JsonConvert.PopulateObject(str, dest);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal bool Support(PaymentMethodId paymentMethodId)
|
internal bool Support(PaymentMethodId paymentMethodId)
|
||||||
{
|
{
|
||||||
var rates = GetPaymentMethods();
|
var rates = GetPaymentMethods();
|
||||||
|
@ -602,6 +694,62 @@ namespace BTCPayServer.Services.Invoices
|
||||||
{
|
{
|
||||||
return new InvoiceState(Status, ExceptionStatus);
|
return new InvoiceState(Status, ExceptionStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoice version < 1 were saving metadata directly under the InvoiceEntity
|
||||||
|
/// object. But in version > 2, the metadata is saved under the InvoiceEntity.Metadata object
|
||||||
|
/// This method is extracting metadata from the InvoiceEntity of version < 1 invoices and put them in InvoiceEntity.Metadata.
|
||||||
|
/// </summary>
|
||||||
|
internal void MigrateLegacyInvoice()
|
||||||
|
{
|
||||||
|
T TryParseMetadata<T>(string field) where T : class
|
||||||
|
{
|
||||||
|
if (AdditionalData.TryGetValue(field, out var token) && token is JObject obj)
|
||||||
|
{
|
||||||
|
return obj.ToObject<T>();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (TryParseMetadata<BuyerInformation>("buyerInformation") is BuyerInformation buyerInformation &&
|
||||||
|
TryParseMetadata<ProductInformation>("productInformation") is ProductInformation productInformation)
|
||||||
|
{
|
||||||
|
var wellknown = new InvoiceMetadata()
|
||||||
|
{
|
||||||
|
BuyerAddress1 = buyerInformation.BuyerAddress1,
|
||||||
|
BuyerAddress2 = buyerInformation.BuyerAddress2,
|
||||||
|
BuyerCity = buyerInformation.BuyerCity,
|
||||||
|
BuyerCountry = buyerInformation.BuyerCountry,
|
||||||
|
BuyerEmail = buyerInformation.BuyerEmail,
|
||||||
|
BuyerName = buyerInformation.BuyerName,
|
||||||
|
BuyerPhone = buyerInformation.BuyerPhone,
|
||||||
|
BuyerState = buyerInformation.BuyerState,
|
||||||
|
BuyerZip = buyerInformation.BuyerZip,
|
||||||
|
ItemCode = productInformation.ItemCode,
|
||||||
|
ItemDesc = productInformation.ItemDesc,
|
||||||
|
Physical = productInformation.Physical,
|
||||||
|
TaxIncluded = productInformation.TaxIncluded
|
||||||
|
};
|
||||||
|
if (AdditionalData.TryGetValue("posData", out var token) &&
|
||||||
|
token is JValue val &&
|
||||||
|
val.Type == JTokenType.String)
|
||||||
|
{
|
||||||
|
wellknown.PosData = val.Value<string>();
|
||||||
|
}
|
||||||
|
if (AdditionalData.TryGetValue("orderId", out var token2) &&
|
||||||
|
token2 is JValue val2 &&
|
||||||
|
val2.Type == JTokenType.String)
|
||||||
|
{
|
||||||
|
wellknown.OrderId = val2.Value<string>();
|
||||||
|
}
|
||||||
|
Metadata = wellknown;
|
||||||
|
Currency = productInformation.Currency;
|
||||||
|
Price = productInformation.Price;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Not a legacy invoice");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -836,7 +984,7 @@ namespace BTCPayServer.Services.Invoices
|
||||||
paymentPredicate = paymentPredicate ?? new Func<PaymentEntity, bool>((p) => true);
|
paymentPredicate = paymentPredicate ?? new Func<PaymentEntity, bool>((p) => true);
|
||||||
var paymentMethods = ParentEntity.GetPaymentMethods();
|
var paymentMethods = ParentEntity.GetPaymentMethods();
|
||||||
|
|
||||||
var totalDue = ParentEntity.ProductInformation.Price / Rate;
|
var totalDue = ParentEntity.Price / Rate;
|
||||||
var paid = 0m;
|
var paid = 0m;
|
||||||
var cryptoPaid = 0.0m;
|
var cryptoPaid = 0.0m;
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using Encoders = NBitcoin.DataEncoders.Encoders;
|
using Encoders = NBitcoin.DataEncoders.Encoders;
|
||||||
using InvoiceData = BTCPayServer.Data.InvoiceData;
|
using InvoiceData = BTCPayServer.Data.InvoiceData;
|
||||||
|
|
||||||
|
@ -65,6 +66,7 @@ retry:
|
||||||
Networks = _Networks,
|
Networks = _Networks,
|
||||||
Version = InvoiceEntity.Lastest_Version,
|
Version = InvoiceEntity.Lastest_Version,
|
||||||
InvoiceTime = DateTimeOffset.UtcNow,
|
InvoiceTime = DateTimeOffset.UtcNow,
|
||||||
|
Metadata = new InvoiceMetadata()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,11 +164,11 @@ retry:
|
||||||
Id = invoice.Id,
|
Id = invoice.Id,
|
||||||
Created = invoice.InvoiceTime,
|
Created = invoice.InvoiceTime,
|
||||||
Blob = ToBytes(invoice, null),
|
Blob = ToBytes(invoice, null),
|
||||||
OrderId = invoice.OrderId,
|
OrderId = invoice.Metadata.OrderId,
|
||||||
#pragma warning disable CS0618 // Type or member is obsolete
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
Status = invoice.StatusString,
|
Status = invoice.StatusString,
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
ItemCode = invoice.ProductInformation.ItemCode,
|
ItemCode = invoice.Metadata.ItemCode,
|
||||||
CustomerEmail = invoice.RefundMail,
|
CustomerEmail = invoice.RefundMail,
|
||||||
Archived = false
|
Archived = false
|
||||||
});
|
});
|
||||||
|
@ -198,10 +200,9 @@ retry:
|
||||||
|
|
||||||
textSearch.Add(invoice.Id);
|
textSearch.Add(invoice.Id);
|
||||||
textSearch.Add(invoice.InvoiceTime.ToString(CultureInfo.InvariantCulture));
|
textSearch.Add(invoice.InvoiceTime.ToString(CultureInfo.InvariantCulture));
|
||||||
textSearch.Add(invoice.ProductInformation.Price.ToString(CultureInfo.InvariantCulture));
|
textSearch.Add(invoice.Price.ToString(CultureInfo.InvariantCulture));
|
||||||
textSearch.Add(invoice.OrderId);
|
textSearch.Add(invoice.Metadata.OrderId);
|
||||||
textSearch.Add(ToString(invoice.BuyerInformation, null));
|
textSearch.Add(ToString(invoice.Metadata, null));
|
||||||
textSearch.Add(ToString(invoice.ProductInformation, null));
|
|
||||||
textSearch.Add(invoice.StoreId);
|
textSearch.Add(invoice.StoreId);
|
||||||
|
|
||||||
AddToTextSearch(invoice.Id, textSearch.ToArray());
|
AddToTextSearch(invoice.Id, textSearch.ToArray());
|
||||||
|
@ -555,10 +556,9 @@ retry:
|
||||||
{
|
{
|
||||||
entity.Events = invoice.Events.OrderBy(c => c.Timestamp).ToList();
|
entity.Events = invoice.Events.OrderBy(c => c.Timestamp).ToList();
|
||||||
}
|
}
|
||||||
|
if (!string.IsNullOrEmpty(entity.RefundMail) && string.IsNullOrEmpty(entity.Metadata.BuyerEmail))
|
||||||
if (!string.IsNullOrEmpty(entity.RefundMail) && string.IsNullOrEmpty(entity.BuyerInformation.BuyerEmail))
|
|
||||||
{
|
{
|
||||||
entity.BuyerInformation.BuyerEmail = entity.RefundMail;
|
entity.Metadata.BuyerEmail = entity.RefundMail;
|
||||||
}
|
}
|
||||||
entity.Archived = invoice.Archived;
|
entity.Archived = invoice.Archived;
|
||||||
return entity;
|
return entity;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
@model InvoiceDetailsModel
|
@model InvoiceDetailsModel
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Invoice " + Model.Id;
|
ViewData["Title"] = "Invoice " + Model.Id;
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Order Id</th>
|
<th>Order Id</th>
|
||||||
<td>@Model.OrderId</td>
|
<td>@Model.TypedMetadata.OrderId</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Total fiat due</th>
|
<th>Total fiat due</th>
|
||||||
|
@ -109,39 +109,39 @@
|
||||||
<table class="table table-sm table-responsive-md removetopborder">
|
<table class="table table-sm table-responsive-md removetopborder">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<td>@Model.BuyerInformation.BuyerName</td>
|
<td>@Model.TypedMetadata.BuyerName</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Email</th>
|
<th>Email</th>
|
||||||
<td><a href="mailto:@Model.BuyerInformation.BuyerEmail">@Model.BuyerInformation.BuyerEmail</a></td>
|
<td><a href="mailto:@Model.TypedMetadata.BuyerEmail">@Model.TypedMetadata.BuyerEmail</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Phone</th>
|
<th>Phone</th>
|
||||||
<td>@Model.BuyerInformation.BuyerPhone</td>
|
<td>@Model.TypedMetadata.BuyerPhone</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Address 1</th>
|
<th>Address 1</th>
|
||||||
<td>@Model.BuyerInformation.BuyerAddress1</td>
|
<td>@Model.TypedMetadata.BuyerAddress1</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Address 2</th>
|
<th>Address 2</th>
|
||||||
<td>@Model.BuyerInformation.BuyerAddress2</td>
|
<td>@Model.TypedMetadata.BuyerAddress2</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>City</th>
|
<th>City</th>
|
||||||
<td>@Model.BuyerInformation.BuyerCity</td>
|
<td>@Model.TypedMetadata.BuyerCity</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>State</th>
|
<th>State</th>
|
||||||
<td>@Model.BuyerInformation.BuyerState</td>
|
<td>@Model.TypedMetadata.BuyerState</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Country</th>
|
<th>Country</th>
|
||||||
<td>@Model.BuyerInformation.BuyerCountry</td>
|
<td>@Model.TypedMetadata.BuyerCountry</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Zip</th>
|
<th>Zip</th>
|
||||||
<td>@Model.BuyerInformation.BuyerZip</td>
|
<td>@Model.TypedMetadata.BuyerZip</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@if (Model.PosData.Count == 0)
|
@if (Model.PosData.Count == 0)
|
||||||
|
@ -150,11 +150,11 @@
|
||||||
<table class="table table-sm table-responsive-md removetopborder">
|
<table class="table table-sm table-responsive-md removetopborder">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Item code</th>
|
<th>Item code</th>
|
||||||
<td>@Model.ProductInformation.ItemCode</td>
|
<td>@Model.TypedMetadata.ItemCode</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Item Description</th>
|
<th>Item Description</th>
|
||||||
<td>@Model.ProductInformation.ItemDesc</td>
|
<td>@Model.TypedMetadata.ItemDesc</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Price</th>
|
<th>Price</th>
|
||||||
|
@ -177,11 +177,11 @@
|
||||||
<table class="table table-sm table-responsive-md removetopborder">
|
<table class="table table-sm table-responsive-md removetopborder">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Item code</th>
|
<th>Item code</th>
|
||||||
<td>@Model.ProductInformation.ItemCode</td>
|
<td>@Model.TypedMetadata.ItemCode</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Item Description</th>
|
<th>Item Description</th>
|
||||||
<td>@Model.ProductInformation.ItemDesc</td>
|
<td>@Model.TypedMetadata.ItemDesc</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Price</th>
|
<th>Price</th>
|
||||||
|
|
Loading…
Add table
Reference in a new issue